pi-mega-compact 0.9.2 → 0.10.1
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 +3 -2
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +95 -47
- package/dist/extensions/dashboard-server/api-contracts/index.js +1 -1
- package/dist/extensions/dashboard-server/api-contracts/turns.js +10 -0
- package/dist/extensions/dashboard-server/api-contracts.test.js +3 -2
- package/dist/extensions/dashboard-server/routes-topics.js +87 -0
- package/dist/extensions/dashboard-server/routes-turns.js +251 -0
- package/dist/extensions/dashboard-server/routes.js +3 -1
- package/dist/extensions/dashboard-server/server.js +6 -2
- package/dist/extensions/mega-compact.js +10 -6
- package/dist/extensions/mega-config.js +4 -0
- package/dist/extensions/mega-events/agent-handlers.js +62 -49
- package/dist/extensions/mega-events/context-handler.js +48 -2
- package/dist/extensions/mega-events/session-handlers.js +24 -0
- package/dist/extensions/mega-metrics-cmds.js +90 -0
- package/dist/extensions/mega-pipeline/recall.js +47 -17
- package/dist/extensions/mega-topics-cmds.js +111 -0
- package/dist/extensions/mega-turn-store.js +110 -0
- package/dist/extensions/mega-turn-store.test.js +68 -0
- package/dist/src/config/turns.js +48 -0
- package/dist/src/fork.js +33 -0
- package/dist/src/fork.test.js +81 -0
- package/dist/src/intent.js +51 -0
- package/dist/src/intent.test.js +78 -0
- package/dist/src/metrics/index.js +4 -0
- package/dist/src/metrics/turns.js +118 -0
- package/dist/src/metrics/turns.test.js +152 -0
- package/dist/src/store/sqlite/raw-transcript.js +5 -3
- package/dist/src/store/sqlite.dbmirror.test.js +9 -0
- package/dist/src/store/turns/connection.js +123 -0
- package/dist/src/store/turns/contract-compliance.test.js +428 -0
- package/dist/src/store/turns/index.js +31 -0
- package/dist/src/store/turns/memory-store.js +357 -0
- package/dist/src/store/turns/memory-store.test.js +9 -0
- package/dist/src/store/turns/migrations.js +178 -0
- package/dist/src/store/turns/migrations.test.js +138 -0
- package/dist/src/store/turns/schema.js +115 -0
- package/dist/src/store/turns/sqlite-store.js +455 -0
- package/dist/src/store/turns/sqlite-store.test.js +144 -0
- package/dist/src/store/turns/types.js +16 -0
- package/dist/src/topics/cluster.js +141 -0
- package/dist/src/topics/cluster.test.js +150 -0
- package/dist/src/topics/index.js +6 -0
- package/dist/src/topics/kselection.js +142 -0
- package/dist/src/topics/labels.js +56 -0
- package/dist/src/topics/store.js +119 -0
- package/dist/src/topics/store.test.js +117 -0
- package/dist/src/topics/types.js +8 -0
- package/dist/src/wiki.js +133 -0
- package/dist/src/wiki.test.js +171 -0
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-QPo46Pwg.js → AchievementTiles-DUulvXUY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-QPo46Pwg.js.map → AchievementTiles-DUulvXUY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BDJxRXRY.js → AchievementsTab-Dx0YtCNf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BDJxRXRY.js.map → AchievementsTab-Dx0YtCNf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CUMqXQjd.js → CacheTab-DtkaUo1v.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CUMqXQjd.js.map → CacheTab-DtkaUo1v.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-j1dRzurG.js → ConfigTab-CLa4KfiZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-j1dRzurG.js.map → ConfigTab-CLa4KfiZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CMglXK4p.js → EventsTab-Q6tEIBBi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CMglXK4p.js.map → EventsTab-Q6tEIBBi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-fpaDoyxi.js → GameTab-Hq7GNWSY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-fpaDoyxi.js.map → GameTab-Hq7GNWSY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWG2crG_.js → MetricsTab-BtmBV6lU.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWG2crG_.js.map → MetricsTab-BtmBV6lU.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BaV2D2nf.js → OverviewTab-CL10PwVU.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BaV2D2nf.js.map → OverviewTab-CL10PwVU.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-C-0kuZ_0.js → ReposTab-DAwANwbI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-C-0kuZ_0.js.map → ReposTab-DAwANwbI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CSYhddCA.js → SessionsTab-Ec08TZRQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CSYhddCA.js.map → SessionsTab-Ec08TZRQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dy4KRjSM.js → TimeSavedCard-COb8dpTy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dy4KRjSM.js.map → TimeSavedCard-COb8dpTy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-kvX3F3dY.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-kvX3F3dY.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-CCUE73Aa.js +2 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-CCUE73Aa.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-D_WtU2TV.js → index-UMrussFX.js} +10 -10
- package/extensions/dashboard-client/dist/assets/index-UMrussFX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-og3ghxyY.css → index-gn66lYD2.css} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-C9yVJlGP.js → useSSE-BDHZ7CeS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-C9yVJlGP.js.map → useSSE-BDHZ7CeS.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +10 -2
- package/extensions/dashboard-client/src/api/client.ts +84 -1
- package/extensions/dashboard-client/src/main.tsx +1 -0
- package/extensions/dashboard-client/src/styles/turns.css +142 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +185 -0
- package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +326 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +389 -291
- package/extensions/dashboard-server/api-contracts/game-types.ts +141 -88
- package/extensions/dashboard-server/api-contracts/index.ts +153 -133
- package/extensions/dashboard-server/api-contracts/turns.ts +114 -0
- package/extensions/dashboard-server/api-contracts.test.ts +920 -919
- package/extensions/dashboard-server/routes-topics.ts +119 -0
- package/extensions/dashboard-server/routes-turns.ts +282 -0
- package/extensions/dashboard-server/routes.ts +8 -1
- package/extensions/dashboard-server/server.ts +6 -2
- package/extensions/mega-compact.ts +75 -59
- package/extensions/mega-config.ts +232 -220
- package/extensions/mega-events/agent-handlers.ts +194 -151
- package/extensions/mega-events/context-handler.ts +50 -1
- package/extensions/mega-events/session-handlers.ts +23 -0
- package/extensions/mega-metrics-cmds.ts +114 -0
- package/extensions/mega-pipeline/recall.ts +215 -152
- package/extensions/mega-topics-cmds.ts +151 -0
- package/extensions/mega-turn-store.test.ts +108 -0
- package/extensions/mega-turn-store.ts +169 -0
- package/package.json +3 -1
- package/src/config/turns.ts +69 -0
- package/src/fork.test.ts +94 -0
- package/src/fork.ts +78 -0
- package/src/intent.test.ts +87 -0
- package/src/intent.ts +115 -0
- package/src/metrics/index.ts +9 -0
- package/src/metrics/turns.test.ts +165 -0
- package/src/metrics/turns.ts +201 -0
- package/src/store/sqlite/raw-transcript.ts +9 -3
- package/src/store/sqlite.dbmirror.test.ts +10 -0
- package/src/store/turns/connection.ts +137 -0
- package/src/store/turns/contract-compliance.test.ts +562 -0
- package/src/store/turns/index.ts +65 -0
- package/src/store/turns/memory-store.test.ts +15 -0
- package/src/store/turns/memory-store.ts +469 -0
- package/src/store/turns/migrations.test.ts +161 -0
- package/src/store/turns/migrations.ts +203 -0
- package/src/store/turns/schema.ts +158 -0
- package/src/store/turns/sqlite-store.test.ts +161 -0
- package/src/store/turns/sqlite-store.ts +656 -0
- package/src/store/turns/types.ts +181 -0
- package/src/topics/cluster.test.ts +176 -0
- package/src/topics/cluster.ts +191 -0
- package/src/topics/index.ts +18 -0
- package/src/topics/kselection.ts +182 -0
- package/src/topics/labels.ts +67 -0
- package/src/topics/store.test.ts +132 -0
- package/src/topics/store.ts +208 -0
- package/src/topics/types.ts +64 -0
- package/src/wiki.test.ts +217 -0
- package/src/wiki.ts +198 -0
- package/dist/extensions/openclaw-mega-compact.js +0 -291
- package/dist/src/minilm.js +0 -92
- package/dist/src/wordpiece.js +0 -129
- package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +0 -1
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
} from "../mega-pipeline.js";
|
|
19
19
|
import { recallMemoriesAndInline } from "../../src/recall.js";
|
|
20
20
|
import { vectorStats } from "../../src/vectorStore.js";
|
|
21
|
+
import { openTurnStore } from "../../src/store/turns/connection.js";
|
|
22
|
+
import { openIntentQueue } from "../../src/intent.js";
|
|
21
23
|
import type { MegaConfig } from "../mega-config.js";
|
|
22
24
|
|
|
23
25
|
/** Register session lifecycle event handlers. */
|
|
@@ -148,6 +150,27 @@ export function registerSessionHandlers(
|
|
|
148
150
|
// ---- Auto-inline injection point: prepend staged recall to systemPrompt ----
|
|
149
151
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
150
152
|
runtime.captureModel(ctx); // most reliable point ctx.model is populated
|
|
153
|
+
// S52A: consume any pending rewind intents the dashboard queued. The host
|
|
154
|
+
// polls the intent queue here (the only safe lifecycle point) and marks each
|
|
155
|
+
// consumed. Best-effort + non-fatal — the store never calls back into the
|
|
156
|
+
// host (ledger protocol). A real rewind (reloading pi's conversation to turn
|
|
157
|
+
// N) is host-specific; this consumes the intent so the round-trip completes.
|
|
158
|
+
try {
|
|
159
|
+
if (config.turnsDbEnabled) {
|
|
160
|
+
const tdb = openTurnStore(runtime.currentStateDir);
|
|
161
|
+
const q = openIntentQueue(tdb);
|
|
162
|
+
const pending = q.pendingIntents();
|
|
163
|
+
for (const intent of pending) {
|
|
164
|
+
q.consumeIntent(intent.id);
|
|
165
|
+
runtime.dashboard.event("rewind_intent_consumed", {
|
|
166
|
+
conversationId: intent.conversationId,
|
|
167
|
+
targetTurnIndex: intent.targetTurnIndex,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
/* non-fatal: intent polling never breaks the agent loop */
|
|
173
|
+
}
|
|
151
174
|
const cpBlock = runtime.pendingRecallBlock;
|
|
152
175
|
const memBlock = runtime.pendingMemoryRecallBlock;
|
|
153
176
|
if (!cpBlock && !memBlock) return;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mega-metrics-cmds.ts — S50C per-turn memory-quality /commands.
|
|
3
|
+
*
|
|
4
|
+
* Registers /mega-metrics and /mega-fork, backed by the pi-agnostic
|
|
5
|
+
* src/metrics/turns.ts rollups and src/fork.ts primitive over the isolated
|
|
6
|
+
* S49 turns.db. Read-only (PREVENT-PI-001/002 — these never mutate memory,
|
|
7
|
+
* drop ranges, or compaction); local SQLite only (PREVENT-PI-004);
|
|
8
|
+
* parameterized queries (PREVENT-002). Both require turnsDbEnabled (the
|
|
9
|
+
* isolated store); they no-op with guidance on the legacy main-db path.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import type { MegaRuntime } from "./mega-runtime.js";
|
|
14
|
+
import type { MegaConfig } from "./mega-config.js";
|
|
15
|
+
import { turnStoreFor, mainDbFor } from "./mega-turn-store.js";
|
|
16
|
+
import { turnMetrics, conversationMetrics } from "../src/metrics/index.js";
|
|
17
|
+
import { forkFromConversation, ForkError } from "../src/fork.js";
|
|
18
|
+
import { ensureConversationIdFor } from "./mega-turn-store.js";
|
|
19
|
+
|
|
20
|
+
function pct(r: number): string {
|
|
21
|
+
return `${(r * 100).toFixed(1)}%`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Register the /mega-metrics + /mega-fork commands. */
|
|
25
|
+
export function registerMetricsCommands(
|
|
26
|
+
pi: ExtensionAPI,
|
|
27
|
+
runtime: MegaRuntime,
|
|
28
|
+
config: MegaConfig,
|
|
29
|
+
): void {
|
|
30
|
+
pi.registerCommand("mega-metrics", {
|
|
31
|
+
description:
|
|
32
|
+
"Show per-turn + per-conversation memory-quality metrics (recall reuse, dedup ratio, compression ratio). Usage: /mega-metrics [conversationId]",
|
|
33
|
+
handler: async (args: string, ctx: ExtensionContext) => {
|
|
34
|
+
try {
|
|
35
|
+
runtime.bindRepo(ctx.cwd);
|
|
36
|
+
const stateDir = runtime.currentStateDir;
|
|
37
|
+
const store = turnStoreFor(config, stateDir);
|
|
38
|
+
if (!store) {
|
|
39
|
+
ctx.ui.notify(
|
|
40
|
+
"[mega-compact] /mega-metrics requires the isolated turns.db (set turnsDbEnabled on).",
|
|
41
|
+
);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const conv =
|
|
45
|
+
args.trim() ||
|
|
46
|
+
ensureConversationIdFor(config, runtime.rt.sessionId, stateDir);
|
|
47
|
+
const mainDb = mainDbFor(stateDir);
|
|
48
|
+
const perTurn = turnMetrics(store, mainDb, conv);
|
|
49
|
+
if (perTurn.length === 0) {
|
|
50
|
+
ctx.ui.notify(`[mega-compact] no turns recorded for conversation ${conv}.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const agg = conversationMetrics(store, mainDb, conv);
|
|
54
|
+
ctx.ui.notify(
|
|
55
|
+
`[mega-compact] conversation ${conv} — ${agg.turnCount} turns, ${agg.epochCount} epoch(s), total recall ${agg.totalRecall}, total raw msgs ${agg.totalRawMessages}`,
|
|
56
|
+
);
|
|
57
|
+
ctx.ui.notify(
|
|
58
|
+
` avg dedup unique ratio ${pct(agg.avgDedupUniqueRatio)} · avg compression ratio ${pct(agg.avgCompressionRatio)}`,
|
|
59
|
+
);
|
|
60
|
+
for (const t of perTurn) {
|
|
61
|
+
ctx.ui.notify(
|
|
62
|
+
` turn ${t.turnIndex} (epoch ${t.epochId ?? "—"}): recall ${t.recallCount}, raw ${t.rawMessageCount}, dedup ${pct(t.dedupUniqueRatio)}, compress ${pct(t.compressionRatio)}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
ctx.ui.notify(`[mega-compact] /mega-metrics failed: ${String(e)}`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
pi.registerCommand("mega-fork", {
|
|
72
|
+
description:
|
|
73
|
+
"Fork the current conversation at turn N into a new child conversation, rehydrating that turn's injected checkpoints (recall-to-point, not window replay). Usage: /mega-fork <turnIndex> [conversationId]",
|
|
74
|
+
handler: async (args: string, ctx: ExtensionContext) => {
|
|
75
|
+
try {
|
|
76
|
+
runtime.bindRepo(ctx.cwd);
|
|
77
|
+
const stateDir = runtime.currentStateDir;
|
|
78
|
+
const store = turnStoreFor(config, stateDir);
|
|
79
|
+
if (!store) {
|
|
80
|
+
ctx.ui.notify(
|
|
81
|
+
"[mega-compact] /mega-fork requires the isolated turns.db (set turnsDbEnabled on).",
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
86
|
+
const turnIndex = Number.parseInt(parts[0] ?? "", 10);
|
|
87
|
+
if (!Number.isFinite(turnIndex)) {
|
|
88
|
+
ctx.ui.notify("[mega-compact] /mega-fork needs a turn index, e.g. /mega-fork 3");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const conv =
|
|
92
|
+
parts[1] ||
|
|
93
|
+
ensureConversationIdFor(config, runtime.rt.sessionId, stateDir);
|
|
94
|
+
const out = forkFromConversation(store, conv, turnIndex);
|
|
95
|
+
runtime.logger.info("fork", {
|
|
96
|
+
parent: conv,
|
|
97
|
+
child: out.childConversationId,
|
|
98
|
+
turnIndex,
|
|
99
|
+
checkpoints: out.checkpointIds,
|
|
100
|
+
});
|
|
101
|
+
ctx.ui.notify(
|
|
102
|
+
`[mega-compact] forked ${conv} @ turn ${turnIndex} → ${out.childConversationId}\n` +
|
|
103
|
+
` replay set (${out.checkpointIds.length} checkpoint(s)): ${out.checkpointIds.join(", ")}`,
|
|
104
|
+
);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
if (e instanceof ForkError) {
|
|
107
|
+
ctx.ui.notify(`[mega-compact] /mega-fork: ${e.message}`);
|
|
108
|
+
} else {
|
|
109
|
+
ctx.ui.notify(`[mega-compact] /mega-fork failed: ${String(e)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -8,15 +8,25 @@
|
|
|
8
8
|
|
|
9
9
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
recallAndInline,
|
|
13
|
+
recallAndInlineAsync,
|
|
14
|
+
formatRecallBlock,
|
|
15
|
+
type RecallInjectResult,
|
|
16
|
+
} from "../../src/recall.js";
|
|
12
17
|
import { normalizeSessionId } from "../../src/store.js";
|
|
13
|
-
import { incRecallInjected, incCacheHitTokens, getIndexDir } from "../../src/store/sqlite.js";
|
|
14
|
-
import { ensureConversationId, recordTurn, recordTurnRecall, type RecallSource } from "../../src/store/sqlite/turns.js";
|
|
15
18
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
incRecallInjected,
|
|
20
|
+
incCacheHitTokens,
|
|
21
|
+
getIndexDir,
|
|
22
|
+
} from "../../src/store/sqlite.js";
|
|
23
|
+
import {
|
|
24
|
+
ensureConversationIdFor,
|
|
25
|
+
recordTurnWrite,
|
|
26
|
+
recordRecallWrite,
|
|
27
|
+
} from "../mega-turn-store.js";
|
|
28
|
+
import { type MegaRuntime, C } from "../mega-runtime.js";
|
|
29
|
+
import type { MegaConfig } from "../mega-config.js";
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
32
|
* Unified recall (Layer 5). The ONE path that injects. Returns the recall
|
|
@@ -24,73 +34,101 @@ import { type MegaConfig } from "../mega-config.js";
|
|
|
24
34
|
* or report it (command).
|
|
25
35
|
*/
|
|
26
36
|
export function doRecall(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
runtime: MegaRuntime,
|
|
38
|
+
config: MegaConfig,
|
|
39
|
+
ctx: ExtensionContext,
|
|
40
|
+
query: string,
|
|
41
|
+
source: "resume" | "command",
|
|
32
42
|
) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
43
|
+
runtime.bindRepo(ctx.cwd);
|
|
44
|
+
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
45
|
+
// Live window text for inline dedupe (Fix C): drop recalled checkpoints that
|
|
46
|
+
// are already resident in the session, so recall never re-injects context the
|
|
47
|
+
// model can already see. Best-effort — an empty window just skips dedupe.
|
|
48
|
+
const liveWindow = config.windowDedupe ? extractLiveWindow(ctx) : undefined;
|
|
49
|
+
const result = recallAndInline(
|
|
50
|
+
{
|
|
51
|
+
sessionId: sid,
|
|
52
|
+
query,
|
|
53
|
+
limit: config.autoInlineK,
|
|
54
|
+
source,
|
|
55
|
+
skipInjected: true,
|
|
56
|
+
recallMaxTokens: config.recallMaxTokens,
|
|
57
|
+
windowDedupe: config.windowDedupe,
|
|
58
|
+
liveWindow,
|
|
59
|
+
dedupSim: config.dedupSim,
|
|
60
|
+
},
|
|
61
|
+
runtime.store,
|
|
62
|
+
);
|
|
63
|
+
runtime.dashboard.event("recall", {
|
|
64
|
+
source,
|
|
65
|
+
query: query.slice(0, 120),
|
|
66
|
+
injected: result.toInject.length,
|
|
67
|
+
empty: result.empty,
|
|
68
|
+
});
|
|
69
|
+
if (!result.empty && result.toInject.length > 0) {
|
|
70
|
+
const top = result.toInject[0];
|
|
71
|
+
const scorePct = Math.round((top.score ?? 0) * 100);
|
|
72
|
+
const files = top.checkpoint.filesModified ?? [];
|
|
73
|
+
const label = files.length
|
|
74
|
+
? files
|
|
75
|
+
.map((f) => f.split("/").pop() ?? f)
|
|
76
|
+
.slice(0, 2)
|
|
77
|
+
.join(", ")
|
|
78
|
+
: top.checkpoint.checkpointId;
|
|
79
|
+
runtime.pushTicker(
|
|
80
|
+
`${C.amber}↩${C.reset} recalled ${top.checkpoint.checkpointId} · ${scorePct}% · ${label}`,
|
|
81
|
+
);
|
|
82
|
+
runtime.lastWhy = `why: recalled@${scorePct}% (${result.toInject.length} chkpt)`;
|
|
83
|
+
}
|
|
84
|
+
if (result.toInject.length > 0) {
|
|
85
|
+
let sumTokens = 0;
|
|
86
|
+
for (const h of result.toInject) sumTokens += h.checkpoint.tokenEstimate;
|
|
87
|
+
runtime.rt.recallInjections += result.toInject.length;
|
|
88
|
+
runtime.rt.cacheHitTokens += sumTokens;
|
|
89
|
+
incRecallInjected(result.toInject.length, runtime.currentStateDir);
|
|
90
|
+
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
91
|
+
// S43: record recall provenance — which checkpoints/summaries served this
|
|
92
|
+
// turn, their score + source path. Linked to the turn row written at
|
|
93
|
+
// turn_end via the conversation+turnIndex. Best-effort + non-fatal.
|
|
94
|
+
try {
|
|
95
|
+
const convId = ensureConversationIdFor(
|
|
96
|
+
config,
|
|
97
|
+
sid,
|
|
98
|
+
runtime.currentStateDir,
|
|
99
|
+
);
|
|
100
|
+
const turnId = recordTurnWrite(
|
|
101
|
+
config,
|
|
102
|
+
{
|
|
103
|
+
conversationId: convId,
|
|
104
|
+
sessionId: sid,
|
|
105
|
+
turnIndex: runtime.currentTurn,
|
|
106
|
+
role: "assistant",
|
|
107
|
+
startedAt: Date.now(),
|
|
108
|
+
},
|
|
109
|
+
runtime.currentStateDir,
|
|
110
|
+
);
|
|
111
|
+
recordRecallWrite(
|
|
112
|
+
config,
|
|
113
|
+
turnId,
|
|
114
|
+
result.toInject.map((h) => ({
|
|
115
|
+
checkpointId: h.checkpoint.checkpointId,
|
|
116
|
+
score: h.score,
|
|
117
|
+
source:
|
|
118
|
+
h.raptorLevel !== undefined
|
|
119
|
+
? "raptor"
|
|
120
|
+
: h.repoId
|
|
121
|
+
? "cross-repo"
|
|
122
|
+
: "flat",
|
|
123
|
+
raptorLevel: h.raptorLevel,
|
|
124
|
+
})),
|
|
125
|
+
runtime.currentStateDir,
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
/* non-fatal: recall provenance never breaks the recall path */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
94
132
|
}
|
|
95
133
|
|
|
96
134
|
/**
|
|
@@ -105,72 +143,96 @@ export function doRecall(
|
|
|
105
143
|
* the same-repo result unchanged.
|
|
106
144
|
*/
|
|
107
145
|
export async function doRecallAsync(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
runtime: MegaRuntime,
|
|
147
|
+
config: MegaConfig,
|
|
148
|
+
ctx: ExtensionContext,
|
|
149
|
+
query: string,
|
|
150
|
+
source: "resume" | "command",
|
|
151
|
+
opts: { crossRepo?: boolean } = {},
|
|
114
152
|
): Promise<RecallInjectResult> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
153
|
+
runtime.bindRepo(ctx.cwd);
|
|
154
|
+
const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
|
|
155
|
+
const liveWindow = config.windowDedupe ? extractLiveWindow(ctx) : undefined;
|
|
156
|
+
// Sync same-repo first (fast, never blocks).
|
|
157
|
+
const sameRepo = recallAndInline(
|
|
158
|
+
{
|
|
159
|
+
sessionId: sid,
|
|
160
|
+
query,
|
|
161
|
+
limit: config.autoInlineK,
|
|
162
|
+
source,
|
|
163
|
+
skipInjected: true,
|
|
164
|
+
recallMaxTokens: config.recallMaxTokens,
|
|
165
|
+
windowDedupe: config.windowDedupe,
|
|
166
|
+
liveWindow,
|
|
167
|
+
dedupSim: config.dedupSim,
|
|
168
|
+
},
|
|
169
|
+
runtime.store,
|
|
170
|
+
);
|
|
171
|
+
if (!config.crossRepoEnabled || !opts.crossRepo) return sameRepo;
|
|
172
|
+
if (sameRepo.toInject.length >= config.autoInlineK) return sameRepo; // same-repo satisfied
|
|
173
|
+
// Augment: cross-repo HNSW (async) with the stricter floor. Non-fatal.
|
|
174
|
+
try {
|
|
175
|
+
const x = await recallAndInlineAsync(
|
|
176
|
+
{
|
|
177
|
+
sessionId: sid,
|
|
178
|
+
query,
|
|
179
|
+
limit: config.autoInlineK,
|
|
180
|
+
source,
|
|
181
|
+
skipInjected: true,
|
|
182
|
+
recallMaxTokens: config.recallMaxTokens,
|
|
183
|
+
windowDedupe: config.windowDedupe,
|
|
184
|
+
liveWindow,
|
|
185
|
+
dedupSim: config.crossRepoCosine,
|
|
186
|
+
crossRepo: true,
|
|
187
|
+
// F2: resolve the machine-wide index dir via the shared resolver so the
|
|
188
|
+
// cross-repo injected-set dedup works even when MEGACOMPACT_INDEX_DIR is
|
|
189
|
+
// unset. The env var still wins when set (getIndexDir checks it first);
|
|
190
|
+
// the default (~/.mega-compact-index) is the same DB mega-commands and the
|
|
191
|
+
// dashboard read, so injection counts stay consistent. Without this, a
|
|
192
|
+
// bare `process.env` read returns undefined → cross-repo hits re-inject in
|
|
193
|
+
// every new session (the global injected-set is never consulted).
|
|
194
|
+
globalIndexDir: getIndexDir(),
|
|
195
|
+
},
|
|
196
|
+
runtime.store,
|
|
197
|
+
);
|
|
198
|
+
runtime.dashboard.event("recall-crossrepo", {
|
|
199
|
+
source,
|
|
200
|
+
query: query.slice(0, 120),
|
|
201
|
+
injected: x.toInject.length,
|
|
202
|
+
sourceRepos: x.toInject.map((h) => h.repoId).filter(Boolean),
|
|
203
|
+
});
|
|
204
|
+
// Merge, dedup by checkpointId, respect the same token cap by reformatting.
|
|
205
|
+
const seen = new Set(
|
|
206
|
+
sameRepo.toInject.map((h) => h.checkpoint.checkpointId),
|
|
207
|
+
);
|
|
208
|
+
const merged = [...sameRepo.toInject];
|
|
209
|
+
for (const h of x.toInject) {
|
|
210
|
+
if (!seen.has(h.checkpoint.checkpointId)) {
|
|
211
|
+
merged.push(h);
|
|
212
|
+
seen.add(h.checkpoint.checkpointId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const block = merged.length ? formatRecallBlock(merged) : "";
|
|
216
|
+
if (merged.length > 0) {
|
|
217
|
+
let sumTokens = 0;
|
|
218
|
+
for (const h of merged) sumTokens += h.checkpoint.tokenEstimate;
|
|
219
|
+
runtime.rt.recallInjections += merged.length;
|
|
220
|
+
runtime.rt.cacheHitTokens += sumTokens;
|
|
221
|
+
incRecallInjected(merged.length, runtime.currentStateDir);
|
|
222
|
+
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
toInject: merged,
|
|
226
|
+
report: merged.map(
|
|
227
|
+
(h) =>
|
|
228
|
+
` • ${h.checkpoint.checkpointId}${h.repoId ? ` (from ${h.repoId.split("/").filter(Boolean).pop()})` : ""}`,
|
|
229
|
+
),
|
|
230
|
+
block,
|
|
231
|
+
empty: merged.length === 0,
|
|
232
|
+
};
|
|
233
|
+
} catch {
|
|
234
|
+
return sameRepo; // cross-repo failure → same-repo only (non-fatal)
|
|
235
|
+
}
|
|
174
236
|
}
|
|
175
237
|
|
|
176
238
|
/**
|
|
@@ -180,18 +242,19 @@ export async function doRecallAsync(
|
|
|
180
242
|
* Mirrors recentUserQuery's use of sessionEntryToContextMessages.
|
|
181
243
|
*/
|
|
182
244
|
function extractLiveWindow(ctx: ExtensionContext): string[] {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
245
|
+
try {
|
|
246
|
+
const entries = ctx.sessionManager.getEntries();
|
|
247
|
+
const texts: string[] = [];
|
|
248
|
+
for (const e of entries) {
|
|
249
|
+
for (const m of sessionEntryToContextMessages(e)) {
|
|
250
|
+
const c = (m as { content?: unknown }).content;
|
|
251
|
+
if (typeof c === "string") texts.push(c);
|
|
252
|
+
else if (Array.isArray(c))
|
|
253
|
+
texts.push(c.map((b: { text?: string }) => b.text ?? "").join(" "));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return texts;
|
|
257
|
+
} catch {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
197
260
|
}
|