greprag 5.61.1 → 5.63.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.
- package/dist/codex-chip-hooks.js +2 -1
- package/dist/codex-chip-hooks.js.map +1 -1
- package/dist/commands/arm-reminder.js +5 -0
- package/dist/commands/arm-reminder.js.map +1 -1
- package/dist/commands/codex-chip/prompt.js +1 -1
- package/dist/commands/codex-chip/prompt.js.map +1 -1
- package/dist/commands/inbox-primer-reminder.js +10 -2
- package/dist/commands/inbox-primer-reminder.js.map +1 -1
- package/dist/commands/load-primer-reminder.d.ts +6 -0
- package/dist/commands/load-primer-reminder.js +22 -2
- package/dist/commands/load-primer-reminder.js.map +1 -1
- package/dist/commands/mechanic-manifest.d.ts +12 -0
- package/dist/commands/mechanic-manifest.js +62 -0
- package/dist/commands/mechanic-manifest.js.map +1 -1
- package/dist/commands/mechanic-spawn.js +37 -2
- package/dist/commands/mechanic-spawn.js.map +1 -1
- package/dist/commands/mechanic.js +29 -1
- package/dist/commands/mechanic.js.map +1 -1
- package/dist/commands/opencode-chip-mission.js +1 -0
- package/dist/commands/opencode-chip-mission.js.map +1 -1
- package/dist/commands/opencode-interrupt.d.ts +15 -5
- package/dist/commands/opencode-interrupt.js +22 -20
- package/dist/commands/opencode-interrupt.js.map +1 -1
- package/dist/commands/os-primer-reminder.js +5 -2
- package/dist/commands/os-primer-reminder.js.map +1 -1
- package/dist/commands/reminder-registry.d.ts +7 -0
- package/dist/commands/reminder-registry.js +9 -1
- package/dist/commands/reminder-registry.js.map +1 -1
- package/dist/commands/reminder-types.d.ts +13 -1
- package/dist/commands/state-trigger.d.ts +17 -0
- package/dist/commands/state-trigger.js +152 -16
- package/dist/commands/state-trigger.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin-png.d.ts +151 -0
- package/dist/opencode-plugin-png.js +275 -0
- package/dist/opencode-plugin-png.js.map +1 -0
- package/dist/opencode-plugin.bundle.js +683 -29
- package/dist/opencode-plugin.js +76 -1
- package/dist/opencode-plugin.js.map +1 -1
- package/package.json +2 -2
- package/skill/greprag/docs/codex-chip.md +5 -0
- package/skill/mechanic/SKILL.md +5 -0
- package/skill/templates/chip-spawn.md +8 -0
- package/skill/templates/codex-chip-spawn.md +23 -0
|
@@ -1747,7 +1747,7 @@ function crushMessages(messages, opts) {
|
|
|
1747
1747
|
|
|
1748
1748
|
// src/commands/os-primer-reminder.ts
|
|
1749
1749
|
function buildOsPrimer(env) {
|
|
1750
|
-
const spawnEntry = env?.platform === "codex" ? "codex-chip-spawn" : "chip-spawn";
|
|
1750
|
+
const spawnEntry = env?.platform === "codex" ? "codex-chip-spawn" : env?.platform === "opencode" ? "chip-bootloader" : "chip-spawn";
|
|
1751
1751
|
return [
|
|
1752
1752
|
"[grepragOS \u2014 the operating laws. Full doctrine: `greprag load os`.]",
|
|
1753
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.',
|
|
@@ -1776,14 +1776,18 @@ function armMonitorCommand(short, ownerPid, assistant = false, mechanic = false)
|
|
|
1776
1776
|
// src/commands/inbox-primer-reminder.ts
|
|
1777
1777
|
function buildInboxPrimer(env) {
|
|
1778
1778
|
const codex = env.platform === "codex";
|
|
1779
|
+
const opencode = env.platform === "opencode";
|
|
1779
1780
|
const arm = armMonitorCommand(env.short, env.ownerPid, env.assistant, env.mechanic);
|
|
1780
1781
|
return [
|
|
1781
1782
|
// LEAD — the forcing function (restored 2026-06-22). ARM is an ALARM, not reference:
|
|
1782
1783
|
// you are unreachable until you do it, and it must be re-done every time the watcher
|
|
1783
1784
|
// drops. It leads the announce and is loud by design — folding it into a buried bullet
|
|
1784
1785
|
// (v5.49.6) softened it into banner-blindness (fix 16a29a8b's failure mode).
|
|
1785
|
-
|
|
1786
|
-
|
|
1786
|
+
// opencode gets NO alarm: its relay self-arms in the plugin (startSessionRelay) —
|
|
1787
|
+
// "ToolSearch select:Monitor" is unactionable there (neither tool exists) and an
|
|
1788
|
+
// un-followable alarm decays into banner-blindness for the announces around it.
|
|
1789
|
+
codex ? "[CODEX INBOX \u2014 greprag is your agent-to-agent mesh. Codex-to-Codex: use native task messaging. Reaching ANY non-Codex peer (Claude Code / opencode / another tenant's agent): `greprag send` IS the live primary path \u2014 native Codex messaging cannot reach them.]" : opencode ? "[OPENCODE INBOX \u2014 greprag is your agent-to-agent mesh. Inbound delivery is AUTOMATIC here: the greprag plugin arms a relay for this session (nothing to arm or re-arm) and inbound peer messages arrive as injected turns.]" : "[\u26A0 ARM YOUR INBOX WATCHER NOW \u2014 you are UNREACHABLE until you do. Peers are live in your mesh trying to coordinate with this session, and the operator can DM you \u2014 but they reach you ONLY while armed. Unarmed, you are invisible and their messages silently pile up. The watcher can DROP ANYTIME (crash / reload / OOM) \u2014 EVERY turn you are flagged unarmed you MUST re-arm, or coordination breaks.]",
|
|
1790
|
+
codex ? "CODEX: Native task/thread messaging is the normal live Codex-to-Codex path. Install the GrepRAG startup watcher with `greprag init --codex --tenant-id <handle> --install-watcher` or `greprag codex startup install` \u2014 it is how messages from non-Codex peers (Claude Code / opencode) reach you; without it they pile up unread. Turn hooks are boundary fallback: if hooks exist but do not fire, open Codex Desktop Settings -> Settings -> Hooks, trust the GrepRAG commands, and start a fresh Codex session. Then drain what is waiting: `greprag inbox`." : opencode ? "DELIVERY: the relay arms itself on this session's first turn and injects inbound messages as they land \u2014 treat an injected `Message from <handle> (session <8hex>):` turn as async peer mail, not the operator typing. Drain anything already waiting: `greprag inbox`." : `ARM: Monitor is a deferred tool \u2014 \`ToolSearch select:Monitor\`, then call Monitor with persistent:true, timeout_ms:3600000, description:"greprag inbox ${env.short}", command: \`${arm}\`. The \`--session ${env.short}\` is REQUIRED \u2014 a bare \`greprag inbox watch\` writes no pidfile, so the arm-check reads you unarmed forever. NEVER run it bare also because it is a foreground stream and hangs the turn. Then drain what is waiting: \`greprag inbox\`.`,
|
|
1787
1791
|
"",
|
|
1788
1792
|
// The facts + directives — a flat list (no MODEL/RULES scaffolding; the labels were
|
|
1789
1793
|
// human doc-structure, dead weight to an agent). Everything that helps the agent DECIDE
|
|
@@ -1818,6 +1822,10 @@ var CHIP_SPAWN_POINTER = [
|
|
|
1818
1822
|
"[greprag chips \u2014 agent coordination is built in (messaging plumbing in the inbox primer).]",
|
|
1819
1823
|
"\u2022 About to spawn a chip (`spawn_task`)? \u2192 run `greprag load chip-spawn` FIRST for the method (Block 1 worktree + Block 2 report-back); it carries the multi-chip upgrade path inline. Non-negotiable \u2014 the spawn validator rejects an unstructured chip."
|
|
1820
1824
|
].join("\n");
|
|
1825
|
+
var OPENCODE_CHIP_SPAWN_POINTER = [
|
|
1826
|
+
"[greprag chips \u2014 agent coordination is built in (messaging plumbing in the inbox primer).]",
|
|
1827
|
+
"\u2022 Delegating a component of a plan to an isolated chip session, or `greprag fix spawn` just printed a FIX-chip mission? \u2192 run `greprag load chip-bootloader` FIRST for the method: OpenCode chips spawn natively via `greprag opencode chip goal create` + `greprag opencode chip spawn` (HTTP child session; manual paste only when the API is down). \u22652 chips at one objective \u2192 `greprag load chip-leader-opencode` BEFORE the first spawn."
|
|
1828
|
+
].join("\n");
|
|
1821
1829
|
var CODEX_CHIP_SPAWN_POINTER = [
|
|
1822
1830
|
"[greprag Codex delegation \u2014 internal subagent, 1\u20132-chip quick path with the initiator as LEAD, or a separate LEAD for larger/seamed missions.]",
|
|
1823
1831
|
"\u2022 About to delegate a Codex task? \u2192 run `greprag load codex-chip-spawn` FIRST. Use exact first-line titles `LEAD: <Mission>` and `Chip A/B/C: <Specific Purview>`. Chips are ordinary writable native tasks; completion is a native Codex task reply to the LEAD after committing. A `MECHANIC: <Mission>` task loads `greprag load mechanic` first."
|
|
@@ -1833,7 +1841,18 @@ var chipSpawnPointerModule = {
|
|
|
1833
1841
|
// Says "greprag load chip-spawn" → the agent must already know the loader exists.
|
|
1834
1842
|
dependsOn: ["load-primer"],
|
|
1835
1843
|
detect: (_env) => ({ tier: "silent" }),
|
|
1836
|
-
|
|
1844
|
+
// Per-platform payload, ONE module: codex has its own sibling module below;
|
|
1845
|
+
// opencode gets the native HTTP-spawn pointer (spawn_task doesn't exist there);
|
|
1846
|
+
// claude-code keeps the spawn_task pointer. Gating INSIDE the module (not a
|
|
1847
|
+
// per-harness registry fork) is what lets harnessModules() stay a pure
|
|
1848
|
+
// exclusion filter.
|
|
1849
|
+
announce: (env) => {
|
|
1850
|
+
if (env.platform === "codex")
|
|
1851
|
+
return null;
|
|
1852
|
+
if (env.platform === "opencode")
|
|
1853
|
+
return OPENCODE_CHIP_SPAWN_POINTER;
|
|
1854
|
+
return CHIP_SPAWN_POINTER;
|
|
1855
|
+
},
|
|
1837
1856
|
reminder: () => null
|
|
1838
1857
|
};
|
|
1839
1858
|
var codexChipSpawnPointerModule = {
|
|
@@ -1969,6 +1988,11 @@ function buildArmReminder(d, env) {
|
|
|
1969
1988
|
}
|
|
1970
1989
|
var watcherArmModule = {
|
|
1971
1990
|
id: "watcher-arm",
|
|
1991
|
+
// The arm poke rides Claude Code's Monitor tool (ToolSearch select:Monitor); codex
|
|
1992
|
+
// keeps its historical full-registry behavior. opencode is EXCLUDED: its inbox
|
|
1993
|
+
// delivery is the plugin-armed relay (startSessionRelay), so "arm your Monitor"
|
|
1994
|
+
// is unactionable noise there.
|
|
1995
|
+
harnesses: ["claude-code", "codex"],
|
|
1972
1996
|
detect: armDetect,
|
|
1973
1997
|
announce: () => null,
|
|
1974
1998
|
reminder: (d, env) => buildArmReminder(d, env)
|
|
@@ -2298,6 +2322,7 @@ var REGISTRY = [
|
|
|
2298
2322
|
collisionMatchModule
|
|
2299
2323
|
// source: 'command' — evaluated at PreToolUse, not UserPromptSubmit
|
|
2300
2324
|
];
|
|
2325
|
+
var harnessModules = (harness, registry = REGISTRY) => registry.filter((m) => !m.harnesses || m.harnesses.includes(harness));
|
|
2301
2326
|
function collectReminders(env, registry = REGISTRY) {
|
|
2302
2327
|
const out = [];
|
|
2303
2328
|
for (const m of registry) {
|
|
@@ -2358,26 +2383,22 @@ function collectAnnounces(env, registry = REGISTRY) {
|
|
|
2358
2383
|
}
|
|
2359
2384
|
|
|
2360
2385
|
// src/commands/opencode-interrupt.ts
|
|
2361
|
-
var
|
|
2362
|
-
"inbox-primer",
|
|
2363
|
-
"load-primer",
|
|
2364
|
-
"corpus-announce",
|
|
2365
|
-
"setup-warning",
|
|
2366
|
-
"version-upgrade",
|
|
2367
|
-
"memory-primer",
|
|
2368
|
-
"procedure-announce",
|
|
2369
|
-
"collision"
|
|
2370
|
-
]);
|
|
2371
|
-
var opencodeRegistry = REGISTRY.filter(
|
|
2372
|
-
(m) => OPENCODE_MODULE_IDS.has(m.id)
|
|
2373
|
-
);
|
|
2386
|
+
var opencodeRegistry = harnessModules("opencode");
|
|
2374
2387
|
function buildOpenCodeEnv(params) {
|
|
2375
2388
|
return {
|
|
2376
2389
|
short: params.short,
|
|
2377
2390
|
turnCount: params.turnCount,
|
|
2391
|
+
// No opencode-native friction detector is wired yet: Claude Code's stress /
|
|
2392
|
+
// bootstrap-friction signals come from state-trigger.ts reading the
|
|
2393
|
+
// transcript in hook.ts, and opencode has no equivalent source plumbed.
|
|
2394
|
+
// 0 = calm keeps the mechanic-friction REMINDER silent while its ANNOUNCE
|
|
2395
|
+
// (the fix-spawn doctrine) still lands every session. Detector design is
|
|
2396
|
+
// parked with scored options in the chip report
|
|
2397
|
+
// (chip/opencode-fix-chip-system-parity).
|
|
2378
2398
|
stress: 0,
|
|
2379
|
-
armed: false,
|
|
2399
|
+
armed: params.armed ?? false,
|
|
2380
2400
|
sessionUnread: 0,
|
|
2401
|
+
platform: "opencode",
|
|
2381
2402
|
projectName: params.projectName,
|
|
2382
2403
|
setupWarning: params.setupWarning ?? null,
|
|
2383
2404
|
corpusApiDocs: params.corpusApiDocs ?? [],
|
|
@@ -2385,9 +2406,8 @@ function buildOpenCodeEnv(params) {
|
|
|
2385
2406
|
procedureAnnounces: params.procedureAnnounces ?? []
|
|
2386
2407
|
};
|
|
2387
2408
|
}
|
|
2388
|
-
var OPENCODE_CHIP_BOOTLOADER_POINTER = '[greprag chips \u2014 agent coordination is built in (messaging plumbing in the inbox primer).]\n\u2022 Delegating a component of a plan to an isolated sub-session (a "chip") for focused, efficient work? \u2192 run `greprag load chip-bootloader` for the copyable bootloader template (Block 1 setup + Block 2 report-back).';
|
|
2389
2409
|
function getOpenCodeAnnounces(env) {
|
|
2390
|
-
return
|
|
2410
|
+
return collectAnnounces(env, opencodeRegistry);
|
|
2391
2411
|
}
|
|
2392
2412
|
function getOpenCodeReminders(env) {
|
|
2393
2413
|
return collectReminders(env, opencodeRegistry);
|
|
@@ -2501,6 +2521,588 @@ function activeProcedureAnnounces(store) {
|
|
|
2501
2521
|
return lines;
|
|
2502
2522
|
}
|
|
2503
2523
|
|
|
2524
|
+
// src/archive/glyph-font.ts
|
|
2525
|
+
var GLYPH_W = 8;
|
|
2526
|
+
var GLYPH_H = 8;
|
|
2527
|
+
var FIRST_CODE = 32;
|
|
2528
|
+
var LAST_CODE = 126;
|
|
2529
|
+
var FONT_HEX = [
|
|
2530
|
+
"0000000000000000",
|
|
2531
|
+
"183c3c1818001800",
|
|
2532
|
+
"3636000000000000",
|
|
2533
|
+
"36367f367f363600",
|
|
2534
|
+
"0c3e031e301f0c00",
|
|
2535
|
+
"006333180c666300",
|
|
2536
|
+
"1c361c6e3b336e00",
|
|
2537
|
+
"0606030000000000",
|
|
2538
|
+
"180c0606060c1800",
|
|
2539
|
+
"060c1818180c0600",
|
|
2540
|
+
"00663cff3c660000",
|
|
2541
|
+
"000c0c3f0c0c0000",
|
|
2542
|
+
"00000000000c0c06",
|
|
2543
|
+
"0000003f00000000",
|
|
2544
|
+
"00000000000c0c00",
|
|
2545
|
+
"6030180c06030100",
|
|
2546
|
+
"3e63737b6f673e00",
|
|
2547
|
+
"0c0e0c0c0c0c3f00",
|
|
2548
|
+
"1e33301c06333f00",
|
|
2549
|
+
"1e33301c30331e00",
|
|
2550
|
+
"383c36337f307800",
|
|
2551
|
+
"3f031f3030331e00",
|
|
2552
|
+
"1c06031f33331e00",
|
|
2553
|
+
"3f3330180c0c0c00",
|
|
2554
|
+
"1e33331e33331e00",
|
|
2555
|
+
"1e33333e30180e00",
|
|
2556
|
+
"000c0c00000c0c00",
|
|
2557
|
+
"000c0c00000c0c06",
|
|
2558
|
+
"180c0603060c1800",
|
|
2559
|
+
"00003f00003f0000",
|
|
2560
|
+
"060c1830180c0600",
|
|
2561
|
+
"1e3330180c000c00",
|
|
2562
|
+
"3e637b7b7b031e00",
|
|
2563
|
+
"0c1e33333f333300",
|
|
2564
|
+
"3f66663e66663f00",
|
|
2565
|
+
"3c66030303663c00",
|
|
2566
|
+
"1f36666666361f00",
|
|
2567
|
+
"7f46161e16467f00",
|
|
2568
|
+
"7f46161e16060f00",
|
|
2569
|
+
"3c66030373667c00",
|
|
2570
|
+
"3333333f33333300",
|
|
2571
|
+
"1e0c0c0c0c0c1e00",
|
|
2572
|
+
"7830303033331e00",
|
|
2573
|
+
"6766361e36666700",
|
|
2574
|
+
"0f06060646667f00",
|
|
2575
|
+
"63777f7f6b636300",
|
|
2576
|
+
"63676f7b73636300",
|
|
2577
|
+
"1c36636363361c00",
|
|
2578
|
+
"3f66663e06060f00",
|
|
2579
|
+
"1e3333333b1e3800",
|
|
2580
|
+
"3f66663e36666700",
|
|
2581
|
+
"1e33070e38331e00",
|
|
2582
|
+
"3f2d0c0c0c0c1e00",
|
|
2583
|
+
"3333333333333f00",
|
|
2584
|
+
"33333333331e0c00",
|
|
2585
|
+
"6363636b7f776300",
|
|
2586
|
+
"6363361c1c366300",
|
|
2587
|
+
"3333331e0c0c1e00",
|
|
2588
|
+
"7f6331184c667f00",
|
|
2589
|
+
"1e06060606061e00",
|
|
2590
|
+
"03060c1830604000",
|
|
2591
|
+
"1e18181818181e00",
|
|
2592
|
+
"081c366300000000",
|
|
2593
|
+
"00000000000000ff",
|
|
2594
|
+
"0c0c180000000000",
|
|
2595
|
+
"00001e303e336e00",
|
|
2596
|
+
"0706063e66663b00",
|
|
2597
|
+
"00001e3303331e00",
|
|
2598
|
+
"3830303e33336e00",
|
|
2599
|
+
"00001e333f031e00",
|
|
2600
|
+
"1c36060f06060f00",
|
|
2601
|
+
"00006e33333e301f",
|
|
2602
|
+
"0706366e66666700",
|
|
2603
|
+
"0c000e0c0c0c1e00",
|
|
2604
|
+
"300030303033331e",
|
|
2605
|
+
"070666361e366700",
|
|
2606
|
+
"0e0c0c0c0c0c1e00",
|
|
2607
|
+
"0000337f7f6b6300",
|
|
2608
|
+
"00001f3333333300",
|
|
2609
|
+
"00001e3333331e00",
|
|
2610
|
+
"00003b66663e060f",
|
|
2611
|
+
"00006e33333e3078",
|
|
2612
|
+
"00003b6e66060f00",
|
|
2613
|
+
"00003e031e301f00",
|
|
2614
|
+
"080c3e0c0c2c1800",
|
|
2615
|
+
"0000333333336e00",
|
|
2616
|
+
"00003333331e0c00",
|
|
2617
|
+
"0000636b7f7f3600",
|
|
2618
|
+
"000063361c366300",
|
|
2619
|
+
"00003333333e301f",
|
|
2620
|
+
"00003f190c263f00",
|
|
2621
|
+
"380c0c070c0c3800",
|
|
2622
|
+
"1818180018181800",
|
|
2623
|
+
"070c0c380c0c0700",
|
|
2624
|
+
"6e3b000000000000"
|
|
2625
|
+
];
|
|
2626
|
+
function glyphRows(code) {
|
|
2627
|
+
if (code < FIRST_CODE || code > LAST_CODE)
|
|
2628
|
+
return null;
|
|
2629
|
+
const hex = FONT_HEX[code - FIRST_CODE];
|
|
2630
|
+
const out = [];
|
|
2631
|
+
for (let i = 0; i < 8; i++)
|
|
2632
|
+
out.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16));
|
|
2633
|
+
return out;
|
|
2634
|
+
}
|
|
2635
|
+
var reverse = null;
|
|
2636
|
+
function codeForSignature(sigHex) {
|
|
2637
|
+
if (!reverse) {
|
|
2638
|
+
reverse = /* @__PURE__ */ new Map();
|
|
2639
|
+
for (let c = FIRST_CODE; c <= LAST_CODE; c++)
|
|
2640
|
+
reverse.set(FONT_HEX[c - FIRST_CODE], c);
|
|
2641
|
+
}
|
|
2642
|
+
return reverse.get(sigHex) ?? null;
|
|
2643
|
+
}
|
|
2644
|
+
function signatureForRows(bytes) {
|
|
2645
|
+
return bytes.map((b) => (b & 255).toString(16).padStart(2, "0")).join("");
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// src/archive/png-codec.ts
|
|
2649
|
+
var zlib = __toESM(require("zlib"));
|
|
2650
|
+
var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
2651
|
+
var crcTable = null;
|
|
2652
|
+
function getCrcTable() {
|
|
2653
|
+
if (crcTable)
|
|
2654
|
+
return crcTable;
|
|
2655
|
+
crcTable = new Uint32Array(256);
|
|
2656
|
+
for (let n = 0; n < 256; n++) {
|
|
2657
|
+
let c = n;
|
|
2658
|
+
for (let k = 0; k < 8; k++) {
|
|
2659
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
2660
|
+
}
|
|
2661
|
+
crcTable[n] = c >>> 0;
|
|
2662
|
+
}
|
|
2663
|
+
return crcTable;
|
|
2664
|
+
}
|
|
2665
|
+
function crc32(buf) {
|
|
2666
|
+
const table = getCrcTable();
|
|
2667
|
+
let c = 4294967295;
|
|
2668
|
+
for (let i = 0; i < buf.length; i++) {
|
|
2669
|
+
c = table[(c ^ buf[i]) & 255] ^ c >>> 8;
|
|
2670
|
+
}
|
|
2671
|
+
return (c ^ 4294967295) >>> 0;
|
|
2672
|
+
}
|
|
2673
|
+
function chunk(type, data) {
|
|
2674
|
+
const len = Buffer.alloc(4);
|
|
2675
|
+
len.writeUInt32BE(data.length, 0);
|
|
2676
|
+
const typeAndData = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
2677
|
+
const crc = Buffer.alloc(4);
|
|
2678
|
+
crc.writeUInt32BE(crc32(typeAndData), 0);
|
|
2679
|
+
return Buffer.concat([len, typeAndData, crc]);
|
|
2680
|
+
}
|
|
2681
|
+
function encodePng(img) {
|
|
2682
|
+
const { width, height, pixels } = img;
|
|
2683
|
+
if (pixels.length !== width * height) {
|
|
2684
|
+
throw new Error(`pixel buffer ${pixels.length} != ${width}x${height}`);
|
|
2685
|
+
}
|
|
2686
|
+
const ihdr = Buffer.alloc(13);
|
|
2687
|
+
ihdr.writeUInt32BE(width, 0);
|
|
2688
|
+
ihdr.writeUInt32BE(height, 4);
|
|
2689
|
+
ihdr[8] = 8;
|
|
2690
|
+
ihdr[9] = 0;
|
|
2691
|
+
ihdr[10] = 0;
|
|
2692
|
+
ihdr[11] = 0;
|
|
2693
|
+
ihdr[12] = 0;
|
|
2694
|
+
const raw = Buffer.alloc(height * (width + 1));
|
|
2695
|
+
for (let y = 0; y < height; y++) {
|
|
2696
|
+
raw[y * (width + 1)] = 0;
|
|
2697
|
+
pixels.copy(raw, y * (width + 1) + 1, y * width, (y + 1) * width);
|
|
2698
|
+
}
|
|
2699
|
+
const idat = zlib.deflateSync(raw, { level: 9 });
|
|
2700
|
+
return Buffer.concat([
|
|
2701
|
+
PNG_SIGNATURE,
|
|
2702
|
+
chunk("IHDR", ihdr),
|
|
2703
|
+
chunk("IDAT", idat),
|
|
2704
|
+
chunk("IEND", Buffer.alloc(0))
|
|
2705
|
+
]);
|
|
2706
|
+
}
|
|
2707
|
+
function paeth(a, b, c) {
|
|
2708
|
+
const p = a + b - c;
|
|
2709
|
+
const pa = Math.abs(p - a);
|
|
2710
|
+
const pb = Math.abs(p - b);
|
|
2711
|
+
const pc = Math.abs(p - c);
|
|
2712
|
+
if (pa <= pb && pa <= pc)
|
|
2713
|
+
return a;
|
|
2714
|
+
if (pb <= pc)
|
|
2715
|
+
return b;
|
|
2716
|
+
return c;
|
|
2717
|
+
}
|
|
2718
|
+
function decodePng(buf) {
|
|
2719
|
+
if (buf.length < 8 || !buf.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
2720
|
+
throw new Error("not a PNG (bad signature)");
|
|
2721
|
+
}
|
|
2722
|
+
let width = 0;
|
|
2723
|
+
let height = 0;
|
|
2724
|
+
const idatParts = [];
|
|
2725
|
+
let off = 8;
|
|
2726
|
+
while (off + 8 <= buf.length) {
|
|
2727
|
+
const len = buf.readUInt32BE(off);
|
|
2728
|
+
const type = buf.toString("ascii", off + 4, off + 8);
|
|
2729
|
+
const data = buf.subarray(off + 8, off + 8 + len);
|
|
2730
|
+
if (type === "IHDR") {
|
|
2731
|
+
width = data.readUInt32BE(0);
|
|
2732
|
+
height = data.readUInt32BE(4);
|
|
2733
|
+
const bitDepth = data[8];
|
|
2734
|
+
const colorType = data[9];
|
|
2735
|
+
const interlace = data[12];
|
|
2736
|
+
if (bitDepth !== 8 || colorType !== 0 || interlace !== 0) {
|
|
2737
|
+
throw new Error(`unsupported PNG (bitDepth=${bitDepth} colorType=${colorType} interlace=${interlace})`);
|
|
2738
|
+
}
|
|
2739
|
+
} else if (type === "IDAT") {
|
|
2740
|
+
idatParts.push(data);
|
|
2741
|
+
} else if (type === "IEND") {
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
off += 8 + len + 4;
|
|
2745
|
+
}
|
|
2746
|
+
if (!width || !height)
|
|
2747
|
+
throw new Error("PNG missing IHDR");
|
|
2748
|
+
if (idatParts.length === 0)
|
|
2749
|
+
throw new Error("PNG missing IDAT");
|
|
2750
|
+
const raw = zlib.inflateSync(Buffer.concat(idatParts));
|
|
2751
|
+
const stride = width + 1;
|
|
2752
|
+
if (raw.length < height * stride) {
|
|
2753
|
+
throw new Error(`PNG data short: ${raw.length} < ${height * stride}`);
|
|
2754
|
+
}
|
|
2755
|
+
const pixels = Buffer.alloc(width * height);
|
|
2756
|
+
for (let y = 0; y < height; y++) {
|
|
2757
|
+
const filter = raw[y * stride];
|
|
2758
|
+
const rowIn = raw.subarray(y * stride + 1, y * stride + 1 + width);
|
|
2759
|
+
const rowOut = pixels.subarray(y * width, (y + 1) * width);
|
|
2760
|
+
for (let x = 0; x < width; x++) {
|
|
2761
|
+
const a = x > 0 ? rowOut[x - 1] : 0;
|
|
2762
|
+
const b = y > 0 ? pixels[(y - 1) * width + x] : 0;
|
|
2763
|
+
const c = x > 0 && y > 0 ? pixels[(y - 1) * width + x - 1] : 0;
|
|
2764
|
+
let v;
|
|
2765
|
+
switch (filter) {
|
|
2766
|
+
case 0:
|
|
2767
|
+
v = rowIn[x];
|
|
2768
|
+
break;
|
|
2769
|
+
case 1:
|
|
2770
|
+
v = rowIn[x] + a;
|
|
2771
|
+
break;
|
|
2772
|
+
case 2:
|
|
2773
|
+
v = rowIn[x] + b;
|
|
2774
|
+
break;
|
|
2775
|
+
case 3:
|
|
2776
|
+
v = rowIn[x] + (a + b >> 1);
|
|
2777
|
+
break;
|
|
2778
|
+
case 4:
|
|
2779
|
+
v = rowIn[x] + paeth(a, b, c);
|
|
2780
|
+
break;
|
|
2781
|
+
default:
|
|
2782
|
+
throw new Error(`unsupported PNG filter ${filter} on row ${y}`);
|
|
2783
|
+
}
|
|
2784
|
+
rowOut[x] = v & 255;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return { width, height, pixels };
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// src/archive/page-renderer.ts
|
|
2791
|
+
var PAGE_SIZE = 1568;
|
|
2792
|
+
var CELL_W = GLYPH_W;
|
|
2793
|
+
var CELL_H = 10;
|
|
2794
|
+
var COLS = Math.floor(PAGE_SIZE / CELL_W);
|
|
2795
|
+
var ROWS = Math.floor(PAGE_SIZE / CELL_H);
|
|
2796
|
+
var CHARS_PER_PAGE = COLS * ROWS;
|
|
2797
|
+
var BG = 255;
|
|
2798
|
+
var FG = 0;
|
|
2799
|
+
function sanitizeForGrid(text) {
|
|
2800
|
+
const out = [];
|
|
2801
|
+
for (const ch of text.replace(/\r\n/g, "\n").replace(/\r/g, "\n")) {
|
|
2802
|
+
const code = ch.codePointAt(0);
|
|
2803
|
+
if (ch === "\n") {
|
|
2804
|
+
out.push("\n");
|
|
2805
|
+
} else if (ch === " ") {
|
|
2806
|
+
out.push("\\t");
|
|
2807
|
+
} else if (code >= 32 && code <= 126) {
|
|
2808
|
+
out.push(ch);
|
|
2809
|
+
} else if (code <= 65535) {
|
|
2810
|
+
out.push("\\u" + code.toString(16).padStart(4, "0"));
|
|
2811
|
+
} else {
|
|
2812
|
+
out.push("\\u{" + code.toString(16) + "}");
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
return out.join("");
|
|
2816
|
+
}
|
|
2817
|
+
function layoutRows(sanitized, cols = COLS) {
|
|
2818
|
+
const rows = [];
|
|
2819
|
+
let offset = 0;
|
|
2820
|
+
for (const line of sanitized.split("\n")) {
|
|
2821
|
+
if (line.length === 0) {
|
|
2822
|
+
rows.push({ text: " ".repeat(cols), charStart: offset });
|
|
2823
|
+
} else {
|
|
2824
|
+
for (let i = 0; i < line.length; i += cols) {
|
|
2825
|
+
const slice = line.slice(i, i + cols);
|
|
2826
|
+
rows.push({ text: slice.padEnd(cols, " "), charStart: offset + i });
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
offset += line.length + 1;
|
|
2830
|
+
}
|
|
2831
|
+
return rows;
|
|
2832
|
+
}
|
|
2833
|
+
function renderPage(pageRows, cols = COLS, rows = ROWS) {
|
|
2834
|
+
const rowCount = Math.max(1, Math.min(pageRows.length, rows));
|
|
2835
|
+
const width = cols * CELL_W;
|
|
2836
|
+
const height = rowCount * CELL_H;
|
|
2837
|
+
const pixels = Buffer.alloc(width * height, BG);
|
|
2838
|
+
for (let r = 0; r < Math.min(pageRows.length, rowCount); r++) {
|
|
2839
|
+
const row = pageRows[r];
|
|
2840
|
+
for (let c = 0; c < cols; c++) {
|
|
2841
|
+
const glyph = glyphRows(row.text.charCodeAt(c));
|
|
2842
|
+
if (!glyph)
|
|
2843
|
+
continue;
|
|
2844
|
+
const px = c * CELL_W;
|
|
2845
|
+
const py = r * CELL_H;
|
|
2846
|
+
for (let gy = 0; gy < GLYPH_H; gy++) {
|
|
2847
|
+
const bits = glyph[gy];
|
|
2848
|
+
if (bits === 0)
|
|
2849
|
+
continue;
|
|
2850
|
+
const base = (py + gy) * width + px;
|
|
2851
|
+
for (let gx = 0; gx < GLYPH_W; gx++) {
|
|
2852
|
+
if (bits & 1 << gx)
|
|
2853
|
+
pixels[base + gx] = FG;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
const img = { width, height, pixels };
|
|
2859
|
+
const charStart = pageRows.length ? pageRows[0].charStart : 0;
|
|
2860
|
+
const last = pageRows[pageRows.length - 1];
|
|
2861
|
+
const charEnd = last ? last.charStart + last.text.trimEnd().length : charStart;
|
|
2862
|
+
return {
|
|
2863
|
+
png: encodePng(img),
|
|
2864
|
+
gridText: pageRows.map((r) => r.text).join("\n"),
|
|
2865
|
+
charStart,
|
|
2866
|
+
charEnd
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function paginate(rowsIn, rowsPerPage = ROWS) {
|
|
2870
|
+
const pages = [];
|
|
2871
|
+
for (let i = 0; i < rowsIn.length; i += rowsPerPage) {
|
|
2872
|
+
pages.push(rowsIn.slice(i, i + rowsPerPage));
|
|
2873
|
+
}
|
|
2874
|
+
return pages;
|
|
2875
|
+
}
|
|
2876
|
+
function decodePageText(png, cols = COLS) {
|
|
2877
|
+
const img = decodePng(png);
|
|
2878
|
+
const rows = Math.floor(img.height / CELL_H);
|
|
2879
|
+
const usableCols = Math.min(cols, Math.floor(img.width / CELL_W));
|
|
2880
|
+
const lines = [];
|
|
2881
|
+
for (let r = 0; r < rows; r++) {
|
|
2882
|
+
const chars = [];
|
|
2883
|
+
for (let c = 0; c < usableCols; c++) {
|
|
2884
|
+
const bytes = [];
|
|
2885
|
+
for (let gy = 0; gy < GLYPH_H; gy++) {
|
|
2886
|
+
let b = 0;
|
|
2887
|
+
const base = (r * CELL_H + gy) * img.width + c * CELL_W;
|
|
2888
|
+
for (let gx = 0; gx < GLYPH_W; gx++) {
|
|
2889
|
+
if (img.pixels[base + gx] < 128)
|
|
2890
|
+
b |= 1 << gx;
|
|
2891
|
+
}
|
|
2892
|
+
bytes.push(b);
|
|
2893
|
+
}
|
|
2894
|
+
const code = codeForSignature(signatureForRows(bytes));
|
|
2895
|
+
chars.push(code === null ? "\uFFFD" : String.fromCharCode(code));
|
|
2896
|
+
}
|
|
2897
|
+
lines.push(chars.join(""));
|
|
2898
|
+
}
|
|
2899
|
+
return lines.join("\n");
|
|
2900
|
+
}
|
|
2901
|
+
function verifyRoundTrip(page) {
|
|
2902
|
+
const decoded = decodePageText(page.png);
|
|
2903
|
+
if (decoded === page.gridText)
|
|
2904
|
+
return null;
|
|
2905
|
+
const a = page.gridText;
|
|
2906
|
+
const b = decoded;
|
|
2907
|
+
const n = Math.min(a.length, b.length);
|
|
2908
|
+
for (let i = 0; i < n; i++) {
|
|
2909
|
+
if (a[i] !== b[i]) {
|
|
2910
|
+
return `mismatch at char ${i}: rendered ${JSON.stringify(a[i])} decoded ${JSON.stringify(b[i])}`;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
return `length mismatch: rendered ${a.length} decoded ${b.length}`;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
// src/opencode-plugin-png.ts
|
|
2917
|
+
var CCR_MARKER_HASH_LEN2 = 16;
|
|
2918
|
+
function ccrPngMarker(hash) {
|
|
2919
|
+
return `<<ccr:${hash.slice(0, CCR_MARKER_HASH_LEN2)}>>`;
|
|
2920
|
+
}
|
|
2921
|
+
var CCR_MARKER_RE2 = /<<ccr:/;
|
|
2922
|
+
var DEFAULT_THRESHOLD_CHARS = 4e5;
|
|
2923
|
+
var DEFAULT_MIN_PART_CHARS = 4e3;
|
|
2924
|
+
var DEFAULT_LIVE_TAIL_MESSAGES = 1;
|
|
2925
|
+
var DEFAULT_VISION_MODEL_SUBSTRINGS = [
|
|
2926
|
+
"gpt-4o",
|
|
2927
|
+
"gpt-4.1",
|
|
2928
|
+
"gpt-4-turbo",
|
|
2929
|
+
"gpt-5",
|
|
2930
|
+
"o1",
|
|
2931
|
+
"o3",
|
|
2932
|
+
"o4",
|
|
2933
|
+
"claude-3",
|
|
2934
|
+
"claude-4",
|
|
2935
|
+
"claude-opus",
|
|
2936
|
+
"claude-sonnet",
|
|
2937
|
+
"claude-haiku",
|
|
2938
|
+
"claude-fable",
|
|
2939
|
+
"gemini",
|
|
2940
|
+
"llama-3.2-vision",
|
|
2941
|
+
"llama-4",
|
|
2942
|
+
"qwen2.5-vl",
|
|
2943
|
+
"qwen2-vl",
|
|
2944
|
+
"qwen3-vl",
|
|
2945
|
+
"pixtral",
|
|
2946
|
+
"mistral-small-3",
|
|
2947
|
+
"grok-vision",
|
|
2948
|
+
"grok-4",
|
|
2949
|
+
"internvl",
|
|
2950
|
+
"-vl",
|
|
2951
|
+
"vision"
|
|
2952
|
+
];
|
|
2953
|
+
function inputModalities(modalities) {
|
|
2954
|
+
if (!modalities)
|
|
2955
|
+
return null;
|
|
2956
|
+
if (Array.isArray(modalities))
|
|
2957
|
+
return modalities.map((m) => String(m));
|
|
2958
|
+
if (typeof modalities === "object" && Array.isArray(modalities.input)) {
|
|
2959
|
+
return modalities.input.map((m) => String(m));
|
|
2960
|
+
}
|
|
2961
|
+
return null;
|
|
2962
|
+
}
|
|
2963
|
+
function isVisionCapable(opts) {
|
|
2964
|
+
const mods = inputModalities(opts.modalities);
|
|
2965
|
+
if (mods) {
|
|
2966
|
+
return mods.some((m) => m.toLowerCase() === "image");
|
|
2967
|
+
}
|
|
2968
|
+
const id = (opts.modelId || "").toLowerCase();
|
|
2969
|
+
if (!id)
|
|
2970
|
+
return false;
|
|
2971
|
+
const allow = [...DEFAULT_VISION_MODEL_SUBSTRINGS, ...opts.visionAllowlist || []];
|
|
2972
|
+
return allow.some((sub) => sub && id.includes(String(sub).toLowerCase()));
|
|
2973
|
+
}
|
|
2974
|
+
function renderTextToImageParts(text, hash) {
|
|
2975
|
+
const sanitized = sanitizeForGrid(text);
|
|
2976
|
+
const pages = paginate(layoutRows(sanitized));
|
|
2977
|
+
const parts = [];
|
|
2978
|
+
let pngBytes = 0;
|
|
2979
|
+
let selfTestError = null;
|
|
2980
|
+
for (let i = 0; i < pages.length; i++) {
|
|
2981
|
+
const rendered = renderPage(pages[i]);
|
|
2982
|
+
const err = verifyRoundTrip(rendered);
|
|
2983
|
+
if (err && !selfTestError)
|
|
2984
|
+
selfTestError = `page ${i + 1}: ${err}`;
|
|
2985
|
+
pngBytes += rendered.png.length;
|
|
2986
|
+
parts.push({
|
|
2987
|
+
type: "file",
|
|
2988
|
+
mime: "image/png",
|
|
2989
|
+
url: `data:image/png;base64,${rendered.png.toString("base64")}`,
|
|
2990
|
+
filename: `greprag-png-${hash.slice(0, CCR_MARKER_HASH_LEN2)}-p${String(i + 1).padStart(3, "0")}.png`
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
return { parts, pages: pages.length, pngBytes, selfTestError };
|
|
2994
|
+
}
|
|
2995
|
+
function partsOf(msg) {
|
|
2996
|
+
if (!msg || typeof msg !== "object")
|
|
2997
|
+
return null;
|
|
2998
|
+
const parts = msg.parts;
|
|
2999
|
+
return Array.isArray(parts) ? parts : null;
|
|
3000
|
+
}
|
|
3001
|
+
function partCharLen(p) {
|
|
3002
|
+
if (!p || typeof p !== "object")
|
|
3003
|
+
return 0;
|
|
3004
|
+
const part = p;
|
|
3005
|
+
if (part.type === "text" && typeof part.text === "string")
|
|
3006
|
+
return part.text.length;
|
|
3007
|
+
if (part.type === "file" && typeof part.url === "string")
|
|
3008
|
+
return part.url.length;
|
|
3009
|
+
if (part.type === "tool" && part.state && typeof part.state === "object") {
|
|
3010
|
+
const out = part.state.output;
|
|
3011
|
+
if (typeof out === "string")
|
|
3012
|
+
return out.length;
|
|
3013
|
+
}
|
|
3014
|
+
return 0;
|
|
3015
|
+
}
|
|
3016
|
+
function maybeRecodePart(p, minPartChars, stash) {
|
|
3017
|
+
if (!p || typeof p !== "object")
|
|
3018
|
+
return null;
|
|
3019
|
+
const part = p;
|
|
3020
|
+
if (part.type !== "text" || typeof part.text !== "string")
|
|
3021
|
+
return null;
|
|
3022
|
+
const text = part.text;
|
|
3023
|
+
if (CCR_MARKER_RE2.test(text))
|
|
3024
|
+
return null;
|
|
3025
|
+
if (text.length < minPartChars)
|
|
3026
|
+
return null;
|
|
3027
|
+
const hash = stash(text);
|
|
3028
|
+
const { parts: fileParts, pages, pngBytes, selfTestError } = renderTextToImageParts(text, hash);
|
|
3029
|
+
const breadcrumb = {
|
|
3030
|
+
type: "text",
|
|
3031
|
+
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)})]`
|
|
3032
|
+
};
|
|
3033
|
+
return { fileParts, breadcrumb, pages, pngBytes, selfTestError };
|
|
3034
|
+
}
|
|
3035
|
+
function recodeMessagesToPng(messages, opts) {
|
|
3036
|
+
const thresholdChars = opts.thresholdChars ?? DEFAULT_THRESHOLD_CHARS;
|
|
3037
|
+
const minPartChars = opts.minPartChars ?? DEFAULT_MIN_PART_CHARS;
|
|
3038
|
+
const liveTail = opts.liveTailMessages ?? DEFAULT_LIVE_TAIL_MESSAGES;
|
|
3039
|
+
const stats = {
|
|
3040
|
+
fired: false,
|
|
3041
|
+
visionCapable: false,
|
|
3042
|
+
totalChars: 0,
|
|
3043
|
+
thresholdChars,
|
|
3044
|
+
partsRecoded: 0,
|
|
3045
|
+
pagesRendered: 0,
|
|
3046
|
+
pngBytes: 0,
|
|
3047
|
+
selfTestError: null,
|
|
3048
|
+
reason: ""
|
|
3049
|
+
};
|
|
3050
|
+
if (!Array.isArray(messages)) {
|
|
3051
|
+
stats.reason = "messages not an array";
|
|
3052
|
+
return stats;
|
|
3053
|
+
}
|
|
3054
|
+
for (const msg of messages) {
|
|
3055
|
+
const parts = partsOf(msg);
|
|
3056
|
+
if (!parts)
|
|
3057
|
+
continue;
|
|
3058
|
+
for (const p of parts)
|
|
3059
|
+
stats.totalChars += partCharLen(p);
|
|
3060
|
+
}
|
|
3061
|
+
const vision = isVisionCapable(opts);
|
|
3062
|
+
stats.visionCapable = vision;
|
|
3063
|
+
if (!vision) {
|
|
3064
|
+
stats.reason = `model not vision-capable (modelId=${opts.modelId || "?"}) \u2014 pass-through`;
|
|
3065
|
+
return stats;
|
|
3066
|
+
}
|
|
3067
|
+
if (stats.totalChars < thresholdChars) {
|
|
3068
|
+
stats.reason = `below threshold (${stats.totalChars} < ${thresholdChars} chars)`;
|
|
3069
|
+
return stats;
|
|
3070
|
+
}
|
|
3071
|
+
const lastRecodableIdx = Math.max(0, messages.length - Math.max(0, liveTail));
|
|
3072
|
+
for (let mi = 0; mi < lastRecodableIdx; mi++) {
|
|
3073
|
+
const msg = messages[mi];
|
|
3074
|
+
const parts = partsOf(msg);
|
|
3075
|
+
if (!parts)
|
|
3076
|
+
continue;
|
|
3077
|
+
let changed = false;
|
|
3078
|
+
const newParts = [];
|
|
3079
|
+
for (const p of parts) {
|
|
3080
|
+
let rec = null;
|
|
3081
|
+
try {
|
|
3082
|
+
rec = maybeRecodePart(p, minPartChars, opts.stash);
|
|
3083
|
+
} catch {
|
|
3084
|
+
rec = null;
|
|
3085
|
+
}
|
|
3086
|
+
if (rec) {
|
|
3087
|
+
newParts.push(...rec.fileParts, rec.breadcrumb);
|
|
3088
|
+
changed = true;
|
|
3089
|
+
stats.partsRecoded++;
|
|
3090
|
+
stats.pagesRendered += rec.pages;
|
|
3091
|
+
stats.pngBytes += rec.pngBytes;
|
|
3092
|
+
if (rec.selfTestError && !stats.selfTestError)
|
|
3093
|
+
stats.selfTestError = rec.selfTestError;
|
|
3094
|
+
} else {
|
|
3095
|
+
newParts.push(p);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
if (changed)
|
|
3099
|
+
msg.parts = newParts;
|
|
3100
|
+
}
|
|
3101
|
+
stats.fired = stats.partsRecoded > 0;
|
|
3102
|
+
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)";
|
|
3103
|
+
return stats;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
2504
3106
|
// src/opencode-plugin.ts
|
|
2505
3107
|
var DEBUG_LOG_PATH = path3.join(os.homedir(), ".greprag", "opencode-plugin-debug.log");
|
|
2506
3108
|
var _debugLogReady = false;
|
|
@@ -2749,11 +3351,11 @@ function startCaptureWatcher() {
|
|
|
2749
3351
|
fs3.writeFileSync(WATCHER_LOCK, String(child.pid));
|
|
2750
3352
|
} catch {
|
|
2751
3353
|
}
|
|
2752
|
-
child.stderr?.on("data", (
|
|
2753
|
-
process.stderr.write(`[greprag-watch] ${
|
|
3354
|
+
child.stderr?.on("data", (chunk2) => {
|
|
3355
|
+
process.stderr.write(`[greprag-watch] ${chunk2.toString("utf-8")}`);
|
|
2754
3356
|
});
|
|
2755
|
-
child.stdout?.on("data", (
|
|
2756
|
-
process.stderr.write(`[greprag-watch] ${
|
|
3357
|
+
child.stdout?.on("data", (chunk2) => {
|
|
3358
|
+
process.stderr.write(`[greprag-watch] ${chunk2.toString("utf-8")}`);
|
|
2757
3359
|
});
|
|
2758
3360
|
child.on("exit", (code, signal) => {
|
|
2759
3361
|
dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
|
|
@@ -2958,11 +3560,11 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
2958
3560
|
fs3.writeFileSync(lockPath, String(child.pid));
|
|
2959
3561
|
} catch {
|
|
2960
3562
|
}
|
|
2961
|
-
child.stderr?.on("data", (
|
|
2962
|
-
process.stderr.write(`[greprag-relay] ${
|
|
3563
|
+
child.stderr?.on("data", (chunk2) => {
|
|
3564
|
+
process.stderr.write(`[greprag-relay] ${chunk2.toString("utf-8")}`);
|
|
2963
3565
|
});
|
|
2964
|
-
child.stdout?.on("data", (
|
|
2965
|
-
process.stderr.write(`[greprag-relay] ${
|
|
3566
|
+
child.stdout?.on("data", (chunk2) => {
|
|
3567
|
+
process.stderr.write(`[greprag-relay] ${chunk2.toString("utf-8")}`);
|
|
2966
3568
|
});
|
|
2967
3569
|
child.on("exit", (code, signal) => {
|
|
2968
3570
|
try {
|
|
@@ -3052,6 +3654,39 @@ function inlineCrushEnabled() {
|
|
|
3052
3654
|
const v = process.env.GREPRAG_OPENCODE_CRUSH;
|
|
3053
3655
|
return v === "true" || v === "1";
|
|
3054
3656
|
}
|
|
3657
|
+
function pngRecodeEnabled() {
|
|
3658
|
+
const v = process.env.GREPRAG_OPENCODE_PNG;
|
|
3659
|
+
return v === "true" || v === "1";
|
|
3660
|
+
}
|
|
3661
|
+
function intEnv(name, allowZero = false) {
|
|
3662
|
+
const n = parseInt(process.env[name] || "", 10);
|
|
3663
|
+
if (!Number.isFinite(n))
|
|
3664
|
+
return void 0;
|
|
3665
|
+
return n > 0 || allowZero && n === 0 ? n : void 0;
|
|
3666
|
+
}
|
|
3667
|
+
function runPngRecode(input, msgs) {
|
|
3668
|
+
const model = input?.model;
|
|
3669
|
+
const modelId = model?.modelID || model?.id || model?.name || null;
|
|
3670
|
+
const modalities = model?.modalities || model?.info?.modalities || null;
|
|
3671
|
+
const allowEnv = (process.env.GREPRAG_OPENCODE_PNG_VISION_MODELS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
3672
|
+
const stash = makeCcrStash();
|
|
3673
|
+
const stats = recodeMessagesToPng(msgs, {
|
|
3674
|
+
stash: (original) => stash(original, "log"),
|
|
3675
|
+
modelId,
|
|
3676
|
+
modalities,
|
|
3677
|
+
thresholdChars: intEnv("GREPRAG_OPENCODE_PNG_THRESHOLD"),
|
|
3678
|
+
minPartChars: intEnv("GREPRAG_OPENCODE_PNG_MIN_PART"),
|
|
3679
|
+
liveTailMessages: intEnv("GREPRAG_OPENCODE_PNG_TAIL", true),
|
|
3680
|
+
visionAllowlist: allowEnv.length ? allowEnv : void 0
|
|
3681
|
+
});
|
|
3682
|
+
if (stats.partsRecoded > 0) {
|
|
3683
|
+
dlog(
|
|
3684
|
+
`messages.transform png: recoded=${stats.partsRecoded} pages=${stats.pagesRendered} pngBytes=${stats.pngBytes} totalChars=${stats.totalChars} vision=${stats.visionCapable}` + (stats.selfTestError ? ` selfTestError=${stats.selfTestError}` : "")
|
|
3685
|
+
);
|
|
3686
|
+
} else {
|
|
3687
|
+
dlog(`messages.transform png: no-op (${stats.reason})`);
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3055
3690
|
function makeCcrStash() {
|
|
3056
3691
|
return (original, sourceType) => {
|
|
3057
3692
|
const hash = crypto2.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
|
|
@@ -3147,6 +3782,13 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
3147
3782
|
);
|
|
3148
3783
|
dlog(`pushed session-id awareness for sid=${sid82}`);
|
|
3149
3784
|
}
|
|
3785
|
+
if (inlineCrushEnabled()) {
|
|
3786
|
+
pushSystemPrompt(
|
|
3787
|
+
output,
|
|
3788
|
+
"Context compression: when you see a `<<ccr:HASH>>` marker, the text immediately before it was losslessly compressed to save tokens. To recover the original verbatim, run `greprag retrieve <HASH>` in your shell (HASH = the 16 hex chars in the marker). Retrieve only when you actually need the elided detail."
|
|
3789
|
+
);
|
|
3790
|
+
dlog(`pushed CCR retrieve instruction for sid=${sidShort}`);
|
|
3791
|
+
}
|
|
3150
3792
|
const sid8 = sid && sidShort && sidShort !== "<none>" ? sidShort : sid ? sid.slice(0, 8) : "";
|
|
3151
3793
|
if (sid8) {
|
|
3152
3794
|
const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
|
|
@@ -3155,6 +3797,9 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
3155
3797
|
short: sid8,
|
|
3156
3798
|
turnCount,
|
|
3157
3799
|
projectName: anchor.projectName,
|
|
3800
|
+
// The relay IS opencode's watcher: armed the moment startSessionRelay
|
|
3801
|
+
// committed the arm for this session (earlier in this same fire).
|
|
3802
|
+
armed: sid ? relayArmedBySession.has(sid) : false,
|
|
3158
3803
|
procedureAnnounces: (() => {
|
|
3159
3804
|
try {
|
|
3160
3805
|
return activeProcedureAnnounces(readProcedureStore(anchor.projectId));
|
|
@@ -3190,7 +3835,16 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
3190
3835
|
// Contract: we mutate ONLY this request-scoped `output.messages` copy,
|
|
3191
3836
|
// never opencode's persisted session history. adr:
|
|
3192
3837
|
// adr/opencode-context-compressor.md
|
|
3193
|
-
"experimental.chat.messages.transform": async (
|
|
3838
|
+
"experimental.chat.messages.transform": async (input, output) => {
|
|
3839
|
+
try {
|
|
3840
|
+
if (pngRecodeEnabled()) {
|
|
3841
|
+
const msgs0 = output && output.messages;
|
|
3842
|
+
if (Array.isArray(msgs0))
|
|
3843
|
+
runPngRecode(input, msgs0);
|
|
3844
|
+
}
|
|
3845
|
+
} catch (e) {
|
|
3846
|
+
dlog(`messages.transform png: swallowed error (turn unaffected): ${e.message}`);
|
|
3847
|
+
}
|
|
3194
3848
|
try {
|
|
3195
3849
|
if (!inlineCrushEnabled())
|
|
3196
3850
|
return;
|