@psiclawops/hypermem 0.7.0 → 0.8.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/ARCHITECTURE.md +30 -38
- package/README.md +83 -35
- package/dist/background-indexer.d.ts +14 -3
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +126 -18
- package/dist/budget-policy.d.ts +22 -0
- package/dist/budget-policy.d.ts.map +1 -0
- package/dist/budget-policy.js +27 -0
- package/dist/cache.d.ts +11 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/compositor-utils.d.ts +31 -0
- package/dist/compositor-utils.d.ts.map +1 -0
- package/dist/compositor-utils.js +47 -0
- package/dist/compositor.d.ts +163 -1
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +862 -130
- package/dist/content-hash.d.ts +43 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +75 -0
- package/dist/context-store.d.ts +54 -0
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +102 -0
- package/dist/contradiction-audit-store.d.ts +54 -0
- package/dist/contradiction-audit-store.d.ts.map +1 -0
- package/dist/contradiction-audit-store.js +88 -0
- package/dist/contradiction-resolution-policy.d.ts +21 -0
- package/dist/contradiction-resolution-policy.d.ts.map +1 -0
- package/dist/contradiction-resolution-policy.js +17 -0
- package/dist/degradation.d.ts +102 -0
- package/dist/degradation.d.ts.map +1 -0
- package/dist/degradation.js +141 -0
- package/dist/dreaming-promoter.d.ts +38 -0
- package/dist/dreaming-promoter.d.ts.map +1 -1
- package/dist/dreaming-promoter.js +68 -2
- package/dist/index.d.ts +68 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +402 -26
- package/dist/knowledge-lint.d.ts +2 -0
- package/dist/knowledge-lint.d.ts.map +1 -1
- package/dist/knowledge-lint.js +40 -1
- package/dist/library-schema.d.ts +7 -2
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +236 -1
- package/dist/message-store.d.ts +64 -1
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +137 -1
- package/dist/open-domain.js +1 -1
- package/dist/proactive-pass.d.ts +2 -2
- package/dist/proactive-pass.d.ts.map +1 -1
- package/dist/proactive-pass.js +66 -12
- package/dist/replay-recovery.d.ts +29 -0
- package/dist/replay-recovery.d.ts.map +1 -0
- package/dist/replay-recovery.js +82 -0
- package/dist/reranker.d.ts +95 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +308 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -1
- package/dist/session-flusher.d.ts +2 -2
- package/dist/session-flusher.d.ts.map +1 -1
- package/dist/session-flusher.js +1 -1
- package/dist/temporal-store.js +2 -2
- package/dist/tool-artifact-store.d.ts +98 -0
- package/dist/tool-artifact-store.d.ts.map +1 -0
- package/dist/tool-artifact-store.js +244 -0
- package/dist/topic-detector.js +2 -2
- package/dist/topic-store.d.ts +6 -0
- package/dist/topic-store.d.ts.map +1 -1
- package/dist/topic-store.js +39 -0
- package/dist/types.d.ts +233 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +2 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +3 -0
- package/dist/version.d.ts +10 -10
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -10
- package/package.json +6 -4
package/dist/library-schema.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* 9. Work items (fleet kanban)
|
|
17
17
|
* 10. Topics (cross-session thread tracking)
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
20
|
+
import { mkdirSync, renameSync, existsSync } from 'node:fs';
|
|
21
|
+
import { join as pathJoin, dirname } from 'node:path';
|
|
22
|
+
export const LIBRARY_SCHEMA_VERSION = 19;
|
|
20
23
|
function nowIso() {
|
|
21
24
|
return new Date().toISOString();
|
|
22
25
|
}
|
|
@@ -321,6 +324,7 @@ function applyV3Collections(db) {
|
|
|
321
324
|
)
|
|
322
325
|
`);
|
|
323
326
|
db.exec('CREATE INDEX IF NOT EXISTS idx_topics_agent ON topics(agent_id, status, updated_at DESC)');
|
|
327
|
+
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_topics_dedup ON topics(agent_id, lower(name))');
|
|
324
328
|
// ── Fleet registry ──
|
|
325
329
|
db.exec(`
|
|
326
330
|
CREATE TABLE IF NOT EXISTS fleet_agents (
|
|
@@ -868,6 +872,92 @@ function applyV12FosMod(db) {
|
|
|
868
872
|
}
|
|
869
873
|
}
|
|
870
874
|
}
|
|
875
|
+
// ── Repair utility ───────────────────────────────────────────
|
|
876
|
+
// Safe to call BEFORE opening the main DB connection.
|
|
877
|
+
// Handles the case where library.db has duplicate topics AND B-tree corruption,
|
|
878
|
+
// making in-place DELETE impossible.
|
|
879
|
+
//
|
|
880
|
+
// Strategy:
|
|
881
|
+
// 1. Detect duplicates (read-only: works on corrupt DBs)
|
|
882
|
+
// 2. VACUUM INTO temp file (writes to a new clean file)
|
|
883
|
+
// 3. Dedup + integrity check in temp
|
|
884
|
+
// 4. Backup original via VACUUM INTO backups dir
|
|
885
|
+
// 5. Atomic rename temp → original
|
|
886
|
+
export function repairLibraryDb(dbPath) {
|
|
887
|
+
if (!existsSync(dbPath)) {
|
|
888
|
+
return { repaired: false, message: `DB not found at ${dbPath}. Nothing to repair.` };
|
|
889
|
+
}
|
|
890
|
+
const backupsDir = pathJoin(dirname(dbPath), 'backups');
|
|
891
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
892
|
+
const tempPath = `${dbPath}.repair-${ts}.sqlite`;
|
|
893
|
+
const backupPath = pathJoin(backupsDir, `library.db.pre-repair-${ts}.sqlite`);
|
|
894
|
+
const src = new DatabaseSync(dbPath);
|
|
895
|
+
// Step 1: detect duplicates
|
|
896
|
+
const dupeRow = src.prepare(`
|
|
897
|
+
SELECT COUNT(*) AS cnt FROM (
|
|
898
|
+
SELECT agent_id, lower(name) FROM topics
|
|
899
|
+
GROUP BY agent_id, lower(name) HAVING COUNT(*) > 1
|
|
900
|
+
)
|
|
901
|
+
`).get();
|
|
902
|
+
if (dupeRow.cnt === 0) {
|
|
903
|
+
src.close();
|
|
904
|
+
return { repaired: false, message: 'No duplicate topics found. No repair needed.' };
|
|
905
|
+
}
|
|
906
|
+
console.log(`[hypermem-repair] ${dupeRow.cnt} duplicate topic group(s) found. Starting repair...`);
|
|
907
|
+
// Step 2: VACUUM INTO temp (reads clean pages, writes fresh file)
|
|
908
|
+
try {
|
|
909
|
+
src.exec(`VACUUM INTO '${tempPath}'`);
|
|
910
|
+
}
|
|
911
|
+
catch (err) {
|
|
912
|
+
src.close();
|
|
913
|
+
throw new Error(`[hypermem-repair] VACUUM INTO failed: ${err.message}. Cannot auto-repair.`);
|
|
914
|
+
}
|
|
915
|
+
src.close();
|
|
916
|
+
// Step 3: dedup + verify in temp
|
|
917
|
+
const tmp = new DatabaseSync(tempPath);
|
|
918
|
+
try {
|
|
919
|
+
tmp.exec(`
|
|
920
|
+
DELETE FROM topics WHERE id IN (
|
|
921
|
+
WITH ranked AS (
|
|
922
|
+
SELECT id, ROW_NUMBER() OVER (
|
|
923
|
+
PARTITION BY agent_id, lower(name)
|
|
924
|
+
ORDER BY updated_at DESC, created_at DESC, id DESC
|
|
925
|
+
) AS rn FROM topics
|
|
926
|
+
)
|
|
927
|
+
SELECT id FROM ranked WHERE rn > 1
|
|
928
|
+
)
|
|
929
|
+
`);
|
|
930
|
+
tmp.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_topics_dedup ON topics(agent_id, lower(name))');
|
|
931
|
+
const integ = tmp.prepare('PRAGMA integrity_check').get();
|
|
932
|
+
if (integ.integrity_check !== 'ok') {
|
|
933
|
+
throw new Error(`Repaired DB failed integrity check: ${integ.integrity_check}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
finally {
|
|
937
|
+
tmp.close();
|
|
938
|
+
}
|
|
939
|
+
// Step 4: backup original
|
|
940
|
+
mkdirSync(backupsDir, { recursive: true });
|
|
941
|
+
let savedBackup = false;
|
|
942
|
+
try {
|
|
943
|
+
const srcForBackup = new DatabaseSync(dbPath);
|
|
944
|
+
srcForBackup.exec(`VACUUM INTO '${backupPath}'`);
|
|
945
|
+
srcForBackup.close();
|
|
946
|
+
savedBackup = true;
|
|
947
|
+
console.log(`[hypermem-repair] Backup saved → ${backupPath}`);
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
console.warn('[hypermem-repair] Could not save backup, proceeding with repair anyway.');
|
|
951
|
+
}
|
|
952
|
+
// Step 5: atomic swap
|
|
953
|
+
renameSync(tempPath, dbPath);
|
|
954
|
+
const msg = [
|
|
955
|
+
`Repair complete. ${dupeRow.cnt} duplicate topic group(s) removed.`,
|
|
956
|
+
savedBackup ? `Original backed up to: ${backupPath}` : 'Note: backup could not be saved.',
|
|
957
|
+
].join(' ');
|
|
958
|
+
console.log(`[hypermem-repair] ${msg}`);
|
|
959
|
+
return { repaired: true, backupPath: savedBackup ? backupPath : undefined, message: msg };
|
|
960
|
+
}
|
|
871
961
|
// ── Migration runner ──────────────────────────────────────────
|
|
872
962
|
export function migrateLibrary(db, engineVersion) {
|
|
873
963
|
db.exec(`
|
|
@@ -1086,6 +1176,151 @@ export function migrateLibrary(db, engineVersion) {
|
|
|
1086
1176
|
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
1087
1177
|
.run(15, nowIso());
|
|
1088
1178
|
}
|
|
1179
|
+
if (currentVersion < 16) {
|
|
1180
|
+
// contradiction_audits table — tracks detected contradictions for review
|
|
1181
|
+
db.exec(`
|
|
1182
|
+
CREATE TABLE IF NOT EXISTS contradiction_audits (
|
|
1183
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1184
|
+
agent_id TEXT NOT NULL,
|
|
1185
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('fact')),
|
|
1186
|
+
new_content TEXT NOT NULL,
|
|
1187
|
+
new_domain TEXT,
|
|
1188
|
+
existing_fact_id INTEGER NOT NULL,
|
|
1189
|
+
existing_content TEXT NOT NULL,
|
|
1190
|
+
similarity_score REAL NOT NULL,
|
|
1191
|
+
contradiction_score REAL NOT NULL,
|
|
1192
|
+
reason TEXT NOT NULL,
|
|
1193
|
+
detector TEXT NOT NULL DEFAULT 'heuristic_v1',
|
|
1194
|
+
suggested_resolution TEXT NOT NULL DEFAULT 'review',
|
|
1195
|
+
source_ref TEXT,
|
|
1196
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'accepted', 'dismissed')),
|
|
1197
|
+
resolution_notes TEXT,
|
|
1198
|
+
created_at TEXT NOT NULL,
|
|
1199
|
+
resolved_at TEXT
|
|
1200
|
+
)
|
|
1201
|
+
`);
|
|
1202
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent_status ON contradiction_audits(agent_id, status, created_at DESC)');
|
|
1203
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_existing_fact ON contradiction_audits(existing_fact_id, status)');
|
|
1204
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent ON contradiction_audits(agent_id, created_at DESC)');
|
|
1205
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
1206
|
+
.run(16, nowIso());
|
|
1207
|
+
}
|
|
1208
|
+
if (currentVersion < 17) {
|
|
1209
|
+
// Stamp v17 — previously applied by an older engine build alongside contradiction_audits.
|
|
1210
|
+
// No additional DDL needed; the table was already created in v16.
|
|
1211
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
1212
|
+
.run(17, nowIso());
|
|
1213
|
+
}
|
|
1214
|
+
if (currentVersion < 18) {
|
|
1215
|
+
// V18: collapse duplicate topics, then enforce unique constraint.
|
|
1216
|
+
//
|
|
1217
|
+
// Safety pattern for existing users who may have both duplicates AND B-tree corruption
|
|
1218
|
+
// (e.g. from a WAL desync during a gateway crash):
|
|
1219
|
+
// 1. Detect duplicates up front (read-only — works even on malformed DBs)
|
|
1220
|
+
// 2. If dupes found: VACUUM INTO a backup file first (reads clean pages, writes new file)
|
|
1221
|
+
// 3. Attempt in-place DELETE (works on healthy DBs)
|
|
1222
|
+
// 4. If DELETE fails: throw a clear error pointing to the backup + repair command
|
|
1223
|
+
const dupeCheck = db.prepare(`
|
|
1224
|
+
SELECT COUNT(*) AS cnt FROM (
|
|
1225
|
+
SELECT agent_id, lower(name) FROM topics
|
|
1226
|
+
GROUP BY agent_id, lower(name) HAVING COUNT(*) > 1
|
|
1227
|
+
)
|
|
1228
|
+
`).get();
|
|
1229
|
+
let v18BackupPath;
|
|
1230
|
+
if (dupeCheck.cnt > 0) {
|
|
1231
|
+
// Auto-backup via VACUUM INTO before any writes.
|
|
1232
|
+
// VACUUM INTO reads clean pages and writes to a new file — works even on marginally
|
|
1233
|
+
// corrupt DBs where regular writes fail.
|
|
1234
|
+
const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
|
|
1235
|
+
const backupsDir = pathJoin(home, '.openclaw', 'hypermem', 'backups');
|
|
1236
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
1237
|
+
v18BackupPath = pathJoin(backupsDir, `library.db.v18-pre-dedup-${ts}.sqlite`);
|
|
1238
|
+
try {
|
|
1239
|
+
mkdirSync(backupsDir, { recursive: true });
|
|
1240
|
+
db.exec(`VACUUM INTO '${v18BackupPath}'`);
|
|
1241
|
+
console.log(`[hypermem-library] V18: backup saved → ${v18BackupPath}`);
|
|
1242
|
+
}
|
|
1243
|
+
catch (e) {
|
|
1244
|
+
console.warn(`[hypermem-library] V18: backup failed (${e.message}), proceeding without backup`);
|
|
1245
|
+
v18BackupPath = undefined;
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
db.exec(`
|
|
1249
|
+
DELETE FROM topics
|
|
1250
|
+
WHERE id IN (
|
|
1251
|
+
WITH ranked AS (
|
|
1252
|
+
SELECT
|
|
1253
|
+
id,
|
|
1254
|
+
ROW_NUMBER() OVER (
|
|
1255
|
+
PARTITION BY agent_id, lower(name)
|
|
1256
|
+
ORDER BY updated_at DESC, created_at DESC, id DESC
|
|
1257
|
+
) AS rn
|
|
1258
|
+
FROM topics
|
|
1259
|
+
)
|
|
1260
|
+
SELECT id FROM ranked WHERE rn > 1
|
|
1261
|
+
)
|
|
1262
|
+
`);
|
|
1263
|
+
}
|
|
1264
|
+
catch (err) {
|
|
1265
|
+
const backupMsg = v18BackupPath
|
|
1266
|
+
? `A VACUUM backup was saved to:\n ${v18BackupPath}\nRun: hypermem repair-library to recover automatically.`
|
|
1267
|
+
: 'Run: hypermem repair-library to recover.';
|
|
1268
|
+
throw new Error(`[hypermem-library] V18 migration: failed to deduplicate topics (${err.message}).\n` +
|
|
1269
|
+
`Your library.db has ${dupeCheck.cnt} duplicate topic group(s) that could not be removed in-place.\n` +
|
|
1270
|
+
`This usually indicates B-tree corruption from a previous gateway crash.\n` +
|
|
1271
|
+
backupMsg);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_topics_dedup ON topics(agent_id, lower(name))');
|
|
1275
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
1276
|
+
.run(18, nowIso());
|
|
1277
|
+
}
|
|
1278
|
+
if (currentVersion < 19) {
|
|
1279
|
+
// V19: Extend contradiction_audits status CHECK constraint to include
|
|
1280
|
+
// auto-resolution values. SQLite cannot ALTER CHECK constraints — recreate
|
|
1281
|
+
// the table with the new constraint, preserving all existing rows.
|
|
1282
|
+
// Use an explicit column list (not SELECT *) to guard against column drift.
|
|
1283
|
+
db.exec(`
|
|
1284
|
+
CREATE TABLE contradiction_audits_v19 (
|
|
1285
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1286
|
+
agent_id TEXT NOT NULL,
|
|
1287
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('fact')),
|
|
1288
|
+
new_content TEXT NOT NULL,
|
|
1289
|
+
new_domain TEXT,
|
|
1290
|
+
existing_fact_id INTEGER NOT NULL,
|
|
1291
|
+
existing_content TEXT NOT NULL,
|
|
1292
|
+
similarity_score REAL NOT NULL,
|
|
1293
|
+
contradiction_score REAL NOT NULL,
|
|
1294
|
+
reason TEXT NOT NULL,
|
|
1295
|
+
detector TEXT NOT NULL DEFAULT 'heuristic_v1',
|
|
1296
|
+
suggested_resolution TEXT NOT NULL DEFAULT 'review',
|
|
1297
|
+
source_ref TEXT,
|
|
1298
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
1299
|
+
CHECK(status IN ('pending', 'accepted', 'dismissed', 'auto-superseded', 'auto-invalidated')),
|
|
1300
|
+
resolution_notes TEXT,
|
|
1301
|
+
created_at TEXT NOT NULL,
|
|
1302
|
+
resolved_at TEXT
|
|
1303
|
+
);
|
|
1304
|
+
INSERT INTO contradiction_audits_v19
|
|
1305
|
+
(id, agent_id, entity_type, new_content, new_domain, existing_fact_id,
|
|
1306
|
+
existing_content, similarity_score, contradiction_score, reason,
|
|
1307
|
+
detector, suggested_resolution, source_ref, status, resolution_notes,
|
|
1308
|
+
created_at, resolved_at)
|
|
1309
|
+
SELECT
|
|
1310
|
+
id, agent_id, entity_type, new_content, new_domain, existing_fact_id,
|
|
1311
|
+
existing_content, similarity_score, contradiction_score, reason,
|
|
1312
|
+
detector, suggested_resolution, source_ref, status, resolution_notes,
|
|
1313
|
+
created_at, resolved_at
|
|
1314
|
+
FROM contradiction_audits;
|
|
1315
|
+
DROP TABLE contradiction_audits;
|
|
1316
|
+
ALTER TABLE contradiction_audits_v19 RENAME TO contradiction_audits;
|
|
1317
|
+
`);
|
|
1318
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent_status ON contradiction_audits(agent_id, status, created_at DESC)');
|
|
1319
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_existing_fact ON contradiction_audits(existing_fact_id, status)');
|
|
1320
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent ON contradiction_audits(agent_id, created_at DESC)');
|
|
1321
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
1322
|
+
.run(19, nowIso());
|
|
1323
|
+
}
|
|
1089
1324
|
// Always ensure meta exists before stamping the running engine version.
|
|
1090
1325
|
// Some legacy/stale DBs reached schema >=10 without the V10 migration having
|
|
1091
1326
|
// actually created the table, which would make startup fail with
|
package/dist/message-store.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This is the write-through layer: Redis → here.
|
|
7
7
|
*/
|
|
8
8
|
import type { DatabaseSync } from 'node:sqlite';
|
|
9
|
-
import type { NeutralMessage, StoredMessage, Conversation, ChannelType, ConversationStatus, RecentTurn } from './types.js';
|
|
9
|
+
import type { NeutralMessage, StoredMessage, Conversation, ChannelType, ConversationStatus, RecentTurn, ArchivedMiningQuery, ArchivedMiningResult, MultiContextMiningOptions } from './types.js';
|
|
10
10
|
export declare class MessageStore {
|
|
11
11
|
private readonly db;
|
|
12
12
|
constructor(db: DatabaseSync);
|
|
@@ -91,6 +91,12 @@ export declare class MessageStore {
|
|
|
91
91
|
*
|
|
92
92
|
* Falls back to getRecentMessages if the head message has no parent chain
|
|
93
93
|
* (e.g., legacy data before backfill).
|
|
94
|
+
*
|
|
95
|
+
* @boundary SHARED DAG PRIMITIVE — not for direct call at mining call sites.
|
|
96
|
+
* @policy See specs/DAG_HELPER_POLICY.md for operator-boundary rules.
|
|
97
|
+
* Use mineArchivedContext / mineArchivedContexts for archived context mining,
|
|
98
|
+
* and the active-composition paths for live session history. Direct call sites
|
|
99
|
+
* outside this class should be limited to exceptional diagnostic use.
|
|
94
100
|
*/
|
|
95
101
|
getHistoryByDAGWalk(headMessageId: number, limit?: number): StoredMessage[];
|
|
96
102
|
/**
|
|
@@ -110,6 +116,63 @@ export declare class MessageStore {
|
|
|
110
116
|
* Get message count for a conversation.
|
|
111
117
|
*/
|
|
112
118
|
getMessageCount(conversationId: number): number;
|
|
119
|
+
/**
|
|
120
|
+
* Get the full message chain for an archived or forked context.
|
|
121
|
+
*
|
|
122
|
+
* Throws if the context does not exist or is active (not archived/forked).
|
|
123
|
+
* Returns an empty array if the context has no head message.
|
|
124
|
+
* Delegates to getHistoryByDAGWalk for the actual chain retrieval.
|
|
125
|
+
*/
|
|
126
|
+
getArchivedChain(contextId: number, limit?: number): StoredMessage[];
|
|
127
|
+
/**
|
|
128
|
+
* Default maximum number of contextIds accepted by mineArchivedContexts.
|
|
129
|
+
* Callers may supply a lower value but not a higher one.
|
|
130
|
+
*/
|
|
131
|
+
static readonly ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX = 20;
|
|
132
|
+
/**
|
|
133
|
+
* Hard ceiling for mineArchivedContexts.
|
|
134
|
+
* Values above this are clamped to this number regardless of caller intent.
|
|
135
|
+
* This prevents unbounded DB fan-out on misconfigured or adversarial inputs.
|
|
136
|
+
*/
|
|
137
|
+
static readonly ARCHIVED_MULTI_CONTEXT_HARD_CEILING = 50;
|
|
138
|
+
/**
|
|
139
|
+
* Mine messages from a single archived or forked context.
|
|
140
|
+
*
|
|
141
|
+
* - Rejects active or missing contexts with a clear error.
|
|
142
|
+
* - Hard-caps limit at 200.
|
|
143
|
+
* - Defaults excludeHeartbeats to true.
|
|
144
|
+
* - Optionally filters by ftsQuery (client-side substring match for Sprint 3; SQL FTS is deferred).
|
|
145
|
+
* - Routes through getHistoryByDAGWalk for DAG-native retrieval.
|
|
146
|
+
* - Returns ArchivedMiningResult<StoredMessage[]> with isHistorical: true.
|
|
147
|
+
*
|
|
148
|
+
* This method does NOT widen active composition — it only operates on
|
|
149
|
+
* explicitly non-active (archived/forked) contexts.
|
|
150
|
+
*/
|
|
151
|
+
mineArchivedContext(query: ArchivedMiningQuery): ArchivedMiningResult<StoredMessage[]>;
|
|
152
|
+
/**
|
|
153
|
+
* Mine messages from multiple archived or forked contexts.
|
|
154
|
+
*
|
|
155
|
+
* ## maxContexts gate (Phase 4 Sprint 3, Task 1)
|
|
156
|
+
* Accepts an optional `maxContexts` in opts to control how many contextIds
|
|
157
|
+
* are accepted in a single call:
|
|
158
|
+
* - Default: ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX (20).
|
|
159
|
+
* - Hard ceiling: ARCHIVED_MULTI_CONTEXT_HARD_CEILING (50).
|
|
160
|
+
* - A caller-supplied value above the hard ceiling is clamped to the ceiling
|
|
161
|
+
* (not rejected), so callers need not know the exact constant.
|
|
162
|
+
* - A caller-supplied value at or below the ceiling is used as-is.
|
|
163
|
+
* - If contextIds.length exceeds the effective max, this method THROWS
|
|
164
|
+
* immediately — it does NOT soft-skip or truncate.
|
|
165
|
+
*
|
|
166
|
+
* ## Other behaviors (unchanged from Sprint 2)
|
|
167
|
+
* - Soft-skips active or missing contextIds with a warning (does not throw).
|
|
168
|
+
* - Preserves input order in the result array.
|
|
169
|
+
* - Applies per-context limit and same filters as mineArchivedContext.
|
|
170
|
+
* - Returns one ArchivedMiningResult per valid archived context.
|
|
171
|
+
*
|
|
172
|
+
* This method does NOT widen active composition — it only operates on
|
|
173
|
+
* explicitly non-active (archived/forked) contexts.
|
|
174
|
+
*/
|
|
175
|
+
mineArchivedContexts(contextIds: number[], opts?: MultiContextMiningOptions): ArchivedMiningResult<StoredMessage[]>[];
|
|
113
176
|
/**
|
|
114
177
|
* Infer channel type from session key format.
|
|
115
178
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-store.d.ts","sourceRoot":"","sources":["../src/message-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,UAAU,
|
|
1
|
+
{"version":3,"file":"message-store.d.ts","sourceRoot":"","sources":["../src/message-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AA+CpB,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAI7C;;OAEG;IACH,uBAAuB,CACrB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,WAAW,CAAC;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,YAAY;IAmDf;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAQxD;;OAEG;IACH,gBAAgB,CACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,kBAAkB,CAAC;QAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,YAAY,EAAE;IAwBjB;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE;QAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,kBAAkB,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI;IA2BR;;;;;;;OAOG;IACH,aAAa,CACX,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EACvB,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,aAAa;IAwFhB;;OAEG;IACH,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAerG;;;;;OAKG;IACH,wBAAwB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAe7H;;OAEG;IACH,gBAAgB,CACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,GACA,aAAa,EAAE;IAuBlB;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,aAAa,EAAE;IAmBnF;;;;OAIG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IA+B3D;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,aAAa,EAAE;IAiC/E;;;OAGG;IACH,sBAAsB,CACpB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAY,EACnB,IAAI,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5D,aAAa,EAAE;IAkBlB;;;OAGG;IACH,yBAAyB,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB,aAAa,EAAE;IAkBlB;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM;IAO/C;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAgBpE;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,kCAAkC,MAAM;IAExD;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,mCAAmC,MAAM;IAIzD;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAAC,aAAa,EAAE,CAAC;IA4CtF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,oBAAoB,CAClB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,CAAC,EAAE,yBAAyB,GAC/B,oBAAoB,CAAC,aAAa,EAAE,CAAC,EAAE;IA6C1C;;OAEG;IACH,OAAO,CAAC,gBAAgB;CASzB"}
|
package/dist/message-store.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* All messages are stored in provider-neutral format.
|
|
6
6
|
* This is the write-through layer: Redis → here.
|
|
7
7
|
*/
|
|
8
|
-
import { getOrCreateActiveContext, updateContextHead } from './context-store.js';
|
|
8
|
+
import { getOrCreateActiveContext, updateContextHead, getArchivedContext } from './context-store.js';
|
|
9
9
|
function nowIso() {
|
|
10
10
|
return new Date().toISOString();
|
|
11
11
|
}
|
|
@@ -332,6 +332,12 @@ export class MessageStore {
|
|
|
332
332
|
*
|
|
333
333
|
* Falls back to getRecentMessages if the head message has no parent chain
|
|
334
334
|
* (e.g., legacy data before backfill).
|
|
335
|
+
*
|
|
336
|
+
* @boundary SHARED DAG PRIMITIVE — not for direct call at mining call sites.
|
|
337
|
+
* @policy See specs/DAG_HELPER_POLICY.md for operator-boundary rules.
|
|
338
|
+
* Use mineArchivedContext / mineArchivedContexts for archived context mining,
|
|
339
|
+
* and the active-composition paths for live session history. Direct call sites
|
|
340
|
+
* outside this class should be limited to exceptional diagnostic use.
|
|
335
341
|
*/
|
|
336
342
|
getHistoryByDAGWalk(headMessageId, limit = 50) {
|
|
337
343
|
try {
|
|
@@ -414,6 +420,136 @@ export class MessageStore {
|
|
|
414
420
|
.get(conversationId);
|
|
415
421
|
return row.count;
|
|
416
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Get the full message chain for an archived or forked context.
|
|
425
|
+
*
|
|
426
|
+
* Throws if the context does not exist or is active (not archived/forked).
|
|
427
|
+
* Returns an empty array if the context has no head message.
|
|
428
|
+
* Delegates to getHistoryByDAGWalk for the actual chain retrieval.
|
|
429
|
+
*/
|
|
430
|
+
getArchivedChain(contextId, limit) {
|
|
431
|
+
const context = getArchivedContext(this.db, contextId);
|
|
432
|
+
if (!context) {
|
|
433
|
+
throw new Error('getArchivedChain: context must be archived or forked');
|
|
434
|
+
}
|
|
435
|
+
if (context.headMessageId === null) {
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
return this.getHistoryByDAGWalk(context.headMessageId, limit ?? 200);
|
|
439
|
+
}
|
|
440
|
+
// ─── Archived Mining (Phase 4 Sprint 2 / Sprint 3) ─────────────
|
|
441
|
+
/**
|
|
442
|
+
* Default maximum number of contextIds accepted by mineArchivedContexts.
|
|
443
|
+
* Callers may supply a lower value but not a higher one.
|
|
444
|
+
*/
|
|
445
|
+
static ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX = 20;
|
|
446
|
+
/**
|
|
447
|
+
* Hard ceiling for mineArchivedContexts.
|
|
448
|
+
* Values above this are clamped to this number regardless of caller intent.
|
|
449
|
+
* This prevents unbounded DB fan-out on misconfigured or adversarial inputs.
|
|
450
|
+
*/
|
|
451
|
+
static ARCHIVED_MULTI_CONTEXT_HARD_CEILING = 50;
|
|
452
|
+
// ─── Archived Mining (Phase 4 Sprint 2) ───────────────────────
|
|
453
|
+
/**
|
|
454
|
+
* Mine messages from a single archived or forked context.
|
|
455
|
+
*
|
|
456
|
+
* - Rejects active or missing contexts with a clear error.
|
|
457
|
+
* - Hard-caps limit at 200.
|
|
458
|
+
* - Defaults excludeHeartbeats to true.
|
|
459
|
+
* - Optionally filters by ftsQuery (client-side substring match for Sprint 3; SQL FTS is deferred).
|
|
460
|
+
* - Routes through getHistoryByDAGWalk for DAG-native retrieval.
|
|
461
|
+
* - Returns ArchivedMiningResult<StoredMessage[]> with isHistorical: true.
|
|
462
|
+
*
|
|
463
|
+
* This method does NOT widen active composition — it only operates on
|
|
464
|
+
* explicitly non-active (archived/forked) contexts.
|
|
465
|
+
*/
|
|
466
|
+
mineArchivedContext(query) {
|
|
467
|
+
const { contextId, limit, excludeHeartbeats = true, ftsQuery } = query;
|
|
468
|
+
const context = getArchivedContext(this.db, contextId);
|
|
469
|
+
if (!context) {
|
|
470
|
+
throw new Error(`mineArchivedContext: context ${contextId} does not exist or is not archived/forked. ` +
|
|
471
|
+
`Only archived or forked contexts may be mined.`);
|
|
472
|
+
}
|
|
473
|
+
// Hard cap at 200
|
|
474
|
+
const effectiveLimit = Math.min(limit ?? 200, 200);
|
|
475
|
+
let messages = [];
|
|
476
|
+
if (context.headMessageId !== null) {
|
|
477
|
+
messages = this.getHistoryByDAGWalk(context.headMessageId, effectiveLimit);
|
|
478
|
+
}
|
|
479
|
+
// Apply heartbeat filter (default: exclude)
|
|
480
|
+
if (excludeHeartbeats) {
|
|
481
|
+
messages = messages.filter(m => !m.isHeartbeat);
|
|
482
|
+
}
|
|
483
|
+
// Client-side ftsQuery filter (substring match for Sprint 2)
|
|
484
|
+
if (ftsQuery && ftsQuery.trim().length > 0) {
|
|
485
|
+
const q = ftsQuery.trim().toLowerCase();
|
|
486
|
+
messages = messages.filter(m => (m.textContent ?? '').toLowerCase().includes(q));
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
isHistorical: true,
|
|
490
|
+
contextId: context.id,
|
|
491
|
+
agentId: context.agentId,
|
|
492
|
+
sessionKey: context.sessionKey,
|
|
493
|
+
contextStatus: context.status,
|
|
494
|
+
contextUpdatedAt: context.updatedAt,
|
|
495
|
+
data: messages,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Mine messages from multiple archived or forked contexts.
|
|
500
|
+
*
|
|
501
|
+
* ## maxContexts gate (Phase 4 Sprint 3, Task 1)
|
|
502
|
+
* Accepts an optional `maxContexts` in opts to control how many contextIds
|
|
503
|
+
* are accepted in a single call:
|
|
504
|
+
* - Default: ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX (20).
|
|
505
|
+
* - Hard ceiling: ARCHIVED_MULTI_CONTEXT_HARD_CEILING (50).
|
|
506
|
+
* - A caller-supplied value above the hard ceiling is clamped to the ceiling
|
|
507
|
+
* (not rejected), so callers need not know the exact constant.
|
|
508
|
+
* - A caller-supplied value at or below the ceiling is used as-is.
|
|
509
|
+
* - If contextIds.length exceeds the effective max, this method THROWS
|
|
510
|
+
* immediately — it does NOT soft-skip or truncate.
|
|
511
|
+
*
|
|
512
|
+
* ## Other behaviors (unchanged from Sprint 2)
|
|
513
|
+
* - Soft-skips active or missing contextIds with a warning (does not throw).
|
|
514
|
+
* - Preserves input order in the result array.
|
|
515
|
+
* - Applies per-context limit and same filters as mineArchivedContext.
|
|
516
|
+
* - Returns one ArchivedMiningResult per valid archived context.
|
|
517
|
+
*
|
|
518
|
+
* This method does NOT widen active composition — it only operates on
|
|
519
|
+
* explicitly non-active (archived/forked) contexts.
|
|
520
|
+
*/
|
|
521
|
+
mineArchivedContexts(contextIds, opts) {
|
|
522
|
+
// ── maxContexts gate ──────────────────────────────────────────────────
|
|
523
|
+
const { maxContexts: callerMax, ...perContextOpts } = opts ?? {};
|
|
524
|
+
const effectiveMax = callerMax !== undefined
|
|
525
|
+
? Math.min(callerMax, MessageStore.ARCHIVED_MULTI_CONTEXT_HARD_CEILING)
|
|
526
|
+
: MessageStore.ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX;
|
|
527
|
+
if (contextIds.length > effectiveMax) {
|
|
528
|
+
throw new Error(`mineArchivedContexts: too many contextIds (${contextIds.length}). ` +
|
|
529
|
+
`Effective limit is ${effectiveMax} ` +
|
|
530
|
+
`(hard ceiling: ${MessageStore.ARCHIVED_MULTI_CONTEXT_HARD_CEILING}, ` +
|
|
531
|
+
`default: ${MessageStore.ARCHIVED_MULTI_CONTEXT_DEFAULT_MAX}). ` +
|
|
532
|
+
`Pass fewer contextIds or supply a higher maxContexts (max: ${MessageStore.ARCHIVED_MULTI_CONTEXT_HARD_CEILING}).`);
|
|
533
|
+
}
|
|
534
|
+
// ── end gate ─────────────────────────────────────────────────────────
|
|
535
|
+
const results = [];
|
|
536
|
+
for (const contextId of contextIds) {
|
|
537
|
+
const context = getArchivedContext(this.db, contextId);
|
|
538
|
+
if (!context) {
|
|
539
|
+
console.warn(`[hypermem:message-store] mineArchivedContexts: skipping contextId ${contextId} ` +
|
|
540
|
+
`— does not exist or is not archived/forked (may be active or missing).`);
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
results.push(this.mineArchivedContext({ contextId, ...perContextOpts }));
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
console.warn(`[hypermem:message-store] mineArchivedContexts: skipping contextId ${contextId} ` +
|
|
548
|
+
`— ${err.message}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return results;
|
|
552
|
+
}
|
|
417
553
|
// ─── Helpers ─────────────────────────────────────────────────
|
|
418
554
|
/**
|
|
419
555
|
* Infer channel type from session key format.
|
package/dist/open-domain.js
CHANGED
package/dist/proactive-pass.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface ToolDecayResult {
|
|
|
42
42
|
* Deletions are wrapped in a single transaction. The FTS5 trigger handles
|
|
43
43
|
* index cleanup automatically (msg_fts_ad fires on DELETE).
|
|
44
44
|
*/
|
|
45
|
-
export declare function runNoiseSweep(db: DatabaseSync, conversationId: number, recentWindowSize?: number): NoiseSweepResult;
|
|
45
|
+
export declare function runNoiseSweep(db: DatabaseSync, conversationId: number, recentWindowSize?: number, maxCandidates?: number): NoiseSweepResult;
|
|
46
46
|
/**
|
|
47
47
|
* Truncate oversized tool_results outside the recent window.
|
|
48
48
|
*
|
|
@@ -59,5 +59,5 @@ export declare function runNoiseSweep(db: DatabaseSync, conversationId: number,
|
|
|
59
59
|
*
|
|
60
60
|
* Mutations are committed in a single transaction.
|
|
61
61
|
*/
|
|
62
|
-
export declare function runToolDecay(db: DatabaseSync, conversationId: number, recentWindowSize?: number): ToolDecayResult;
|
|
62
|
+
export declare function runToolDecay(db: DatabaseSync, conversationId: number, recentWindowSize?: number, maxCandidates?: number): ToolDecayResult;
|
|
63
63
|
//# sourceMappingURL=proactive-pass.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proactive-pass.d.ts","sourceRoot":"","sources":["../src/proactive-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"proactive-pass.d.ts","sourceRoot":"","sources":["../src/proactive-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAwFD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,EAC7B,aAAa,GAAE,MAAiB,GAC/B,gBAAgB,CAwFlB;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,EAC7B,aAAa,GAAE,MAAiB,GAC/B,eAAe,CAgHjB"}
|