stratagate-dsh 0.2.19 → 0.2.21
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 +14 -0
- package/README.md +10 -0
- package/dist/client.js +320 -53
- package/dist/index.js +316 -51
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -360,7 +360,7 @@ function rrfRank(rankings) {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
// ../../src/storage.ts
|
|
363
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
363
|
+
var STRATAGATE_STORAGE_SCHEMA_VERSION = 7;
|
|
364
364
|
var StorageConflictError = class extends Error {
|
|
365
365
|
constructor(namespace, expectedRevision, actualRevision) {
|
|
366
366
|
super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
|
|
@@ -380,7 +380,7 @@ function migrateLegacyBlocks(blocks) {
|
|
|
380
380
|
return blocks.map((block) => {
|
|
381
381
|
const { pointerAnchorTurn, ...current } = block;
|
|
382
382
|
const position = blocks.filter((candidate) => candidate.threadId === block.threadId && candidate.endTurn <= pointerAnchorTurn).length;
|
|
383
|
-
return { ...current, pointerAnchorBlockPosition: Math.max(1, position) };
|
|
383
|
+
return { ...current, pointerAnchorBlockPosition: Math.max(1, position), lastLiftedBy: null };
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
386
|
function normalizeSnapshot(value) {
|
|
@@ -432,6 +432,13 @@ function normalizeSnapshot(value) {
|
|
|
432
432
|
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
433
433
|
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
434
434
|
};
|
|
435
|
+
} else if (schemaVersion === 6) {
|
|
436
|
+
const legacy = value;
|
|
437
|
+
snapshot = {
|
|
438
|
+
...structuredClone(legacy),
|
|
439
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
440
|
+
blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null }))
|
|
441
|
+
};
|
|
435
442
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
436
443
|
snapshot = structuredClone(value);
|
|
437
444
|
} else {
|
|
@@ -454,6 +461,9 @@ function normalizeSnapshot(value) {
|
|
|
454
461
|
if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
|
|
455
462
|
throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
|
|
456
463
|
}
|
|
464
|
+
if (block.lastLiftedBy !== null && block.lastLiftedBy !== "user" && block.lastLiftedBy !== "agent") {
|
|
465
|
+
throw new TypeError("Invalid StrataGate snapshot: lastLiftedBy must be user, agent, or null");
|
|
466
|
+
}
|
|
457
467
|
}
|
|
458
468
|
if (snapshot.successfulModelResponses.length > 5) {
|
|
459
469
|
snapshot.successfulModelResponses = snapshot.successfulModelResponses.slice(-5);
|
|
@@ -652,6 +662,7 @@ CREATE TABLE IF NOT EXISTS blocks (
|
|
|
652
662
|
pointer_anchor_level INTEGER NOT NULL,
|
|
653
663
|
pointer_anchor_block_position INTEGER NOT NULL,
|
|
654
664
|
last_lifted_at TEXT,
|
|
665
|
+
last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent')),
|
|
655
666
|
PRIMARY KEY (namespace, id),
|
|
656
667
|
UNIQUE (namespace, sequence),
|
|
657
668
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
@@ -918,7 +929,8 @@ var SqliteStorage = class {
|
|
|
918
929
|
pointerCurrentLevel: row.pointer_current_level,
|
|
919
930
|
pointerAnchorLevel: row.pointer_anchor_level,
|
|
920
931
|
pointerAnchorBlockPosition: row.pointer_anchor_block_position,
|
|
921
|
-
lastLiftedAt: row.last_lifted_at
|
|
932
|
+
lastLiftedAt: row.last_lifted_at,
|
|
933
|
+
lastLiftedBy: row.last_lifted_by
|
|
922
934
|
}));
|
|
923
935
|
const sourceRows = this.database.prepare(`
|
|
924
936
|
SELECT event_id, message_id, position FROM event_sources
|
|
@@ -1154,8 +1166,8 @@ var SqliteStorage = class {
|
|
|
1154
1166
|
INSERT INTO blocks (
|
|
1155
1167
|
namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
|
|
1156
1168
|
l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
|
|
1157
|
-
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at
|
|
1158
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1169
|
+
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at, last_lifted_by
|
|
1170
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1159
1171
|
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
1160
1172
|
thread_id = excluded.thread_id,
|
|
1161
1173
|
sequence = excluded.sequence,
|
|
@@ -1172,7 +1184,8 @@ var SqliteStorage = class {
|
|
|
1172
1184
|
pointer_current_level = excluded.pointer_current_level,
|
|
1173
1185
|
pointer_anchor_level = excluded.pointer_anchor_level,
|
|
1174
1186
|
pointer_anchor_block_position = excluded.pointer_anchor_block_position,
|
|
1175
|
-
last_lifted_at = excluded.last_lifted_at
|
|
1187
|
+
last_lifted_at = excluded.last_lifted_at,
|
|
1188
|
+
last_lifted_by = excluded.last_lifted_by
|
|
1176
1189
|
`);
|
|
1177
1190
|
for (const block of snapshot.blocks) {
|
|
1178
1191
|
insertBlock.run(
|
|
@@ -1193,7 +1206,8 @@ var SqliteStorage = class {
|
|
|
1193
1206
|
block.pointerCurrentLevel,
|
|
1194
1207
|
block.pointerAnchorLevel,
|
|
1195
1208
|
block.pointerAnchorBlockPosition,
|
|
1196
|
-
block.lastLiftedAt
|
|
1209
|
+
block.lastLiftedAt,
|
|
1210
|
+
block.lastLiftedBy
|
|
1197
1211
|
);
|
|
1198
1212
|
}
|
|
1199
1213
|
const insertMessage = this.database.prepare(`
|
|
@@ -1462,7 +1476,7 @@ var SqliteStorage = class {
|
|
|
1462
1476
|
this.database.exec(THREAD_INDEXES);
|
|
1463
1477
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1464
1478
|
});
|
|
1465
|
-
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5) {
|
|
1479
|
+
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6) {
|
|
1466
1480
|
this.immediateTransaction(() => {
|
|
1467
1481
|
this.database.exec(SCHEMA);
|
|
1468
1482
|
if (version === 1) {
|
|
@@ -1495,6 +1509,9 @@ var SqliteStorage = class {
|
|
|
1495
1509
|
))
|
|
1496
1510
|
`);
|
|
1497
1511
|
}
|
|
1512
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "last_lifted_by")) {
|
|
1513
|
+
this.database.exec("ALTER TABLE blocks ADD COLUMN last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent'))");
|
|
1514
|
+
}
|
|
1498
1515
|
const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
|
|
1499
1516
|
if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1500
1517
|
this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
|
|
@@ -2108,7 +2125,7 @@ var StrataGate = class _StrataGate {
|
|
|
2108
2125
|
};
|
|
2109
2126
|
});
|
|
2110
2127
|
}
|
|
2111
|
-
async expandBlock(id, target = "next") {
|
|
2128
|
+
async expandBlock(id, target = "next", source = "agent") {
|
|
2112
2129
|
return this.commitMutation(() => {
|
|
2113
2130
|
const block = this.blocks.find((candidate) => candidate.id === id);
|
|
2114
2131
|
if (!block) throw new Error(`Unknown block: ${id}`);
|
|
@@ -2125,6 +2142,7 @@ var StrataGate = class _StrataGate {
|
|
|
2125
2142
|
block.pointerAnchorLevel = level;
|
|
2126
2143
|
block.pointerAnchorBlockPosition = latestBlockPosition;
|
|
2127
2144
|
block.lastLiftedAt = toUtc8Iso(this.now());
|
|
2145
|
+
block.lastLiftedBy = source;
|
|
2128
2146
|
return {
|
|
2129
2147
|
id: block.id,
|
|
2130
2148
|
...block.threadId ? { threadId: block.threadId } : {},
|
|
@@ -2354,7 +2372,8 @@ var StrataGate = class _StrataGate {
|
|
|
2354
2372
|
pointerCurrentLevel: 5,
|
|
2355
2373
|
pointerAnchorLevel: 5,
|
|
2356
2374
|
pointerAnchorBlockPosition: blockPosition,
|
|
2357
|
-
lastLiftedAt: null
|
|
2375
|
+
lastLiftedAt: null,
|
|
2376
|
+
lastLiftedBy: null
|
|
2358
2377
|
};
|
|
2359
2378
|
const sealedIds = new Set(raw.map((message) => message.id));
|
|
2360
2379
|
const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
|
|
@@ -3302,7 +3321,7 @@ var StrataGateRuntime = class {
|
|
|
3302
3321
|
}
|
|
3303
3322
|
async expandBlock(session, id, target) {
|
|
3304
3323
|
await this.flush();
|
|
3305
|
-
const result = await (await this.space(session)).expandBlock(id, target);
|
|
3324
|
+
const result = await (await this.space(session)).expandBlock(id, target, "agent");
|
|
3306
3325
|
return this.batch(session, [{
|
|
3307
3326
|
ref: `block:${result.id}:level:${result.level}`,
|
|
3308
3327
|
target: { eventIds: [], elementIds: [] }
|
|
@@ -3527,6 +3546,37 @@ var StrataGateRuntime = class {
|
|
|
3527
3546
|
await update;
|
|
3528
3547
|
return value;
|
|
3529
3548
|
}
|
|
3549
|
+
async adminExpandBlock(namespace, id, target) {
|
|
3550
|
+
const key = namespace.trim();
|
|
3551
|
+
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
3552
|
+
const update = this.settingsTail.catch(() => {
|
|
3553
|
+
}).then(async () => {
|
|
3554
|
+
await this.flush();
|
|
3555
|
+
const active = this.spaces.get(key);
|
|
3556
|
+
if (active) return (await active).expandBlock(id, target, "user");
|
|
3557
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
3558
|
+
throw new Error(`Unknown StrataGate namespace: ${key}`);
|
|
3559
|
+
}
|
|
3560
|
+
const memory = await StrataGate.open({
|
|
3561
|
+
database: this.config.database,
|
|
3562
|
+
namespace: key,
|
|
3563
|
+
blockTurnSize: this.config.blockTurnSize,
|
|
3564
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
3565
|
+
summarizer: this.models.summarizer,
|
|
3566
|
+
extractor: this.models.extractor,
|
|
3567
|
+
elementProjector: this.models.projector
|
|
3568
|
+
});
|
|
3569
|
+
try {
|
|
3570
|
+
return await memory.expandBlock(id, target, "user");
|
|
3571
|
+
} finally {
|
|
3572
|
+
await memory.close();
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
this.settingsTail = update.then(() => {
|
|
3576
|
+
}, () => {
|
|
3577
|
+
});
|
|
3578
|
+
return update;
|
|
3579
|
+
}
|
|
3530
3580
|
async applyBlockDecayLambda(value) {
|
|
3531
3581
|
await this.flush();
|
|
3532
3582
|
this.blockDecayLambda = value;
|
|
@@ -3911,6 +3961,20 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3911
3961
|
}
|
|
3912
3962
|
|
|
3913
3963
|
// src/web.ts
|
|
3964
|
+
import { createRequire } from "node:module";
|
|
3965
|
+
var STRATAGATE_DSH_VERSION = "0.2.21";
|
|
3966
|
+
var LEGACY_THREAD_ID = "__legacy__";
|
|
3967
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
3968
|
+
function installedPackageVersion(names) {
|
|
3969
|
+
for (const name2 of names) {
|
|
3970
|
+
try {
|
|
3971
|
+
const value = nodeRequire(`${name2}/package.json`);
|
|
3972
|
+
if (typeof value.version === "string" && value.version.trim()) return value.version;
|
|
3973
|
+
} catch {
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
return "unknown";
|
|
3977
|
+
}
|
|
3914
3978
|
function sendJson(res, status, body) {
|
|
3915
3979
|
res.statusCode = status;
|
|
3916
3980
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -3953,6 +4017,17 @@ function sourceMessages(snapshot, ids) {
|
|
|
3953
4017
|
}
|
|
3954
4018
|
return output;
|
|
3955
4019
|
}
|
|
4020
|
+
function blockLayers(block) {
|
|
4021
|
+
return [
|
|
4022
|
+
{ level: 0, content: `${block.l0Title}
|
|
4023
|
+
\u6807\u7B7E\uFF1A${block.l0Tags.join("\u3001") || "\u65E0"}` },
|
|
4024
|
+
{ level: 1, content: block.l1Summary || block.l0Title },
|
|
4025
|
+
{ level: 2, content: block.l2Keypoints.map((point) => `\u2022 ${point}`).join("\n") || block.l1Summary || block.l0Title },
|
|
4026
|
+
{ level: 3, content: block.l3Condensed || block.l2Keypoints.join("\n") || block.l1Summary },
|
|
4027
|
+
{ level: 4, content: block.l4Readable || block.l3Condensed },
|
|
4028
|
+
{ level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
4029
|
+
];
|
|
4030
|
+
}
|
|
3956
4031
|
function eventSummary(event) {
|
|
3957
4032
|
return {
|
|
3958
4033
|
id: event.id,
|
|
@@ -4056,7 +4131,13 @@ async function overview(runtime) {
|
|
|
4056
4131
|
lastActivityAt: timestamps.at(-1) ?? null
|
|
4057
4132
|
});
|
|
4058
4133
|
}
|
|
4059
|
-
return {
|
|
4134
|
+
return {
|
|
4135
|
+
readonly: true,
|
|
4136
|
+
settingsWritable: true,
|
|
4137
|
+
pluginVersion: STRATAGATE_DSH_VERSION,
|
|
4138
|
+
harnessVersion: installedPackageVersion(["@deepseek-ai/dsh", "@deepseek-ai/dsh-session"]),
|
|
4139
|
+
namespaces: rows
|
|
4140
|
+
};
|
|
4060
4141
|
}
|
|
4061
4142
|
async function updateSettings(runtime, url) {
|
|
4062
4143
|
const raw = url.searchParams.get("blockDecayLambda")?.trim() ?? "";
|
|
@@ -4066,6 +4147,126 @@ async function updateSettings(runtime, url) {
|
|
|
4066
4147
|
}
|
|
4067
4148
|
return { blockDecayLambda: await runtime.adminSetBlockDecayLambda(value) };
|
|
4068
4149
|
}
|
|
4150
|
+
function receiptThreadId(id) {
|
|
4151
|
+
const match = /^dsh:(.+):turn:\d+$/.exec(id);
|
|
4152
|
+
return match?.[1]?.trim() || null;
|
|
4153
|
+
}
|
|
4154
|
+
function timestampKey(value) {
|
|
4155
|
+
const parsed = Date.parse(value);
|
|
4156
|
+
return Number.isFinite(parsed) ? String(parsed) : value;
|
|
4157
|
+
}
|
|
4158
|
+
function recoverSnapshotView(snapshot) {
|
|
4159
|
+
const receiptThreads = /* @__PURE__ */ new Map();
|
|
4160
|
+
const receiptActivity = /* @__PURE__ */ new Map();
|
|
4161
|
+
const receiptCandidates = /* @__PURE__ */ new Map();
|
|
4162
|
+
for (const receipt of snapshot.ingestionReceipts) {
|
|
4163
|
+
const threadId = receiptThreadId(receipt.id);
|
|
4164
|
+
if (!threadId) continue;
|
|
4165
|
+
receiptThreads.set(receipt.id, threadId);
|
|
4166
|
+
const currentActivity = receiptActivity.get(threadId);
|
|
4167
|
+
if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
|
|
4168
|
+
const key = timestampKey(receipt.createdAt);
|
|
4169
|
+
const candidates = receiptCandidates.get(key) ?? /* @__PURE__ */ new Set();
|
|
4170
|
+
candidates.add(threadId);
|
|
4171
|
+
receiptCandidates.set(key, candidates);
|
|
4172
|
+
}
|
|
4173
|
+
const exactThreadAt = new Map([...receiptCandidates].filter(([, ids]) => ids.size === 1).map(([createdAt, ids]) => [createdAt, [...ids][0]]));
|
|
4174
|
+
const recoverMessages = (messages) => {
|
|
4175
|
+
let precedingThreadId = null;
|
|
4176
|
+
return messages.map((message) => {
|
|
4177
|
+
const explicit = message.threadId?.trim();
|
|
4178
|
+
const exact = exactThreadAt.get(timestampKey(message.createdAt));
|
|
4179
|
+
const recovered = explicit || exact || (message.role === "assistant" ? precedingThreadId : null);
|
|
4180
|
+
const threadId = recovered || LEGACY_THREAD_ID;
|
|
4181
|
+
if (message.role === "user" || explicit || exact) precedingThreadId = threadId;
|
|
4182
|
+
return { message, threadId };
|
|
4183
|
+
});
|
|
4184
|
+
};
|
|
4185
|
+
const blocks = [];
|
|
4186
|
+
for (const source of snapshot.blocks) {
|
|
4187
|
+
const recovered = recoverMessages(source.l5Raw);
|
|
4188
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4189
|
+
for (const item of recovered) {
|
|
4190
|
+
const messages = groups.get(item.threadId) ?? [];
|
|
4191
|
+
messages.push(item.message);
|
|
4192
|
+
groups.set(item.threadId, messages);
|
|
4193
|
+
}
|
|
4194
|
+
const entries = [...groups];
|
|
4195
|
+
for (const [threadId, messages] of entries) {
|
|
4196
|
+
const virtual = !source.threadId && (entries.length > 1 || threadId !== LEGACY_THREAD_ID);
|
|
4197
|
+
blocks.push({
|
|
4198
|
+
id: entries.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
|
|
4199
|
+
source,
|
|
4200
|
+
threadId,
|
|
4201
|
+
messages,
|
|
4202
|
+
virtual,
|
|
4203
|
+
turnRange: [0, 0]
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
const turnCounters = /* @__PURE__ */ new Map();
|
|
4208
|
+
for (const block of blocks) {
|
|
4209
|
+
if (block.source.threadId) {
|
|
4210
|
+
block.turnRange = [block.source.startTurn, block.source.endTurn];
|
|
4211
|
+
turnCounters.set(block.threadId, Math.max(turnCounters.get(block.threadId) ?? 0, block.source.endTurn));
|
|
4212
|
+
continue;
|
|
4213
|
+
}
|
|
4214
|
+
const turns = Math.max(1, block.messages.filter(({ role }) => role === "user").length);
|
|
4215
|
+
const start = (turnCounters.get(block.threadId) ?? 0) + 1;
|
|
4216
|
+
block.turnRange = [start, start + turns - 1];
|
|
4217
|
+
turnCounters.set(block.threadId, start + turns - 1);
|
|
4218
|
+
}
|
|
4219
|
+
return {
|
|
4220
|
+
blocks,
|
|
4221
|
+
openMessages: recoverMessages(snapshot.openTail),
|
|
4222
|
+
receiptThreads,
|
|
4223
|
+
receiptActivity
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
function virtualBlockLayers(block) {
|
|
4227
|
+
if (!block.virtual || block.messages.length === block.source.l5Raw.length) return blockLayers(block.source);
|
|
4228
|
+
const deterministic = deterministicBlockLayers(block.messages);
|
|
4229
|
+
const natural = block.messages.filter(({ role }) => role === "user" || role === "assistant");
|
|
4230
|
+
const firstUser = natural.find(({ role, content }) => role === "user" && content.trim());
|
|
4231
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5";
|
|
4232
|
+
const summary = natural.map(({ content }) => content.replace(/\s+/g, " ").trim()).filter(Boolean).join(" ").slice(0, 500);
|
|
4233
|
+
const keypoints = natural.filter(({ role }) => role === "user").map(({ content }) => content.replace(/\s+/g, " ").trim().slice(0, 160));
|
|
4234
|
+
return [
|
|
4235
|
+
{ level: 0, content: title },
|
|
4236
|
+
{ level: 1, content: summary || title },
|
|
4237
|
+
{ level: 2, content: keypoints.map((point) => `\u2022 ${point}`).join("\n") || summary || title },
|
|
4238
|
+
{ level: 3, content: deterministic.l3Condensed },
|
|
4239
|
+
{ level: 4, content: deterministic.l4Readable },
|
|
4240
|
+
{ level: 5, content: block.messages.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
4241
|
+
];
|
|
4242
|
+
}
|
|
4243
|
+
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
4244
|
+
const ids = /* @__PURE__ */ new Set([
|
|
4245
|
+
...view.blocks.map((block) => block.threadId),
|
|
4246
|
+
...view.openMessages.map(({ threadId }) => threadId),
|
|
4247
|
+
...view.receiptThreads.values()
|
|
4248
|
+
]);
|
|
4249
|
+
return [...ids].map((id) => {
|
|
4250
|
+
const blocks = view.blocks.filter((block) => block.threadId === id);
|
|
4251
|
+
const messages = [
|
|
4252
|
+
...blocks.flatMap((block) => block.messages),
|
|
4253
|
+
...view.openMessages.filter((message) => message.threadId === id).map(({ message }) => message)
|
|
4254
|
+
];
|
|
4255
|
+
const firstUser = messages.find(({ role, content }) => role === "user" && content.trim());
|
|
4256
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 28);
|
|
4257
|
+
const timestamps = [
|
|
4258
|
+
...blocks.map(({ source }) => source.createdAt),
|
|
4259
|
+
...messages.map(({ createdAt }) => createdAt),
|
|
4260
|
+
...view.receiptActivity.get(id) ? [view.receiptActivity.get(id)] : []
|
|
4261
|
+
].sort();
|
|
4262
|
+
return {
|
|
4263
|
+
id,
|
|
4264
|
+
label: id === LEGACY_THREAD_ID ? "\u5386\u53F2\u5BF9\u8BDD" : title || `\u5BF9\u8BDD ${id.slice(0, 8)}`,
|
|
4265
|
+
blocks: blocks.length,
|
|
4266
|
+
lastActivityAt: timestamps.at(-1) ?? null
|
|
4267
|
+
};
|
|
4268
|
+
}).sort((left, right) => String(right.lastActivityAt).localeCompare(String(left.lastActivityAt)));
|
|
4269
|
+
}
|
|
4069
4270
|
async function memories(runtime, url) {
|
|
4070
4271
|
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
4071
4272
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
@@ -4080,44 +4281,85 @@ async function memories(runtime, url) {
|
|
|
4080
4281
|
relatedElements: snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.includes(event.id)).map(({ id, name: name2 }) => ({ id, name: name2 }))
|
|
4081
4282
|
}));
|
|
4082
4283
|
else if (kind === "elements") values = snapshot.elements.map(elementSummary);
|
|
4083
|
-
else if (kind === "blocks")
|
|
4084
|
-
const
|
|
4085
|
-
const
|
|
4086
|
-
const
|
|
4087
|
-
const
|
|
4088
|
-
const
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4284
|
+
else if (kind === "blocks") {
|
|
4285
|
+
const recovered = recoverSnapshotView(snapshot);
|
|
4286
|
+
const conversations = conversationRows(snapshot, recovered);
|
|
4287
|
+
const requestedThreadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
4288
|
+
const activeThreadId = requestedThreadId || conversations[0]?.id || null;
|
|
4289
|
+
const scopedBlocks = activeThreadId ? recovered.blocks.filter((block) => block.threadId === activeThreadId) : [];
|
|
4290
|
+
values = scopedBlocks.map((block) => {
|
|
4291
|
+
const source = block.source;
|
|
4292
|
+
const extraction = snapshot.extractionJobs.find(({ blockId }) => blockId === source.id);
|
|
4293
|
+
const blockMessageIds = new Set(block.messages.map(({ id }) => id));
|
|
4294
|
+
const relatedEvents = snapshot.events.filter((event) => event.sourceBlockId === source.id && (!block.virtual || event.sourceMessageIds.some((id) => blockMessageIds.has(id))));
|
|
4295
|
+
const eventIds = new Set(relatedEvents.map(({ id }) => id));
|
|
4296
|
+
const projections = snapshot.elementProjectionJobs.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
4297
|
+
const relatedElements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id))).map(({ id, name: name2 }) => ({ id, name: name2 }));
|
|
4298
|
+
const failedProjection = projections.find(({ status: status2 }) => status2 === "failed");
|
|
4299
|
+
const pendingProjection = projections.some(({ status: status2 }) => status2 === "pending" || status2 === "running");
|
|
4300
|
+
const needsExtraction = source.shouldExtract === true;
|
|
4301
|
+
const status = extraction?.status === "failed" || failedProjection ? "failed" : extraction?.status === "succeeded" || extraction?.status === "skipped" ? pendingProjection ? "processing" : "organized" : needsExtraction ? "waiting" : "organized";
|
|
4302
|
+
const blockPosition = scopedBlocks.findIndex(({ id }) => id === block.id) + 1;
|
|
4303
|
+
const latestBlockPosition = scopedBlocks.length;
|
|
4304
|
+
const currentLevel = getDecayedBlockLevel(
|
|
4305
|
+
source.pointerAnchorLevel,
|
|
4306
|
+
source.threadId ? source.pointerAnchorBlockPosition : Math.min(source.pointerAnchorBlockPosition, blockPosition),
|
|
4307
|
+
latestBlockPosition,
|
|
4308
|
+
snapshot.blockDecayLambda
|
|
4309
|
+
);
|
|
4310
|
+
return {
|
|
4311
|
+
id: block.id,
|
|
4312
|
+
sourceBlockId: source.id,
|
|
4313
|
+
threadId: block.threadId,
|
|
4314
|
+
sequence: source.sequence,
|
|
4315
|
+
turnRange: block.turnRange,
|
|
4316
|
+
title: block.virtual && block.messages.length !== source.l5Raw.length ? block.messages.find(({ role }) => role === "user")?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5" : source.l0Title,
|
|
4317
|
+
tags: source.l0Tags,
|
|
4318
|
+
summary: source.l1Summary,
|
|
4319
|
+
keypoints: source.l2Keypoints,
|
|
4320
|
+
currentLevel,
|
|
4321
|
+
distanceFromLatest: Math.max(0, latestBlockPosition - blockPosition),
|
|
4322
|
+
expansionSource: source.lastLiftedAt ? source.lastLiftedBy ?? "legacy" : null,
|
|
4323
|
+
lastLiftedAt: source.lastLiftedAt,
|
|
4324
|
+
sourceMessages: block.messages.length,
|
|
4325
|
+
createdAt: source.createdAt,
|
|
4326
|
+
virtual: block.virtual,
|
|
4327
|
+
status,
|
|
4328
|
+
eventExtraction: extraction ? {
|
|
4329
|
+
status: extraction.status,
|
|
4330
|
+
attempts: extraction.attempts,
|
|
4331
|
+
updatedAt: extraction.updatedAt,
|
|
4332
|
+
lastError: extraction.lastError
|
|
4333
|
+
} : null,
|
|
4334
|
+
elementProjection: projections.length ? {
|
|
4335
|
+
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
4336
|
+
jobs: projections.length,
|
|
4337
|
+
lastError: failedProjection?.lastError ?? null
|
|
4338
|
+
} : null,
|
|
4339
|
+
relatedEvents: relatedEvents.map(eventSummary),
|
|
4340
|
+
relatedElements
|
|
4341
|
+
};
|
|
4342
|
+
});
|
|
4343
|
+
const filtered2 = values.filter((value) => matchesQuery(value, query));
|
|
4344
|
+
const latestSealedTurn = scopedBlocks.reduce((latest, block) => Math.max(latest, block.turnRange[1]), 0);
|
|
4345
|
+
const openMessages = activeThreadId ? recovered.openMessages.filter((message) => message.threadId === activeThreadId).map(({ message }) => message) : [];
|
|
4346
|
+
const openTurns = openMessages.filter(({ role }) => role === "user").length;
|
|
4093
4347
|
return {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
attempts: extraction.attempts,
|
|
4108
|
-
updatedAt: extraction.updatedAt,
|
|
4109
|
-
lastError: extraction.lastError
|
|
4110
|
-
} : null,
|
|
4111
|
-
elementProjection: projections.length ? {
|
|
4112
|
-
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
4113
|
-
jobs: projections.length,
|
|
4114
|
-
lastError: failedProjection?.lastError ?? null
|
|
4115
|
-
} : null,
|
|
4116
|
-
relatedEvents: relatedEvents.map(eventSummary),
|
|
4117
|
-
relatedElements
|
|
4348
|
+
namespace,
|
|
4349
|
+
kind,
|
|
4350
|
+
total: filtered2.length,
|
|
4351
|
+
offset,
|
|
4352
|
+
limit,
|
|
4353
|
+
items: filtered2.slice(offset, offset + limit),
|
|
4354
|
+
openBlock: {
|
|
4355
|
+
turnRange: openTurns > 0 ? [latestSealedTurn + 1, latestSealedTurn + openTurns] : null,
|
|
4356
|
+
messages: openMessages.length,
|
|
4357
|
+
status: "open"
|
|
4358
|
+
},
|
|
4359
|
+
conversations,
|
|
4360
|
+
activeThreadId
|
|
4118
4361
|
};
|
|
4119
|
-
});
|
|
4120
|
-
else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
4362
|
+
} else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
4121
4363
|
const filtered = values.filter((value) => matchesQuery(value, query));
|
|
4122
4364
|
return { namespace, kind, total: filtered.length, offset, limit, items: filtered.slice(offset, offset + limit) };
|
|
4123
4365
|
}
|
|
@@ -4143,12 +4385,22 @@ async function sources(runtime, url) {
|
|
|
4143
4385
|
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
4144
4386
|
ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
4145
4387
|
} else if (blockId) {
|
|
4146
|
-
const
|
|
4388
|
+
const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
|
|
4389
|
+
const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
|
|
4147
4390
|
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
4148
|
-
|
|
4149
|
-
|
|
4391
|
+
const messages = displayBlock?.messages ?? block.l5Raw;
|
|
4392
|
+
ids = new Set(messages.map(({ id }) => id));
|
|
4393
|
+
events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids.has(id))));
|
|
4150
4394
|
const eventIds = new Set(events.map(({ id }) => id));
|
|
4151
4395
|
elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
4396
|
+
return {
|
|
4397
|
+
namespace,
|
|
4398
|
+
events: events.map(eventSummary),
|
|
4399
|
+
elements: elements.map(elementSummary),
|
|
4400
|
+
messages: sourceMessages(snapshot, ids),
|
|
4401
|
+
layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
|
|
4402
|
+
virtual: displayBlock?.virtual ?? false
|
|
4403
|
+
};
|
|
4152
4404
|
} else {
|
|
4153
4405
|
throw new AdminHttpError(400, "eventId, elementId, or blockId is required");
|
|
4154
4406
|
}
|
|
@@ -4159,6 +4411,16 @@ async function sources(runtime, url) {
|
|
|
4159
4411
|
messages: sourceMessages(snapshot, ids)
|
|
4160
4412
|
};
|
|
4161
4413
|
}
|
|
4414
|
+
async function expandBlock(runtime, url) {
|
|
4415
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
4416
|
+
const blockId = url.searchParams.get("blockId")?.trim() ?? "";
|
|
4417
|
+
const target = url.searchParams.get("level")?.trim() ?? "";
|
|
4418
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
4419
|
+
if (!blockId) throw new AdminHttpError(400, "blockId is required");
|
|
4420
|
+
if (blockId.startsWith("virtual:")) throw new AdminHttpError(409, "Recovered legacy fragments are read-only display data");
|
|
4421
|
+
if (!/^L?[0-5]$/i.test(target)) throw new AdminHttpError(400, "level must be L0 through L5");
|
|
4422
|
+
return runtime.adminExpandBlock(namespace, blockId, target);
|
|
4423
|
+
}
|
|
4162
4424
|
function receiptSources(snapshot, receipt) {
|
|
4163
4425
|
const events = snapshot.events.filter(({ id }) => receipt.eventIds.includes(id));
|
|
4164
4426
|
const elements = snapshot.elements.filter(({ id }) => receipt.elementIds.includes(id));
|
|
@@ -4194,6 +4456,9 @@ async function handleAdminRequest(runtime, req, res) {
|
|
|
4194
4456
|
if (path === "/api/stratagate/settings") {
|
|
4195
4457
|
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate settings require PATCH");
|
|
4196
4458
|
sendJson(res, 200, await updateSettings(runtime, url));
|
|
4459
|
+
} else if (path === "/api/stratagate/blocks/expand") {
|
|
4460
|
+
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate Block expansion requires PATCH");
|
|
4461
|
+
sendJson(res, 200, await expandBlock(runtime, url));
|
|
4197
4462
|
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
4198
4463
|
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
4199
4464
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|