greprag 5.61.0 → 5.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1746,13 +1746,14 @@ function crushMessages(messages, opts) {
1746
1746
  }
1747
1747
 
1748
1748
  // src/commands/os-primer-reminder.ts
1749
- function buildOsPrimer() {
1749
+ function buildOsPrimer(env) {
1750
+ const spawnEntry = env?.platform === "codex" ? "codex-chip-spawn" : "chip-spawn";
1750
1751
  return [
1751
1752
  "[grepragOS \u2014 the operating laws. Full doctrine: `greprag load os`.]",
1752
1753
  '\u2022 Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE \u2014 read these first" block naming exact paths.',
1753
1754
  "\u2022 Discoverability: every durable artifact must be findable next session \u2014 docs auto-register, skills auto-mirror, decisions get a dated ADR/decision-log entry, everything else gets its path named in the owning skill/doc. If nothing points at it, you didn't finish.",
1754
1755
  "\u2022 Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows.",
1755
- '\u2022 Friction \u21D2 fix NOW: one unit of friction \u21D2 `greprag fix spawn "<unit>"` \u2014 one chip per unit; it investigates, then confidence \u226590 repairs autonomously / <90 parks for operator co-design. Never queue friction.',
1756
+ `\u2022 Friction \u21D2 fix NOW: one unit of friction \u21D2 \`greprag fix spawn "<unit>"\`. It PRINTS a fix-chip mission (a chip = a spawned child task/session) \u2014 YOU then create the child task with that mission as its first message (how: \`greprag load ${spawnEntry}\`, or your harness's native task tool). One unit per chip; the chip investigates, then confidence \u226590 repairs autonomously / <90 parks for operator co-design. Never queue friction.`,
1756
1757
  "\u2022 Teach the system, not the chat: explained twice by the operator \u21D2 it belongs in a skill / load entry / STATE block / ADR, not the conversation."
1757
1758
  ].join("\n");
1758
1759
  }
@@ -1760,7 +1761,7 @@ var osPrimerModule = {
1760
1761
  id: "os-primer",
1761
1762
  detect: (_env) => ({ tier: "silent" }),
1762
1763
  // never a per-turn nag
1763
- announce: () => buildOsPrimer(),
1764
+ announce: (env) => buildOsPrimer(env),
1764
1765
  reminder: () => null
1765
1766
  };
1766
1767
 
@@ -2500,6 +2501,588 @@ function activeProcedureAnnounces(store) {
2500
2501
  return lines;
2501
2502
  }
2502
2503
 
2504
+ // src/archive/glyph-font.ts
2505
+ var GLYPH_W = 8;
2506
+ var GLYPH_H = 8;
2507
+ var FIRST_CODE = 32;
2508
+ var LAST_CODE = 126;
2509
+ var FONT_HEX = [
2510
+ "0000000000000000",
2511
+ "183c3c1818001800",
2512
+ "3636000000000000",
2513
+ "36367f367f363600",
2514
+ "0c3e031e301f0c00",
2515
+ "006333180c666300",
2516
+ "1c361c6e3b336e00",
2517
+ "0606030000000000",
2518
+ "180c0606060c1800",
2519
+ "060c1818180c0600",
2520
+ "00663cff3c660000",
2521
+ "000c0c3f0c0c0000",
2522
+ "00000000000c0c06",
2523
+ "0000003f00000000",
2524
+ "00000000000c0c00",
2525
+ "6030180c06030100",
2526
+ "3e63737b6f673e00",
2527
+ "0c0e0c0c0c0c3f00",
2528
+ "1e33301c06333f00",
2529
+ "1e33301c30331e00",
2530
+ "383c36337f307800",
2531
+ "3f031f3030331e00",
2532
+ "1c06031f33331e00",
2533
+ "3f3330180c0c0c00",
2534
+ "1e33331e33331e00",
2535
+ "1e33333e30180e00",
2536
+ "000c0c00000c0c00",
2537
+ "000c0c00000c0c06",
2538
+ "180c0603060c1800",
2539
+ "00003f00003f0000",
2540
+ "060c1830180c0600",
2541
+ "1e3330180c000c00",
2542
+ "3e637b7b7b031e00",
2543
+ "0c1e33333f333300",
2544
+ "3f66663e66663f00",
2545
+ "3c66030303663c00",
2546
+ "1f36666666361f00",
2547
+ "7f46161e16467f00",
2548
+ "7f46161e16060f00",
2549
+ "3c66030373667c00",
2550
+ "3333333f33333300",
2551
+ "1e0c0c0c0c0c1e00",
2552
+ "7830303033331e00",
2553
+ "6766361e36666700",
2554
+ "0f06060646667f00",
2555
+ "63777f7f6b636300",
2556
+ "63676f7b73636300",
2557
+ "1c36636363361c00",
2558
+ "3f66663e06060f00",
2559
+ "1e3333333b1e3800",
2560
+ "3f66663e36666700",
2561
+ "1e33070e38331e00",
2562
+ "3f2d0c0c0c0c1e00",
2563
+ "3333333333333f00",
2564
+ "33333333331e0c00",
2565
+ "6363636b7f776300",
2566
+ "6363361c1c366300",
2567
+ "3333331e0c0c1e00",
2568
+ "7f6331184c667f00",
2569
+ "1e06060606061e00",
2570
+ "03060c1830604000",
2571
+ "1e18181818181e00",
2572
+ "081c366300000000",
2573
+ "00000000000000ff",
2574
+ "0c0c180000000000",
2575
+ "00001e303e336e00",
2576
+ "0706063e66663b00",
2577
+ "00001e3303331e00",
2578
+ "3830303e33336e00",
2579
+ "00001e333f031e00",
2580
+ "1c36060f06060f00",
2581
+ "00006e33333e301f",
2582
+ "0706366e66666700",
2583
+ "0c000e0c0c0c1e00",
2584
+ "300030303033331e",
2585
+ "070666361e366700",
2586
+ "0e0c0c0c0c0c1e00",
2587
+ "0000337f7f6b6300",
2588
+ "00001f3333333300",
2589
+ "00001e3333331e00",
2590
+ "00003b66663e060f",
2591
+ "00006e33333e3078",
2592
+ "00003b6e66060f00",
2593
+ "00003e031e301f00",
2594
+ "080c3e0c0c2c1800",
2595
+ "0000333333336e00",
2596
+ "00003333331e0c00",
2597
+ "0000636b7f7f3600",
2598
+ "000063361c366300",
2599
+ "00003333333e301f",
2600
+ "00003f190c263f00",
2601
+ "380c0c070c0c3800",
2602
+ "1818180018181800",
2603
+ "070c0c380c0c0700",
2604
+ "6e3b000000000000"
2605
+ ];
2606
+ function glyphRows(code) {
2607
+ if (code < FIRST_CODE || code > LAST_CODE)
2608
+ return null;
2609
+ const hex = FONT_HEX[code - FIRST_CODE];
2610
+ const out = [];
2611
+ for (let i = 0; i < 8; i++)
2612
+ out.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16));
2613
+ return out;
2614
+ }
2615
+ var reverse = null;
2616
+ function codeForSignature(sigHex) {
2617
+ if (!reverse) {
2618
+ reverse = /* @__PURE__ */ new Map();
2619
+ for (let c = FIRST_CODE; c <= LAST_CODE; c++)
2620
+ reverse.set(FONT_HEX[c - FIRST_CODE], c);
2621
+ }
2622
+ return reverse.get(sigHex) ?? null;
2623
+ }
2624
+ function signatureForRows(bytes) {
2625
+ return bytes.map((b) => (b & 255).toString(16).padStart(2, "0")).join("");
2626
+ }
2627
+
2628
+ // src/archive/png-codec.ts
2629
+ var zlib = __toESM(require("zlib"));
2630
+ var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
2631
+ var crcTable = null;
2632
+ function getCrcTable() {
2633
+ if (crcTable)
2634
+ return crcTable;
2635
+ crcTable = new Uint32Array(256);
2636
+ for (let n = 0; n < 256; n++) {
2637
+ let c = n;
2638
+ for (let k = 0; k < 8; k++) {
2639
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
2640
+ }
2641
+ crcTable[n] = c >>> 0;
2642
+ }
2643
+ return crcTable;
2644
+ }
2645
+ function crc32(buf) {
2646
+ const table = getCrcTable();
2647
+ let c = 4294967295;
2648
+ for (let i = 0; i < buf.length; i++) {
2649
+ c = table[(c ^ buf[i]) & 255] ^ c >>> 8;
2650
+ }
2651
+ return (c ^ 4294967295) >>> 0;
2652
+ }
2653
+ function chunk(type, data) {
2654
+ const len = Buffer.alloc(4);
2655
+ len.writeUInt32BE(data.length, 0);
2656
+ const typeAndData = Buffer.concat([Buffer.from(type, "ascii"), data]);
2657
+ const crc = Buffer.alloc(4);
2658
+ crc.writeUInt32BE(crc32(typeAndData), 0);
2659
+ return Buffer.concat([len, typeAndData, crc]);
2660
+ }
2661
+ function encodePng(img) {
2662
+ const { width, height, pixels } = img;
2663
+ if (pixels.length !== width * height) {
2664
+ throw new Error(`pixel buffer ${pixels.length} != ${width}x${height}`);
2665
+ }
2666
+ const ihdr = Buffer.alloc(13);
2667
+ ihdr.writeUInt32BE(width, 0);
2668
+ ihdr.writeUInt32BE(height, 4);
2669
+ ihdr[8] = 8;
2670
+ ihdr[9] = 0;
2671
+ ihdr[10] = 0;
2672
+ ihdr[11] = 0;
2673
+ ihdr[12] = 0;
2674
+ const raw = Buffer.alloc(height * (width + 1));
2675
+ for (let y = 0; y < height; y++) {
2676
+ raw[y * (width + 1)] = 0;
2677
+ pixels.copy(raw, y * (width + 1) + 1, y * width, (y + 1) * width);
2678
+ }
2679
+ const idat = zlib.deflateSync(raw, { level: 9 });
2680
+ return Buffer.concat([
2681
+ PNG_SIGNATURE,
2682
+ chunk("IHDR", ihdr),
2683
+ chunk("IDAT", idat),
2684
+ chunk("IEND", Buffer.alloc(0))
2685
+ ]);
2686
+ }
2687
+ function paeth(a, b, c) {
2688
+ const p = a + b - c;
2689
+ const pa = Math.abs(p - a);
2690
+ const pb = Math.abs(p - b);
2691
+ const pc = Math.abs(p - c);
2692
+ if (pa <= pb && pa <= pc)
2693
+ return a;
2694
+ if (pb <= pc)
2695
+ return b;
2696
+ return c;
2697
+ }
2698
+ function decodePng(buf) {
2699
+ if (buf.length < 8 || !buf.subarray(0, 8).equals(PNG_SIGNATURE)) {
2700
+ throw new Error("not a PNG (bad signature)");
2701
+ }
2702
+ let width = 0;
2703
+ let height = 0;
2704
+ const idatParts = [];
2705
+ let off = 8;
2706
+ while (off + 8 <= buf.length) {
2707
+ const len = buf.readUInt32BE(off);
2708
+ const type = buf.toString("ascii", off + 4, off + 8);
2709
+ const data = buf.subarray(off + 8, off + 8 + len);
2710
+ if (type === "IHDR") {
2711
+ width = data.readUInt32BE(0);
2712
+ height = data.readUInt32BE(4);
2713
+ const bitDepth = data[8];
2714
+ const colorType = data[9];
2715
+ const interlace = data[12];
2716
+ if (bitDepth !== 8 || colorType !== 0 || interlace !== 0) {
2717
+ throw new Error(`unsupported PNG (bitDepth=${bitDepth} colorType=${colorType} interlace=${interlace})`);
2718
+ }
2719
+ } else if (type === "IDAT") {
2720
+ idatParts.push(data);
2721
+ } else if (type === "IEND") {
2722
+ break;
2723
+ }
2724
+ off += 8 + len + 4;
2725
+ }
2726
+ if (!width || !height)
2727
+ throw new Error("PNG missing IHDR");
2728
+ if (idatParts.length === 0)
2729
+ throw new Error("PNG missing IDAT");
2730
+ const raw = zlib.inflateSync(Buffer.concat(idatParts));
2731
+ const stride = width + 1;
2732
+ if (raw.length < height * stride) {
2733
+ throw new Error(`PNG data short: ${raw.length} < ${height * stride}`);
2734
+ }
2735
+ const pixels = Buffer.alloc(width * height);
2736
+ for (let y = 0; y < height; y++) {
2737
+ const filter = raw[y * stride];
2738
+ const rowIn = raw.subarray(y * stride + 1, y * stride + 1 + width);
2739
+ const rowOut = pixels.subarray(y * width, (y + 1) * width);
2740
+ for (let x = 0; x < width; x++) {
2741
+ const a = x > 0 ? rowOut[x - 1] : 0;
2742
+ const b = y > 0 ? pixels[(y - 1) * width + x] : 0;
2743
+ const c = x > 0 && y > 0 ? pixels[(y - 1) * width + x - 1] : 0;
2744
+ let v;
2745
+ switch (filter) {
2746
+ case 0:
2747
+ v = rowIn[x];
2748
+ break;
2749
+ case 1:
2750
+ v = rowIn[x] + a;
2751
+ break;
2752
+ case 2:
2753
+ v = rowIn[x] + b;
2754
+ break;
2755
+ case 3:
2756
+ v = rowIn[x] + (a + b >> 1);
2757
+ break;
2758
+ case 4:
2759
+ v = rowIn[x] + paeth(a, b, c);
2760
+ break;
2761
+ default:
2762
+ throw new Error(`unsupported PNG filter ${filter} on row ${y}`);
2763
+ }
2764
+ rowOut[x] = v & 255;
2765
+ }
2766
+ }
2767
+ return { width, height, pixels };
2768
+ }
2769
+
2770
+ // src/archive/page-renderer.ts
2771
+ var PAGE_SIZE = 1568;
2772
+ var CELL_W = GLYPH_W;
2773
+ var CELL_H = 10;
2774
+ var COLS = Math.floor(PAGE_SIZE / CELL_W);
2775
+ var ROWS = Math.floor(PAGE_SIZE / CELL_H);
2776
+ var CHARS_PER_PAGE = COLS * ROWS;
2777
+ var BG = 255;
2778
+ var FG = 0;
2779
+ function sanitizeForGrid(text) {
2780
+ const out = [];
2781
+ for (const ch of text.replace(/\r\n/g, "\n").replace(/\r/g, "\n")) {
2782
+ const code = ch.codePointAt(0);
2783
+ if (ch === "\n") {
2784
+ out.push("\n");
2785
+ } else if (ch === " ") {
2786
+ out.push("\\t");
2787
+ } else if (code >= 32 && code <= 126) {
2788
+ out.push(ch);
2789
+ } else if (code <= 65535) {
2790
+ out.push("\\u" + code.toString(16).padStart(4, "0"));
2791
+ } else {
2792
+ out.push("\\u{" + code.toString(16) + "}");
2793
+ }
2794
+ }
2795
+ return out.join("");
2796
+ }
2797
+ function layoutRows(sanitized, cols = COLS) {
2798
+ const rows = [];
2799
+ let offset = 0;
2800
+ for (const line of sanitized.split("\n")) {
2801
+ if (line.length === 0) {
2802
+ rows.push({ text: " ".repeat(cols), charStart: offset });
2803
+ } else {
2804
+ for (let i = 0; i < line.length; i += cols) {
2805
+ const slice = line.slice(i, i + cols);
2806
+ rows.push({ text: slice.padEnd(cols, " "), charStart: offset + i });
2807
+ }
2808
+ }
2809
+ offset += line.length + 1;
2810
+ }
2811
+ return rows;
2812
+ }
2813
+ function renderPage(pageRows, cols = COLS, rows = ROWS) {
2814
+ const rowCount = Math.max(1, Math.min(pageRows.length, rows));
2815
+ const width = cols * CELL_W;
2816
+ const height = rowCount * CELL_H;
2817
+ const pixels = Buffer.alloc(width * height, BG);
2818
+ for (let r = 0; r < Math.min(pageRows.length, rowCount); r++) {
2819
+ const row = pageRows[r];
2820
+ for (let c = 0; c < cols; c++) {
2821
+ const glyph = glyphRows(row.text.charCodeAt(c));
2822
+ if (!glyph)
2823
+ continue;
2824
+ const px = c * CELL_W;
2825
+ const py = r * CELL_H;
2826
+ for (let gy = 0; gy < GLYPH_H; gy++) {
2827
+ const bits = glyph[gy];
2828
+ if (bits === 0)
2829
+ continue;
2830
+ const base = (py + gy) * width + px;
2831
+ for (let gx = 0; gx < GLYPH_W; gx++) {
2832
+ if (bits & 1 << gx)
2833
+ pixels[base + gx] = FG;
2834
+ }
2835
+ }
2836
+ }
2837
+ }
2838
+ const img = { width, height, pixels };
2839
+ const charStart = pageRows.length ? pageRows[0].charStart : 0;
2840
+ const last = pageRows[pageRows.length - 1];
2841
+ const charEnd = last ? last.charStart + last.text.trimEnd().length : charStart;
2842
+ return {
2843
+ png: encodePng(img),
2844
+ gridText: pageRows.map((r) => r.text).join("\n"),
2845
+ charStart,
2846
+ charEnd
2847
+ };
2848
+ }
2849
+ function paginate(rowsIn, rowsPerPage = ROWS) {
2850
+ const pages = [];
2851
+ for (let i = 0; i < rowsIn.length; i += rowsPerPage) {
2852
+ pages.push(rowsIn.slice(i, i + rowsPerPage));
2853
+ }
2854
+ return pages;
2855
+ }
2856
+ function decodePageText(png, cols = COLS) {
2857
+ const img = decodePng(png);
2858
+ const rows = Math.floor(img.height / CELL_H);
2859
+ const usableCols = Math.min(cols, Math.floor(img.width / CELL_W));
2860
+ const lines = [];
2861
+ for (let r = 0; r < rows; r++) {
2862
+ const chars = [];
2863
+ for (let c = 0; c < usableCols; c++) {
2864
+ const bytes = [];
2865
+ for (let gy = 0; gy < GLYPH_H; gy++) {
2866
+ let b = 0;
2867
+ const base = (r * CELL_H + gy) * img.width + c * CELL_W;
2868
+ for (let gx = 0; gx < GLYPH_W; gx++) {
2869
+ if (img.pixels[base + gx] < 128)
2870
+ b |= 1 << gx;
2871
+ }
2872
+ bytes.push(b);
2873
+ }
2874
+ const code = codeForSignature(signatureForRows(bytes));
2875
+ chars.push(code === null ? "\uFFFD" : String.fromCharCode(code));
2876
+ }
2877
+ lines.push(chars.join(""));
2878
+ }
2879
+ return lines.join("\n");
2880
+ }
2881
+ function verifyRoundTrip(page) {
2882
+ const decoded = decodePageText(page.png);
2883
+ if (decoded === page.gridText)
2884
+ return null;
2885
+ const a = page.gridText;
2886
+ const b = decoded;
2887
+ const n = Math.min(a.length, b.length);
2888
+ for (let i = 0; i < n; i++) {
2889
+ if (a[i] !== b[i]) {
2890
+ return `mismatch at char ${i}: rendered ${JSON.stringify(a[i])} decoded ${JSON.stringify(b[i])}`;
2891
+ }
2892
+ }
2893
+ return `length mismatch: rendered ${a.length} decoded ${b.length}`;
2894
+ }
2895
+
2896
+ // src/opencode-plugin-png.ts
2897
+ var CCR_MARKER_HASH_LEN2 = 16;
2898
+ function ccrPngMarker(hash) {
2899
+ return `<<ccr:${hash.slice(0, CCR_MARKER_HASH_LEN2)}>>`;
2900
+ }
2901
+ var CCR_MARKER_RE2 = /<<ccr:/;
2902
+ var DEFAULT_THRESHOLD_CHARS = 4e5;
2903
+ var DEFAULT_MIN_PART_CHARS = 4e3;
2904
+ var DEFAULT_LIVE_TAIL_MESSAGES = 1;
2905
+ var DEFAULT_VISION_MODEL_SUBSTRINGS = [
2906
+ "gpt-4o",
2907
+ "gpt-4.1",
2908
+ "gpt-4-turbo",
2909
+ "gpt-5",
2910
+ "o1",
2911
+ "o3",
2912
+ "o4",
2913
+ "claude-3",
2914
+ "claude-4",
2915
+ "claude-opus",
2916
+ "claude-sonnet",
2917
+ "claude-haiku",
2918
+ "claude-fable",
2919
+ "gemini",
2920
+ "llama-3.2-vision",
2921
+ "llama-4",
2922
+ "qwen2.5-vl",
2923
+ "qwen2-vl",
2924
+ "qwen3-vl",
2925
+ "pixtral",
2926
+ "mistral-small-3",
2927
+ "grok-vision",
2928
+ "grok-4",
2929
+ "internvl",
2930
+ "-vl",
2931
+ "vision"
2932
+ ];
2933
+ function inputModalities(modalities) {
2934
+ if (!modalities)
2935
+ return null;
2936
+ if (Array.isArray(modalities))
2937
+ return modalities.map((m) => String(m));
2938
+ if (typeof modalities === "object" && Array.isArray(modalities.input)) {
2939
+ return modalities.input.map((m) => String(m));
2940
+ }
2941
+ return null;
2942
+ }
2943
+ function isVisionCapable(opts) {
2944
+ const mods = inputModalities(opts.modalities);
2945
+ if (mods) {
2946
+ return mods.some((m) => m.toLowerCase() === "image");
2947
+ }
2948
+ const id = (opts.modelId || "").toLowerCase();
2949
+ if (!id)
2950
+ return false;
2951
+ const allow = [...DEFAULT_VISION_MODEL_SUBSTRINGS, ...opts.visionAllowlist || []];
2952
+ return allow.some((sub) => sub && id.includes(String(sub).toLowerCase()));
2953
+ }
2954
+ function renderTextToImageParts(text, hash) {
2955
+ const sanitized = sanitizeForGrid(text);
2956
+ const pages = paginate(layoutRows(sanitized));
2957
+ const parts = [];
2958
+ let pngBytes = 0;
2959
+ let selfTestError = null;
2960
+ for (let i = 0; i < pages.length; i++) {
2961
+ const rendered = renderPage(pages[i]);
2962
+ const err = verifyRoundTrip(rendered);
2963
+ if (err && !selfTestError)
2964
+ selfTestError = `page ${i + 1}: ${err}`;
2965
+ pngBytes += rendered.png.length;
2966
+ parts.push({
2967
+ type: "file",
2968
+ mime: "image/png",
2969
+ url: `data:image/png;base64,${rendered.png.toString("base64")}`,
2970
+ filename: `greprag-png-${hash.slice(0, CCR_MARKER_HASH_LEN2)}-p${String(i + 1).padStart(3, "0")}.png`
2971
+ });
2972
+ }
2973
+ return { parts, pages: pages.length, pngBytes, selfTestError };
2974
+ }
2975
+ function partsOf(msg) {
2976
+ if (!msg || typeof msg !== "object")
2977
+ return null;
2978
+ const parts = msg.parts;
2979
+ return Array.isArray(parts) ? parts : null;
2980
+ }
2981
+ function partCharLen(p) {
2982
+ if (!p || typeof p !== "object")
2983
+ return 0;
2984
+ const part = p;
2985
+ if (part.type === "text" && typeof part.text === "string")
2986
+ return part.text.length;
2987
+ if (part.type === "file" && typeof part.url === "string")
2988
+ return part.url.length;
2989
+ if (part.type === "tool" && part.state && typeof part.state === "object") {
2990
+ const out = part.state.output;
2991
+ if (typeof out === "string")
2992
+ return out.length;
2993
+ }
2994
+ return 0;
2995
+ }
2996
+ function maybeRecodePart(p, minPartChars, stash) {
2997
+ if (!p || typeof p !== "object")
2998
+ return null;
2999
+ const part = p;
3000
+ if (part.type !== "text" || typeof part.text !== "string")
3001
+ return null;
3002
+ const text = part.text;
3003
+ if (CCR_MARKER_RE2.test(text))
3004
+ return null;
3005
+ if (text.length < minPartChars)
3006
+ return null;
3007
+ const hash = stash(text);
3008
+ const { parts: fileParts, pages, pngBytes, selfTestError } = renderTextToImageParts(text, hash);
3009
+ const breadcrumb = {
3010
+ type: "text",
3011
+ text: `[greprag-png-recode: ${pages} page(s) of prior history rendered as pixel-text image(s) above; verbatim original ${ccrPngMarker(hash)} (greprag retrieve ${hash.slice(0, CCR_MARKER_HASH_LEN2)})]`
3012
+ };
3013
+ return { fileParts, breadcrumb, pages, pngBytes, selfTestError };
3014
+ }
3015
+ function recodeMessagesToPng(messages, opts) {
3016
+ const thresholdChars = opts.thresholdChars ?? DEFAULT_THRESHOLD_CHARS;
3017
+ const minPartChars = opts.minPartChars ?? DEFAULT_MIN_PART_CHARS;
3018
+ const liveTail = opts.liveTailMessages ?? DEFAULT_LIVE_TAIL_MESSAGES;
3019
+ const stats = {
3020
+ fired: false,
3021
+ visionCapable: false,
3022
+ totalChars: 0,
3023
+ thresholdChars,
3024
+ partsRecoded: 0,
3025
+ pagesRendered: 0,
3026
+ pngBytes: 0,
3027
+ selfTestError: null,
3028
+ reason: ""
3029
+ };
3030
+ if (!Array.isArray(messages)) {
3031
+ stats.reason = "messages not an array";
3032
+ return stats;
3033
+ }
3034
+ for (const msg of messages) {
3035
+ const parts = partsOf(msg);
3036
+ if (!parts)
3037
+ continue;
3038
+ for (const p of parts)
3039
+ stats.totalChars += partCharLen(p);
3040
+ }
3041
+ const vision = isVisionCapable(opts);
3042
+ stats.visionCapable = vision;
3043
+ if (!vision) {
3044
+ stats.reason = `model not vision-capable (modelId=${opts.modelId || "?"}) \u2014 pass-through`;
3045
+ return stats;
3046
+ }
3047
+ if (stats.totalChars < thresholdChars) {
3048
+ stats.reason = `below threshold (${stats.totalChars} < ${thresholdChars} chars)`;
3049
+ return stats;
3050
+ }
3051
+ const lastRecodableIdx = Math.max(0, messages.length - Math.max(0, liveTail));
3052
+ for (let mi = 0; mi < lastRecodableIdx; mi++) {
3053
+ const msg = messages[mi];
3054
+ const parts = partsOf(msg);
3055
+ if (!parts)
3056
+ continue;
3057
+ let changed = false;
3058
+ const newParts = [];
3059
+ for (const p of parts) {
3060
+ let rec = null;
3061
+ try {
3062
+ rec = maybeRecodePart(p, minPartChars, opts.stash);
3063
+ } catch {
3064
+ rec = null;
3065
+ }
3066
+ if (rec) {
3067
+ newParts.push(...rec.fileParts, rec.breadcrumb);
3068
+ changed = true;
3069
+ stats.partsRecoded++;
3070
+ stats.pagesRendered += rec.pages;
3071
+ stats.pngBytes += rec.pngBytes;
3072
+ if (rec.selfTestError && !stats.selfTestError)
3073
+ stats.selfTestError = rec.selfTestError;
3074
+ } else {
3075
+ newParts.push(p);
3076
+ }
3077
+ }
3078
+ if (changed)
3079
+ msg.parts = newParts;
3080
+ }
3081
+ stats.fired = stats.partsRecoded > 0;
3082
+ stats.reason = stats.fired ? `recoded ${stats.partsRecoded} part(s) -> ${stats.pagesRendered} page(s), ${stats.pngBytes} png bytes` : "threshold crossed but no recodable text parts (all below per-part floor or already marked)";
3083
+ return stats;
3084
+ }
3085
+
2503
3086
  // src/opencode-plugin.ts
2504
3087
  var DEBUG_LOG_PATH = path3.join(os.homedir(), ".greprag", "opencode-plugin-debug.log");
2505
3088
  var _debugLogReady = false;
@@ -2748,11 +3331,11 @@ function startCaptureWatcher() {
2748
3331
  fs3.writeFileSync(WATCHER_LOCK, String(child.pid));
2749
3332
  } catch {
2750
3333
  }
2751
- child.stderr?.on("data", (chunk) => {
2752
- process.stderr.write(`[greprag-watch] ${chunk.toString("utf-8")}`);
3334
+ child.stderr?.on("data", (chunk2) => {
3335
+ process.stderr.write(`[greprag-watch] ${chunk2.toString("utf-8")}`);
2753
3336
  });
2754
- child.stdout?.on("data", (chunk) => {
2755
- process.stderr.write(`[greprag-watch] ${chunk.toString("utf-8")}`);
3337
+ child.stdout?.on("data", (chunk2) => {
3338
+ process.stderr.write(`[greprag-watch] ${chunk2.toString("utf-8")}`);
2756
3339
  });
2757
3340
  child.on("exit", (code, signal) => {
2758
3341
  dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
@@ -2957,11 +3540,11 @@ function startSessionRelay(sessionId, serverUrl) {
2957
3540
  fs3.writeFileSync(lockPath, String(child.pid));
2958
3541
  } catch {
2959
3542
  }
2960
- child.stderr?.on("data", (chunk) => {
2961
- process.stderr.write(`[greprag-relay] ${chunk.toString("utf-8")}`);
3543
+ child.stderr?.on("data", (chunk2) => {
3544
+ process.stderr.write(`[greprag-relay] ${chunk2.toString("utf-8")}`);
2962
3545
  });
2963
- child.stdout?.on("data", (chunk) => {
2964
- process.stderr.write(`[greprag-relay] ${chunk.toString("utf-8")}`);
3546
+ child.stdout?.on("data", (chunk2) => {
3547
+ process.stderr.write(`[greprag-relay] ${chunk2.toString("utf-8")}`);
2965
3548
  });
2966
3549
  child.on("exit", (code, signal) => {
2967
3550
  try {
@@ -3051,6 +3634,39 @@ function inlineCrushEnabled() {
3051
3634
  const v = process.env.GREPRAG_OPENCODE_CRUSH;
3052
3635
  return v === "true" || v === "1";
3053
3636
  }
3637
+ function pngRecodeEnabled() {
3638
+ const v = process.env.GREPRAG_OPENCODE_PNG;
3639
+ return v === "true" || v === "1";
3640
+ }
3641
+ function intEnv(name, allowZero = false) {
3642
+ const n = parseInt(process.env[name] || "", 10);
3643
+ if (!Number.isFinite(n))
3644
+ return void 0;
3645
+ return n > 0 || allowZero && n === 0 ? n : void 0;
3646
+ }
3647
+ function runPngRecode(input, msgs) {
3648
+ const model = input?.model;
3649
+ const modelId = model?.modelID || model?.id || model?.name || null;
3650
+ const modalities = model?.modalities || model?.info?.modalities || null;
3651
+ const allowEnv = (process.env.GREPRAG_OPENCODE_PNG_VISION_MODELS || "").split(",").map((s) => s.trim()).filter(Boolean);
3652
+ const stash = makeCcrStash();
3653
+ const stats = recodeMessagesToPng(msgs, {
3654
+ stash: (original) => stash(original, "log"),
3655
+ modelId,
3656
+ modalities,
3657
+ thresholdChars: intEnv("GREPRAG_OPENCODE_PNG_THRESHOLD"),
3658
+ minPartChars: intEnv("GREPRAG_OPENCODE_PNG_MIN_PART"),
3659
+ liveTailMessages: intEnv("GREPRAG_OPENCODE_PNG_TAIL", true),
3660
+ visionAllowlist: allowEnv.length ? allowEnv : void 0
3661
+ });
3662
+ if (stats.partsRecoded > 0) {
3663
+ dlog(
3664
+ `messages.transform png: recoded=${stats.partsRecoded} pages=${stats.pagesRendered} pngBytes=${stats.pngBytes} totalChars=${stats.totalChars} vision=${stats.visionCapable}` + (stats.selfTestError ? ` selfTestError=${stats.selfTestError}` : "")
3665
+ );
3666
+ } else {
3667
+ dlog(`messages.transform png: no-op (${stats.reason})`);
3668
+ }
3669
+ }
3054
3670
  function makeCcrStash() {
3055
3671
  return (original, sourceType) => {
3056
3672
  const hash = crypto2.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
@@ -3189,7 +3805,16 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
3189
3805
  // Contract: we mutate ONLY this request-scoped `output.messages` copy,
3190
3806
  // never opencode's persisted session history. adr:
3191
3807
  // adr/opencode-context-compressor.md
3192
- "experimental.chat.messages.transform": async (_input, output) => {
3808
+ "experimental.chat.messages.transform": async (input, output) => {
3809
+ try {
3810
+ if (pngRecodeEnabled()) {
3811
+ const msgs0 = output && output.messages;
3812
+ if (Array.isArray(msgs0))
3813
+ runPngRecode(input, msgs0);
3814
+ }
3815
+ } catch (e) {
3816
+ dlog(`messages.transform png: swallowed error (turn unaffected): ${e.message}`);
3817
+ }
3193
3818
  try {
3194
3819
  if (!inlineCrushEnabled())
3195
3820
  return;