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/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 = 3;
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
- id: row.receipt_id,
1123
- eventIds: parseJson(row.event_ids_json, "usage_receipts.event_ids_json"),
1124
- elementIds: parseJson(row.element_ids_json, "usage_receipts.element_ids_json"),
1125
- createdAt: row.created_at
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 receiptColumns = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
1502
- if (!receiptColumns.some(({ name: name2 }) => name2 === "element_ids_json")) {
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, { eventIds: [...eventIds], elementIds: [...elementIds] });
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
- await (await this.space(session)).recordMemoryUse(refs, { receiptId: `dsh:${key}:tool:${receiptId}` });
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 () => runtime.close();
3154
+ return async () => {
3155
+ disposeAdminRoutes?.();
3156
+ await runtime.close();
3157
+ };
2842
3158
  }
2843
3159
  export {
2844
3160
  Config,