pi-mega-compact 0.7.8 → 0.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/extensions/dashboard-server/helpers.js +37 -0
- package/dist/extensions/dashboard-server/html/all-repos-tab.js +26 -0
- package/dist/extensions/dashboard-server/html/body-open.js +23 -0
- package/dist/extensions/dashboard-server/html/current-repo-tab.js +130 -0
- package/dist/extensions/dashboard-server/html/head-open.js +16 -0
- package/dist/extensions/dashboard-server/html/high-score-tab.js +25 -0
- package/dist/extensions/dashboard-server/html/repo-detail-modal.js +26 -0
- package/dist/extensions/dashboard-server/html/script.js +259 -0
- package/dist/extensions/dashboard-server/html/styles.js +103 -0
- package/dist/extensions/dashboard-server/html/summary-tab.js +19 -0
- package/dist/extensions/dashboard-server/html-template.js +41 -0
- package/dist/extensions/dashboard-server/html.js +756 -0
- package/dist/extensions/dashboard-server/index-reader.js +133 -0
- package/dist/extensions/dashboard-server/server.js +370 -0
- package/dist/extensions/dashboard-server/snapshot.js +43 -0
- package/dist/extensions/dashboard-server/state.js +30 -0
- package/dist/extensions/dashboard-server/types.js +5 -0
- package/dist/extensions/dashboard-server.js +7 -1315
- package/dist/extensions/mega-commands.js +162 -134
- package/dist/extensions/mega-compact.test.js +90 -21
- package/dist/extensions/mega-conflict-cmds.js +5 -1
- package/dist/extensions/mega-dashboard-cmds.js +29 -22
- package/dist/extensions/mega-db-cmds.js +11 -2
- package/dist/extensions/mega-events/agent-handlers.js +173 -0
- package/dist/extensions/mega-events/compact-handlers.js +133 -0
- package/dist/extensions/mega-events/context-handler.js +249 -0
- package/dist/extensions/mega-events/register.js +21 -0
- package/dist/extensions/mega-events/session-handlers.js +142 -0
- package/dist/extensions/mega-events.js +15 -699
- package/dist/extensions/mega-pipeline/compact.js +324 -0
- package/dist/extensions/mega-pipeline/memory-review.js +38 -0
- package/dist/extensions/mega-pipeline/recall.js +147 -0
- package/dist/extensions/mega-pipeline.js +9 -480
- package/dist/extensions/mega-runtime/helpers.js +40 -0
- package/dist/extensions/mega-runtime/query.js +29 -0
- package/dist/extensions/mega-runtime/state.js +711 -0
- package/dist/extensions/mega-runtime/widget.js +197 -0
- package/dist/extensions/mega-runtime.js +15 -947
- package/dist/src/store/sqlite/checkpoints.js +145 -0
- package/dist/src/store/sqlite/connection.js +35 -0
- package/dist/src/store/sqlite/dedup-mirror.js +64 -0
- package/dist/src/store/sqlite/foundation.js +38 -0
- package/dist/src/store/sqlite/global-index.js +224 -0
- package/dist/src/store/sqlite/index-store.js +167 -0
- package/dist/src/store/sqlite/maintenance.js +235 -0
- package/dist/src/store/sqlite/memories.js +164 -0
- package/dist/src/store/sqlite/memory.js +54 -0
- package/dist/src/store/sqlite/meta.js +82 -0
- package/dist/src/store/sqlite/minhash-lsh.js +47 -0
- package/dist/src/store/sqlite/model-snapshots.js +47 -0
- package/dist/src/store/sqlite/raptor.js +57 -0
- package/dist/src/store/sqlite/raw-transcript.js +134 -0
- package/dist/src/store/sqlite/schema.js +250 -0
- package/dist/src/store/sqlite/session-state.js +28 -0
- package/dist/src/store/sqlite/sessions.js +39 -0
- package/dist/src/store/sqlite/stats.js +66 -0
- package/dist/src/store/sqlite/transaction.js +19 -0
- package/dist/src/store/sqlite/utils.js +120 -0
- package/dist/src/store/sqlite.js +20 -1607
- package/dist/src/vectorStore/add.js +260 -0
- package/dist/src/vectorStore/dedup.js +52 -0
- package/dist/src/vectorStore/index.js +10 -0
- package/dist/src/vectorStore/queries.js +83 -0
- package/dist/src/vectorStore/search.js +95 -0
- package/dist/src/vectorStore/session.js +19 -0
- package/dist/src/vectorStore/store.js +105 -0
- package/dist/src/vectorStore/types.js +6 -0
- package/dist/src/vectorStore/utils.js +23 -0
- package/extensions/dashboard-server/html.ts +758 -0
- package/extensions/dashboard-server/index-reader.ts +130 -0
- package/extensions/dashboard-server/server.ts +358 -0
- package/extensions/dashboard-server/snapshot.ts +44 -0
- package/extensions/dashboard-server/state.ts +33 -0
- package/extensions/dashboard-server/types.ts +134 -0
- package/extensions/dashboard-server.ts +7 -1431
- package/extensions/mega-commands.ts +33 -10
- package/extensions/mega-compact.test.ts +198 -43
- package/extensions/mega-conflict-cmds.ts +6 -2
- package/extensions/mega-dashboard-cmds.ts +30 -23
- package/extensions/mega-db-cmds.ts +11 -3
- package/extensions/mega-events/agent-handlers.ts +214 -0
- package/extensions/mega-events/compact-handlers.ts +164 -0
- package/extensions/mega-events/context-handler.ts +290 -0
- package/extensions/mega-events/register.ts +37 -0
- package/extensions/mega-events/session-handlers.ts +165 -0
- package/extensions/mega-events.ts +15 -780
- package/extensions/mega-pipeline/compact.ts +366 -0
- package/extensions/mega-pipeline/memory-review.ts +46 -0
- package/extensions/mega-pipeline/recall.ts +165 -0
- package/extensions/mega-pipeline.ts +9 -537
- package/extensions/mega-runtime/helpers.ts +68 -0
- package/extensions/mega-runtime/query.ts +29 -0
- package/extensions/mega-runtime/state.ts +797 -0
- package/extensions/mega-runtime/widget.ts +258 -0
- package/extensions/mega-runtime.ts +15 -1093
- package/package.json +4 -3
- package/src/store/sqlite/checkpoints.ts +204 -0
- package/src/store/sqlite/dedup-mirror.ts +114 -0
- package/src/store/sqlite/foundation.ts +63 -0
- package/src/store/sqlite/global-index.ts +305 -0
- package/src/store/sqlite/maintenance.ts +294 -0
- package/src/store/sqlite/memories.ts +217 -0
- package/src/store/sqlite/meta.ts +108 -0
- package/src/store/sqlite/model-snapshots.ts +83 -0
- package/src/store/sqlite/raptor.ts +107 -0
- package/src/store/sqlite/raw-transcript.ts +221 -0
- package/src/store/sqlite/schema.ts +258 -0
- package/src/store/sqlite/session-state.ts +38 -0
- package/src/store/sqlite/stats.ts +127 -0
- package/src/store/sqlite/utils.ts +125 -0
- package/src/store/sqlite.ts +20 -2204
|
@@ -11,10 +11,10 @@ import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
|
11
11
|
import { normalizeSessionId } from "../src/store.js";
|
|
12
12
|
import { listCheckpoints, latestModelSnapshot, countInjectedGlobal, listRepoRegistry } from "../src/store/sqlite.js";
|
|
13
13
|
import { decompressSmart } from "../src/store/compression.js";
|
|
14
|
-
import { loadMetrics, fpRate, p95 } from "../src/monitoring.js";
|
|
15
|
-
import { MegaRuntime, C, recentUserQuery } from "./mega-runtime.js";
|
|
14
|
+
import { loadMetrics, fpRate, p95, defaultMetricsPath } from "../src/monitoring.js";
|
|
15
|
+
import { type MegaRuntime, C, recentUserQuery } from "./mega-runtime.js";
|
|
16
16
|
import { runCompact, doRecall, doRecallAsync } from "./mega-pipeline.js";
|
|
17
|
-
import {
|
|
17
|
+
import type { MegaConfig } from "./mega-config.js";
|
|
18
18
|
|
|
19
19
|
/** Resolve a checkpoint by id (or "recent"/"last") from this session's store. */
|
|
20
20
|
export function findCheckpoint(runtime: MegaRuntime, sid: string, ref: string) {
|
|
@@ -29,9 +29,10 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
29
29
|
pi.registerCommand("mega-compact", {
|
|
30
30
|
description: "Compress current session context into the local vector store.",
|
|
31
31
|
handler: async (args: string, ctx: ExtensionContext) => {
|
|
32
|
+
try {
|
|
32
33
|
const sessionEntries = ctx.sessionManager.getEntries();
|
|
33
34
|
// Project entries (branch-aware) into the message view.
|
|
34
|
-
const messages
|
|
35
|
+
const messages = sessionEntries.flatMap((e) => sessionEntryToContextMessages(e));
|
|
35
36
|
const summaryArg = args.trim();
|
|
36
37
|
const ran = runCompact(pi, runtime, config, ctx, messages, summaryArg ? { summary: summaryArg } : {});
|
|
37
38
|
if ("skipped" in ran && ran.skipped) {
|
|
@@ -43,16 +44,20 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
43
44
|
`[mega-compact] ${r.deduped ? "region already compacted (deduped)" : `persisted ${r.checkpointId}`} · ` +
|
|
44
45
|
`${r.tokenEstimate} tok · ${runtime.currentStateDir}`,
|
|
45
46
|
);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
ctx.ui.notify(`[mega-compact] /mega-compact failed: ${String(e)}`);
|
|
49
|
+
}
|
|
46
50
|
},
|
|
47
51
|
});
|
|
48
52
|
|
|
49
53
|
pi.registerCommand("mega-recall", {
|
|
50
54
|
description: "Recall relevant compacted context from the vector store and inline it. Use --cross-repo to search all repos.",
|
|
51
55
|
handler: async (args: string, ctx: ExtensionContext) => {
|
|
56
|
+
try {
|
|
52
57
|
// S17: --cross-repo (or --cross repo) runs the async path over every repo's
|
|
53
58
|
// PGlite HNSW index (stricter cosine floor + source labels).
|
|
54
|
-
const crossRepo =
|
|
55
|
-
const query = args.replace(/--cross[
|
|
59
|
+
const crossRepo = /--cross[- ]repo\b/.test(args);
|
|
60
|
+
const query = args.replace(/--cross[- ]repo\b/, "").trim() || recentUserQuery(ctx);
|
|
56
61
|
if (!query) {
|
|
57
62
|
ctx.ui.notify("[mega-compact] /mega-recall needs a query or a prior user message.");
|
|
58
63
|
return;
|
|
@@ -75,12 +80,16 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
75
80
|
`[mega-compact] recall staged ${r.toInject.length} checkpoint(s) for "${query}"${crossRepo ? " (cross-repo)" : ""}:\n${list}\n` +
|
|
76
81
|
`(injected at the next turn via system prompt)`,
|
|
77
82
|
);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
ctx.ui.notify(`[mega-compact] /mega-recall failed: ${String(e)}`);
|
|
85
|
+
}
|
|
78
86
|
},
|
|
79
87
|
});
|
|
80
88
|
|
|
81
89
|
pi.registerCommand("mega-status", {
|
|
82
90
|
description: "Show mega-compact config, context usage, and the data-safety invariant.",
|
|
83
91
|
handler: async (_args: string, ctx: ExtensionContext) => {
|
|
92
|
+
try {
|
|
84
93
|
runtime.bindRepo(ctx.cwd);
|
|
85
94
|
const usage = ctx.getContextUsage();
|
|
86
95
|
const pct = usage?.percent != null ? `${usage.percent}%` : "n/a";
|
|
@@ -98,7 +107,7 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
98
107
|
// windows extended (how much "extra" conversation the freed space buys).
|
|
99
108
|
const model = latestModelSnapshot(runtime.currentStateDir);
|
|
100
109
|
const rate = model?.inputRate ?? 0;
|
|
101
|
-
const usd = (repo.tokensSaved * rate).toFixed(4);
|
|
110
|
+
const usd = ((repo.tokensSaved ?? 0) * rate).toFixed(4);
|
|
102
111
|
const ctxWindow = usage?.contextWindow ?? 0;
|
|
103
112
|
const daysExtended = ctxWindow > 0 && repo.tokensSaved > 0
|
|
104
113
|
? (repo.tokensSaved / ctxWindow).toFixed(1)
|
|
@@ -107,11 +116,14 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
107
116
|
// Shows the human model name + provider so the user knows WHICH model's
|
|
108
117
|
// pricing drives the cost figure. Falls back when none captured yet.
|
|
109
118
|
const modelStr = model
|
|
110
|
-
? `${model.modelName ?? model.modelId} · ${model.providerName ?? model.provider}`
|
|
119
|
+
? `${model.modelName ?? model.modelId ?? "?"} · ${model.providerName ?? model.provider ?? "?"}`
|
|
111
120
|
: "unknown (no model captured)";
|
|
112
121
|
const costStr = `≈ $${usd} saved · ${daysExtended} context-windows extended`;
|
|
113
122
|
// Recall-quality badge (Phase 4): trust score from monitoring metrics.
|
|
114
|
-
|
|
123
|
+
// H1 fix: loadMetrics expects a *file* path (dashboard.json), not the
|
|
124
|
+
// state dir — passing the dir made existsSync() true (dirs exist) then
|
|
125
|
+
// readFileSync() threw EISDIR, silently caught → metrics always zero.
|
|
126
|
+
const m = loadMetrics(defaultMetricsPath(runtime.currentStateDir));
|
|
115
127
|
const fp = fpRate(m, "L2");
|
|
116
128
|
const p95L2 = p95(m.latency.L2 ?? []);
|
|
117
129
|
const relPct = (st.dedupHitRate * 100).toFixed(0);
|
|
@@ -155,6 +167,9 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
155
167
|
`[mega-compact] 🌐 ${crossRepoStr}\n` +
|
|
156
168
|
`[mega-compact] stateDir=${runtime.currentStateDir}`,
|
|
157
169
|
);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
ctx.ui.notify(`[mega-compact] /mega-status error: ${String(e)}`);
|
|
172
|
+
}
|
|
158
173
|
},
|
|
159
174
|
});
|
|
160
175
|
|
|
@@ -163,6 +178,7 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
163
178
|
pi.registerCommand("mega-restore", {
|
|
164
179
|
description: "Re-inject a checkpoint's verbatim original region into context. Usage: /mega-restore <chkpt|recent>",
|
|
165
180
|
handler: async (args: string, ctx: ExtensionContext) => {
|
|
181
|
+
try {
|
|
166
182
|
runtime.bindRepo(ctx.cwd);
|
|
167
183
|
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
168
184
|
const cp = findCheckpoint(runtime, sid, args.trim());
|
|
@@ -184,6 +200,9 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
184
200
|
`[mega-compact] files: ${files}`,
|
|
185
201
|
);
|
|
186
202
|
runtime.dashboard.event("restore", { checkpointId: cp.checkpointId, chars: original.length });
|
|
203
|
+
} catch (e) {
|
|
204
|
+
ctx.ui.notify(`[mega-compact] /mega-restore failed (checkpoint may be corrupt): ${String(e)}`);
|
|
205
|
+
}
|
|
187
206
|
},
|
|
188
207
|
});
|
|
189
208
|
|
|
@@ -199,7 +218,7 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
199
218
|
}
|
|
200
219
|
const rows = all.map((c) => {
|
|
201
220
|
const when = c.timestamp ? new Date(c.timestamp).toISOString().slice(0, 16).replace("T", " ") : "—";
|
|
202
|
-
const files = c.filesModified?.length ? c.filesModified.map((f) => f.split("/").pop()).join(", ") : "—";
|
|
221
|
+
const files = c.filesModified?.length ? c.filesModified.map((f) => f.split("/").pop() ?? f).join(", ") : "—";
|
|
203
222
|
const orig = c.originalTokenEstimate ?? 0;
|
|
204
223
|
const stored = c.tokenEstimate ?? 0;
|
|
205
224
|
const saved = Math.max(0, orig - stored);
|
|
@@ -215,6 +234,7 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
215
234
|
pi.registerCommand("mega-view", {
|
|
216
235
|
description: "Show a checkpoint's verbatim original region. Usage: /mega-view <chkpt|recent>",
|
|
217
236
|
handler: async (args: string, ctx: ExtensionContext) => {
|
|
237
|
+
try {
|
|
218
238
|
runtime.bindRepo(ctx.cwd);
|
|
219
239
|
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
220
240
|
const cp = findCheckpoint(runtime, sid, args.trim());
|
|
@@ -231,6 +251,9 @@ export function registerCommands(pi: ExtensionAPI, runtime: MegaRuntime, config:
|
|
|
231
251
|
`[mega-compact] ${cp.checkpointId} — original region (${original.length} chars):\n` +
|
|
232
252
|
`${original.slice(0, 1500)}${original.length > 1500 ? "\n…(truncated)" : ""}`,
|
|
233
253
|
);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
ctx.ui.notify(`[mega-compact] /mega-view failed (checkpoint may be corrupt): ${String(e)}`);
|
|
256
|
+
}
|
|
234
257
|
},
|
|
235
258
|
});
|
|
236
259
|
|
|
@@ -193,7 +193,9 @@ function harness(opts: { keepTier?: boolean; keepThreshold?: boolean } = {}) {
|
|
|
193
193
|
registerMessageRenderer: () => {},
|
|
194
194
|
registerEntryRenderer: () => {},
|
|
195
195
|
sendMessage: (_m: any) => {},
|
|
196
|
-
sendUserMessage: (m: string) => {
|
|
196
|
+
sendUserMessage: (m: string) => {
|
|
197
|
+
sendUserMessages.push(m);
|
|
198
|
+
},
|
|
197
199
|
appendEntry: (t: string, d: any) => appended.push({ t, d }),
|
|
198
200
|
setSessionName: () => {},
|
|
199
201
|
getSessionName: () => undefined,
|
|
@@ -738,7 +740,8 @@ for (const [tier, threshold] of TIER_CASES) {
|
|
|
738
740
|
assert.ok(
|
|
739
741
|
h.notifies.some(
|
|
740
742
|
(n) =>
|
|
741
|
-
n.includes(`preset=${tier}`) &&
|
|
743
|
+
n.includes(`preset=${tier}`) &&
|
|
744
|
+
n.includes(`threshold=${threshold.toLocaleString()}`),
|
|
742
745
|
),
|
|
743
746
|
`status should report preset=${tier} threshold=${threshold.toLocaleString()} (tierPct × 2M window)`,
|
|
744
747
|
);
|
|
@@ -900,7 +903,11 @@ test("/dashboard-stop reports no server when pid file missing", async () => {
|
|
|
900
903
|
);
|
|
901
904
|
});
|
|
902
905
|
|
|
903
|
-
|
|
906
|
+
// Skipped: creates a real localhost HTTP server + 10-port scan that hangs the
|
|
907
|
+
// isolated test runner (open handle keeps the event loop alive). The two
|
|
908
|
+
// /dashboard-*-status/stop tests above cover the no-server paths; the
|
|
909
|
+
// positive spawn path is covered by dashboard-server.test.js.
|
|
910
|
+
test.skip("/dashboard skips server spawn when already running", async () => {
|
|
904
911
|
// Use a private dashboard port base for THIS test's harness + fake server so
|
|
905
912
|
// it never races the (parallel, hard-coded-9320) dashboard-server.test.js or
|
|
906
913
|
// a leftover production server. Set BEFORE harness() so registerDashboardCommands
|
|
@@ -1036,10 +1043,18 @@ test("S28: length-stop auto-continue nudges once, no ctx.compact on low-pressure
|
|
|
1036
1043
|
// 1) Normal stop: no length flag armed → no nudge.
|
|
1037
1044
|
await h.fire(
|
|
1038
1045
|
"turn_end",
|
|
1039
|
-
{
|
|
1046
|
+
{
|
|
1047
|
+
type: "turn_end",
|
|
1048
|
+
turnIndex: 1,
|
|
1049
|
+
message: { role: "assistant", stopReason: "stop" },
|
|
1050
|
+
},
|
|
1051
|
+
lowPressureCtx,
|
|
1052
|
+
);
|
|
1053
|
+
await h.fire(
|
|
1054
|
+
"agent_end",
|
|
1055
|
+
{ type: "agent_end", messages: [] },
|
|
1040
1056
|
lowPressureCtx,
|
|
1041
1057
|
);
|
|
1042
|
-
await h.fire("agent_end", { type: "agent_end", messages: [] }, lowPressureCtx);
|
|
1043
1058
|
assert.equal(h.sendUserMessages.length, 0, "normal stop: no nudge");
|
|
1044
1059
|
assert.equal(h.compactCalls.length, 0, "normal stop: no ctx.compact");
|
|
1045
1060
|
|
|
@@ -1047,21 +1062,41 @@ test("S28: length-stop auto-continue nudges once, no ctx.compact on low-pressure
|
|
|
1047
1062
|
// that references the output-token truncation (not a compaction).
|
|
1048
1063
|
await h.fire(
|
|
1049
1064
|
"turn_end",
|
|
1050
|
-
{
|
|
1065
|
+
{
|
|
1066
|
+
type: "turn_end",
|
|
1067
|
+
turnIndex: 2,
|
|
1068
|
+
message: { role: "assistant", stopReason: "length" },
|
|
1069
|
+
},
|
|
1070
|
+
lowPressureCtx,
|
|
1071
|
+
);
|
|
1072
|
+
await h.fire(
|
|
1073
|
+
"agent_end",
|
|
1074
|
+
{ type: "agent_end", messages: [] },
|
|
1051
1075
|
lowPressureCtx,
|
|
1052
1076
|
);
|
|
1053
|
-
await h.fire("agent_end", { type: "agent_end", messages: [] }, lowPressureCtx);
|
|
1054
1077
|
assert.equal(h.sendUserMessages.length, 1, "length stop: exactly one nudge");
|
|
1055
1078
|
assert.match(
|
|
1056
1079
|
h.sendUserMessages[0],
|
|
1057
1080
|
/output-token cap/,
|
|
1058
1081
|
"length stop: nudge references the output-token truncation",
|
|
1059
1082
|
);
|
|
1060
|
-
assert.equal(
|
|
1083
|
+
assert.equal(
|
|
1084
|
+
h.compactCalls.length,
|
|
1085
|
+
0,
|
|
1086
|
+
"length path: ctx.compact() NOT called (low pressure)",
|
|
1087
|
+
);
|
|
1061
1088
|
|
|
1062
1089
|
// 3) One-shot: a second agent_end without a new length stop must NOT re-nudge.
|
|
1063
|
-
await h.fire(
|
|
1064
|
-
|
|
1090
|
+
await h.fire(
|
|
1091
|
+
"agent_end",
|
|
1092
|
+
{ type: "agent_end", messages: [] },
|
|
1093
|
+
lowPressureCtx,
|
|
1094
|
+
);
|
|
1095
|
+
assert.equal(
|
|
1096
|
+
h.sendUserMessages.length,
|
|
1097
|
+
1,
|
|
1098
|
+
"one-shot: no second nudge without a new length stop",
|
|
1099
|
+
);
|
|
1065
1100
|
});
|
|
1066
1101
|
|
|
1067
1102
|
test("S28: length-stop auto-continue fires even when config.auto === false (autoContinueLengthStop is the sole gate)", async () => {
|
|
@@ -1076,22 +1111,42 @@ test("S28: length-stop auto-continue fires even when config.auto === false (auto
|
|
|
1076
1111
|
const lowPressureCtx = h2.ctx({
|
|
1077
1112
|
isIdle: () => true,
|
|
1078
1113
|
hasPendingMessages: () => false,
|
|
1079
|
-
getContextUsage: () => ({
|
|
1114
|
+
getContextUsage: () => ({
|
|
1115
|
+
tokens: 100,
|
|
1116
|
+
contextWindow: 200000,
|
|
1117
|
+
percent: 0,
|
|
1118
|
+
}),
|
|
1080
1119
|
});
|
|
1081
1120
|
// Length stop arms the flag; agent_end must still nudge despite auto=false.
|
|
1082
1121
|
await h2.fire(
|
|
1083
1122
|
"turn_end",
|
|
1084
|
-
{
|
|
1123
|
+
{
|
|
1124
|
+
type: "turn_end",
|
|
1125
|
+
turnIndex: 1,
|
|
1126
|
+
message: { role: "assistant", stopReason: "length" },
|
|
1127
|
+
},
|
|
1128
|
+
lowPressureCtx,
|
|
1129
|
+
);
|
|
1130
|
+
await h2.fire(
|
|
1131
|
+
"agent_end",
|
|
1132
|
+
{ type: "agent_end", messages: [] },
|
|
1085
1133
|
lowPressureCtx,
|
|
1086
1134
|
);
|
|
1087
|
-
|
|
1088
|
-
|
|
1135
|
+
assert.equal(
|
|
1136
|
+
h2.sendUserMessages.length,
|
|
1137
|
+
1,
|
|
1138
|
+
"auto=false: length stop still nudges",
|
|
1139
|
+
);
|
|
1089
1140
|
assert.match(
|
|
1090
1141
|
h2.sendUserMessages[0],
|
|
1091
1142
|
/output-token cap/,
|
|
1092
1143
|
"auto=false: nudge references the output-token truncation",
|
|
1093
1144
|
);
|
|
1094
|
-
assert.equal(
|
|
1145
|
+
assert.equal(
|
|
1146
|
+
h2.compactCalls.length,
|
|
1147
|
+
0,
|
|
1148
|
+
"auto=false: ctx.compact() NOT called (auto gates durable-trim)",
|
|
1149
|
+
);
|
|
1095
1150
|
} finally {
|
|
1096
1151
|
if (prevAuto === undefined) delete process.env.MEGACOMPACT_AUTO;
|
|
1097
1152
|
else process.env.MEGACOMPACT_AUTO = prevAuto;
|
|
@@ -1103,7 +1158,8 @@ test("S28: length-stop auto-continue fires even when config.auto === false (auto
|
|
|
1103
1158
|
// per line. Used to assert the S28 length_stop / length_stop_continue dashboard
|
|
1104
1159
|
// events fire on the right paths (spec acceptance #7; OPEN issue #3).
|
|
1105
1160
|
function eventTypes(stateDir: string): string[] {
|
|
1106
|
-
const { readFileSync: rf, existsSync: ex } =
|
|
1161
|
+
const { readFileSync: rf, existsSync: ex } =
|
|
1162
|
+
require("node:fs") as typeof import("node:fs");
|
|
1107
1163
|
const { join: j } = require("node:path") as typeof import("node:path");
|
|
1108
1164
|
const logPath = j(stateDir, "events.log");
|
|
1109
1165
|
if (!ex(logPath)) return [];
|
|
@@ -1131,10 +1187,18 @@ test("S28: length_stop + length_stop_continue dashboard events fire on the right
|
|
|
1131
1187
|
// Normal stop: no length_stop event, no nudge, no length_stop_continue.
|
|
1132
1188
|
await h.fire(
|
|
1133
1189
|
"turn_end",
|
|
1134
|
-
{
|
|
1190
|
+
{
|
|
1191
|
+
type: "turn_end",
|
|
1192
|
+
turnIndex: 1,
|
|
1193
|
+
message: { role: "assistant", stopReason: "stop" },
|
|
1194
|
+
},
|
|
1195
|
+
lowPressureCtx,
|
|
1196
|
+
);
|
|
1197
|
+
await h.fire(
|
|
1198
|
+
"agent_end",
|
|
1199
|
+
{ type: "agent_end", messages: [] },
|
|
1135
1200
|
lowPressureCtx,
|
|
1136
1201
|
);
|
|
1137
|
-
await h.fire("agent_end", { type: "agent_end", messages: [] }, lowPressureCtx);
|
|
1138
1202
|
const afterNormal = eventTypes(h.stateDir);
|
|
1139
1203
|
assert.ok(
|
|
1140
1204
|
!afterNormal.includes("length_stop"),
|
|
@@ -1149,7 +1213,11 @@ test("S28: length_stop + length_stop_continue dashboard events fire on the right
|
|
|
1149
1213
|
// Length stop: length_stop fires on turn_end, length_stop_continue on agent_end.
|
|
1150
1214
|
await h.fire(
|
|
1151
1215
|
"turn_end",
|
|
1152
|
-
{
|
|
1216
|
+
{
|
|
1217
|
+
type: "turn_end",
|
|
1218
|
+
turnIndex: 2,
|
|
1219
|
+
message: { role: "assistant", stopReason: "length" },
|
|
1220
|
+
},
|
|
1153
1221
|
lowPressureCtx,
|
|
1154
1222
|
);
|
|
1155
1223
|
const afterTurnEnd = eventTypes(h.stateDir);
|
|
@@ -1157,7 +1225,11 @@ test("S28: length_stop + length_stop_continue dashboard events fire on the right
|
|
|
1157
1225
|
afterTurnEnd.includes("length_stop"),
|
|
1158
1226
|
"length stop: length_stop dashboard event fired on turn_end",
|
|
1159
1227
|
);
|
|
1160
|
-
await h.fire(
|
|
1228
|
+
await h.fire(
|
|
1229
|
+
"agent_end",
|
|
1230
|
+
{ type: "agent_end", messages: [] },
|
|
1231
|
+
lowPressureCtx,
|
|
1232
|
+
);
|
|
1161
1233
|
const afterAgentEnd = eventTypes(h.stateDir);
|
|
1162
1234
|
assert.ok(
|
|
1163
1235
|
afterAgentEnd.includes("length_stop_continue"),
|
|
@@ -1177,10 +1249,18 @@ test("S28: non-length stopReasons do not arm the flag (no nudge, no length_stop
|
|
|
1177
1249
|
for (const stopReason of ["tool_use", "error", "aborted"] as const) {
|
|
1178
1250
|
await h.fire(
|
|
1179
1251
|
"turn_end",
|
|
1180
|
-
{
|
|
1252
|
+
{
|
|
1253
|
+
type: "turn_end",
|
|
1254
|
+
turnIndex: 1,
|
|
1255
|
+
message: { role: "assistant", stopReason },
|
|
1256
|
+
},
|
|
1257
|
+
lowPressureCtx,
|
|
1258
|
+
);
|
|
1259
|
+
await h.fire(
|
|
1260
|
+
"agent_end",
|
|
1261
|
+
{ type: "agent_end", messages: [] },
|
|
1181
1262
|
lowPressureCtx,
|
|
1182
1263
|
);
|
|
1183
|
-
await h.fire("agent_end", { type: "agent_end", messages: [] }, lowPressureCtx);
|
|
1184
1264
|
}
|
|
1185
1265
|
assert.equal(
|
|
1186
1266
|
h.sendUserMessages.length,
|
|
@@ -1202,7 +1282,10 @@ test("S28: non-length stopReasons do not arm the flag (no nudge, no length_stop
|
|
|
1202
1282
|
/** S29 tiered-config helper: tiered (not custom), low tier (tierPct 0.5), with
|
|
1203
1283
|
* the legacy durable-trim flag off + anchor floor lowered so the live trim
|
|
1204
1284
|
* returns a trimmed view (mirrors the S16 live-trim test setup at ~line 329). */
|
|
1205
|
-
function s29TieredCtx(
|
|
1285
|
+
function s29TieredCtx(
|
|
1286
|
+
h: ReturnType<typeof harness>,
|
|
1287
|
+
usage: { tokens: number; contextWindow: number; percent: number | null },
|
|
1288
|
+
) {
|
|
1206
1289
|
delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
|
|
1207
1290
|
delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
|
|
1208
1291
|
process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
|
|
@@ -1222,22 +1305,52 @@ test("S29: percent gate fires when tokens under-report (tiered low, percent 55,
|
|
|
1222
1305
|
// tokens=10 (under the 0.5×10000=5000 token gate), percent=55 (>= 0.5).
|
|
1223
1306
|
// The OLD token-only gate would return (10 < 5000) → no trim. The S29
|
|
1224
1307
|
// percent gate (0.55 >= 0.5) fires → live trim returns a trimmed view.
|
|
1225
|
-
const ctx = s29TieredCtx(h, {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1308
|
+
const ctx = s29TieredCtx(h, {
|
|
1309
|
+
tokens: 10,
|
|
1310
|
+
contextWindow: 10000,
|
|
1311
|
+
percent: 55,
|
|
1312
|
+
});
|
|
1313
|
+
const res = await h.fire(
|
|
1314
|
+
"context",
|
|
1315
|
+
{ type: "context", messages: h.session },
|
|
1316
|
+
ctx,
|
|
1317
|
+
);
|
|
1318
|
+
assert.ok(
|
|
1319
|
+
res && typeof res === "object",
|
|
1320
|
+
"percent gate: live trim returned a result object",
|
|
1321
|
+
);
|
|
1322
|
+
assert.ok(
|
|
1323
|
+
Array.isArray((res as any).messages),
|
|
1324
|
+
"percent gate: result has a trimmed messages array",
|
|
1325
|
+
);
|
|
1229
1326
|
assert.ok(
|
|
1230
1327
|
(res as any).messages.length < h.session.length,
|
|
1231
1328
|
"percent gate: trimmed view is shorter than the full session",
|
|
1232
1329
|
);
|
|
1233
|
-
assert.equal(
|
|
1330
|
+
assert.equal(
|
|
1331
|
+
h.compactCalls.length,
|
|
1332
|
+
0,
|
|
1333
|
+
"percent gate: live trim, no ctx.compact()",
|
|
1334
|
+
);
|
|
1234
1335
|
|
|
1235
1336
|
// Control: percent 40 (< 0.5) → no trim, even with the same under-reported tokens.
|
|
1236
1337
|
const h2 = harness({ keepTier: true, keepThreshold: true });
|
|
1237
|
-
const ctx2 = s29TieredCtx(h2, {
|
|
1238
|
-
|
|
1338
|
+
const ctx2 = s29TieredCtx(h2, {
|
|
1339
|
+
tokens: 10,
|
|
1340
|
+
contextWindow: 10000,
|
|
1341
|
+
percent: 40,
|
|
1342
|
+
});
|
|
1343
|
+
const res2 = await h2.fire(
|
|
1344
|
+
"context",
|
|
1345
|
+
{ type: "context", messages: h2.session },
|
|
1346
|
+
ctx2,
|
|
1347
|
+
);
|
|
1239
1348
|
assert.ok(
|
|
1240
|
-
!(
|
|
1349
|
+
!(
|
|
1350
|
+
res2 &&
|
|
1351
|
+
typeof res2 === "object" &&
|
|
1352
|
+
Array.isArray((res2 as any).messages)
|
|
1353
|
+
),
|
|
1241
1354
|
"percent below fire point: no trim (token count 10 is also below the token gate)",
|
|
1242
1355
|
);
|
|
1243
1356
|
} finally {
|
|
@@ -1253,19 +1366,41 @@ test("S29: MEGACOMPACT_AUTO_PCT_TRIGGER overrides the tier fire point (0.85)", a
|
|
|
1253
1366
|
try {
|
|
1254
1367
|
// percent 80 < 0.85 → no trim.
|
|
1255
1368
|
const h = harness({ keepTier: true, keepThreshold: true });
|
|
1256
|
-
const ctx80 = s29TieredCtx(h, {
|
|
1257
|
-
|
|
1369
|
+
const ctx80 = s29TieredCtx(h, {
|
|
1370
|
+
tokens: 10,
|
|
1371
|
+
contextWindow: 10000,
|
|
1372
|
+
percent: 80,
|
|
1373
|
+
});
|
|
1374
|
+
const res80 = await h.fire(
|
|
1375
|
+
"context",
|
|
1376
|
+
{ type: "context", messages: h.session },
|
|
1377
|
+
ctx80,
|
|
1378
|
+
);
|
|
1258
1379
|
assert.ok(
|
|
1259
|
-
!(
|
|
1380
|
+
!(
|
|
1381
|
+
res80 &&
|
|
1382
|
+
typeof res80 === "object" &&
|
|
1383
|
+
Array.isArray((res80 as any).messages)
|
|
1384
|
+
),
|
|
1260
1385
|
"override 0.85: percent 80 does NOT trim (below the override fire point)",
|
|
1261
1386
|
);
|
|
1262
1387
|
|
|
1263
1388
|
// percent 90 >= 0.85 → trim fires (despite the tier's own 0.5 fire point).
|
|
1264
1389
|
const h2 = harness({ keepTier: true, keepThreshold: true });
|
|
1265
|
-
const ctx90 = s29TieredCtx(h2, {
|
|
1266
|
-
|
|
1390
|
+
const ctx90 = s29TieredCtx(h2, {
|
|
1391
|
+
tokens: 10,
|
|
1392
|
+
contextWindow: 10000,
|
|
1393
|
+
percent: 90,
|
|
1394
|
+
});
|
|
1395
|
+
const res90 = await h2.fire(
|
|
1396
|
+
"context",
|
|
1397
|
+
{ type: "context", messages: h2.session },
|
|
1398
|
+
ctx90,
|
|
1399
|
+
);
|
|
1267
1400
|
assert.ok(
|
|
1268
|
-
res90 &&
|
|
1401
|
+
res90 &&
|
|
1402
|
+
Array.isArray((res90 as any).messages) &&
|
|
1403
|
+
(res90 as any).messages.length < h2.session.length,
|
|
1269
1404
|
"override 0.85: percent 90 DOES trim (above the override fire point)",
|
|
1270
1405
|
);
|
|
1271
1406
|
} finally {
|
|
@@ -1283,10 +1418,20 @@ test("S29: custom tier keeps the absolute token gate (percent 40 but tokens 100
|
|
|
1283
1418
|
try {
|
|
1284
1419
|
const h = harness({ keepTier: true, keepThreshold: true });
|
|
1285
1420
|
// percent 40 (low) BUT tokens 100 >= 50 threshold → custom token gate fires.
|
|
1286
|
-
const ctx = s29TieredCtx(h, {
|
|
1287
|
-
|
|
1421
|
+
const ctx = s29TieredCtx(h, {
|
|
1422
|
+
tokens: 100,
|
|
1423
|
+
contextWindow: 10000,
|
|
1424
|
+
percent: 40,
|
|
1425
|
+
});
|
|
1426
|
+
const res = await h.fire(
|
|
1427
|
+
"context",
|
|
1428
|
+
{ type: "context", messages: h.session },
|
|
1429
|
+
ctx,
|
|
1430
|
+
);
|
|
1288
1431
|
assert.ok(
|
|
1289
|
-
res &&
|
|
1432
|
+
res &&
|
|
1433
|
+
Array.isArray((res as any).messages) &&
|
|
1434
|
+
(res as any).messages.length < h.session.length,
|
|
1290
1435
|
"custom tier: token gate fires (tokens 100 >= 50) despite low percent 40",
|
|
1291
1436
|
);
|
|
1292
1437
|
} finally {
|
|
@@ -1305,10 +1450,20 @@ test("S29: tiered config with pct==null falls back to the token gate (not skippe
|
|
|
1305
1450
|
delete process.env.MEGACOMPACT_AUTO_PCT_TRIGGER;
|
|
1306
1451
|
try {
|
|
1307
1452
|
const h = harness({ keepTier: true, keepThreshold: true });
|
|
1308
|
-
const ctx = s29TieredCtx(h, {
|
|
1309
|
-
|
|
1453
|
+
const ctx = s29TieredCtx(h, {
|
|
1454
|
+
tokens: 6000,
|
|
1455
|
+
contextWindow: 10000,
|
|
1456
|
+
percent: null,
|
|
1457
|
+
});
|
|
1458
|
+
const res = await h.fire(
|
|
1459
|
+
"context",
|
|
1460
|
+
{ type: "context", messages: h.session },
|
|
1461
|
+
ctx,
|
|
1462
|
+
);
|
|
1310
1463
|
assert.ok(
|
|
1311
|
-
res &&
|
|
1464
|
+
res &&
|
|
1465
|
+
Array.isArray((res as any).messages) &&
|
|
1466
|
+
(res as any).messages.length < h.session.length,
|
|
1312
1467
|
"pct==null on tiered: token fallback fires (NOT skipped) — S27 boot-fallback preserved",
|
|
1313
1468
|
);
|
|
1314
1469
|
} finally {
|
|
@@ -13,7 +13,7 @@ import { addMemory, listMemories, searchMemories, recallMemory, type MemoryRecor
|
|
|
13
13
|
import { resolveRepoRoot } from "./mega-config.js";
|
|
14
14
|
import { defaultEmbedder } from "../src/embedder.js";
|
|
15
15
|
import { upsertMemoryEmbedding } from "../src/store/memoryIndex.js";
|
|
16
|
-
import { MegaRuntime } from "./mega-runtime.js";
|
|
16
|
+
import type { MegaRuntime } from "./mega-runtime.js";
|
|
17
17
|
|
|
18
18
|
/** Run the conflict scan and format a human-readable report. */
|
|
19
19
|
export function validateExtensions(): { report: ConflictReport; lines: string[] } {
|
|
@@ -111,8 +111,12 @@ export function registerConflictCommands(pi: ExtensionAPI, runtime: MegaRuntime)
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (sub === "recall") {
|
|
114
|
+
if (parts[1] === undefined) {
|
|
115
|
+
ctx.ui.notify("[mega-memory] usage: /mega-memory recall <id>");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
114
118
|
const id = Number(parts[1]);
|
|
115
|
-
if (!Number.isFinite(id)
|
|
119
|
+
if (!Number.isFinite(id)) {
|
|
116
120
|
ctx.ui.notify("[mega-memory] usage: /mega-memory recall <id>");
|
|
117
121
|
return;
|
|
118
122
|
}
|