stratagate-dsh 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -21
- package/README.md +110 -78
- package/cordis.patch.yml +12 -12
- package/dist/client.d.ts +1 -0
- package/dist/client.js +193 -0
- package/dist/index.js +334 -18
- package/dist/index.js.map +1 -1
- package/package.json +85 -51
package/dist/index.js
CHANGED
|
@@ -188,6 +188,7 @@ var DshModelBridge = class {
|
|
|
188
188
|
|
|
189
189
|
// src/runtime.ts
|
|
190
190
|
import { createHash } from "node:crypto";
|
|
191
|
+
import { existsSync } from "node:fs";
|
|
191
192
|
import { resolve } from "node:path";
|
|
192
193
|
|
|
193
194
|
// ../../src/blocks.ts
|
|
@@ -505,7 +506,7 @@ function rrfRank(rankings) {
|
|
|
505
506
|
}
|
|
506
507
|
|
|
507
508
|
// ../../src/storage.ts
|
|
508
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
509
|
+
var STRATAGATE_STORAGE_SCHEMA_VERSION = 4;
|
|
509
510
|
var StorageConflictError = class extends Error {
|
|
510
511
|
constructor(namespace, expectedRevision, actualRevision) {
|
|
511
512
|
super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
|
|
@@ -541,6 +542,11 @@ function normalizeSnapshot(value) {
|
|
|
541
542
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
542
543
|
ingestionReceipts: []
|
|
543
544
|
};
|
|
545
|
+
} else if (schemaVersion === 3) {
|
|
546
|
+
snapshot = {
|
|
547
|
+
...structuredClone(value),
|
|
548
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
|
|
549
|
+
};
|
|
544
550
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
545
551
|
snapshot = structuredClone(value);
|
|
546
552
|
} else {
|
|
@@ -882,6 +888,7 @@ CREATE TABLE IF NOT EXISTS usage_receipts (
|
|
|
882
888
|
receipt_id TEXT NOT NULL,
|
|
883
889
|
event_ids_json TEXT NOT NULL,
|
|
884
890
|
element_ids_json TEXT NOT NULL,
|
|
891
|
+
audit_json TEXT NOT NULL DEFAULT '{}',
|
|
885
892
|
created_at TEXT NOT NULL,
|
|
886
893
|
PRIMARY KEY (namespace, receipt_id),
|
|
887
894
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
@@ -1116,14 +1123,18 @@ var SqliteStorage = class {
|
|
|
1116
1123
|
updatedAt: row.updated_at
|
|
1117
1124
|
}));
|
|
1118
1125
|
const usageReceipts = this.database.prepare(`
|
|
1119
|
-
SELECT receipt_id, event_ids_json, element_ids_json, created_at
|
|
1126
|
+
SELECT receipt_id, event_ids_json, element_ids_json, audit_json, created_at
|
|
1120
1127
|
FROM usage_receipts WHERE namespace = ? ORDER BY created_at, receipt_id
|
|
1121
|
-
`).all(key).map((row) =>
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1128
|
+
`).all(key).map((row) => {
|
|
1129
|
+
const audit2 = parseJson(row.audit_json, "usage_receipts.audit_json");
|
|
1130
|
+
return {
|
|
1131
|
+
id: row.receipt_id,
|
|
1132
|
+
eventIds: parseJson(row.event_ids_json, "usage_receipts.event_ids_json"),
|
|
1133
|
+
elementIds: parseJson(row.element_ids_json, "usage_receipts.element_ids_json"),
|
|
1134
|
+
...Object.keys(audit2).length === 0 ? {} : { audit: audit2 },
|
|
1135
|
+
createdAt: row.created_at
|
|
1136
|
+
};
|
|
1137
|
+
});
|
|
1127
1138
|
const ingestionReceipts = this.database.prepare(`
|
|
1128
1139
|
SELECT receipt_id, created_at
|
|
1129
1140
|
FROM ingestion_receipts WHERE namespace = ? ORDER BY created_at, receipt_id
|
|
@@ -1462,8 +1473,8 @@ var SqliteStorage = class {
|
|
|
1462
1473
|
);
|
|
1463
1474
|
}
|
|
1464
1475
|
const insertReceipt = this.database.prepare(`
|
|
1465
|
-
INSERT INTO usage_receipts (namespace, receipt_id, event_ids_json, element_ids_json, created_at)
|
|
1466
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1476
|
+
INSERT INTO usage_receipts (namespace, receipt_id, event_ids_json, element_ids_json, audit_json, created_at)
|
|
1477
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1467
1478
|
ON CONFLICT (namespace, receipt_id) DO NOTHING
|
|
1468
1479
|
`);
|
|
1469
1480
|
for (const receipt of snapshot.usageReceipts) {
|
|
@@ -1472,6 +1483,7 @@ var SqliteStorage = class {
|
|
|
1472
1483
|
receipt.id,
|
|
1473
1484
|
JSON.stringify(receipt.eventIds),
|
|
1474
1485
|
JSON.stringify(receipt.elementIds),
|
|
1486
|
+
JSON.stringify(receipt.audit ?? {}),
|
|
1475
1487
|
receipt.createdAt
|
|
1476
1488
|
);
|
|
1477
1489
|
}
|
|
@@ -1495,14 +1507,18 @@ var SqliteStorage = class {
|
|
|
1495
1507
|
this.database.exec(SCHEMA);
|
|
1496
1508
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1497
1509
|
});
|
|
1498
|
-
} else if (version === 1 || version === 2) {
|
|
1510
|
+
} else if (version === 1 || version === 2 || version === 3) {
|
|
1499
1511
|
this.immediateTransaction(() => {
|
|
1500
1512
|
if (version === 1) {
|
|
1501
|
-
const
|
|
1502
|
-
if (!
|
|
1513
|
+
const receiptColumns2 = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
|
|
1514
|
+
if (!receiptColumns2.some(({ name: name2 }) => name2 === "element_ids_json")) {
|
|
1503
1515
|
this.database.exec("ALTER TABLE usage_receipts ADD COLUMN element_ids_json TEXT NOT NULL DEFAULT '[]'");
|
|
1504
1516
|
}
|
|
1505
1517
|
}
|
|
1518
|
+
const receiptColumns = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
|
|
1519
|
+
if (!receiptColumns.some(({ name: name2 }) => name2 === "audit_json")) {
|
|
1520
|
+
this.database.exec("ALTER TABLE usage_receipts ADD COLUMN audit_json TEXT NOT NULL DEFAULT '{}'");
|
|
1521
|
+
}
|
|
1506
1522
|
this.database.exec(SCHEMA);
|
|
1507
1523
|
this.database.prepare("UPDATE memory_spaces SET schema_version = ? WHERE schema_version < ?").run(STRATAGATE_STORAGE_SCHEMA_VERSION, STRATAGATE_STORAGE_SCHEMA_VERSION);
|
|
1508
1524
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
@@ -1537,6 +1553,10 @@ var SqliteStorage = class {
|
|
|
1537
1553
|
assertOpen() {
|
|
1538
1554
|
if (this.closed) throw new Error("SQLite storage is closed");
|
|
1539
1555
|
}
|
|
1556
|
+
listNamespaces() {
|
|
1557
|
+
this.assertOpen();
|
|
1558
|
+
return this.database.prepare("SELECT namespace FROM memory_spaces ORDER BY namespace").all().map(({ namespace }) => namespace);
|
|
1559
|
+
}
|
|
1540
1560
|
};
|
|
1541
1561
|
|
|
1542
1562
|
// ../../src/store.ts
|
|
@@ -1716,6 +1736,9 @@ var StrataGate = class _StrataGate {
|
|
|
1716
1736
|
listElementProjectionJobs() {
|
|
1717
1737
|
return [...this.elementProjectionJobs.values()];
|
|
1718
1738
|
}
|
|
1739
|
+
listUsageReceipts() {
|
|
1740
|
+
return [...this.usageReceipts.values()];
|
|
1741
|
+
}
|
|
1719
1742
|
exportSnapshot() {
|
|
1720
1743
|
return cloneSnapshot({
|
|
1721
1744
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
@@ -2036,11 +2059,12 @@ var StrataGate = class _StrataGate {
|
|
|
2036
2059
|
const normalizedRefs = Array.isArray(refs) ? { eventIds: refs } : refs;
|
|
2037
2060
|
const requestedEventIds = [...new Set(normalizedRefs.eventIds ?? [])];
|
|
2038
2061
|
const requestedElementIds = [...new Set(normalizedRefs.elementIds ?? [])];
|
|
2062
|
+
const audit2 = options.audit === void 0 ? void 0 : structuredClone(options.audit);
|
|
2039
2063
|
if (receiptId) {
|
|
2040
2064
|
const existing = this.usageReceipts.get(receiptId);
|
|
2041
2065
|
if (existing) {
|
|
2042
|
-
if (!sameIds(existing.eventIds, requestedEventIds) || !sameIds(existing.elementIds, requestedElementIds)) {
|
|
2043
|
-
throw new Error(`Usage receipt ${receiptId} was already recorded with different memory IDs`);
|
|
2066
|
+
if (!sameIds(existing.eventIds, requestedEventIds) || !sameIds(existing.elementIds, requestedElementIds) || JSON.stringify(existing.audit ?? null) !== JSON.stringify(audit2 ?? null)) {
|
|
2067
|
+
throw new Error(`Usage receipt ${receiptId} was already recorded with different memory IDs or audit metadata`);
|
|
2044
2068
|
}
|
|
2045
2069
|
return;
|
|
2046
2070
|
}
|
|
@@ -2065,6 +2089,7 @@ var StrataGate = class _StrataGate {
|
|
|
2065
2089
|
id: receiptId,
|
|
2066
2090
|
eventIds: requestedEventIds,
|
|
2067
2091
|
elementIds: requestedElementIds,
|
|
2092
|
+
...audit2 === void 0 ? {} : { audit: audit2 },
|
|
2068
2093
|
createdAt: now
|
|
2069
2094
|
});
|
|
2070
2095
|
});
|
|
@@ -2624,7 +2649,12 @@ var StrataGateRuntime = class {
|
|
|
2624
2649
|
for (const id of target?.eventIds ?? []) eventIds.add(id);
|
|
2625
2650
|
for (const id of target?.elementIds ?? []) elementIds.add(id);
|
|
2626
2651
|
}
|
|
2627
|
-
this.adopted.set(key, {
|
|
2652
|
+
this.adopted.set(key, {
|
|
2653
|
+
eventIds: [...eventIds],
|
|
2654
|
+
elementIds: [...elementIds],
|
|
2655
|
+
batchId: batch.id,
|
|
2656
|
+
assessment
|
|
2657
|
+
});
|
|
2628
2658
|
} else {
|
|
2629
2659
|
this.adopted.delete(key);
|
|
2630
2660
|
}
|
|
@@ -2634,7 +2664,20 @@ var StrataGateRuntime = class {
|
|
|
2634
2664
|
const key = String(session.id);
|
|
2635
2665
|
const refs = this.adopted.get(key);
|
|
2636
2666
|
if (!refs) throw new Error("No sufficient StrataGate evidence has been assessed for this session");
|
|
2637
|
-
|
|
2667
|
+
const turn = activeTurn(session);
|
|
2668
|
+
await (await this.space(session)).recordMemoryUse(refs, {
|
|
2669
|
+
receiptId: `dsh:${key}:tool:${receiptId}`,
|
|
2670
|
+
audit: {
|
|
2671
|
+
sessionId: key,
|
|
2672
|
+
...turn === void 0 ? {} : { turn },
|
|
2673
|
+
batchId: refs.batchId,
|
|
2674
|
+
evidenceRefs: refs.assessment.evidenceRefs,
|
|
2675
|
+
verdict: refs.assessment.verdict,
|
|
2676
|
+
fit: refs.assessment.fit,
|
|
2677
|
+
missing: refs.assessment.missing,
|
|
2678
|
+
nextStrategy: refs.assessment.nextStrategy
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2638
2681
|
this.adopted.delete(key);
|
|
2639
2682
|
return { recorded: true, eventIds: refs.eventIds, elementIds: refs.elementIds };
|
|
2640
2683
|
}
|
|
@@ -2665,6 +2708,28 @@ var StrataGateRuntime = class {
|
|
|
2665
2708
|
if (this.config.namespaceMode === "session") return `${prefix}:session:${String(session.id)}`;
|
|
2666
2709
|
return `${prefix}:project:${projectKey(session.header.cwd)}`;
|
|
2667
2710
|
}
|
|
2711
|
+
async adminNamespaces() {
|
|
2712
|
+
await this.flush();
|
|
2713
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
2714
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
2715
|
+
try {
|
|
2716
|
+
return storage.listNamespaces();
|
|
2717
|
+
} finally {
|
|
2718
|
+
await storage.close();
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
async adminSnapshot(namespace) {
|
|
2722
|
+
await this.flush();
|
|
2723
|
+
const key = namespace.trim();
|
|
2724
|
+
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
2725
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
2726
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
2727
|
+
try {
|
|
2728
|
+
return (await storage.load(key))?.snapshot ?? null;
|
|
2729
|
+
} finally {
|
|
2730
|
+
await storage.close();
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2668
2733
|
space(session) {
|
|
2669
2734
|
const namespace = this.namespaceFor(session);
|
|
2670
2735
|
let opening = this.spaces.get(namespace);
|
|
@@ -2692,6 +2757,13 @@ var StrataGateRuntime = class {
|
|
|
2692
2757
|
return { batchId: id, evidenceRefs: [...refs.keys()], results };
|
|
2693
2758
|
}
|
|
2694
2759
|
};
|
|
2760
|
+
function activeTurn(session) {
|
|
2761
|
+
for (let index = session.events.length - 1; index >= 0; index -= 1) {
|
|
2762
|
+
const event = session.events[index];
|
|
2763
|
+
if (event?.type === "turn/start") return event.data.turn;
|
|
2764
|
+
}
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2695
2767
|
|
|
2696
2768
|
// src/tools.ts
|
|
2697
2769
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -2813,6 +2885,246 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
2813
2885
|
}));
|
|
2814
2886
|
}
|
|
2815
2887
|
|
|
2888
|
+
// src/web.ts
|
|
2889
|
+
function sendJson(res, status, body) {
|
|
2890
|
+
res.statusCode = status;
|
|
2891
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
2892
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2893
|
+
res.end(JSON.stringify(redactValue(body)));
|
|
2894
|
+
}
|
|
2895
|
+
function numeric(value, fallback, minimum, maximum) {
|
|
2896
|
+
const parsed = Number(value);
|
|
2897
|
+
return Number.isFinite(parsed) ? Math.min(maximum, Math.max(minimum, Math.floor(parsed))) : fallback;
|
|
2898
|
+
}
|
|
2899
|
+
function redact(text2) {
|
|
2900
|
+
return text2.replace(/\b(?:sk|gh[opasu]|github_pat)_[A-Za-z0-9_-]{12,}\b/g, "[REDACTED_TOKEN]").replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]{12,}={0,2}\b/gi, "$1[REDACTED]").replace(/\b(api[_-]?key|token|password|secret)\s*[:=]\s*([^\s,;]+)/gi, "$1=[REDACTED]");
|
|
2901
|
+
}
|
|
2902
|
+
function redactValue(value) {
|
|
2903
|
+
if (typeof value === "string") return redact(value);
|
|
2904
|
+
if (Array.isArray(value)) return value.map(redactValue);
|
|
2905
|
+
if (value && typeof value === "object") {
|
|
2906
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)]));
|
|
2907
|
+
}
|
|
2908
|
+
return value;
|
|
2909
|
+
}
|
|
2910
|
+
function redactedMessage(message, blockId) {
|
|
2911
|
+
const { toolCalls, ...base } = message;
|
|
2912
|
+
const common = { ...base, content: redact(message.content), blockId };
|
|
2913
|
+
return toolCalls ? { ...common, toolCalls: redactValue(toolCalls) } : common;
|
|
2914
|
+
}
|
|
2915
|
+
function sourceMessages(snapshot, ids) {
|
|
2916
|
+
const output = [];
|
|
2917
|
+
for (const block of snapshot.blocks) {
|
|
2918
|
+
for (const message of block.l5Raw) {
|
|
2919
|
+
if (!ids || ids.has(message.id)) {
|
|
2920
|
+
output.push(redactedMessage(message, block.id));
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
for (const message of snapshot.openTail) {
|
|
2925
|
+
if (!ids || ids.has(message.id)) {
|
|
2926
|
+
output.push(redactedMessage(message, null));
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return output;
|
|
2930
|
+
}
|
|
2931
|
+
function eventSummary(event) {
|
|
2932
|
+
return {
|
|
2933
|
+
id: event.id,
|
|
2934
|
+
title: event.title,
|
|
2935
|
+
summary: event.summary,
|
|
2936
|
+
tags: event.tags,
|
|
2937
|
+
sourceBlockId: event.sourceBlockId,
|
|
2938
|
+
sourceMessageIds: event.sourceMessageIds,
|
|
2939
|
+
temporal: event.temporal,
|
|
2940
|
+
scope: event.scope,
|
|
2941
|
+
criticality: event.criticality,
|
|
2942
|
+
confidence: event.confidence,
|
|
2943
|
+
status: event.status,
|
|
2944
|
+
supersededBy: event.supersededBy,
|
|
2945
|
+
weight: event.weight,
|
|
2946
|
+
createdAt: event.createdAt,
|
|
2947
|
+
updatedAt: event.updatedAt
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
function elementSummary(element) {
|
|
2951
|
+
return {
|
|
2952
|
+
id: element.id,
|
|
2953
|
+
name: element.name,
|
|
2954
|
+
type: element.type,
|
|
2955
|
+
aliases: element.aliases,
|
|
2956
|
+
currentState: element.currentState,
|
|
2957
|
+
facts: element.facts,
|
|
2958
|
+
sourceEventIds: element.sourceEventIds,
|
|
2959
|
+
sourceMessageIds: element.sourceMessageIds,
|
|
2960
|
+
weight: element.weight,
|
|
2961
|
+
createdAt: element.createdAt,
|
|
2962
|
+
updatedAt: element.updatedAt
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
function matchesQuery(value, query) {
|
|
2966
|
+
if (!query) return true;
|
|
2967
|
+
return JSON.stringify(value).toLocaleLowerCase().includes(query.toLocaleLowerCase());
|
|
2968
|
+
}
|
|
2969
|
+
async function requiredSnapshot(runtime, namespace) {
|
|
2970
|
+
const snapshot = await runtime.adminSnapshot(namespace);
|
|
2971
|
+
if (!snapshot) throw new AdminHttpError(404, `Unknown StrataGate namespace: ${namespace}`);
|
|
2972
|
+
return snapshot;
|
|
2973
|
+
}
|
|
2974
|
+
var AdminHttpError = class extends Error {
|
|
2975
|
+
constructor(status, message) {
|
|
2976
|
+
super(message);
|
|
2977
|
+
this.status = status;
|
|
2978
|
+
}
|
|
2979
|
+
status;
|
|
2980
|
+
};
|
|
2981
|
+
async function overview(runtime) {
|
|
2982
|
+
const namespaces = await runtime.adminNamespaces();
|
|
2983
|
+
const rows = [];
|
|
2984
|
+
for (const namespace of namespaces) {
|
|
2985
|
+
const snapshot = await runtime.adminSnapshot(namespace);
|
|
2986
|
+
if (!snapshot) continue;
|
|
2987
|
+
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.elementProjectionJobs.filter(({ status }) => status === "failed").length;
|
|
2988
|
+
const timestamps = [
|
|
2989
|
+
...snapshot.blocks.map(({ createdAt }) => createdAt),
|
|
2990
|
+
...snapshot.events.map(({ updatedAt }) => updatedAt),
|
|
2991
|
+
...snapshot.elements.map(({ updatedAt }) => updatedAt),
|
|
2992
|
+
...snapshot.usageReceipts.map(({ createdAt }) => createdAt)
|
|
2993
|
+
].sort();
|
|
2994
|
+
rows.push({
|
|
2995
|
+
namespace,
|
|
2996
|
+
schemaVersion: snapshot.schemaVersion,
|
|
2997
|
+
currentTurn: snapshot.currentTurn,
|
|
2998
|
+
blockTurnSize: snapshot.blockTurnSize,
|
|
2999
|
+
blocks: snapshot.blocks.length,
|
|
3000
|
+
openTailMessages: snapshot.openTail.length,
|
|
3001
|
+
events: snapshot.events.length,
|
|
3002
|
+
activeEvents: snapshot.events.filter(({ status }) => status === "active").length,
|
|
3003
|
+
elements: snapshot.elements.length,
|
|
3004
|
+
usageReceipts: snapshot.usageReceipts.length,
|
|
3005
|
+
failedJobs,
|
|
3006
|
+
lastActivityAt: timestamps.at(-1) ?? null
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
return { readonly: true, namespaces: rows };
|
|
3010
|
+
}
|
|
3011
|
+
async function memories(runtime, url) {
|
|
3012
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
3013
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
3014
|
+
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
3015
|
+
const kind = url.searchParams.get("kind") ?? "events";
|
|
3016
|
+
const query = url.searchParams.get("q")?.trim() ?? "";
|
|
3017
|
+
const offset = numeric(url.searchParams.get("offset"), 0, 0, Number.MAX_SAFE_INTEGER);
|
|
3018
|
+
const limit = numeric(url.searchParams.get("limit"), 100, 1, 200);
|
|
3019
|
+
let values;
|
|
3020
|
+
if (kind === "events") values = snapshot.events.map(eventSummary);
|
|
3021
|
+
else if (kind === "elements") values = snapshot.elements.map(elementSummary);
|
|
3022
|
+
else if (kind === "blocks") values = snapshot.blocks.map((block) => ({
|
|
3023
|
+
id: block.id,
|
|
3024
|
+
sequence: block.sequence,
|
|
3025
|
+
turnRange: [block.startTurn, block.endTurn],
|
|
3026
|
+
title: block.l0Title,
|
|
3027
|
+
tags: block.l0Tags,
|
|
3028
|
+
summary: block.l1Summary,
|
|
3029
|
+
keypoints: block.l2Keypoints,
|
|
3030
|
+
currentLevel: block.pointerCurrentLevel,
|
|
3031
|
+
sourceMessages: block.l5Raw.length,
|
|
3032
|
+
createdAt: block.createdAt
|
|
3033
|
+
}));
|
|
3034
|
+
else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
3035
|
+
const filtered = values.filter((value) => matchesQuery(value, query));
|
|
3036
|
+
return { namespace, kind, total: filtered.length, offset, limit, items: filtered.slice(offset, offset + limit) };
|
|
3037
|
+
}
|
|
3038
|
+
async function sources(runtime, url) {
|
|
3039
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
3040
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
3041
|
+
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
3042
|
+
const eventId = url.searchParams.get("eventId");
|
|
3043
|
+
const elementId = url.searchParams.get("elementId");
|
|
3044
|
+
const blockId = url.searchParams.get("blockId");
|
|
3045
|
+
let events = [];
|
|
3046
|
+
let elements = [];
|
|
3047
|
+
let ids = /* @__PURE__ */ new Set();
|
|
3048
|
+
if (eventId) {
|
|
3049
|
+
const event = snapshot.events.find(({ id }) => id === eventId);
|
|
3050
|
+
if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
|
|
3051
|
+
events = [event];
|
|
3052
|
+
ids = new Set(event.sourceMessageIds);
|
|
3053
|
+
} else if (elementId) {
|
|
3054
|
+
const element = snapshot.elements.find(({ id }) => id === elementId);
|
|
3055
|
+
if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
|
|
3056
|
+
elements = [element];
|
|
3057
|
+
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
3058
|
+
ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
3059
|
+
} else if (blockId) {
|
|
3060
|
+
const block = snapshot.blocks.find(({ id }) => id === blockId);
|
|
3061
|
+
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
3062
|
+
ids = new Set(block.l5Raw.map(({ id }) => id));
|
|
3063
|
+
events = snapshot.events.filter(({ sourceBlockId }) => sourceBlockId === blockId);
|
|
3064
|
+
} else {
|
|
3065
|
+
throw new AdminHttpError(400, "eventId, elementId, or blockId is required");
|
|
3066
|
+
}
|
|
3067
|
+
return {
|
|
3068
|
+
namespace,
|
|
3069
|
+
events: events.map(eventSummary),
|
|
3070
|
+
elements: elements.map(elementSummary),
|
|
3071
|
+
messages: sourceMessages(snapshot, ids)
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
function receiptSources(snapshot, receipt) {
|
|
3075
|
+
const events = snapshot.events.filter(({ id }) => receipt.eventIds.includes(id));
|
|
3076
|
+
const elements = snapshot.elements.filter(({ id }) => receipt.elementIds.includes(id));
|
|
3077
|
+
const eventIds = /* @__PURE__ */ new Set([...receipt.eventIds, ...elements.flatMap(({ sourceEventIds }) => sourceEventIds)]);
|
|
3078
|
+
const supportingEvents = snapshot.events.filter(({ id }) => eventIds.has(id));
|
|
3079
|
+
const messageIds = new Set(supportingEvents.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
3080
|
+
return {
|
|
3081
|
+
...receipt,
|
|
3082
|
+
events: events.map(eventSummary),
|
|
3083
|
+
elements: elements.map(elementSummary),
|
|
3084
|
+
sourceMessages: sourceMessages(snapshot, messageIds)
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
async function audit(runtime, url) {
|
|
3088
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
3089
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
3090
|
+
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
3091
|
+
const offset = numeric(url.searchParams.get("offset"), 0, 0, Number.MAX_SAFE_INTEGER);
|
|
3092
|
+
const limit = numeric(url.searchParams.get("limit"), 50, 1, 100);
|
|
3093
|
+
const receipts = [...snapshot.usageReceipts].reverse();
|
|
3094
|
+
return {
|
|
3095
|
+
namespace,
|
|
3096
|
+
total: receipts.length,
|
|
3097
|
+
offset,
|
|
3098
|
+
limit,
|
|
3099
|
+
items: receipts.slice(offset, offset + limit).map((receipt) => receiptSources(snapshot, receipt))
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
async function handleAdminRequest(runtime, req, res) {
|
|
3103
|
+
try {
|
|
3104
|
+
if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate Memory UI is read-only");
|
|
3105
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3106
|
+
const path = url.pathname.replace(/\/$/, "");
|
|
3107
|
+
if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
3108
|
+
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
3109
|
+
else if (path === "/api/stratagate/sources") sendJson(res, 200, await sources(runtime, url));
|
|
3110
|
+
else if (path === "/api/stratagate/audit") sendJson(res, 200, await audit(runtime, url));
|
|
3111
|
+
else throw new AdminHttpError(404, "Unknown StrataGate admin route");
|
|
3112
|
+
} catch (error) {
|
|
3113
|
+
const status = error instanceof AdminHttpError ? error.status : 500;
|
|
3114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3115
|
+
sendJson(res, status, { error: message });
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
function registerAdminRoutes(ctx, runtime) {
|
|
3119
|
+
const webServer = ctx.get("webServer");
|
|
3120
|
+
if (!webServer) return void 0;
|
|
3121
|
+
return webServer.register({
|
|
3122
|
+
kind: "prefix",
|
|
3123
|
+
path: "/api/stratagate",
|
|
3124
|
+
handler: (req, res) => handleAdminRequest(runtime, req, res)
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2816
3128
|
// src/index.ts
|
|
2817
3129
|
var name = "stratagate-memory";
|
|
2818
3130
|
var inject = ["tools", "systemPrompt", "llm", "agentDefaultModel"];
|
|
@@ -2836,9 +3148,13 @@ async function apply(ctx, config) {
|
|
|
2836
3148
|
});
|
|
2837
3149
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
2838
3150
|
registerMemoryTools(ctx, runtime);
|
|
3151
|
+
const disposeAdminRoutes = registerAdminRoutes(ctx, runtime);
|
|
2839
3152
|
ctx.on("session/event", (session, event) => runtime.acceptEvent(session, event));
|
|
2840
3153
|
ctx.logger.info(`stratagate-memory ready (${resolved.namespaceMode} namespaces, ${resolved.database})`);
|
|
2841
|
-
return async () =>
|
|
3154
|
+
return async () => {
|
|
3155
|
+
disposeAdminRoutes?.();
|
|
3156
|
+
await runtime.close();
|
|
3157
|
+
};
|
|
2842
3158
|
}
|
|
2843
3159
|
export {
|
|
2844
3160
|
Config,
|