stratagate-dsh 0.2.37 → 0.2.39
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 +9 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/client.js +4 -4
- package/dist/index.js +860 -62
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/DSH.md +1 -1
- package/docs/DSH.zh-CN.md +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1263,7 +1263,7 @@ function normalizeRetrievalAssessment(input, batchEvidenceRefs) {
|
|
|
1263
1263
|
}
|
|
1264
1264
|
|
|
1265
1265
|
// packages/core/src/storage.ts
|
|
1266
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
1266
|
+
var STRATAGATE_STORAGE_SCHEMA_VERSION = 10;
|
|
1267
1267
|
var KNOWLEDGE_GRAPH_PROJECTOR_VERSION = 1;
|
|
1268
1268
|
var StorageConflictError = class extends Error {
|
|
1269
1269
|
constructor(namespace, expectedRevision, actualRevision) {
|
|
@@ -1387,7 +1387,15 @@ function normalizeSnapshot(value) {
|
|
|
1387
1387
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1388
1388
|
blocks: readyLegacyBlocks(legacy.blocks),
|
|
1389
1389
|
summaryJobs: [],
|
|
1390
|
-
extractionJobs: legacyExtractionJobs(legacy.extractionJobs)
|
|
1390
|
+
extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
|
|
1391
|
+
externalMemoryImportJobs: []
|
|
1392
|
+
};
|
|
1393
|
+
} else if (schemaVersion === 9) {
|
|
1394
|
+
const legacy = value;
|
|
1395
|
+
snapshot = {
|
|
1396
|
+
...structuredClone(legacy),
|
|
1397
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1398
|
+
externalMemoryImportJobs: []
|
|
1391
1399
|
};
|
|
1392
1400
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
1393
1401
|
snapshot = structuredClone(value);
|
|
@@ -1403,7 +1411,8 @@ function normalizeSnapshot(value) {
|
|
|
1403
1411
|
if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
|
|
1404
1412
|
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
1405
1413
|
}
|
|
1406
|
-
|
|
1414
|
+
if (!Array.isArray(snapshot.externalMemoryImportJobs)) snapshot.externalMemoryImportJobs = [];
|
|
1415
|
+
for (const key of ["openTail", "blocks", "summaryJobs", "events", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts", "externalMemoryImportJobs"]) {
|
|
1407
1416
|
if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
|
|
1408
1417
|
}
|
|
1409
1418
|
if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
|
|
@@ -1817,6 +1826,16 @@ CREATE TABLE IF NOT EXISTS ingestion_receipts (
|
|
|
1817
1826
|
PRIMARY KEY (namespace, receipt_id),
|
|
1818
1827
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
1819
1828
|
) STRICT;
|
|
1829
|
+
|
|
1830
|
+
CREATE TABLE IF NOT EXISTS external_memory_import_jobs (
|
|
1831
|
+
namespace TEXT NOT NULL,
|
|
1832
|
+
id TEXT NOT NULL,
|
|
1833
|
+
payload_json TEXT NOT NULL,
|
|
1834
|
+
created_at TEXT NOT NULL,
|
|
1835
|
+
updated_at TEXT NOT NULL,
|
|
1836
|
+
PRIMARY KEY (namespace, id),
|
|
1837
|
+
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
1838
|
+
) STRICT;
|
|
1820
1839
|
`;
|
|
1821
1840
|
var THREAD_INDEXES = `
|
|
1822
1841
|
CREATE INDEX IF NOT EXISTS messages_thread_idx ON messages(namespace, thread_id, position);
|
|
@@ -2092,6 +2111,10 @@ var SqliteStorage = class {
|
|
|
2092
2111
|
id: row.receipt_id,
|
|
2093
2112
|
createdAt: row.created_at
|
|
2094
2113
|
}));
|
|
2114
|
+
const externalMemoryImportJobs = this.database.prepare(`
|
|
2115
|
+
SELECT id, payload_json FROM external_memory_import_jobs
|
|
2116
|
+
WHERE namespace = ? ORDER BY created_at, id
|
|
2117
|
+
`).all(key).map((row) => parseJson(row.payload_json, "external_memory_import_jobs.payload_json"));
|
|
2095
2118
|
const graphState = this.database.prepare(`
|
|
2096
2119
|
SELECT nodes_json, edges_json, jobs_json FROM graph_state WHERE namespace = ?
|
|
2097
2120
|
`).get(key);
|
|
@@ -2115,6 +2138,7 @@ var SqliteStorage = class {
|
|
|
2115
2138
|
elementProjectionJobs,
|
|
2116
2139
|
usageReceipts,
|
|
2117
2140
|
ingestionReceipts,
|
|
2141
|
+
externalMemoryImportJobs,
|
|
2118
2142
|
successfulModelResponses
|
|
2119
2143
|
};
|
|
2120
2144
|
assertValidSnapshot(snapshot);
|
|
@@ -2498,6 +2522,14 @@ var SqliteStorage = class {
|
|
|
2498
2522
|
for (const receipt of snapshot.ingestionReceipts) {
|
|
2499
2523
|
insertIngestionReceipt.run(namespace, receipt.id, receipt.createdAt);
|
|
2500
2524
|
}
|
|
2525
|
+
this.database.prepare("DELETE FROM external_memory_import_jobs WHERE namespace = ?").run(namespace);
|
|
2526
|
+
const insertExternalMemoryImportJob = this.database.prepare(`
|
|
2527
|
+
INSERT INTO external_memory_import_jobs (namespace, id, payload_json, created_at, updated_at)
|
|
2528
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2529
|
+
`);
|
|
2530
|
+
for (const job of snapshot.externalMemoryImportJobs) {
|
|
2531
|
+
insertExternalMemoryImportJob.run(namespace, job.id, JSON.stringify(job), job.createdAt, job.updatedAt);
|
|
2532
|
+
}
|
|
2501
2533
|
this.database.prepare("DELETE FROM model_response_history WHERE namespace = ?").run(namespace);
|
|
2502
2534
|
const insertSuccessfulModelResponse = this.database.prepare(`
|
|
2503
2535
|
INSERT INTO model_response_history (namespace, id, kind, response, created_at)
|
|
@@ -2607,6 +2639,10 @@ var SqliteStorage = class {
|
|
|
2607
2639
|
this.assertOpen();
|
|
2608
2640
|
return this.database.prepare("SELECT namespace FROM memory_spaces ORDER BY namespace").all().map(({ namespace }) => namespace);
|
|
2609
2641
|
}
|
|
2642
|
+
listNamespaceRevisions() {
|
|
2643
|
+
this.assertOpen();
|
|
2644
|
+
return this.database.prepare("SELECT namespace, revision FROM memory_spaces ORDER BY namespace").all();
|
|
2645
|
+
}
|
|
2610
2646
|
};
|
|
2611
2647
|
|
|
2612
2648
|
// packages/core/src/store.ts
|
|
@@ -2642,6 +2678,10 @@ function errorMessage(error) {
|
|
|
2642
2678
|
return error instanceof Error ? error.message : String(error);
|
|
2643
2679
|
}
|
|
2644
2680
|
var STRATAGATE_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol("StrataGate constructor");
|
|
2681
|
+
var EXTERNAL_MEMORY_AUTO_APPLY_CONFIDENCE = 0.85;
|
|
2682
|
+
function externalMemoryFingerprint(value) {
|
|
2683
|
+
return `${normalizeSearchText(value.title)}\0${normalizeSearchText(value.summary)}`;
|
|
2684
|
+
}
|
|
2645
2685
|
var DERIVATION_MAX_ATTEMPTS = 3;
|
|
2646
2686
|
var DERIVATION_BACKOFF_MS = 1e3;
|
|
2647
2687
|
var StrataGate = class _StrataGate {
|
|
@@ -2669,6 +2709,7 @@ var StrataGate = class _StrataGate {
|
|
|
2669
2709
|
usageReceipts = /* @__PURE__ */ new Map();
|
|
2670
2710
|
successfulModelResponses = [];
|
|
2671
2711
|
ingestionReceipts = /* @__PURE__ */ new Map();
|
|
2712
|
+
externalMemoryImportJobs = /* @__PURE__ */ new Map();
|
|
2672
2713
|
currentTurn = 0;
|
|
2673
2714
|
storage;
|
|
2674
2715
|
namespace;
|
|
@@ -2842,6 +2883,25 @@ var StrataGate = class _StrataGate {
|
|
|
2842
2883
|
get storageRevision() {
|
|
2843
2884
|
return this.revision;
|
|
2844
2885
|
}
|
|
2886
|
+
/** Replace an out-of-date in-memory view with the latest durable namespace snapshot. */
|
|
2887
|
+
async reloadFromStorage() {
|
|
2888
|
+
if (!this.storage || !this.namespace) return false;
|
|
2889
|
+
const previous = this.mutationQueue;
|
|
2890
|
+
let release;
|
|
2891
|
+
this.mutationQueue = new Promise((resolve2) => {
|
|
2892
|
+
release = resolve2;
|
|
2893
|
+
});
|
|
2894
|
+
await previous;
|
|
2895
|
+
try {
|
|
2896
|
+
const loaded = await this.storage.load(this.namespace);
|
|
2897
|
+
if (!loaded || loaded.revision === this.revision) return false;
|
|
2898
|
+
this.restoreSnapshot(loaded.snapshot);
|
|
2899
|
+
this.revision = loaded.revision;
|
|
2900
|
+
return true;
|
|
2901
|
+
} finally {
|
|
2902
|
+
release();
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2845
2905
|
get blockTurnSize() {
|
|
2846
2906
|
return this.blockTurnSizeValue;
|
|
2847
2907
|
}
|
|
@@ -2903,6 +2963,9 @@ var StrataGate = class _StrataGate {
|
|
|
2903
2963
|
listSuccessfulModelResponses() {
|
|
2904
2964
|
return this.successfulModelResponses;
|
|
2905
2965
|
}
|
|
2966
|
+
listExternalMemoryImportJobs() {
|
|
2967
|
+
return [...this.externalMemoryImportJobs.values()].map((job) => structuredClone(job));
|
|
2968
|
+
}
|
|
2906
2969
|
async recordSuccessfulModelResponses(responses) {
|
|
2907
2970
|
if (responses.length === 0) return;
|
|
2908
2971
|
await this.commitMutation(() => {
|
|
@@ -2933,6 +2996,7 @@ var StrataGate = class _StrataGate {
|
|
|
2933
2996
|
elementProjectionJobs: [...this.elementProjectionJobs.values()],
|
|
2934
2997
|
usageReceipts: [...this.usageReceipts.values()],
|
|
2935
2998
|
ingestionReceipts: [...this.ingestionReceipts.values()],
|
|
2999
|
+
externalMemoryImportJobs: [...this.externalMemoryImportJobs.values()],
|
|
2936
3000
|
successfulModelResponses: this.successfulModelResponses
|
|
2937
3001
|
});
|
|
2938
3002
|
}
|
|
@@ -3040,7 +3104,41 @@ var StrataGate = class _StrataGate {
|
|
|
3040
3104
|
* overwritten: MERGE and SUPERSEDE create a new canonical Event that points
|
|
3041
3105
|
* back to the older Events.
|
|
3042
3106
|
*/
|
|
3043
|
-
|
|
3107
|
+
normalizeExternalMemoryDecision(candidate, matches, decision, forceConfirmation = false) {
|
|
3108
|
+
const allowed = new Set(matches.map(({ event }) => event.id));
|
|
3109
|
+
const exact = this.events.find((event) => event.status !== "forgotten" && event.status !== "archived" && externalMemoryFingerprint(event) === externalMemoryFingerprint(candidate));
|
|
3110
|
+
if (exact) allowed.add(exact.id);
|
|
3111
|
+
const existingEventIds = [...new Set((decision.existingEventIds ?? []).filter((id) => allowed.has(id)))];
|
|
3112
|
+
const requestedAction = this.normalizeExternalAction(decision.action);
|
|
3113
|
+
const missingTarget = requestedAction !== "ADD" && requestedAction !== "IGNORE" && existingEventIds.length === 0;
|
|
3114
|
+
const action = missingTarget ? "IGNORE" : requestedAction;
|
|
3115
|
+
const confidence2 = missingTarget ? 0.5 : Number.isFinite(decision.confidence) ? Math.max(0, Math.min(1, decision.confidence)) : 0.5;
|
|
3116
|
+
return {
|
|
3117
|
+
candidate: structuredClone(candidate),
|
|
3118
|
+
action,
|
|
3119
|
+
existingEventIds,
|
|
3120
|
+
matches: structuredClone([...matches]),
|
|
3121
|
+
confidence: confidence2,
|
|
3122
|
+
requiresConfirmation: forceConfirmation || confidence2 < EXTERNAL_MEMORY_AUTO_APPLY_CONFIDENCE,
|
|
3123
|
+
...decision.mergedCandidate ? { mergedCandidate: structuredClone(decision.mergedCandidate) } : {},
|
|
3124
|
+
...missingTarget ? { reason: "\u6A21\u578B\u672A\u5173\u8054\u5230\u5141\u8BB8\u8303\u56F4\u5185\u7684\u73B0\u6709\u8BB0\u5FC6\uFF0C\u5DF2\u5B89\u5168\u964D\u7EA7\u4E3A\u5FFD\u7565" } : typeof decision.reason === "string" && decision.reason.trim() ? { reason: decision.reason.trim().slice(0, 500) } : {}
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
async decideExternalMemoryCandidate(candidate, priorFingerprints, decider, topK, forceConfirmation = false) {
|
|
3128
|
+
const fingerprint = externalMemoryFingerprint(candidate);
|
|
3129
|
+
const exact = this.events.find((event) => event.status !== "forgotten" && event.status !== "archived" && externalMemoryFingerprint(event) === fingerprint);
|
|
3130
|
+
const duplicateInImport = priorFingerprints.has(fingerprint);
|
|
3131
|
+
const query = `${candidate.title} ${candidate.summary} ${(candidate.tags ?? []).join(" ")}`.trim();
|
|
3132
|
+
const matches = await this.searchEvents(query, { limit: topK, trackRetrieval: false });
|
|
3133
|
+
const decision = exact || duplicateInImport ? {
|
|
3134
|
+
action: "IGNORE",
|
|
3135
|
+
existingEventIds: exact ? [exact.id] : [],
|
|
3136
|
+
reason: exact ? "\u4E0E\u73B0\u6709\u8BB0\u5FC6\u5B8C\u5168\u91CD\u590D" : "\u4E0E\u672C\u6279\u6B21\u4E2D\u7684\u5019\u9009\u5B8C\u5168\u91CD\u590D",
|
|
3137
|
+
confidence: 1
|
|
3138
|
+
} : await decider({ candidate: structuredClone(candidate), matches: structuredClone(matches) });
|
|
3139
|
+
return this.normalizeExternalMemoryDecision(candidate, matches, decision, forceConfirmation);
|
|
3140
|
+
}
|
|
3141
|
+
async previewExternalMemoryImport(options) {
|
|
3044
3142
|
const text3 = options.text.trim();
|
|
3045
3143
|
if (!text3) throw new TypeError("External memory text must not be empty");
|
|
3046
3144
|
if (typeof options.decider !== "function") {
|
|
@@ -3051,28 +3149,198 @@ var StrataGate = class _StrataGate {
|
|
|
3051
3149
|
const extracted = await extractor({ text: text3, importedAt });
|
|
3052
3150
|
const candidates = Array.isArray(extracted?.candidates) ? extracted.candidates.slice(0, 200) : [];
|
|
3053
3151
|
const topK = Math.max(1, Math.min(20, Math.floor(options.topK ?? 5)));
|
|
3054
|
-
const
|
|
3055
|
-
const
|
|
3152
|
+
const seenFingerprints = /* @__PURE__ */ new Set();
|
|
3153
|
+
const decisions = [];
|
|
3056
3154
|
for (const candidate of candidates) {
|
|
3057
3155
|
if (!candidate || typeof candidate.title !== "string" || typeof candidate.summary !== "string") continue;
|
|
3058
|
-
const
|
|
3059
|
-
const
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3156
|
+
const fingerprint = externalMemoryFingerprint(candidate);
|
|
3157
|
+
const preview = await this.decideExternalMemoryCandidate(candidate, seenFingerprints, options.decider, topK);
|
|
3158
|
+
seenFingerprints.add(fingerprint);
|
|
3159
|
+
decisions.push(preview);
|
|
3160
|
+
}
|
|
3161
|
+
return { importedAt, baseRevision: this.revision, decisions };
|
|
3162
|
+
}
|
|
3163
|
+
getExternalMemoryImportJob(jobId) {
|
|
3164
|
+
const job = this.externalMemoryImportJobs.get(jobId.trim());
|
|
3165
|
+
return job ? structuredClone(job) : null;
|
|
3166
|
+
}
|
|
3167
|
+
async createExternalMemoryImportJob(text3) {
|
|
3168
|
+
const normalized = text3.trim();
|
|
3169
|
+
if (!normalized) throw new TypeError("External memory text must not be empty");
|
|
3170
|
+
const now = toUtc8Iso(this.now());
|
|
3171
|
+
let candidates = [];
|
|
3172
|
+
let parseError = null;
|
|
3173
|
+
try {
|
|
3174
|
+
candidates = parseExternalMemoryExport(normalized).candidates;
|
|
3175
|
+
} catch (error) {
|
|
3176
|
+
parseError = errorMessage(error).slice(0, 2e3);
|
|
3064
3177
|
}
|
|
3178
|
+
const job = {
|
|
3179
|
+
id: `import_${crypto.randomUUID()}`,
|
|
3180
|
+
text: normalized,
|
|
3181
|
+
importedAt: now,
|
|
3182
|
+
status: parseError ? "extracting" : candidates.length > 0 ? "processing" : "ready",
|
|
3183
|
+
candidates: structuredClone(candidates),
|
|
3184
|
+
decisions: [],
|
|
3185
|
+
processedCount: 0,
|
|
3186
|
+
totalCount: candidates.length,
|
|
3187
|
+
recoveredFromInvalidJson: false,
|
|
3188
|
+
parseError,
|
|
3189
|
+
lastError: null,
|
|
3190
|
+
sourceBlockId: null,
|
|
3191
|
+
importedCount: 0,
|
|
3192
|
+
createdAt: now,
|
|
3193
|
+
updatedAt: now
|
|
3194
|
+
};
|
|
3195
|
+
await this.commitMutation(() => this.externalMemoryImportJobs.set(job.id, structuredClone(job)));
|
|
3196
|
+
return structuredClone(job);
|
|
3197
|
+
}
|
|
3198
|
+
async completeExternalMemoryFallback(jobId, result) {
|
|
3199
|
+
const candidates = (Array.isArray(result.candidates) ? result.candidates : []).filter((candidate) => candidate && typeof candidate.title === "string" && typeof candidate.summary === "string").slice(0, 200);
|
|
3065
3200
|
return this.commitMutation(() => {
|
|
3201
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3202
|
+
if (job.status !== "extracting") return structuredClone(job);
|
|
3203
|
+
job.candidates = structuredClone(candidates);
|
|
3204
|
+
job.decisions = [];
|
|
3205
|
+
job.processedCount = 0;
|
|
3206
|
+
job.totalCount = candidates.length;
|
|
3207
|
+
job.recoveredFromInvalidJson = true;
|
|
3208
|
+
job.status = candidates.length > 0 ? "processing" : "failed";
|
|
3209
|
+
job.lastError = candidates.length > 0 ? null : "\u6A21\u578B\u672A\u80FD\u4ECE\u4E0D\u5408\u683C\u5185\u5BB9\u4E2D\u6062\u590D\u51FA\u4EFB\u4F55\u5019\u9009\u8BB0\u5FC6";
|
|
3210
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3211
|
+
return structuredClone(job);
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
async processNextExternalMemoryImport(jobId, decider, topK = 5) {
|
|
3215
|
+
if (typeof decider !== "function") throw new TypeError("External memory decider is required");
|
|
3216
|
+
const work = await this.prepareNextExternalMemoryImport(jobId, topK);
|
|
3217
|
+
if (!work) {
|
|
3218
|
+
const current = this.requireExternalMemoryImportJob(jobId);
|
|
3219
|
+
return structuredClone(current);
|
|
3220
|
+
}
|
|
3221
|
+
const decision = work.deterministicDecision ?? await decider({
|
|
3222
|
+
candidate: structuredClone(work.candidate),
|
|
3223
|
+
matches: structuredClone(work.matches)
|
|
3224
|
+
});
|
|
3225
|
+
return this.completeNextExternalMemoryImport(
|
|
3226
|
+
work.jobId,
|
|
3227
|
+
work.index,
|
|
3228
|
+
decision,
|
|
3229
|
+
work.matches,
|
|
3230
|
+
work.forceConfirmation
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
async prepareNextExternalMemoryImport(jobId, topK = 5) {
|
|
3234
|
+
const current = this.requireExternalMemoryImportJob(jobId);
|
|
3235
|
+
if (current.status !== "processing") return null;
|
|
3236
|
+
const index = current.processedCount;
|
|
3237
|
+
const candidate = current.candidates[index];
|
|
3238
|
+
if (!candidate) return null;
|
|
3239
|
+
const priorFingerprints = new Set(current.candidates.slice(0, index).map(externalMemoryFingerprint));
|
|
3240
|
+
const fingerprint = externalMemoryFingerprint(candidate);
|
|
3241
|
+
const exact = this.events.find((event) => event.status !== "forgotten" && event.status !== "archived" && externalMemoryFingerprint(event) === fingerprint);
|
|
3242
|
+
const duplicateInImport = priorFingerprints.has(fingerprint);
|
|
3243
|
+
const query = `${candidate.title} ${candidate.summary} ${(candidate.tags ?? []).join(" ")}`.trim();
|
|
3244
|
+
const matches = await this.searchEvents(query, {
|
|
3245
|
+
limit: Math.max(1, Math.min(20, Math.floor(topK))),
|
|
3246
|
+
trackRetrieval: false
|
|
3247
|
+
});
|
|
3248
|
+
const deterministicDecision = exact || duplicateInImport ? {
|
|
3249
|
+
action: "IGNORE",
|
|
3250
|
+
existingEventIds: exact ? [exact.id] : [],
|
|
3251
|
+
reason: exact ? "\u4E0E\u73B0\u6709\u8BB0\u5FC6\u5B8C\u5168\u91CD\u590D" : "\u4E0E\u672C\u6279\u6B21\u4E2D\u7684\u5019\u9009\u5B8C\u5168\u91CD\u590D",
|
|
3252
|
+
confidence: 1
|
|
3253
|
+
} : void 0;
|
|
3254
|
+
return {
|
|
3255
|
+
jobId: current.id,
|
|
3256
|
+
index,
|
|
3257
|
+
candidate: structuredClone(candidate),
|
|
3258
|
+
matches: structuredClone(matches),
|
|
3259
|
+
forceConfirmation: current.recoveredFromInvalidJson,
|
|
3260
|
+
...deterministicDecision ? { deterministicDecision } : {}
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
async completeNextExternalMemoryImport(jobId, index, decision, matches, forceConfirmation = false) {
|
|
3264
|
+
return this.commitMutation(() => {
|
|
3265
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3266
|
+
if (job.processedCount > index) return structuredClone(job);
|
|
3267
|
+
if (job.status !== "processing" || job.processedCount !== index) {
|
|
3268
|
+
throw new Error(`External memory import ${jobId} is no longer at candidate ${index}`);
|
|
3269
|
+
}
|
|
3270
|
+
const candidate = job.candidates[index];
|
|
3271
|
+
if (!candidate) throw new Error(`External memory import ${jobId} has no candidate ${index}`);
|
|
3272
|
+
const normalized = this.normalizeExternalMemoryDecision(candidate, matches, decision, forceConfirmation);
|
|
3273
|
+
job.decisions.push(structuredClone(normalized));
|
|
3274
|
+
job.processedCount += 1;
|
|
3275
|
+
if (job.processedCount >= job.totalCount) {
|
|
3276
|
+
job.status = job.decisions.some(({ requiresConfirmation }) => requiresConfirmation) ? "awaiting_confirmation" : "ready";
|
|
3277
|
+
}
|
|
3278
|
+
job.lastError = null;
|
|
3279
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3280
|
+
return structuredClone(job);
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
async failExternalMemoryImportJob(jobId, error) {
|
|
3284
|
+
return this.commitMutation(() => {
|
|
3285
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3286
|
+
job.status = "failed";
|
|
3287
|
+
job.lastError = errorMessage(error).slice(0, 2e3);
|
|
3288
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3289
|
+
return structuredClone(job);
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
async retryExternalMemoryImportJob(jobId) {
|
|
3293
|
+
return this.commitMutation(() => {
|
|
3294
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3295
|
+
if (job.status !== "failed") return structuredClone(job);
|
|
3296
|
+
job.status = job.candidates.length === 0 && job.parseError ? "extracting" : "processing";
|
|
3297
|
+
job.lastError = null;
|
|
3298
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3299
|
+
return structuredClone(job);
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3302
|
+
async completeExternalMemoryImportJob(jobId, result) {
|
|
3303
|
+
return this.commitMutation(() => {
|
|
3304
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3305
|
+
job.status = "committed";
|
|
3306
|
+
job.sourceBlockId = result.sourceBlockId;
|
|
3307
|
+
job.importedCount = result.addedEvents.length;
|
|
3308
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3309
|
+
return structuredClone(job);
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
async markExternalMemoryImportUndone(jobId) {
|
|
3313
|
+
return this.commitMutation(() => {
|
|
3314
|
+
const job = this.requireExternalMemoryImportJob(jobId);
|
|
3315
|
+
job.status = "undone";
|
|
3316
|
+
job.updatedAt = toUtc8Iso(this.now());
|
|
3317
|
+
return structuredClone(job);
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
async commitExternalMemoryImport(options) {
|
|
3321
|
+
const text3 = options.text.trim();
|
|
3322
|
+
if (!text3) throw new TypeError("External memory text must not be empty");
|
|
3323
|
+
if (options.candidates.length !== options.decisions.length) {
|
|
3324
|
+
throw new TypeError("External memory candidates and decisions must have the same length");
|
|
3325
|
+
}
|
|
3326
|
+
return this.commitMutation(() => {
|
|
3327
|
+
if (this.revision !== options.baseRevision) {
|
|
3328
|
+
throw new Error(`External memory preview is stale: expected revision ${options.baseRevision}, found ${this.revision}`);
|
|
3329
|
+
}
|
|
3330
|
+
const importedAt = toUtc8Iso(options.importedAt);
|
|
3331
|
+
const source = this.createExternalSourceBlock(text3, importedAt);
|
|
3066
3332
|
const addedEvents = [];
|
|
3067
3333
|
const changedEventIds = /* @__PURE__ */ new Set();
|
|
3068
3334
|
const decisions = [];
|
|
3069
|
-
for (const item of
|
|
3070
|
-
const action = this.normalizeExternalAction(item.
|
|
3071
|
-
const
|
|
3072
|
-
const
|
|
3335
|
+
for (const [index, item] of options.decisions.entries()) {
|
|
3336
|
+
const action = this.normalizeExternalAction(item.action);
|
|
3337
|
+
const allowed = new Set(item.matches.map(({ event }) => event.id));
|
|
3338
|
+
const targets = [...new Set(item.existingEventIds.filter((id) => allowed.has(id)))];
|
|
3339
|
+
const reason = typeof item.reason === "string" ? item.reason.trim().slice(0, 500) : void 0;
|
|
3073
3340
|
let createdEvent;
|
|
3074
|
-
const proposed = item.
|
|
3075
|
-
const
|
|
3341
|
+
const proposed = item.mergedCandidate;
|
|
3342
|
+
const original = options.candidates[index];
|
|
3343
|
+
const candidate = proposed && typeof proposed.title === "string" && typeof proposed.summary === "string" ? proposed : original;
|
|
3076
3344
|
if ((action === "ADD" || action === "MERGE" || action === "SUPERSEDE" || action === "CONFLICT") && (action === "ADD" || targets.length > 0)) {
|
|
3077
3345
|
const temporal = {
|
|
3078
3346
|
...candidate.temporal ?? {},
|
|
@@ -3098,11 +3366,12 @@ var StrataGate = class _StrataGate {
|
|
|
3098
3366
|
}
|
|
3099
3367
|
}
|
|
3100
3368
|
const audit2 = {
|
|
3101
|
-
candidate: structuredClone(
|
|
3369
|
+
candidate: structuredClone(original),
|
|
3102
3370
|
action,
|
|
3103
3371
|
existingEventIds: targets,
|
|
3104
3372
|
...createdEvent ? { createdEventId: createdEvent.id } : {},
|
|
3105
|
-
...reason ? { reason } : {}
|
|
3373
|
+
...reason ? { reason } : {},
|
|
3374
|
+
...typeof item.confidence === "number" ? { confidence: item.confidence } : {}
|
|
3106
3375
|
};
|
|
3107
3376
|
decisions.push(audit2);
|
|
3108
3377
|
}
|
|
@@ -3119,6 +3388,92 @@ var StrataGate = class _StrataGate {
|
|
|
3119
3388
|
};
|
|
3120
3389
|
});
|
|
3121
3390
|
}
|
|
3391
|
+
async importExternalMemory(options) {
|
|
3392
|
+
const preview = await this.previewExternalMemoryImport(options);
|
|
3393
|
+
return this.commitExternalMemoryImport({
|
|
3394
|
+
text: options.text,
|
|
3395
|
+
importedAt: preview.importedAt,
|
|
3396
|
+
baseRevision: preview.baseRevision,
|
|
3397
|
+
candidates: preview.decisions.map(({ candidate }) => candidate),
|
|
3398
|
+
decisions: preview.decisions
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
async undoExternalMemoryImport(sourceBlockId) {
|
|
3402
|
+
const id = sourceBlockId.trim();
|
|
3403
|
+
if (!id) throw new TypeError("External memory source block ID must not be empty");
|
|
3404
|
+
return this.commitMutation(() => {
|
|
3405
|
+
const sourceIndex = this.blocks.findIndex((block) => block.id === id && block.l0Tags?.includes("external-memory-import"));
|
|
3406
|
+
if (sourceIndex < 0) throw new Error(`Unknown external memory import: ${id}`);
|
|
3407
|
+
const source = this.blocks[sourceIndex];
|
|
3408
|
+
const sourceMessageIds = new Set(source.l5Raw.map(({ id: id2 }) => id2));
|
|
3409
|
+
const importedEventIds = new Set(this.events.filter((event) => event.sourceBlockId === id).map(({ id: id2 }) => id2));
|
|
3410
|
+
const restoredEventIds = /* @__PURE__ */ new Set();
|
|
3411
|
+
const now = toUtc8Iso(this.now());
|
|
3412
|
+
this.events.splice(0, this.events.length, ...this.events.filter((event) => !importedEventIds.has(event.id)));
|
|
3413
|
+
for (const event of this.events) {
|
|
3414
|
+
for (const field of ["conflictsWithEventIds", "supersedesEventIds", "beforeEventIds", "afterEventIds", "relatedEventIds"]) {
|
|
3415
|
+
const previous = event.temporal[field] ?? [];
|
|
3416
|
+
const filtered = previous.filter((target) => !importedEventIds.has(target));
|
|
3417
|
+
if (filtered.length !== previous.length) {
|
|
3418
|
+
event.temporal[field] = filtered;
|
|
3419
|
+
restoredEventIds.add(event.id);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (event.temporal.sameEventId && importedEventIds.has(event.temporal.sameEventId)) {
|
|
3423
|
+
delete event.temporal.sameEventId;
|
|
3424
|
+
restoredEventIds.add(event.id);
|
|
3425
|
+
}
|
|
3426
|
+
if (event.supersededBy && importedEventIds.has(event.supersededBy)) {
|
|
3427
|
+
const replacement = this.events.find((candidate) => candidate.id !== event.id && (candidate.temporal.supersedesEventIds ?? []).includes(event.id));
|
|
3428
|
+
event.status = replacement ? "superseded" : "active";
|
|
3429
|
+
event.supersededBy = replacement?.id ?? null;
|
|
3430
|
+
if (!replacement && event.weight.forcedCap === 0.1) event.weight.forcedCap = null;
|
|
3431
|
+
restoredEventIds.add(event.id);
|
|
3432
|
+
}
|
|
3433
|
+
if (restoredEventIds.has(event.id)) event.updatedAt = now;
|
|
3434
|
+
}
|
|
3435
|
+
for (const [jobId, job] of this.elementProjectionJobs) {
|
|
3436
|
+
if (job.sourceEventIds.some((eventId) => importedEventIds.has(eventId))) this.elementProjectionJobs.delete(jobId);
|
|
3437
|
+
}
|
|
3438
|
+
for (const [jobId, job] of this.graphProjectionJobs) {
|
|
3439
|
+
if (job.sourceEventIds.some((eventId) => importedEventIds.has(eventId))) this.graphProjectionJobs.delete(jobId);
|
|
3440
|
+
}
|
|
3441
|
+
for (const element of this.elements) {
|
|
3442
|
+
element.sourceEventIds = element.sourceEventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3443
|
+
element.sourceMessageIds = element.sourceMessageIds.filter((messageId) => !sourceMessageIds.has(messageId));
|
|
3444
|
+
element.facts = element.facts.flatMap((fact) => {
|
|
3445
|
+
fact.sourceEventIds = fact.sourceEventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3446
|
+
return fact.sourceEventIds.length > 0 ? [fact] : [];
|
|
3447
|
+
});
|
|
3448
|
+
const current = [...element.facts].reverse().find((fact) => fact.status === "active" && fact.mode === "state");
|
|
3449
|
+
element.currentState = current ? Array.isArray(current.value) ? current.value.join("\u3001") : current.value : "";
|
|
3450
|
+
}
|
|
3451
|
+
this.elements.splice(0, this.elements.length, ...this.elements.filter((element) => element.sourceEventIds.length > 0 || element.facts.length > 0));
|
|
3452
|
+
for (const node of this.graphNodes) {
|
|
3453
|
+
node.sourceEventIds = node.sourceEventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3454
|
+
node.facts = node.facts.flatMap((fact) => {
|
|
3455
|
+
fact.sourceEventIds = fact.sourceEventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3456
|
+
return fact.sourceEventIds.length > 0 ? [fact] : [];
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
const removedNodeIds = new Set(this.graphNodes.filter((node) => node.sourceEventIds.length === 0 && node.facts.length === 0).map(({ id: id2 }) => id2));
|
|
3460
|
+
this.graphNodes.splice(0, this.graphNodes.length, ...this.graphNodes.filter((node) => !removedNodeIds.has(node.id)));
|
|
3461
|
+
for (const edge of this.graphEdges) {
|
|
3462
|
+
edge.sourceEventIds = edge.sourceEventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3463
|
+
}
|
|
3464
|
+
this.graphEdges.splice(0, this.graphEdges.length, ...this.graphEdges.filter((edge) => edge.sourceEventIds.length > 0 && !removedNodeIds.has(edge.fromNodeId) && !removedNodeIds.has(edge.toNodeId)));
|
|
3465
|
+
for (const [receiptId, receipt] of this.usageReceipts) {
|
|
3466
|
+
receipt.eventIds = receipt.eventIds.filter((eventId) => !importedEventIds.has(eventId));
|
|
3467
|
+
if (receipt.eventIds.length === 0 && receipt.elementIds.length === 0) this.usageReceipts.delete(receiptId);
|
|
3468
|
+
}
|
|
3469
|
+
this.blocks.splice(sourceIndex, 1);
|
|
3470
|
+
return {
|
|
3471
|
+
sourceBlockId: id,
|
|
3472
|
+
removedEventIds: [...importedEventIds],
|
|
3473
|
+
restoredEventIds: [...restoredEventIds]
|
|
3474
|
+
};
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3122
3477
|
async searchEvents(query, options = {}) {
|
|
3123
3478
|
const limit = Math.max(1, Math.min(20, options.limit ?? 6));
|
|
3124
3479
|
const participants = (options.participants ?? []).map(normalizeSearchText).filter(Boolean);
|
|
@@ -3172,7 +3527,7 @@ var StrataGate = class _StrataGate {
|
|
|
3172
3527
|
rankings.push(structured(candidates));
|
|
3173
3528
|
}
|
|
3174
3529
|
const ranked = rrfRank(rankings).slice(0, limit).map(({ item: event, score }) => ({ event, score }));
|
|
3175
|
-
if (ranked.length > 0) {
|
|
3530
|
+
if (ranked.length > 0 && options.trackRetrieval !== false) {
|
|
3176
3531
|
const now = toUtc8Iso(this.now());
|
|
3177
3532
|
await this.commitMutation(() => {
|
|
3178
3533
|
for (const { event } of ranked) event.weight.lastRetrievedAt = now;
|
|
@@ -3509,6 +3864,11 @@ var StrataGate = class _StrataGate {
|
|
|
3509
3864
|
const action = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
3510
3865
|
return action === "ADD" || action === "MERGE" || action === "SUPERSEDE" || action === "CONFLICT" || action === "IGNORE" ? action : "IGNORE";
|
|
3511
3866
|
}
|
|
3867
|
+
requireExternalMemoryImportJob(id) {
|
|
3868
|
+
const job = this.externalMemoryImportJobs.get(id.trim());
|
|
3869
|
+
if (!job) throw new Error(`Unknown external memory import job: ${id}`);
|
|
3870
|
+
return job;
|
|
3871
|
+
}
|
|
3512
3872
|
createExternalSourceBlock(text3, importedAt) {
|
|
3513
3873
|
const blockId = this.idFactory("blk");
|
|
3514
3874
|
const threadId = `external-import:${blockId}`;
|
|
@@ -3523,7 +3883,7 @@ var StrataGate = class _StrataGate {
|
|
|
3523
3883
|
const block = {
|
|
3524
3884
|
id: blockId,
|
|
3525
3885
|
threadId,
|
|
3526
|
-
sequence: this.blocks.
|
|
3886
|
+
sequence: Math.max(0, ...this.blocks.map(({ sequence }) => sequence)) + 1,
|
|
3527
3887
|
startTurn: 1,
|
|
3528
3888
|
endTurn: 1,
|
|
3529
3889
|
createdAt: importedAt,
|
|
@@ -4055,6 +4415,8 @@ var StrataGate = class _StrataGate {
|
|
|
4055
4415
|
for (const receipt of copy.usageReceipts) this.usageReceipts.set(receipt.id, receipt);
|
|
4056
4416
|
this.ingestionReceipts.clear();
|
|
4057
4417
|
for (const receipt of copy.ingestionReceipts) this.ingestionReceipts.set(receipt.id, receipt);
|
|
4418
|
+
this.externalMemoryImportJobs.clear();
|
|
4419
|
+
for (const job of copy.externalMemoryImportJobs) this.externalMemoryImportJobs.set(job.id, job);
|
|
4058
4420
|
this.successfulModelResponses.splice(0, this.successfulModelResponses.length, ...copy.successfulModelResponses ?? []);
|
|
4059
4421
|
this.validateReferences();
|
|
4060
4422
|
}
|
|
@@ -4323,7 +4685,9 @@ var STRUCTURED_FIELDS = {
|
|
|
4323
4685
|
summarizer: ["l0Title", "l0Tags", "l1Summary", "l2Keypoints", "shouldExtract"],
|
|
4324
4686
|
extractor: ["shouldExtract", "reason", "events"],
|
|
4325
4687
|
projector: ["reason", "changes"],
|
|
4326
|
-
graphProjector: ["reason", "nodes", "edges"]
|
|
4688
|
+
graphProjector: ["reason", "nodes", "edges"],
|
|
4689
|
+
externalMemoryExtractor: ["reason", "candidates"],
|
|
4690
|
+
externalMemoryDecider: ["action", "reason", "confidence"]
|
|
4327
4691
|
};
|
|
4328
4692
|
var STRING_ARRAY = { type: "array", items: { type: "string" } };
|
|
4329
4693
|
var OPEN_OBJECT = { type: "object", additionalProperties: true };
|
|
@@ -4431,6 +4795,17 @@ var GRAPH_PROJECTOR_PARAMETERS = {
|
|
|
4431
4795
|
nodes: { type: "array", items: GRAPH_NODE, required: true },
|
|
4432
4796
|
edges: { type: "array", items: GRAPH_EDGE, required: true }
|
|
4433
4797
|
};
|
|
4798
|
+
var EXTERNAL_MEMORY_DECIDER_PARAMETERS = {
|
|
4799
|
+
action: { type: "string", enum: ["ADD", "MERGE", "SUPERSEDE", "CONFLICT", "IGNORE"], required: true },
|
|
4800
|
+
existingEventIds: STRING_ARRAY,
|
|
4801
|
+
mergedCandidate: OPEN_OBJECT,
|
|
4802
|
+
reason: { type: "string", required: true },
|
|
4803
|
+
confidence: { type: "number", required: true }
|
|
4804
|
+
};
|
|
4805
|
+
var EXTERNAL_MEMORY_EXTRACTOR_PARAMETERS = {
|
|
4806
|
+
reason: { type: "string", required: true },
|
|
4807
|
+
candidates: { type: "array", items: OPEN_OBJECT, required: true }
|
|
4808
|
+
};
|
|
4434
4809
|
var STRUCTURED_TOOLS = {
|
|
4435
4810
|
summarizer: {
|
|
4436
4811
|
name: "stratagate_summarize_block",
|
|
@@ -4451,6 +4826,16 @@ var STRUCTURED_TOOLS = {
|
|
|
4451
4826
|
name: "stratagate_project_knowledge_graph",
|
|
4452
4827
|
description: "Project stable graph nodes and directed edges from supplied event evidence.",
|
|
4453
4828
|
parameters: GRAPH_PROJECTOR_PARAMETERS
|
|
4829
|
+
},
|
|
4830
|
+
externalMemoryDecider: {
|
|
4831
|
+
name: "stratagate_decide_external_memory",
|
|
4832
|
+
description: "Decide how one external memory candidate relates to retrieved local events.",
|
|
4833
|
+
parameters: EXTERNAL_MEMORY_DECIDER_PARAMETERS
|
|
4834
|
+
},
|
|
4835
|
+
externalMemoryExtractor: {
|
|
4836
|
+
name: "stratagate_recover_external_memory",
|
|
4837
|
+
description: "Recover structured external-memory candidates from malformed JSON or plain text.",
|
|
4838
|
+
parameters: EXTERNAL_MEMORY_EXTRACTOR_PARAMETERS
|
|
4454
4839
|
}
|
|
4455
4840
|
};
|
|
4456
4841
|
function toolSchema(kind) {
|
|
@@ -4479,7 +4864,10 @@ var DshModelBridge = class {
|
|
|
4479
4864
|
successfulResponses = [];
|
|
4480
4865
|
offCapabilities = /* @__PURE__ */ new Map();
|
|
4481
4866
|
run(session, operation) {
|
|
4482
|
-
return this.sessions.run(session, operation);
|
|
4867
|
+
return this.sessions.run({ session, sessionId: session.id }, operation);
|
|
4868
|
+
}
|
|
4869
|
+
runDetached(sessionId, operation) {
|
|
4870
|
+
return this.sessions.run({ sessionId }, operation);
|
|
4483
4871
|
}
|
|
4484
4872
|
takeSuccessfulResponses() {
|
|
4485
4873
|
const responses = this.successfulResponses.splice(0, this.successfulResponses.length);
|
|
@@ -4621,10 +5009,43 @@ var DshModelBridge = class {
|
|
|
4621
5009
|
});
|
|
4622
5010
|
return { reason: text2(raw.reason, "Projected Event evidence into the Knowledge Graph."), nodes, edges };
|
|
4623
5011
|
};
|
|
5012
|
+
externalMemoryDecider = async (context) => {
|
|
5013
|
+
const raw = object(await this.callStructured(
|
|
5014
|
+
"externalMemoryDecider",
|
|
5015
|
+
`${EXTERNAL_MEMORY_DECIDER_PROMPT_ZH_CN}
|
|
5016
|
+
|
|
5017
|
+
\u8C03\u7528 ${STRUCTURED_TOOLS.externalMemoryDecider.name} \u6070\u597D\u4E00\u6B21\uFF0C\u4E0D\u8981\u8FD4\u56DE\u666E\u901A\u6587\u672C\u3002confidence \u5FC5\u987B\u662F 0 \u5230 1\uFF0C\u8868\u793A\u8BE5 action \u5224\u65AD\u7684\u628A\u63E1\u7A0B\u5EA6\u3002`,
|
|
5018
|
+
context
|
|
5019
|
+
));
|
|
5020
|
+
const action = text2(raw.action).toUpperCase();
|
|
5021
|
+
const allowedActions = /* @__PURE__ */ new Set(["ADD", "MERGE", "SUPERSEDE", "CONFLICT", "IGNORE"]);
|
|
5022
|
+
const proposed = object(raw.mergedCandidate);
|
|
5023
|
+
const mergedCandidate = text2(proposed.title) && text2(proposed.summary) ? proposed : void 0;
|
|
5024
|
+
return {
|
|
5025
|
+
action: allowedActions.has(action) ? action : "IGNORE",
|
|
5026
|
+
existingEventIds: strings2(raw.existingEventIds),
|
|
5027
|
+
...mergedCandidate ? { mergedCandidate } : {},
|
|
5028
|
+
reason: text2(raw.reason, "\u6A21\u578B\u672A\u63D0\u4F9B\u88C1\u51B3\u7406\u7531\u3002").slice(0, 500),
|
|
5029
|
+
confidence: typeof raw.confidence === "number" ? Math.max(0, Math.min(1, raw.confidence)) : 0.5
|
|
5030
|
+
};
|
|
5031
|
+
};
|
|
5032
|
+
externalMemoryExtractor = async ({ text: source, importedAt }) => {
|
|
5033
|
+
const raw = object(await this.callStructured(
|
|
5034
|
+
"externalMemoryExtractor",
|
|
5035
|
+
`Recover durable memory candidates from the supplied malformed external-memory export. Use only facts present in sourceText; never invent missing facts. Preserve uncertainty and omit unsupported dates. Call ${STRUCTURED_TOOLS.externalMemoryExtractor.name} exactly once with reason and candidates. Each candidate needs a concise title and self-contained summary. Do not return ordinary text.`,
|
|
5036
|
+
{ sourceText: source, importedAt }
|
|
5037
|
+
));
|
|
5038
|
+
const parsed = parseExternalMemoryExport(JSON.stringify({
|
|
5039
|
+
schemaVersion: "stratagate.external-memory.v2",
|
|
5040
|
+
sourceType: "external_ai_memory_export",
|
|
5041
|
+
candidates: Array.isArray(raw.candidates) ? raw.candidates : []
|
|
5042
|
+
}));
|
|
5043
|
+
return { candidates: parsed.candidates, reason: text2(raw.reason, parsed.reason) };
|
|
5044
|
+
};
|
|
4624
5045
|
async callStructured(kind, system, payload) {
|
|
4625
|
-
const
|
|
4626
|
-
if (!
|
|
4627
|
-
const baseRoute = this.resolveRoute(session);
|
|
5046
|
+
const execution = this.sessions.getStore();
|
|
5047
|
+
if (!execution) throw new Error("StrataGate model callback ran without an execution context");
|
|
5048
|
+
const baseRoute = this.resolveRoute(execution.session);
|
|
4628
5049
|
const routeKey = `${baseRoute.provider}\0${baseRoute.model}`;
|
|
4629
5050
|
let useOff = await this.shouldUseOff(baseRoute);
|
|
4630
5051
|
let lastError;
|
|
@@ -4652,7 +5073,7 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4652
5073
|
function: { name: STRUCTURED_TOOLS[kind].name }
|
|
4653
5074
|
},
|
|
4654
5075
|
maxTokens: this.config.maxOutputTokens,
|
|
4655
|
-
sessionId:
|
|
5076
|
+
sessionId: execution.sessionId,
|
|
4656
5077
|
purpose: "compaction"
|
|
4657
5078
|
};
|
|
4658
5079
|
try {
|
|
@@ -4801,7 +5222,7 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4801
5222
|
}
|
|
4802
5223
|
}
|
|
4803
5224
|
resolveRoute(session) {
|
|
4804
|
-
const request = session
|
|
5225
|
+
const request = session?.requestHeader()?.config;
|
|
4805
5226
|
if (this.config.provider && this.config.model) {
|
|
4806
5227
|
return { provider: this.config.provider, model: this.config.model };
|
|
4807
5228
|
}
|
|
@@ -5047,6 +5468,8 @@ var StrataGateRuntime = class {
|
|
|
5047
5468
|
migrationTimers = /* @__PURE__ */ new Map();
|
|
5048
5469
|
derivationTimers = /* @__PURE__ */ new Map();
|
|
5049
5470
|
derivationRuns = /* @__PURE__ */ new Map();
|
|
5471
|
+
adminSnapshotCache = /* @__PURE__ */ new Map();
|
|
5472
|
+
externalImportRuns = /* @__PURE__ */ new Map();
|
|
5050
5473
|
ingestTail = Promise.resolve();
|
|
5051
5474
|
settingsTail = Promise.resolve();
|
|
5052
5475
|
batchSequence = 0;
|
|
@@ -5388,6 +5811,7 @@ var StrataGateRuntime = class {
|
|
|
5388
5811
|
}
|
|
5389
5812
|
const settled = await Promise.allSettled(this.spaces.values());
|
|
5390
5813
|
await Promise.allSettled(this.derivationRuns.values());
|
|
5814
|
+
await Promise.allSettled(this.externalImportRuns.values());
|
|
5391
5815
|
await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
|
|
5392
5816
|
if (flushError !== void 0) throw flushError;
|
|
5393
5817
|
}
|
|
@@ -5406,6 +5830,42 @@ var StrataGateRuntime = class {
|
|
|
5406
5830
|
await storage.close();
|
|
5407
5831
|
}
|
|
5408
5832
|
}
|
|
5833
|
+
async adminSnapshotEntries() {
|
|
5834
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
5835
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
5836
|
+
try {
|
|
5837
|
+
const entries = [];
|
|
5838
|
+
for (const { namespace, revision } of storage.listNamespaceRevisions()) {
|
|
5839
|
+
const opening = this.spaces.get(namespace);
|
|
5840
|
+
if (opening) {
|
|
5841
|
+
const memory = await opening;
|
|
5842
|
+
const currentRevision = memory.storageRevision;
|
|
5843
|
+
const cached2 = this.adminSnapshotCache.get(namespace);
|
|
5844
|
+
const entry2 = cached2?.revision === currentRevision ? cached2 : { namespace, revision: currentRevision, snapshot: memory.exportSnapshot() };
|
|
5845
|
+
this.adminSnapshotCache.set(namespace, entry2);
|
|
5846
|
+
entries.push(entry2);
|
|
5847
|
+
continue;
|
|
5848
|
+
}
|
|
5849
|
+
const cached = this.adminSnapshotCache.get(namespace);
|
|
5850
|
+
if (cached?.revision === revision) {
|
|
5851
|
+
entries.push(cached);
|
|
5852
|
+
continue;
|
|
5853
|
+
}
|
|
5854
|
+
const loaded = await storage.load(namespace);
|
|
5855
|
+
if (!loaded) continue;
|
|
5856
|
+
const entry = { namespace, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
5857
|
+
this.adminSnapshotCache.set(namespace, entry);
|
|
5858
|
+
entries.push(entry);
|
|
5859
|
+
}
|
|
5860
|
+
const activeNamespaces = new Set(entries.map(({ namespace }) => namespace));
|
|
5861
|
+
for (const namespace of this.adminSnapshotCache.keys()) {
|
|
5862
|
+
if (!activeNamespaces.has(namespace)) this.adminSnapshotCache.delete(namespace);
|
|
5863
|
+
}
|
|
5864
|
+
return entries;
|
|
5865
|
+
} finally {
|
|
5866
|
+
await storage.close();
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5409
5869
|
async syncConfiguredSettings() {
|
|
5410
5870
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return;
|
|
5411
5871
|
const metadata = new DshMetadataStore(this.config.database);
|
|
@@ -5434,43 +5894,238 @@ var StrataGateRuntime = class {
|
|
|
5434
5894
|
const key = namespace.trim();
|
|
5435
5895
|
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
5436
5896
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
5897
|
+
const opening = this.spaces.get(key);
|
|
5898
|
+
if (opening) {
|
|
5899
|
+
const memory = await opening;
|
|
5900
|
+
const revision = memory.storageRevision;
|
|
5901
|
+
const cached = this.adminSnapshotCache.get(key);
|
|
5902
|
+
if (cached?.revision === revision) return cached.snapshot;
|
|
5903
|
+
const entry = { namespace: key, revision, snapshot: memory.exportSnapshot() };
|
|
5904
|
+
this.adminSnapshotCache.set(key, entry);
|
|
5905
|
+
return entry.snapshot;
|
|
5906
|
+
}
|
|
5437
5907
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
5438
5908
|
try {
|
|
5439
|
-
|
|
5909
|
+
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key);
|
|
5910
|
+
if (!head) return null;
|
|
5911
|
+
const cached = this.adminSnapshotCache.get(key);
|
|
5912
|
+
if (cached?.revision === head.revision) return cached.snapshot;
|
|
5913
|
+
const loaded = await storage.load(key);
|
|
5914
|
+
if (!loaded) return null;
|
|
5915
|
+
const entry = { namespace: key, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
5916
|
+
this.adminSnapshotCache.set(key, entry);
|
|
5917
|
+
return entry.snapshot;
|
|
5440
5918
|
} finally {
|
|
5441
5919
|
await storage.close();
|
|
5442
5920
|
}
|
|
5443
5921
|
}
|
|
5444
|
-
|
|
5445
|
-
|
|
5922
|
+
externalImportView(job) {
|
|
5923
|
+
return {
|
|
5924
|
+
jobId: job.id,
|
|
5925
|
+
status: job.status,
|
|
5926
|
+
processedCount: job.processedCount,
|
|
5927
|
+
totalCount: job.totalCount,
|
|
5928
|
+
recoveredFromInvalidJson: job.recoveredFromInvalidJson,
|
|
5929
|
+
parseError: job.parseError,
|
|
5930
|
+
lastError: job.lastError,
|
|
5931
|
+
sourceBlockId: job.sourceBlockId,
|
|
5932
|
+
importedCount: job.importedCount,
|
|
5933
|
+
createdAt: job.createdAt,
|
|
5934
|
+
updatedAt: job.updatedAt,
|
|
5935
|
+
decisions: job.decisions.map(({ matches: _matches, mergedCandidate: _mergedCandidate, ...decision }) => decision),
|
|
5936
|
+
requiresConfirmationCount: job.decisions.filter(({ requiresConfirmation }) => requiresConfirmation).length
|
|
5937
|
+
};
|
|
5938
|
+
}
|
|
5939
|
+
async refreshExternalImportMemory(namespace, memory) {
|
|
5940
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return;
|
|
5941
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
5942
|
+
try {
|
|
5943
|
+
const head = storage.listNamespaceRevisions().find((entry) => entry.namespace === namespace);
|
|
5944
|
+
if (head && head.revision !== memory.storageRevision) await memory.reloadFromStorage();
|
|
5945
|
+
} finally {
|
|
5946
|
+
await storage.close();
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
async refreshActiveExternalImportMemory(namespace) {
|
|
5950
|
+
const opening = this.spaces.get(namespace);
|
|
5951
|
+
if (!opening) return;
|
|
5952
|
+
await (await opening).reloadFromStorage();
|
|
5953
|
+
}
|
|
5954
|
+
async retryExternalImportWrite(namespace, operation) {
|
|
5955
|
+
let lastConflict;
|
|
5956
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
5957
|
+
const { memory, owned } = await this.openAdminMemory(namespace);
|
|
5958
|
+
try {
|
|
5959
|
+
await this.refreshExternalImportMemory(namespace, memory);
|
|
5960
|
+
const result = await operation(memory);
|
|
5961
|
+
if (owned) await this.refreshActiveExternalImportMemory(namespace);
|
|
5962
|
+
return result;
|
|
5963
|
+
} catch (error) {
|
|
5964
|
+
if (!(error instanceof StorageConflictError)) throw error;
|
|
5965
|
+
lastConflict = error;
|
|
5966
|
+
if (!owned) await memory.reloadFromStorage();
|
|
5967
|
+
} finally {
|
|
5968
|
+
if (owned) await memory.close();
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
throw lastConflict ?? new Error(`Unable to update external memory import ${namespace}`);
|
|
5972
|
+
}
|
|
5973
|
+
takeSuccessfulResponses() {
|
|
5974
|
+
return typeof this.models.takeSuccessfulResponses === "function" ? this.models.takeSuccessfulResponses() : [];
|
|
5975
|
+
}
|
|
5976
|
+
async persistExternalImportResponses(namespace, responses) {
|
|
5977
|
+
if (responses.length === 0) return;
|
|
5978
|
+
await this.retryExternalImportWrite(namespace, (memory) => memory.recordSuccessfulModelResponses(responses));
|
|
5979
|
+
}
|
|
5980
|
+
scheduleExternalMemoryImport(namespace, jobId) {
|
|
5981
|
+
if (this.closed || this.externalImportRuns.has(jobId)) return;
|
|
5982
|
+
const run = (async () => {
|
|
5983
|
+
while (!this.closed) {
|
|
5984
|
+
let job = null;
|
|
5985
|
+
let work = null;
|
|
5986
|
+
const prepared = await this.openAdminMemory(namespace);
|
|
5987
|
+
try {
|
|
5988
|
+
await this.refreshExternalImportMemory(namespace, prepared.memory);
|
|
5989
|
+
job = prepared.memory.getExternalMemoryImportJob(jobId);
|
|
5990
|
+
if (!job) return;
|
|
5991
|
+
if (job.status === "processing") {
|
|
5992
|
+
work = await prepared.memory.prepareNextExternalMemoryImport(jobId);
|
|
5993
|
+
} else if (job.status !== "extracting") return;
|
|
5994
|
+
} finally {
|
|
5995
|
+
if (prepared.owned) await prepared.memory.close();
|
|
5996
|
+
}
|
|
5997
|
+
try {
|
|
5998
|
+
if (job.status === "extracting") {
|
|
5999
|
+
const recovered = await this.models.runDetached(`admin-import-recovery:${jobId}`, () => this.models.externalMemoryExtractor({ text: job.text, importedAt: job.importedAt }));
|
|
6000
|
+
const responses2 = this.takeSuccessfulResponses();
|
|
6001
|
+
await this.retryExternalImportWrite(namespace, (memory) => memory.completeExternalMemoryFallback(jobId, recovered));
|
|
6002
|
+
await this.persistExternalImportResponses(namespace, responses2);
|
|
6003
|
+
continue;
|
|
6004
|
+
}
|
|
6005
|
+
if (!work) return;
|
|
6006
|
+
let decision;
|
|
6007
|
+
let responses = [];
|
|
6008
|
+
if (work.deterministicDecision) {
|
|
6009
|
+
decision = work.deterministicDecision;
|
|
6010
|
+
} else {
|
|
6011
|
+
decision = await this.models.runDetached(`admin-import:${jobId}`, () => this.models.externalMemoryDecider({
|
|
6012
|
+
candidate: structuredClone(work.candidate),
|
|
6013
|
+
matches: structuredClone(work.matches)
|
|
6014
|
+
}));
|
|
6015
|
+
responses = this.takeSuccessfulResponses();
|
|
6016
|
+
}
|
|
6017
|
+
await this.retryExternalImportWrite(namespace, (memory) => memory.completeNextExternalMemoryImport(
|
|
6018
|
+
work.jobId,
|
|
6019
|
+
work.index,
|
|
6020
|
+
decision,
|
|
6021
|
+
work.matches,
|
|
6022
|
+
work.forceConfirmation
|
|
6023
|
+
));
|
|
6024
|
+
await this.persistExternalImportResponses(namespace, responses);
|
|
6025
|
+
} catch (error) {
|
|
6026
|
+
if (error instanceof StorageConflictError) {
|
|
6027
|
+
this.onIngestError(error);
|
|
6028
|
+
return;
|
|
6029
|
+
}
|
|
6030
|
+
try {
|
|
6031
|
+
await this.retryExternalImportWrite(namespace, (memory) => memory.failExternalMemoryImportJob(jobId, error));
|
|
6032
|
+
} catch (persistError) {
|
|
6033
|
+
this.onIngestError(persistError);
|
|
6034
|
+
}
|
|
6035
|
+
return;
|
|
6036
|
+
}
|
|
6037
|
+
}
|
|
6038
|
+
})().catch((error) => this.onIngestError(error)).finally(() => {
|
|
6039
|
+
this.externalImportRuns.delete(jobId);
|
|
6040
|
+
});
|
|
6041
|
+
this.externalImportRuns.set(jobId, run);
|
|
6042
|
+
}
|
|
6043
|
+
/** Create a durable analysis job and return before model-backed work begins. */
|
|
6044
|
+
async adminPreviewExternalMemory(namespace, text3) {
|
|
5446
6045
|
const key = namespace.trim();
|
|
5447
6046
|
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
6047
|
+
if (!text3.trim()) throw new TypeError("External memory text must not be empty");
|
|
5448
6048
|
await this.flush();
|
|
5449
|
-
const
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
6049
|
+
const { memory, owned } = await this.openAdminMemory(key);
|
|
6050
|
+
try {
|
|
6051
|
+
await this.refreshExternalImportMemory(key, memory);
|
|
6052
|
+
const job = await memory.createExternalMemoryImportJob(text3);
|
|
6053
|
+
this.scheduleExternalMemoryImport(key, job.id);
|
|
6054
|
+
return this.externalImportView(job);
|
|
6055
|
+
} finally {
|
|
6056
|
+
if (owned) await memory.close();
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
async adminExternalMemoryStatus(namespace, jobId) {
|
|
6060
|
+
const key = namespace.trim();
|
|
6061
|
+
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
6062
|
+
const { memory, owned } = await this.openAdminMemory(key);
|
|
6063
|
+
try {
|
|
6064
|
+
await this.refreshExternalImportMemory(key, memory);
|
|
6065
|
+
const jobs = memory.listExternalMemoryImportJobs();
|
|
6066
|
+
const job = jobId ? jobs.find(({ id }) => id === jobId) : [...jobs].sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
|
|
6067
|
+
if (!job) return { job: null };
|
|
6068
|
+
if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(key, job.id);
|
|
6069
|
+
return this.externalImportView(job);
|
|
6070
|
+
} finally {
|
|
6071
|
+
if (owned) await memory.close();
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
async adminRetryExternalMemory(namespace, jobId) {
|
|
6075
|
+
const key = namespace.trim();
|
|
6076
|
+
let lastConflict;
|
|
6077
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
6078
|
+
const { memory, owned } = await this.openAdminMemory(key);
|
|
6079
|
+
try {
|
|
6080
|
+
await this.refreshExternalImportMemory(key, memory);
|
|
6081
|
+
const job = await memory.retryExternalMemoryImportJob(jobId);
|
|
6082
|
+
this.scheduleExternalMemoryImport(key, job.id);
|
|
6083
|
+
return this.externalImportView(job);
|
|
6084
|
+
} catch (error) {
|
|
6085
|
+
if (!(error instanceof StorageConflictError)) throw error;
|
|
6086
|
+
lastConflict = error;
|
|
6087
|
+
if (!owned) await memory.reloadFromStorage();
|
|
6088
|
+
} finally {
|
|
6089
|
+
if (owned) await memory.close();
|
|
5457
6090
|
}
|
|
5458
|
-
memory = await StrataGate.open({
|
|
5459
|
-
database: this.config.database,
|
|
5460
|
-
namespace: key,
|
|
5461
|
-
blockTurnSize: this.blockTurnSize,
|
|
5462
|
-
blockDecayLambda: this.blockDecayLambda,
|
|
5463
|
-
graphProjector: this.models.graphProjector,
|
|
5464
|
-
disableElementProjection: true
|
|
5465
|
-
});
|
|
5466
|
-
owned = true;
|
|
5467
6091
|
}
|
|
6092
|
+
throw lastConflict;
|
|
6093
|
+
}
|
|
6094
|
+
async adminCommitExternalMemory(namespace, jobId, choices) {
|
|
6095
|
+
const key = namespace.trim();
|
|
6096
|
+
const { memory, owned } = await this.openAdminMemory(key);
|
|
5468
6097
|
try {
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
6098
|
+
await this.refreshExternalImportMemory(key, memory);
|
|
6099
|
+
const job = memory.getExternalMemoryImportJob(jobId);
|
|
6100
|
+
if (!job) throw new Error("\u627E\u4E0D\u5230\u5BFC\u5165\u4EFB\u52A1");
|
|
6101
|
+
if (job.status !== "ready" && job.status !== "awaiting_confirmation") {
|
|
6102
|
+
throw new Error("\u5BFC\u5165\u5206\u6790\u5C1A\u672A\u5B8C\u6210");
|
|
6103
|
+
}
|
|
6104
|
+
const selected = new Map(choices.filter(({ index, action }) => Number.isSafeInteger(index) && ["ADD", "MERGE", "SUPERSEDE", "CONFLICT", "IGNORE"].includes(action)).map(({ index, action }) => [index, action]));
|
|
6105
|
+
const decisions = job.decisions.map((decision, index) => {
|
|
6106
|
+
if (!decision.requiresConfirmation) return decision;
|
|
6107
|
+
const action = selected.get(index) ?? "IGNORE";
|
|
6108
|
+
const needsTarget = action === "MERGE" || action === "SUPERSEDE" || action === "CONFLICT";
|
|
6109
|
+
if (needsTarget && decision.existingEventIds.length === 0) {
|
|
6110
|
+
return { ...decision, action: "IGNORE", existingEventIds: [], reason: "\u7528\u6237\u9009\u62E9\u7684\u64CD\u4F5C\u6CA1\u6709\u53EF\u5173\u8054\u65E7\u8BB0\u5FC6\uFF0C\u5DF2\u5B89\u5168\u5FFD\u7565" };
|
|
6111
|
+
}
|
|
6112
|
+
return {
|
|
6113
|
+
...decision,
|
|
6114
|
+
action,
|
|
6115
|
+
existingEventIds: action === "ADD" || action === "IGNORE" ? [] : decision.existingEventIds,
|
|
6116
|
+
reason: `\u7528\u6237\u786E\u8BA4\uFF1A${action}`
|
|
6117
|
+
};
|
|
6118
|
+
});
|
|
6119
|
+
const result = await memory.commitExternalMemoryImport({
|
|
6120
|
+
text: job.text,
|
|
6121
|
+
importedAt: job.importedAt,
|
|
6122
|
+
baseRevision: memory.storageRevision,
|
|
6123
|
+
candidates: job.candidates,
|
|
6124
|
+
decisions
|
|
5472
6125
|
});
|
|
6126
|
+
const completed = await memory.completeExternalMemoryImportJob(job.id, result);
|
|
5473
6127
|
return {
|
|
6128
|
+
...this.externalImportView(completed),
|
|
5474
6129
|
sourceBlockId: result.sourceBlockId,
|
|
5475
6130
|
decisions: result.decisions,
|
|
5476
6131
|
importedCount: result.addedEvents.length,
|
|
@@ -5480,6 +6135,19 @@ var StrataGateRuntime = class {
|
|
|
5480
6135
|
if (owned) await memory.close();
|
|
5481
6136
|
}
|
|
5482
6137
|
}
|
|
6138
|
+
async adminUndoExternalMemory(namespace, sourceBlockId) {
|
|
6139
|
+
const key = namespace.trim();
|
|
6140
|
+
const { memory, owned } = await this.openAdminMemory(key);
|
|
6141
|
+
try {
|
|
6142
|
+
await this.refreshExternalImportMemory(key, memory);
|
|
6143
|
+
const result = await memory.undoExternalMemoryImport(sourceBlockId);
|
|
6144
|
+
const job = memory.listExternalMemoryImportJobs().find((candidate) => candidate.sourceBlockId === sourceBlockId);
|
|
6145
|
+
if (job) await memory.markExternalMemoryImportUndone(job.id);
|
|
6146
|
+
return result;
|
|
6147
|
+
} finally {
|
|
6148
|
+
if (owned) await memory.close();
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
5483
6151
|
adminWorkspaceName(namespace) {
|
|
5484
6152
|
const remembered = this.workspaceNames.get(namespace);
|
|
5485
6153
|
if (remembered) return remembered;
|
|
@@ -5725,6 +6393,24 @@ var StrataGateRuntime = class {
|
|
|
5725
6393
|
timer.unref?.();
|
|
5726
6394
|
this.migrationTimers.set(namespace, timer);
|
|
5727
6395
|
}
|
|
6396
|
+
async openAdminMemory(namespace) {
|
|
6397
|
+
const active = this.spaces.get(namespace);
|
|
6398
|
+
if (active) return { memory: await active, owned: false };
|
|
6399
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
6400
|
+
throw new Error(`Unknown StrataGate namespace: ${namespace}`);
|
|
6401
|
+
}
|
|
6402
|
+
return {
|
|
6403
|
+
memory: await StrataGate.open({
|
|
6404
|
+
database: this.config.database,
|
|
6405
|
+
namespace,
|
|
6406
|
+
blockTurnSize: this.blockTurnSize,
|
|
6407
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
6408
|
+
graphProjector: this.models.graphProjector,
|
|
6409
|
+
disableElementProjection: true
|
|
6410
|
+
}),
|
|
6411
|
+
owned: true
|
|
6412
|
+
};
|
|
6413
|
+
}
|
|
5728
6414
|
rememberWorkspace(namespace, cwd) {
|
|
5729
6415
|
const name2 = workspaceDisplayName(cwd);
|
|
5730
6416
|
this.workspaceNames.set(namespace, name2);
|
|
@@ -6230,6 +6916,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
6230
6916
|
}
|
|
6231
6917
|
|
|
6232
6918
|
// src/web.ts
|
|
6919
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6233
6920
|
import { createRequire } from "node:module";
|
|
6234
6921
|
|
|
6235
6922
|
// src/graph-clustering.ts
|
|
@@ -6467,14 +7154,17 @@ var AdminHttpError = class extends Error {
|
|
|
6467
7154
|
}
|
|
6468
7155
|
status;
|
|
6469
7156
|
};
|
|
6470
|
-
async function overview(runtime) {
|
|
6471
|
-
const
|
|
7157
|
+
async function overview(runtime, cachedEntries) {
|
|
7158
|
+
const entries = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
|
|
7159
|
+
namespace,
|
|
7160
|
+
revision: 0,
|
|
7161
|
+
snapshot: await runtime.adminSnapshot(namespace)
|
|
7162
|
+
})));
|
|
6472
7163
|
const rows = [];
|
|
6473
|
-
for (const namespace of
|
|
6474
|
-
const snapshot = await runtime.adminSnapshot(namespace);
|
|
7164
|
+
for (const { namespace, snapshot } of entries) {
|
|
6475
7165
|
if (!snapshot) continue;
|
|
6476
7166
|
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
|
|
6477
|
-
const processingJobs = snapshot.extractionJobs.filter(({ status }) => status === "running").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
|
|
7167
|
+
const processingJobs = snapshot.summaryJobs.filter(({ status, nextRetryAt }) => status === "pending" || status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.extractionJobs.filter(({ status, nextRetryAt }) => status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
|
|
6478
7168
|
const failedJobDetails = [
|
|
6479
7169
|
...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
6480
7170
|
id: job.blockId,
|
|
@@ -6591,10 +7281,41 @@ async function importExternalMemory(runtime, req) {
|
|
|
6591
7281
|
throw new AdminHttpError(400, "\u5BFC\u5165\u8BF7\u6C42\u7F3A\u5C11 JSON body");
|
|
6592
7282
|
}
|
|
6593
7283
|
const namespace = typeof body.namespace === "string" ? body.namespace.trim() : "";
|
|
6594
|
-
const text3 = typeof body.text === "string" ? body.text.trim() : "";
|
|
6595
7284
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
6596
|
-
|
|
6597
|
-
|
|
7285
|
+
const operation = typeof body.operation === "string" ? body.operation : "preview";
|
|
7286
|
+
if (operation === "preview") {
|
|
7287
|
+
const text3 = typeof body.text === "string" ? body.text.trim() : "";
|
|
7288
|
+
if (!text3) throw new AdminHttpError(400, "text is required");
|
|
7289
|
+
return runtime.adminPreviewExternalMemory(namespace, text3);
|
|
7290
|
+
}
|
|
7291
|
+
if (operation === "status") {
|
|
7292
|
+
const jobId = typeof body.jobId === "string" ? body.jobId.trim() : void 0;
|
|
7293
|
+
return runtime.adminExternalMemoryStatus(namespace, jobId);
|
|
7294
|
+
}
|
|
7295
|
+
if (operation === "retry") {
|
|
7296
|
+
const jobId = typeof body.jobId === "string" ? body.jobId.trim() : "";
|
|
7297
|
+
if (!jobId) throw new AdminHttpError(400, "jobId is required");
|
|
7298
|
+
return runtime.adminRetryExternalMemory(namespace, jobId);
|
|
7299
|
+
}
|
|
7300
|
+
if (operation === "commit") {
|
|
7301
|
+
const jobId = typeof body.jobId === "string" ? body.jobId.trim() : "";
|
|
7302
|
+
if (!jobId) throw new AdminHttpError(400, "jobId is required");
|
|
7303
|
+
const allowed = /* @__PURE__ */ new Set(["ADD", "MERGE", "SUPERSEDE", "CONFLICT", "IGNORE"]);
|
|
7304
|
+
const choices = Array.isArray(body.choices) ? body.choices.flatMap((value) => {
|
|
7305
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
7306
|
+
const item = value;
|
|
7307
|
+
const index = item.index;
|
|
7308
|
+
const action = typeof item.action === "string" ? item.action.toUpperCase() : "IGNORE";
|
|
7309
|
+
return Number.isSafeInteger(index) && allowed.has(action) ? [{ index, action }] : [];
|
|
7310
|
+
}) : [];
|
|
7311
|
+
return runtime.adminCommitExternalMemory(namespace, jobId, choices);
|
|
7312
|
+
}
|
|
7313
|
+
if (operation === "undo") {
|
|
7314
|
+
const sourceBlockId = typeof body.sourceBlockId === "string" ? body.sourceBlockId.trim() : "";
|
|
7315
|
+
if (!sourceBlockId) throw new AdminHttpError(400, "sourceBlockId is required");
|
|
7316
|
+
return runtime.adminUndoExternalMemory(namespace, sourceBlockId);
|
|
7317
|
+
}
|
|
7318
|
+
throw new AdminHttpError(400, "operation must be preview, status, retry, commit, or undo");
|
|
6598
7319
|
}
|
|
6599
7320
|
function externalMemoryPrompt() {
|
|
6600
7321
|
return { prompt: EXTERNAL_MEMORY_EXPORT_PROMPT_ZH_CN, schemaVersion: "stratagate.external-memory.v2" };
|
|
@@ -6943,6 +7664,73 @@ async function audit(runtime, url) {
|
|
|
6943
7664
|
items: receipts.slice(offset, offset + limit).map((receipt) => receiptSources(snapshot, receipt))
|
|
6944
7665
|
};
|
|
6945
7666
|
}
|
|
7667
|
+
function requestHeader(req, name2) {
|
|
7668
|
+
const headers = req.headers ?? {};
|
|
7669
|
+
const key = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
|
|
7670
|
+
const value = key ? headers[key] : void 0;
|
|
7671
|
+
return Array.isArray(value) ? value.join(", ") : value ?? "";
|
|
7672
|
+
}
|
|
7673
|
+
async function dashboard(runtime, url, ifNoneMatch) {
|
|
7674
|
+
const entries = await runtime.adminSnapshotEntries();
|
|
7675
|
+
const requestedNamespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
7676
|
+
const selected = entries.find(({ namespace }) => namespace === requestedNamespace) ?? entries[0];
|
|
7677
|
+
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
7678
|
+
const revisionKey = entries.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
|
|
7679
|
+
const etag = `"${createHash2("sha256").update(`${revisionKey}\0${selected?.namespace ?? ""}\0${threadId}`).digest("base64url").slice(0, 24)}"`;
|
|
7680
|
+
if (ifNoneMatch.split(",").map((value) => value.trim()).includes(etag)) return { etag, notModified: true };
|
|
7681
|
+
const overviewValue = await overview(runtime, entries);
|
|
7682
|
+
if (!selected) {
|
|
7683
|
+
return { etag, notModified: false, body: { namespace: null, overview: overviewValue, data: null, processing: false } };
|
|
7684
|
+
}
|
|
7685
|
+
const snapshotRuntime = {
|
|
7686
|
+
adminSnapshot: async (namespace) => namespace === selected.namespace ? selected.snapshot : null
|
|
7687
|
+
};
|
|
7688
|
+
const memoryUrl = (kind, limit) => {
|
|
7689
|
+
const target = new URL(url);
|
|
7690
|
+
target.searchParams.set("namespace", selected.namespace);
|
|
7691
|
+
target.searchParams.set("kind", kind);
|
|
7692
|
+
if (limit) target.searchParams.set("limit", limit);
|
|
7693
|
+
return target;
|
|
7694
|
+
};
|
|
7695
|
+
const [eventResult, graphResult, blockResult, auditResult] = await Promise.all([
|
|
7696
|
+
memories(snapshotRuntime, memoryUrl("events", "200")),
|
|
7697
|
+
memories(snapshotRuntime, memoryUrl("graph")),
|
|
7698
|
+
memories(snapshotRuntime, memoryUrl("blocks", "200")),
|
|
7699
|
+
audit(snapshotRuntime, memoryUrl("audit", "100"))
|
|
7700
|
+
]);
|
|
7701
|
+
const selectedOverview = overviewValue.namespaces?.find(({ namespace }) => namespace === selected.namespace);
|
|
7702
|
+
return {
|
|
7703
|
+
etag,
|
|
7704
|
+
notModified: false,
|
|
7705
|
+
body: {
|
|
7706
|
+
namespace: selected.namespace,
|
|
7707
|
+
revision: selected.revision,
|
|
7708
|
+
overview: overviewValue,
|
|
7709
|
+
processing: Number(selectedOverview?.processingJobs ?? 0) > 0,
|
|
7710
|
+
data: {
|
|
7711
|
+
events: eventResult.items ?? [],
|
|
7712
|
+
graph: graphResult,
|
|
7713
|
+
blocks: blockResult.items ?? [],
|
|
7714
|
+
openBlock: blockResult.openBlock ?? null,
|
|
7715
|
+
conversations: blockResult.conversations ?? [],
|
|
7716
|
+
activeThreadId: blockResult.activeThreadId ?? null,
|
|
7717
|
+
audit: auditResult.items ?? []
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
};
|
|
7721
|
+
}
|
|
7722
|
+
function sendDashboard(res, result) {
|
|
7723
|
+
res.setHeader("ETag", result.etag);
|
|
7724
|
+
res.setHeader("Cache-Control", "private, no-cache");
|
|
7725
|
+
if (result.notModified) {
|
|
7726
|
+
res.statusCode = 304;
|
|
7727
|
+
res.end("");
|
|
7728
|
+
return;
|
|
7729
|
+
}
|
|
7730
|
+
res.statusCode = 200;
|
|
7731
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
7732
|
+
res.end(JSON.stringify(redactValue(result.body)));
|
|
7733
|
+
}
|
|
6946
7734
|
async function handleAdminRequest(runtime, req, res) {
|
|
6947
7735
|
try {
|
|
6948
7736
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -6954,10 +7742,20 @@ async function handleAdminRequest(runtime, req, res) {
|
|
|
6954
7742
|
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate Block expansion requires PATCH");
|
|
6955
7743
|
sendJson(res, 200, await expandBlock(runtime, url));
|
|
6956
7744
|
} else if (path === "/api/stratagate/import") {
|
|
6957
|
-
if (req.method === "GET")
|
|
6958
|
-
|
|
7745
|
+
if (req.method === "GET") {
|
|
7746
|
+
const operation = url.searchParams.get("operation");
|
|
7747
|
+
if (operation === "status") {
|
|
7748
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
7749
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
7750
|
+
const jobId = url.searchParams.get("jobId")?.trim() || void 0;
|
|
7751
|
+
sendJson(res, 200, await runtime.adminExternalMemoryStatus(namespace, jobId));
|
|
7752
|
+
} else {
|
|
7753
|
+
sendJson(res, 200, externalMemoryPrompt());
|
|
7754
|
+
}
|
|
7755
|
+
} else if (req.method === "POST") sendJson(res, 200, await importExternalMemory(runtime, req));
|
|
6959
7756
|
else throw new AdminHttpError(405, "External memory import requires GET or POST");
|
|
6960
7757
|
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
7758
|
+
else if (path === "/api/stratagate/dashboard") sendDashboard(res, await dashboard(runtime, url, requestHeader(req, "if-none-match")));
|
|
6961
7759
|
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
6962
7760
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
6963
7761
|
else if (path === "/api/stratagate/sources") sendJson(res, 200, await sources(runtime, url));
|