pi-mega-compact 0.4.8 → 0.4.10
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/extensions/mega-compact.js +98 -45
- package/extensions/mega-compact.ts +92 -37
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* before_agent_start systemPrompt prepend (PREVENT-PI-003).
|
|
25
25
|
*/
|
|
26
26
|
import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { join, dirname } from "node:path";
|
|
27
|
+
import { join, dirname, sep } from "node:path";
|
|
28
28
|
import { fileURLToPath } from "node:url";
|
|
29
29
|
import { STATE_DIR_DEFAULT } from "../src/config.js";
|
|
30
30
|
import { VectorStore } from "../src/vectorStore.js";
|
|
@@ -651,46 +651,93 @@ export default function (pi) {
|
|
|
651
651
|
// ---- Dashboard server commands ----------------------------------------
|
|
652
652
|
const portFile = join(currentStateDir, "port.pid");
|
|
653
653
|
const runnerFile = join(currentStateDir, "_dashboard-runner.mjs");
|
|
654
|
+
const launchLog = join(currentStateDir, "_dashboard-launch.log");
|
|
655
|
+
// Whether the runner must be spawned with --experimental-strip-types (true only
|
|
656
|
+
// when we fall back to the .ts source outside node_modules; false when using
|
|
657
|
+
// the shipped compiled dist/extensions/dashboard-server.js).
|
|
658
|
+
let dashboardNeedsStrip = false;
|
|
659
|
+
// The dashboard server binds 9320–9329 (TARGET_PORT..TARGET_PORT+PORT_RANGE-1
|
|
660
|
+
// in dashboard-server.js). Probe each for a live /api/snapshot so we can detect
|
|
661
|
+
// readiness even when port.pid landed in a different state dir than we poll.
|
|
662
|
+
async function findLivePort() {
|
|
663
|
+
for (let port = 9320; port <= 9329; port++) {
|
|
664
|
+
try {
|
|
665
|
+
const res = await fetch(`http://localhost:${port}/api/snapshot`, { signal: AbortSignal.timeout(800) }); // guardrails-allow PREVENT-PI-004: localhost liveness probe of the dashboard server this extension spawned
|
|
666
|
+
if (res.ok)
|
|
667
|
+
return port;
|
|
668
|
+
}
|
|
669
|
+
catch { /* not on this port — try next */ }
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
654
673
|
/** Try to reach a running dashboard server. Returns { port, url } or null. */
|
|
655
674
|
async function isServerRunning() {
|
|
656
|
-
|
|
675
|
+
const port = await findLivePort();
|
|
676
|
+
if (!port) {
|
|
677
|
+
// Stale marker with no live server behind it — clean up.
|
|
678
|
+
if (existsSync(portFile)) {
|
|
679
|
+
try {
|
|
680
|
+
unlinkSync(portFile);
|
|
681
|
+
}
|
|
682
|
+
catch { /* ignore */ }
|
|
683
|
+
}
|
|
657
684
|
return null;
|
|
658
|
-
try {
|
|
659
|
-
const info = JSON.parse(readFileSync(portFile, "utf-8"));
|
|
660
|
-
if (!info?.port)
|
|
661
|
-
return null;
|
|
662
|
-
const url = `http://localhost:${info.port}`; // guardrails-allow PREVENT-PI-004: localhost URL of the dashboard server this extension spawned
|
|
663
|
-
// Quick liveness probe
|
|
664
|
-
const res = await fetch(`${url}/api/snapshot`, { signal: AbortSignal.timeout(1500) }); // guardrails-allow PREVENT-PI-004: localhost probe to the dashboard server this extension spawned
|
|
665
|
-
if (res.ok)
|
|
666
|
-
return { port: info.port, url };
|
|
667
685
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
686
|
+
return { port, url: `http://localhost:${port}` }; // guardrails-allow PREVENT-PI-004: localhost URL of the dashboard server this extension spawned
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Resolve the launchable dashboard-server module.
|
|
690
|
+
*
|
|
691
|
+
* CRITICAL: Node's `--experimental-strip-types` REFUSES to strip .ts files that
|
|
692
|
+
* live under `node_modules` (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). Since
|
|
693
|
+
* the published package installs under node_modules, importing the .ts source
|
|
694
|
+
* fails in every real install (it only worked from a source checkout). So we
|
|
695
|
+
* prefer the COMPILED dist/extensions/dashboard-server.js (which the package
|
|
696
|
+
* ships from v0.4.6 — it imports only Node built-ins, so it runs standalone),
|
|
697
|
+
* and only fall back to the .ts source (with strip-types) when the compiled
|
|
698
|
+
* file is absent AND we're not under node_modules (dev checkout without a build).
|
|
699
|
+
*
|
|
700
|
+
* Returns { entry, needsStripTypes }.
|
|
701
|
+
*/
|
|
702
|
+
function resolveDashboardEntry() {
|
|
703
|
+
const here = dirname(fileURLToPath(import.meta.url)); // .../extensions
|
|
704
|
+
const candidates = [
|
|
705
|
+
// 1. Compiled sibling when running from dist/ (import.meta is dist/extensions/…js)
|
|
706
|
+
{ entry: join(here, "dashboard-server.js"), strip: false },
|
|
707
|
+
// 2. Compiled under the package's dist/ when running from source extensions/…ts
|
|
708
|
+
{ entry: join(here, "..", "dist", "extensions", "dashboard-server.js"), strip: false },
|
|
709
|
+
// 3. Last resort: the .ts source (only strippable OUTSIDE node_modules)
|
|
710
|
+
{ entry: join(here, "dashboard-server.ts"), strip: true },
|
|
711
|
+
];
|
|
712
|
+
for (const c of candidates) {
|
|
713
|
+
if (!existsSync(c.entry))
|
|
714
|
+
continue;
|
|
715
|
+
if (c.strip && c.entry.includes(`${sep}node_modules${sep}`))
|
|
716
|
+
continue; // unstrippable
|
|
717
|
+
return { entry: c.entry, needsStripTypes: c.strip };
|
|
674
718
|
}
|
|
675
719
|
return null;
|
|
676
720
|
}
|
|
677
721
|
/** Write a small ESM runner script that imports and launches the dashboard server. */
|
|
678
722
|
function writeRunnerScript() {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
// child is spawned with that flag (see the spawn below) so the import below
|
|
684
|
-
// resolves from the source install path.
|
|
685
|
-
const sourceServer = join(dirname(fileURLToPath(import.meta.url)), "dashboard-server.ts");
|
|
723
|
+
const resolved = resolveDashboardEntry();
|
|
724
|
+
if (!resolved)
|
|
725
|
+
return false;
|
|
726
|
+
dashboardNeedsStrip = resolved.needsStripTypes;
|
|
686
727
|
const script = [
|
|
687
|
-
`import {
|
|
688
|
-
`
|
|
689
|
-
`
|
|
728
|
+
`import { appendFileSync } from "node:fs";`,
|
|
729
|
+
`const __log = ${JSON.stringify(launchLog)};`,
|
|
730
|
+
`function __fail(err) {`,
|
|
731
|
+
` const msg = "[mega-compact] dashboard failed: " + (err && err.stack ? err.stack : String(err));`,
|
|
732
|
+
` try { appendFileSync(__log, msg + "\\n"); } catch { /* ignore */ }`,
|
|
733
|
+
` console.error(msg);`,
|
|
690
734
|
` process.exit(1);`,
|
|
691
|
-
`}
|
|
735
|
+
`}`,
|
|
736
|
+
`import { launchDashboardServer } from ${JSON.stringify(resolved.entry)};`,
|
|
737
|
+
`launchDashboardServer(${JSON.stringify(currentStateDir)}).catch(__fail);`,
|
|
692
738
|
].join("\n");
|
|
693
739
|
writeFileSync(runnerFile, script);
|
|
740
|
+
return true;
|
|
694
741
|
}
|
|
695
742
|
/** Open a URL in the default browser. Platform-aware. Uses spawn (not exec) to avoid shell injection. */
|
|
696
743
|
function openBrowser(url) {
|
|
@@ -718,30 +765,36 @@ export default function (pi) {
|
|
|
718
765
|
}
|
|
719
766
|
// Start the server
|
|
720
767
|
ctx.ui.notify("[mega-compact] starting dashboard server…");
|
|
721
|
-
writeRunnerScript()
|
|
722
|
-
|
|
768
|
+
if (!writeRunnerScript()) {
|
|
769
|
+
ctx.ui.notify("[mega-compact] dashboard entry not found — check logs.");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const args = dashboardNeedsStrip ? ["--experimental-strip-types", runnerFile] : [runnerFile];
|
|
773
|
+
const child = spawn(process.execPath, args, {
|
|
723
774
|
detached: true,
|
|
724
775
|
stdio: "ignore",
|
|
725
776
|
});
|
|
726
777
|
child.unref();
|
|
727
|
-
// Poll for
|
|
728
|
-
|
|
729
|
-
|
|
778
|
+
// Poll for a live server (port 9320–9329) instead of relying solely on the
|
|
779
|
+
// port.pid marker, which can land in a different state dir than the one we
|
|
780
|
+
// poll when a prior compact left currentStateDir pointing elsewhere.
|
|
781
|
+
const deadline = Date.now() + 6_000;
|
|
782
|
+
let port = null;
|
|
730
783
|
while (Date.now() < deadline) {
|
|
731
|
-
await new Promise((
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (raw?.port) {
|
|
736
|
-
port = raw.port;
|
|
737
|
-
break;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
catch { /* keep polling */ }
|
|
741
|
-
}
|
|
784
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
785
|
+
port = await findLivePort();
|
|
786
|
+
if (port)
|
|
787
|
+
break;
|
|
742
788
|
}
|
|
743
789
|
if (!port) {
|
|
744
|
-
|
|
790
|
+
let detail = "";
|
|
791
|
+
try {
|
|
792
|
+
const log = readFileSync(launchLog, "utf-8").trim();
|
|
793
|
+
if (log)
|
|
794
|
+
detail = ` — ${log.split("\n").slice(-3).join("; ")}`;
|
|
795
|
+
}
|
|
796
|
+
catch { /* no log yet */ }
|
|
797
|
+
ctx.ui.notify(`[mega-compact] dashboard server failed to start${detail}. See ${launchLog}`);
|
|
745
798
|
return;
|
|
746
799
|
}
|
|
747
800
|
const url = `http://localhost:${port}`; // guardrails-allow PREVENT-PI-004: localhost URL of the dashboard server this extension spawned
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import type { ExtensionAPI, ExtensionContext, ContextEvent, SessionBeforeCompactEvent } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
29
29
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
30
|
-
import { join, dirname } from "node:path";
|
|
30
|
+
import { join, dirname, sep } from "node:path";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import { STATE_DIR_DEFAULT } from "../src/config.js";
|
|
33
33
|
import { VectorStore } from "../src/vectorStore.js";
|
|
@@ -790,41 +790,89 @@ export default function (pi: ExtensionAPI) {
|
|
|
790
790
|
|
|
791
791
|
const portFile = join(currentStateDir, "port.pid");
|
|
792
792
|
const runnerFile = join(currentStateDir, "_dashboard-runner.mjs");
|
|
793
|
+
const launchLog = join(currentStateDir, "_dashboard-launch.log");
|
|
794
|
+
// Whether the runner must be spawned with --experimental-strip-types (true only
|
|
795
|
+
// when we fall back to the .ts source outside node_modules; false when using
|
|
796
|
+
// the shipped compiled dist/extensions/dashboard-server.js).
|
|
797
|
+
let dashboardNeedsStrip = false;
|
|
798
|
+
|
|
799
|
+
// The dashboard server binds 9320–9329 (TARGET_PORT..TARGET_PORT+PORT_RANGE-1
|
|
800
|
+
// in dashboard-server.js). Probe each for a live /api/snapshot so we can detect
|
|
801
|
+
// readiness even when port.pid landed in a different state dir than we poll.
|
|
802
|
+
async function findLivePort(): Promise<number | null> {
|
|
803
|
+
for (let port = 9320; port <= 9329; port++) {
|
|
804
|
+
try {
|
|
805
|
+
const res = await fetch(`http://localhost:${port}/api/snapshot`, { signal: AbortSignal.timeout(800) }); // guardrails-allow PREVENT-PI-004: localhost liveness probe of the dashboard server this extension spawned
|
|
806
|
+
if (res.ok) return port;
|
|
807
|
+
} catch { /* not on this port — try next */ }
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
793
811
|
|
|
794
812
|
/** Try to reach a running dashboard server. Returns { port, url } or null. */
|
|
795
813
|
async function isServerRunning(): Promise<{ port: number; url: string } | null> {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
814
|
+
const port = await findLivePort();
|
|
815
|
+
if (!port) {
|
|
816
|
+
// Stale marker with no live server behind it — clean up.
|
|
817
|
+
if (existsSync(portFile)) {
|
|
818
|
+
try { unlinkSync(portFile); } catch { /* ignore */ }
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
return { port, url: `http://localhost:${port}` }; // guardrails-allow PREVENT-PI-004: localhost URL of the dashboard server this extension spawned
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Resolve the launchable dashboard-server module.
|
|
827
|
+
*
|
|
828
|
+
* CRITICAL: Node's `--experimental-strip-types` REFUSES to strip .ts files that
|
|
829
|
+
* live under `node_modules` (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). Since
|
|
830
|
+
* the published package installs under node_modules, importing the .ts source
|
|
831
|
+
* fails in every real install (it only worked from a source checkout). So we
|
|
832
|
+
* prefer the COMPILED dist/extensions/dashboard-server.js (which the package
|
|
833
|
+
* ships from v0.4.6 — it imports only Node built-ins, so it runs standalone),
|
|
834
|
+
* and only fall back to the .ts source (with strip-types) when the compiled
|
|
835
|
+
* file is absent AND we're not under node_modules (dev checkout without a build).
|
|
836
|
+
*
|
|
837
|
+
* Returns { entry, needsStripTypes }.
|
|
838
|
+
*/
|
|
839
|
+
function resolveDashboardEntry(): { entry: string; needsStripTypes: boolean } | null {
|
|
840
|
+
const here = dirname(fileURLToPath(import.meta.url)); // .../extensions
|
|
841
|
+
const candidates = [
|
|
842
|
+
// 1. Compiled sibling when running from dist/ (import.meta is dist/extensions/…js)
|
|
843
|
+
{ entry: join(here, "dashboard-server.js"), strip: false },
|
|
844
|
+
// 2. Compiled under the package's dist/ when running from source extensions/…ts
|
|
845
|
+
{ entry: join(here, "..", "dist", "extensions", "dashboard-server.js"), strip: false },
|
|
846
|
+
// 3. Last resort: the .ts source (only strippable OUTSIDE node_modules)
|
|
847
|
+
{ entry: join(here, "dashboard-server.ts"), strip: true },
|
|
848
|
+
];
|
|
849
|
+
for (const c of candidates) {
|
|
850
|
+
if (!existsSync(c.entry)) continue;
|
|
851
|
+
if (c.strip && c.entry.includes(`${sep}node_modules${sep}`)) continue; // unstrippable
|
|
852
|
+
return { entry: c.entry, needsStripTypes: c.strip };
|
|
807
853
|
}
|
|
808
854
|
return null;
|
|
809
855
|
}
|
|
810
856
|
|
|
811
857
|
/** Write a small ESM runner script that imports and launches the dashboard server. */
|
|
812
|
-
function writeRunnerScript():
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
// node's --experimental-strip-types without any other compiled code. The
|
|
817
|
-
// child is spawned with that flag (see the spawn below) so the import below
|
|
818
|
-
// resolves from the source install path.
|
|
819
|
-
const sourceServer = join(dirname(fileURLToPath(import.meta.url)), "dashboard-server.ts");
|
|
858
|
+
function writeRunnerScript(): boolean {
|
|
859
|
+
const resolved = resolveDashboardEntry();
|
|
860
|
+
if (!resolved) return false;
|
|
861
|
+
dashboardNeedsStrip = resolved.needsStripTypes;
|
|
820
862
|
const script = [
|
|
821
|
-
`import {
|
|
822
|
-
`
|
|
823
|
-
`
|
|
863
|
+
`import { appendFileSync } from "node:fs";`,
|
|
864
|
+
`const __log = ${JSON.stringify(launchLog)};`,
|
|
865
|
+
`function __fail(err) {`,
|
|
866
|
+
` const msg = "[mega-compact] dashboard failed: " + (err && err.stack ? err.stack : String(err));`,
|
|
867
|
+
` try { appendFileSync(__log, msg + "\\n"); } catch { /* ignore */ }`,
|
|
868
|
+
` console.error(msg);`,
|
|
824
869
|
` process.exit(1);`,
|
|
825
|
-
`}
|
|
870
|
+
`}`,
|
|
871
|
+
`import { launchDashboardServer } from ${JSON.stringify(resolved.entry)};`,
|
|
872
|
+
`launchDashboardServer(${JSON.stringify(currentStateDir)}).catch(__fail);`,
|
|
826
873
|
].join("\n");
|
|
827
874
|
writeFileSync(runnerFile, script);
|
|
875
|
+
return true;
|
|
828
876
|
}
|
|
829
877
|
|
|
830
878
|
/** Open a URL in the default browser. Platform-aware. Uses spawn (not exec) to avoid shell injection. */
|
|
@@ -855,29 +903,36 @@ export default function (pi: ExtensionAPI) {
|
|
|
855
903
|
|
|
856
904
|
// Start the server
|
|
857
905
|
ctx.ui.notify("[mega-compact] starting dashboard server…");
|
|
858
|
-
writeRunnerScript()
|
|
906
|
+
if (!writeRunnerScript()) {
|
|
907
|
+
ctx.ui.notify("[mega-compact] dashboard entry not found — check logs.");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
859
910
|
|
|
860
|
-
const
|
|
911
|
+
const args = dashboardNeedsStrip ? ["--experimental-strip-types", runnerFile] : [runnerFile];
|
|
912
|
+
const child = spawn(process.execPath, args, {
|
|
861
913
|
detached: true,
|
|
862
914
|
stdio: "ignore",
|
|
863
915
|
});
|
|
864
916
|
child.unref();
|
|
865
917
|
|
|
866
|
-
// Poll for
|
|
867
|
-
|
|
868
|
-
|
|
918
|
+
// Poll for a live server (port 9320–9329) instead of relying solely on the
|
|
919
|
+
// port.pid marker, which can land in a different state dir than the one we
|
|
920
|
+
// poll when a prior compact left currentStateDir pointing elsewhere.
|
|
921
|
+
const deadline = Date.now() + 6_000;
|
|
922
|
+
let port: number | null = null;
|
|
869
923
|
while (Date.now() < deadline) {
|
|
870
|
-
await new Promise((
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const raw = JSON.parse(readFileSync(portFile, "utf-8"));
|
|
874
|
-
if (raw?.port) { port = raw.port; break; }
|
|
875
|
-
} catch { /* keep polling */ }
|
|
876
|
-
}
|
|
924
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
925
|
+
port = await findLivePort();
|
|
926
|
+
if (port) break;
|
|
877
927
|
}
|
|
878
928
|
|
|
879
929
|
if (!port) {
|
|
880
|
-
|
|
930
|
+
let detail = "";
|
|
931
|
+
try {
|
|
932
|
+
const log = readFileSync(launchLog, "utf-8").trim();
|
|
933
|
+
if (log) detail = ` — ${log.split("\n").slice(-3).join("; ")}`;
|
|
934
|
+
} catch { /* no log yet */ }
|
|
935
|
+
ctx.ui.notify(`[mega-compact] dashboard server failed to start${detail}. See ${launchLog}`);
|
|
881
936
|
return;
|
|
882
937
|
}
|
|
883
938
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-2-Clause",
|