stratagate-dsh 0.2.49 → 0.2.53
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 +20 -1
- package/dist/client.js +9 -5
- package/dist/index.js +313 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -179,7 +179,10 @@ function summarizeToolJson(value) {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
function removeStandaloneFillers(paragraph) {
|
|
182
|
-
|
|
182
|
+
const pieces = paragraph.split(/(?<=[。!?!?])|\n+/u).map((piece) => piece.trim()).filter(Boolean);
|
|
183
|
+
const kept = pieces.filter((piece) => !isFillerSentence(piece));
|
|
184
|
+
if (kept.length === pieces.length) return paragraph.trim();
|
|
185
|
+
return kept.join("\n");
|
|
183
186
|
}
|
|
184
187
|
function splitTextAndCode(source) {
|
|
185
188
|
const parts = [];
|
|
@@ -218,6 +221,32 @@ function formatReadableTranscript(messages) {
|
|
|
218
221
|
return lines;
|
|
219
222
|
}).join("\n\n");
|
|
220
223
|
}
|
|
224
|
+
function renderRawToolTrace(trace) {
|
|
225
|
+
return `Tool call (raw): ${JSON.stringify(trace)}`;
|
|
226
|
+
}
|
|
227
|
+
function formatRawTranscript(messages) {
|
|
228
|
+
return messages.flatMap((message) => [
|
|
229
|
+
`${message.role}: ${message.content}`,
|
|
230
|
+
...(message.toolCalls ?? []).map(renderRawToolTrace)
|
|
231
|
+
]).join("\n\n");
|
|
232
|
+
}
|
|
233
|
+
function estimateTokens(value) {
|
|
234
|
+
let tokens = 0;
|
|
235
|
+
let asciiRun = 0;
|
|
236
|
+
const flushAscii = () => {
|
|
237
|
+
if (asciiRun > 0) tokens += Math.ceil(asciiRun / 4);
|
|
238
|
+
asciiRun = 0;
|
|
239
|
+
};
|
|
240
|
+
for (const character of value) {
|
|
241
|
+
if (character.codePointAt(0) <= 127) asciiRun += 1;
|
|
242
|
+
else {
|
|
243
|
+
flushAscii();
|
|
244
|
+
tokens += 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
flushAscii();
|
|
248
|
+
return tokens;
|
|
249
|
+
}
|
|
221
250
|
function condenseTranscript(messages) {
|
|
222
251
|
const seen = /* @__PURE__ */ new Set();
|
|
223
252
|
return messages.filter((message) => message.role !== "system").flatMap((message) => {
|
|
@@ -247,9 +276,14 @@ function cloneRawMessages(messages) {
|
|
|
247
276
|
}));
|
|
248
277
|
}
|
|
249
278
|
function deterministicBlockLayers(messages) {
|
|
279
|
+
const l5Rendered = formatRawTranscript(messages);
|
|
280
|
+
const readable = formatReadableTranscript(messages);
|
|
281
|
+
const l4Readable = estimateTokens(readable) <= estimateTokens(l5Rendered) ? readable : l5Rendered;
|
|
282
|
+
const condensed = condenseTranscript(messages);
|
|
283
|
+
const l3Condensed = estimateTokens(condensed) <= estimateTokens(l4Readable) ? condensed : l4Readable;
|
|
250
284
|
return {
|
|
251
|
-
l3Condensed
|
|
252
|
-
l4Readable
|
|
285
|
+
l3Condensed,
|
|
286
|
+
l4Readable,
|
|
253
287
|
l5Raw: cloneRawMessages(messages)
|
|
254
288
|
};
|
|
255
289
|
}
|
|
@@ -2663,9 +2697,10 @@ function renderBlock(block, level) {
|
|
|
2663
2697
|
Tags: ${block.l0Tags.join(", ") || "none"}`;
|
|
2664
2698
|
if (level === 1) return block.l1Summary;
|
|
2665
2699
|
if (level === 2) return block.l2Keypoints.map((point) => `- ${point}`).join("\n") || block.l1Summary;
|
|
2666
|
-
|
|
2667
|
-
if (level ===
|
|
2668
|
-
|
|
2700
|
+
const deterministic = deterministicBlockLayers(block.l5Raw);
|
|
2701
|
+
if (level === 3) return deterministic.l3Condensed;
|
|
2702
|
+
if (level === 4) return deterministic.l4Readable;
|
|
2703
|
+
return formatRawTranscript(block.l5Raw);
|
|
2669
2704
|
}
|
|
2670
2705
|
function sameIds(left, right) {
|
|
2671
2706
|
return left.length === right.length && left.every((id, index) => id === right[index]);
|
|
@@ -5378,6 +5413,12 @@ CREATE TABLE IF NOT EXISTS stratagate_dsh_workspaces (
|
|
|
5378
5413
|
display_name TEXT NOT NULL,
|
|
5379
5414
|
updated_at TEXT NOT NULL
|
|
5380
5415
|
) STRICT;
|
|
5416
|
+
|
|
5417
|
+
CREATE TABLE IF NOT EXISTS stratagate_dsh_feedback_drafts (
|
|
5418
|
+
namespace TEXT PRIMARY KEY,
|
|
5419
|
+
draft_json TEXT NOT NULL,
|
|
5420
|
+
updated_at TEXT NOT NULL
|
|
5421
|
+
) STRICT;
|
|
5381
5422
|
`;
|
|
5382
5423
|
var DshMetadataStore = class {
|
|
5383
5424
|
database;
|
|
@@ -5407,12 +5448,22 @@ var DshMetadataStore = class {
|
|
|
5407
5448
|
}
|
|
5408
5449
|
this.setSetting("blockDecayLambda", value);
|
|
5409
5450
|
}
|
|
5451
|
+
lastFeedbackPromptAt() {
|
|
5452
|
+
const row = this.database.prepare("SELECT value FROM stratagate_dsh_settings WHERE key = 'lastFeedbackPromptAt'").get();
|
|
5453
|
+
return row?.value ?? null;
|
|
5454
|
+
}
|
|
5455
|
+
setLastFeedbackPromptAt(value) {
|
|
5456
|
+
this.setSettingValue("lastFeedbackPromptAt", value);
|
|
5457
|
+
}
|
|
5410
5458
|
setSetting(key, value) {
|
|
5459
|
+
this.setSettingValue(key, String(value));
|
|
5460
|
+
}
|
|
5461
|
+
setSettingValue(key, value) {
|
|
5411
5462
|
this.database.prepare(`
|
|
5412
5463
|
INSERT INTO stratagate_dsh_settings (key, value, updated_at)
|
|
5413
5464
|
VALUES (?, ?, ?)
|
|
5414
5465
|
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
5415
|
-
`).run(key,
|
|
5466
|
+
`).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
|
|
5416
5467
|
}
|
|
5417
5468
|
workspaceName(namespace) {
|
|
5418
5469
|
const row = this.database.prepare("SELECT display_name FROM stratagate_dsh_workspaces WHERE namespace = ?").get(namespace);
|
|
@@ -5427,6 +5478,22 @@ var DshMetadataStore = class {
|
|
|
5427
5478
|
ON CONFLICT (namespace) DO UPDATE SET display_name = excluded.display_name, updated_at = excluded.updated_at
|
|
5428
5479
|
`).run(namespace, name2, (/* @__PURE__ */ new Date()).toISOString());
|
|
5429
5480
|
}
|
|
5481
|
+
feedbackDraft(namespace) {
|
|
5482
|
+
const row = this.database.prepare("SELECT draft_json FROM stratagate_dsh_feedback_drafts WHERE namespace = ?").get(namespace);
|
|
5483
|
+
if (!row) return null;
|
|
5484
|
+
try {
|
|
5485
|
+
return JSON.parse(row.draft_json);
|
|
5486
|
+
} catch {
|
|
5487
|
+
return null;
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
setFeedbackDraft(namespace, draft) {
|
|
5491
|
+
this.database.prepare(`
|
|
5492
|
+
INSERT INTO stratagate_dsh_feedback_drafts (namespace, draft_json, updated_at)
|
|
5493
|
+
VALUES (?, ?, ?)
|
|
5494
|
+
ON CONFLICT (namespace) DO UPDATE SET draft_json = excluded.draft_json, updated_at = excluded.updated_at
|
|
5495
|
+
`).run(namespace, JSON.stringify(draft), (/* @__PURE__ */ new Date()).toISOString());
|
|
5496
|
+
}
|
|
5430
5497
|
close() {
|
|
5431
5498
|
this.database.close();
|
|
5432
5499
|
}
|
|
@@ -5437,6 +5504,7 @@ var AUTO_EVENT_LIMIT = 4;
|
|
|
5437
5504
|
var AUTO_ELEMENT_LIMIT = 4;
|
|
5438
5505
|
var AUTO_MEMORY_TOKEN_BUDGET = 900;
|
|
5439
5506
|
var COMPACTION_SOURCE_PLUGIN = "stratagate-memory";
|
|
5507
|
+
var FEEDBACK_PROMPT_COOLDOWN_MS = 5 * 24 * 60 * 60 * 1e3;
|
|
5440
5508
|
function projectKey(cwd) {
|
|
5441
5509
|
const canonical = resolve(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
|
|
5442
5510
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
@@ -5445,14 +5513,43 @@ function workspaceDisplayName(cwd) {
|
|
|
5445
5513
|
const canonical = (cwd ?? process.cwd()).replace(/[\\/]+$/, "");
|
|
5446
5514
|
return canonical.split(/[\\/]/).at(-1) || "\u5F53\u524D\u5DE5\u4F5C\u533A";
|
|
5447
5515
|
}
|
|
5516
|
+
function feedbackText(value, maximum) {
|
|
5517
|
+
return typeof value === "string" ? value.trim().slice(0, maximum) : "";
|
|
5518
|
+
}
|
|
5519
|
+
function normalizeFeedbackDraft(input, previous) {
|
|
5520
|
+
const has = (key) => Object.prototype.hasOwnProperty.call(input, key);
|
|
5521
|
+
const replacesStructuredBody = has("bodyMarkdown");
|
|
5522
|
+
const reproduction = replacesStructuredBody ? [] : has("reproduction") ? (Array.isArray(input.reproduction) ? input.reproduction : []).map((value) => feedbackText(value, 2e3)).filter(Boolean).slice(0, 20) : previous?.reproduction ?? [];
|
|
5523
|
+
const bodyMarkdown = has("bodyMarkdown") ? feedbackText(input.bodyMarkdown, 5e4) : previous?.bodyMarkdown;
|
|
5524
|
+
return {
|
|
5525
|
+
title: has("title") ? feedbackText(input.title, 240) : previous?.title ?? "",
|
|
5526
|
+
description: replacesStructuredBody ? "" : has("description") ? feedbackText(input.description, 2e4) : previous?.description ?? "",
|
|
5527
|
+
reproduction,
|
|
5528
|
+
expected: replacesStructuredBody ? "" : has("expected") ? feedbackText(input.expected, 1e4) : previous?.expected ?? "",
|
|
5529
|
+
actual: replacesStructuredBody ? "" : has("actual") ? feedbackText(input.actual, 1e4) : previous?.actual ?? "",
|
|
5530
|
+
errorContext: replacesStructuredBody ? "" : has("errorContext") ? feedbackText(input.errorContext, 2e4) : previous?.errorContext ?? "",
|
|
5531
|
+
...bodyMarkdown ? { bodyMarkdown } : {},
|
|
5532
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
function feedbackDraftUrl(namespace, origin) {
|
|
5536
|
+
const params = new URLSearchParams({
|
|
5537
|
+
settings: "stratagate-memory",
|
|
5538
|
+
stratagateView: "feedback",
|
|
5539
|
+
namespace
|
|
5540
|
+
});
|
|
5541
|
+
const route = `/?${params.toString()}`;
|
|
5542
|
+
return origin ? new URL(route, origin).href : route;
|
|
5543
|
+
}
|
|
5448
5544
|
var StrataGateRuntime = class {
|
|
5449
5545
|
constructor(config, models, onIngestError = () => {
|
|
5450
5546
|
}, flushNativeSession = async () => {
|
|
5451
|
-
}) {
|
|
5547
|
+
}, feedbackOrigin = () => void 0) {
|
|
5452
5548
|
this.config = config;
|
|
5453
5549
|
this.models = models;
|
|
5454
5550
|
this.onIngestError = onIngestError;
|
|
5455
5551
|
this.flushNativeSession = flushNativeSession;
|
|
5552
|
+
this.feedbackOrigin = feedbackOrigin;
|
|
5456
5553
|
this.blockTurnSize = config.blockTurnSize;
|
|
5457
5554
|
this.blockDecayLambda = config.blockDecayLambda;
|
|
5458
5555
|
}
|
|
@@ -5460,6 +5557,7 @@ var StrataGateRuntime = class {
|
|
|
5460
5557
|
models;
|
|
5461
5558
|
onIngestError;
|
|
5462
5559
|
flushNativeSession;
|
|
5560
|
+
feedbackOrigin;
|
|
5463
5561
|
folder = new TurnFolder();
|
|
5464
5562
|
spaces = /* @__PURE__ */ new Map();
|
|
5465
5563
|
batches = /* @__PURE__ */ new Map();
|
|
@@ -5470,6 +5568,8 @@ var StrataGateRuntime = class {
|
|
|
5470
5568
|
derivationRuns = /* @__PURE__ */ new Map();
|
|
5471
5569
|
adminSnapshotCache = /* @__PURE__ */ new Map();
|
|
5472
5570
|
externalImportRuns = /* @__PURE__ */ new Map();
|
|
5571
|
+
feedbackDrafts = /* @__PURE__ */ new Map();
|
|
5572
|
+
pendingFeedbackSuggestionSessions = /* @__PURE__ */ new Set();
|
|
5473
5573
|
ingestTail = Promise.resolve();
|
|
5474
5574
|
settingsTail = Promise.resolve();
|
|
5475
5575
|
batchSequence = 0;
|
|
@@ -5477,6 +5577,7 @@ var StrataGateRuntime = class {
|
|
|
5477
5577
|
ingestError;
|
|
5478
5578
|
blockTurnSize;
|
|
5479
5579
|
blockDecayLambda;
|
|
5580
|
+
transientLastFeedbackPromptAt = null;
|
|
5480
5581
|
acceptEvent(session, event) {
|
|
5481
5582
|
if (this.closed) return;
|
|
5482
5583
|
if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
|
|
@@ -5493,6 +5594,7 @@ var StrataGateRuntime = class {
|
|
|
5493
5594
|
}
|
|
5494
5595
|
}).catch((error) => {
|
|
5495
5596
|
this.ingestError = error;
|
|
5597
|
+
this.notePluginError(session, error);
|
|
5496
5598
|
this.onIngestError(error);
|
|
5497
5599
|
});
|
|
5498
5600
|
}
|
|
@@ -5866,6 +5968,99 @@ var StrataGateRuntime = class {
|
|
|
5866
5968
|
if (this.config.namespaceMode === "session") return `${prefix}:session:${String(session.id)}`;
|
|
5867
5969
|
return `${prefix}:project:${projectKey(session.header.cwd)}`;
|
|
5868
5970
|
}
|
|
5971
|
+
async prepareFeedback(session, input) {
|
|
5972
|
+
const namespace = this.namespaceFor(session);
|
|
5973
|
+
const draft = normalizeFeedbackDraft(input);
|
|
5974
|
+
this.saveFeedbackDraft(namespace, draft);
|
|
5975
|
+
const feedbackUrl = feedbackDraftUrl(namespace, this.feedbackOrigin());
|
|
5976
|
+
return {
|
|
5977
|
+
prepared: true,
|
|
5978
|
+
draftCreated: true,
|
|
5979
|
+
submitted: false,
|
|
5980
|
+
namespace,
|
|
5981
|
+
feedbackUrl,
|
|
5982
|
+
message: `\u53CD\u9988\u8349\u7A3F\u5DF2\u7ECF\u51C6\u5907\u597D\u4E86\uFF0C\u8FD8\u6CA1\u6709\u63D0\u4EA4\u5230 GitHub\u3002
|
|
5983
|
+
|
|
5984
|
+
[\u6253\u5F00\u53CD\u9988\u8349\u7A3F](${feedbackUrl})`
|
|
5985
|
+
};
|
|
5986
|
+
}
|
|
5987
|
+
adminFeedbackDraft(namespace) {
|
|
5988
|
+
const key = namespace.trim();
|
|
5989
|
+
if (!key) throw new TypeError("StrataGate feedback namespace must not be empty");
|
|
5990
|
+
return { namespace: key, draft: this.loadFeedbackDraft(key) };
|
|
5991
|
+
}
|
|
5992
|
+
adminSaveFeedbackDraft(namespace, input) {
|
|
5993
|
+
const key = namespace.trim();
|
|
5994
|
+
if (!key) throw new TypeError("StrataGate feedback namespace must not be empty");
|
|
5995
|
+
const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(key));
|
|
5996
|
+
this.saveFeedbackDraft(key, draft);
|
|
5997
|
+
return { namespace: key, draft };
|
|
5998
|
+
}
|
|
5999
|
+
notePluginError(session, _error) {
|
|
6000
|
+
if (!this.closed) this.pendingFeedbackSuggestionSessions.add(String(session.id));
|
|
6001
|
+
}
|
|
6002
|
+
takeFeedbackSuggestion(session, now = Date.now()) {
|
|
6003
|
+
const sessionId = String(session.id);
|
|
6004
|
+
if (!this.pendingFeedbackSuggestionSessions.delete(sessionId)) return "";
|
|
6005
|
+
const last = this.lastFeedbackPromptAt();
|
|
6006
|
+
if (last && Number.isFinite(Date.parse(last)) && now - Date.parse(last) < FEEDBACK_PROMPT_COOLDOWN_MS) return "";
|
|
6007
|
+
const promptedAt = new Date(now).toISOString();
|
|
6008
|
+
this.transientLastFeedbackPromptAt = promptedAt;
|
|
6009
|
+
try {
|
|
6010
|
+
if (this.config.database !== ":memory:") {
|
|
6011
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
6012
|
+
try {
|
|
6013
|
+
metadata.setLastFeedbackPromptAt(promptedAt);
|
|
6014
|
+
} finally {
|
|
6015
|
+
metadata.close();
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
} catch (error) {
|
|
6019
|
+
this.onIngestError(error);
|
|
6020
|
+
}
|
|
6021
|
+
return "StrataGate detected a plugin error. Briefly ask whether the user wants help preparing a GitHub Issue report. If they agree, use only facts from the current conversation and call feedback_prepare. Do not invent missing details or submit anything to GitHub.";
|
|
6022
|
+
}
|
|
6023
|
+
lastFeedbackPromptAt() {
|
|
6024
|
+
if (this.transientLastFeedbackPromptAt) return this.transientLastFeedbackPromptAt;
|
|
6025
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
6026
|
+
try {
|
|
6027
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
6028
|
+
try {
|
|
6029
|
+
this.transientLastFeedbackPromptAt = metadata.lastFeedbackPromptAt();
|
|
6030
|
+
return this.transientLastFeedbackPromptAt;
|
|
6031
|
+
} finally {
|
|
6032
|
+
metadata.close();
|
|
6033
|
+
}
|
|
6034
|
+
} catch (error) {
|
|
6035
|
+
this.onIngestError(error);
|
|
6036
|
+
return null;
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
loadFeedbackDraft(namespace) {
|
|
6040
|
+
const cached = this.feedbackDrafts.get(namespace);
|
|
6041
|
+
if (cached) return structuredClone(cached);
|
|
6042
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
6043
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
6044
|
+
try {
|
|
6045
|
+
const value = metadata.feedbackDraft(namespace);
|
|
6046
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
6047
|
+
const draft = normalizeFeedbackDraft(value);
|
|
6048
|
+
this.feedbackDrafts.set(namespace, draft);
|
|
6049
|
+
return structuredClone(draft);
|
|
6050
|
+
} finally {
|
|
6051
|
+
metadata.close();
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
saveFeedbackDraft(namespace, draft) {
|
|
6055
|
+
this.feedbackDrafts.set(namespace, structuredClone(draft));
|
|
6056
|
+
if (this.config.database === ":memory:") return;
|
|
6057
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
6058
|
+
try {
|
|
6059
|
+
metadata.setFeedbackDraft(namespace, draft);
|
|
6060
|
+
} finally {
|
|
6061
|
+
metadata.close();
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
5869
6064
|
async adminNamespaces() {
|
|
5870
6065
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
5871
6066
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
@@ -6403,8 +6598,10 @@ var StrataGateRuntime = class {
|
|
|
6403
6598
|
const timer = setTimeout(() => {
|
|
6404
6599
|
this.derivationTimers.delete(key);
|
|
6405
6600
|
if (this.closed) return;
|
|
6601
|
+
const failedBefore = this.failedCoreJobs(memory);
|
|
6406
6602
|
const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
|
|
6407
6603
|
await this.persistSuccessfulResponses(memory);
|
|
6604
|
+
this.noteNewCoreJobFailures(session, failedBefore, memory);
|
|
6408
6605
|
const contexts = memory.getBlockContext(threadId);
|
|
6409
6606
|
for (const block of resumed.readyBlocks) {
|
|
6410
6607
|
if (block.threadId !== threadId) continue;
|
|
@@ -6415,6 +6612,7 @@ var StrataGateRuntime = class {
|
|
|
6415
6612
|
const changed = this.syncDecayedBlockSurface(session, contexts);
|
|
6416
6613
|
if (resumed.readyBlocks.length > 0 || changed) await this.flushNativeSession(session);
|
|
6417
6614
|
}).catch((error) => {
|
|
6615
|
+
this.notePluginError(session, error);
|
|
6418
6616
|
this.onIngestError(error);
|
|
6419
6617
|
}).finally(() => {
|
|
6420
6618
|
this.derivationRuns.delete(key);
|
|
@@ -6434,18 +6632,32 @@ var StrataGateRuntime = class {
|
|
|
6434
6632
|
const timer = setTimeout(() => {
|
|
6435
6633
|
this.migrationTimers.delete(namespace);
|
|
6436
6634
|
if (this.closed) return;
|
|
6635
|
+
const failedBefore = this.failedCoreJobs(memory);
|
|
6437
6636
|
const completedBefore = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
|
|
6438
6637
|
void this.models.run(session, () => memory.resumePendingWork()).then(async () => {
|
|
6439
6638
|
await this.persistSuccessfulResponses(memory);
|
|
6639
|
+
this.noteNewCoreJobFailures(session, failedBefore, memory);
|
|
6440
6640
|
const completedAfter = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
|
|
6441
6641
|
if (completedAfter > completedBefore) this.scheduleGraphMigration(session, memory);
|
|
6442
6642
|
}).catch((error) => {
|
|
6643
|
+
this.notePluginError(session, error);
|
|
6443
6644
|
this.onIngestError(error);
|
|
6444
6645
|
});
|
|
6445
6646
|
}, 1500);
|
|
6446
6647
|
timer.unref?.();
|
|
6447
6648
|
this.migrationTimers.set(namespace, timer);
|
|
6448
6649
|
}
|
|
6650
|
+
failedCoreJobs(memory) {
|
|
6651
|
+
const fingerprint = (kind, id, job) => `${kind}:${id}:${job.attempts}:${job.updatedAt}:${job.lastError ?? ""}`;
|
|
6652
|
+
return /* @__PURE__ */ new Set([
|
|
6653
|
+
...memory.listSummaryJobs().filter(({ status }) => status === "failed").map((job) => fingerprint("summary", job.blockId, job)),
|
|
6654
|
+
...memory.listExtractionJobs().filter(({ status }) => status === "failed").map((job) => fingerprint("extraction", job.blockId, job)),
|
|
6655
|
+
...memory.listGraphProjectionJobs().filter(({ status }) => status === "failed").map((job) => fingerprint("graph", job.id, job))
|
|
6656
|
+
]);
|
|
6657
|
+
}
|
|
6658
|
+
noteNewCoreJobFailures(session, before, memory) {
|
|
6659
|
+
if ([...this.failedCoreJobs(memory)].some((failure) => !before.has(failure))) this.notePluginError(session);
|
|
6660
|
+
}
|
|
6449
6661
|
async openAdminMemory(namespace) {
|
|
6450
6662
|
const active = this.spaces.get(namespace);
|
|
6451
6663
|
if (active) return { memory: await active, owned: false };
|
|
@@ -6810,23 +7022,6 @@ KnowledgeGraph:
|
|
|
6810
7022
|
if (eventCount === 0 && nodeCount === 0) lines.push("(no activated memory)");
|
|
6811
7023
|
return lines.join("\n");
|
|
6812
7024
|
}
|
|
6813
|
-
function estimateTokens(value) {
|
|
6814
|
-
let tokens = 0;
|
|
6815
|
-
let asciiRun = 0;
|
|
6816
|
-
const flushAscii = () => {
|
|
6817
|
-
if (asciiRun > 0) tokens += Math.ceil(asciiRun / 4);
|
|
6818
|
-
asciiRun = 0;
|
|
6819
|
-
};
|
|
6820
|
-
for (const character of value) {
|
|
6821
|
-
if (character.codePointAt(0) <= 127) asciiRun += 1;
|
|
6822
|
-
else {
|
|
6823
|
-
flushAscii();
|
|
6824
|
-
tokens += 1;
|
|
6825
|
-
}
|
|
6826
|
-
}
|
|
6827
|
-
flushAscii();
|
|
6828
|
-
return tokens;
|
|
6829
|
-
}
|
|
6830
7025
|
function activeTurn(session) {
|
|
6831
7026
|
for (let index = session.events.length - 1; index >= 0; index -= 1) {
|
|
6832
7027
|
const event = session.events[index];
|
|
@@ -6849,6 +7044,27 @@ function sessionOf(exec) {
|
|
|
6849
7044
|
return exec.agent.session;
|
|
6850
7045
|
}
|
|
6851
7046
|
function registerMemoryTools(ctx, runtime) {
|
|
7047
|
+
ctx.tools.register(defineTool({
|
|
7048
|
+
name: "feedback_prepare",
|
|
7049
|
+
description: 'Prepare a local StrataGate feedback draft from facts known in the current conversation. Unknown fields must stay empty. Never invent versions, logs, Block counts, or diagnostics, and never submit anything to GitHub. After success, respond briefly that the draft is local and not submitted, then render feedbackUrl as a Markdown link labeled "\u6253\u5F00\u53CD\u9988\u8349\u7A3F". Do not print the draft fields or an Issue-content table, and do not direct the user through Settings manually.',
|
|
7050
|
+
parameters: {
|
|
7051
|
+
title: { type: "string" },
|
|
7052
|
+
description: { type: "string" },
|
|
7053
|
+
reproduction: { type: "array", items: { type: "string" } },
|
|
7054
|
+
expected: { type: "string" },
|
|
7055
|
+
actual: { type: "string" },
|
|
7056
|
+
error_context: { type: "string" }
|
|
7057
|
+
},
|
|
7058
|
+
output: jsonOutput,
|
|
7059
|
+
execute: async (args, exec) => runtime.prepareFeedback(sessionOf(exec), {
|
|
7060
|
+
...args.title !== void 0 ? { title: args.title } : {},
|
|
7061
|
+
...args.description !== void 0 ? { description: args.description } : {},
|
|
7062
|
+
...args.reproduction !== void 0 ? { reproduction: args.reproduction } : {},
|
|
7063
|
+
...args.expected !== void 0 ? { expected: args.expected } : {},
|
|
7064
|
+
...args.actual !== void 0 ? { actual: args.actual } : {},
|
|
7065
|
+
...args.error_context !== void 0 ? { errorContext: args.error_context } : {}
|
|
7066
|
+
})
|
|
7067
|
+
}));
|
|
6852
7068
|
ctx.tools.register(defineTool({
|
|
6853
7069
|
name: "memory_search_events",
|
|
6854
7070
|
description: "Search durable StrataGate event memories. Returns a compact batch of event cards (id, title, summary, time, and evidence refs); call memory_expand_event for narrative/quotes/source messages. rankScore is BM25/RRF ordering only, never confidence or factual accuracy. Pass batchId to memory_assess before relying on evidence.",
|
|
@@ -7099,7 +7315,7 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
|
|
|
7099
7315
|
}
|
|
7100
7316
|
|
|
7101
7317
|
// src/web.ts
|
|
7102
|
-
var STRATAGATE_DSH_VERSION = "0.2.
|
|
7318
|
+
var STRATAGATE_DSH_VERSION = "0.2.52";
|
|
7103
7319
|
var LEGACY_THREAD_ID = "__legacy__";
|
|
7104
7320
|
var nodeRequire = createRequire(import.meta.url);
|
|
7105
7321
|
function installedPackageVersion(names) {
|
|
@@ -7164,11 +7380,12 @@ function withLayerMetrics(layers) {
|
|
|
7164
7380
|
});
|
|
7165
7381
|
}
|
|
7166
7382
|
function blockLayers(block) {
|
|
7383
|
+
const deterministic = deterministicBlockLayers(block.l5Raw);
|
|
7167
7384
|
if (block.processingStatus !== "ready" || !block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints) {
|
|
7168
7385
|
return withLayerMetrics([
|
|
7169
|
-
{ level: 3, content:
|
|
7170
|
-
{ level: 4, content:
|
|
7171
|
-
{ level: 5, content: block.l5Raw
|
|
7386
|
+
{ level: 3, content: deterministic.l3Condensed },
|
|
7387
|
+
{ level: 4, content: deterministic.l4Readable },
|
|
7388
|
+
{ level: 5, content: formatRawTranscript(block.l5Raw) }
|
|
7172
7389
|
]);
|
|
7173
7390
|
}
|
|
7174
7391
|
return withLayerMetrics([
|
|
@@ -7176,9 +7393,9 @@ function blockLayers(block) {
|
|
|
7176
7393
|
Tags: ${block.l0Tags.join(", ") || "none"}` },
|
|
7177
7394
|
{ level: 1, content: block.l1Summary || block.l0Title },
|
|
7178
7395
|
{ level: 2, content: block.l2Keypoints.map((point) => `- ${point}`).join("\n") || block.l1Summary || block.l0Title },
|
|
7179
|
-
{ level: 3, content:
|
|
7180
|
-
{ level: 4, content:
|
|
7181
|
-
{ level: 5, content: block.l5Raw
|
|
7396
|
+
{ level: 3, content: deterministic.l3Condensed || block.l2Keypoints.join("\n") || block.l1Summary },
|
|
7397
|
+
{ level: 4, content: deterministic.l4Readable || deterministic.l3Condensed },
|
|
7398
|
+
{ level: 5, content: formatRawTranscript(block.l5Raw) }
|
|
7182
7399
|
]);
|
|
7183
7400
|
}
|
|
7184
7401
|
function eventSummary(event) {
|
|
@@ -7333,6 +7550,49 @@ async function updateSettings(runtime, url) {
|
|
|
7333
7550
|
if (lambda !== void 0) result.blockDecayLambda = await runtime.adminSetBlockDecayLambda(lambda);
|
|
7334
7551
|
return result;
|
|
7335
7552
|
}
|
|
7553
|
+
async function feedback(runtime, req, url) {
|
|
7554
|
+
if (req.method === "GET") {
|
|
7555
|
+
const namespace2 = url.searchParams.get("namespace")?.trim() ?? "";
|
|
7556
|
+
if (!namespace2) throw new AdminHttpError(400, "namespace is required");
|
|
7557
|
+
return runtime.adminFeedbackDraft(namespace2);
|
|
7558
|
+
}
|
|
7559
|
+
if (req.method !== "PUT") throw new AdminHttpError(405, "StrataGate feedback requires GET or PUT");
|
|
7560
|
+
let suppliedBody = req.body;
|
|
7561
|
+
if (suppliedBody === void 0 && typeof req[Symbol.asyncIterator] === "function") {
|
|
7562
|
+
const chunks = [];
|
|
7563
|
+
let size = 0;
|
|
7564
|
+
for await (const chunk of req) {
|
|
7565
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
7566
|
+
size += value.length;
|
|
7567
|
+
if (size > 128 * 1024) throw new AdminHttpError(413, "feedback draft cannot exceed 128 KB");
|
|
7568
|
+
chunks.push(value);
|
|
7569
|
+
}
|
|
7570
|
+
suppliedBody = Buffer.concat(chunks).toString("utf8");
|
|
7571
|
+
}
|
|
7572
|
+
let body;
|
|
7573
|
+
if (typeof suppliedBody === "string") {
|
|
7574
|
+
try {
|
|
7575
|
+
body = JSON.parse(suppliedBody);
|
|
7576
|
+
} catch {
|
|
7577
|
+
throw new AdminHttpError(400, "feedback draft must be valid JSON");
|
|
7578
|
+
}
|
|
7579
|
+
} else if (suppliedBody && typeof suppliedBody === "object" && !Array.isArray(suppliedBody)) {
|
|
7580
|
+
body = suppliedBody;
|
|
7581
|
+
} else {
|
|
7582
|
+
throw new AdminHttpError(400, "feedback request requires a JSON body");
|
|
7583
|
+
}
|
|
7584
|
+
const namespace = typeof body.namespace === "string" ? body.namespace.trim() : "";
|
|
7585
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
7586
|
+
const draft = {};
|
|
7587
|
+
if (typeof body.title === "string") draft.title = body.title;
|
|
7588
|
+
if (typeof body.description === "string") draft.description = body.description;
|
|
7589
|
+
if (Array.isArray(body.reproduction)) draft.reproduction = body.reproduction.filter((value) => typeof value === "string");
|
|
7590
|
+
if (typeof body.expected === "string") draft.expected = body.expected;
|
|
7591
|
+
if (typeof body.actual === "string") draft.actual = body.actual;
|
|
7592
|
+
if (typeof body.errorContext === "string") draft.errorContext = body.errorContext;
|
|
7593
|
+
if (typeof body.bodyMarkdown === "string") draft.bodyMarkdown = body.bodyMarkdown;
|
|
7594
|
+
return runtime.adminSaveFeedbackDraft(namespace, draft);
|
|
7595
|
+
}
|
|
7336
7596
|
async function importExternalMemory(runtime, req) {
|
|
7337
7597
|
let suppliedBody = req.body;
|
|
7338
7598
|
if (suppliedBody === void 0 && typeof req[Symbol.asyncIterator] === "function") {
|
|
@@ -7503,7 +7763,7 @@ function virtualBlockLayers(block) {
|
|
|
7503
7763
|
{ level: 2, content: keypoints.map((point) => `\u2022 ${point}`).join("\n") || summary || title },
|
|
7504
7764
|
{ level: 3, content: deterministic.l3Condensed },
|
|
7505
7765
|
{ level: 4, content: deterministic.l4Readable },
|
|
7506
|
-
{ level: 5, content: block.messages
|
|
7766
|
+
{ level: 5, content: formatRawTranscript(block.messages) }
|
|
7507
7767
|
]);
|
|
7508
7768
|
}
|
|
7509
7769
|
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
@@ -7857,7 +8117,9 @@ async function handleAdminRequest(runtime, req, res) {
|
|
|
7857
8117
|
try {
|
|
7858
8118
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
7859
8119
|
const path = url.pathname.replace(/\/$/, "");
|
|
7860
|
-
if (path === "/api/stratagate/
|
|
8120
|
+
if (path === "/api/stratagate/feedback") {
|
|
8121
|
+
sendJson(res, 200, await feedback(runtime, req, url));
|
|
8122
|
+
} else if (path === "/api/stratagate/settings") {
|
|
7861
8123
|
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate settings require PATCH");
|
|
7862
8124
|
sendJson(res, 200, await updateSettings(runtime, url));
|
|
7863
8125
|
} else if (path === "/api/stratagate/blocks/expand") {
|
|
@@ -7915,6 +8177,10 @@ StrataGate provides durable, evidence-gated memory through memory_* tools.
|
|
|
7915
8177
|
function renderError(error) {
|
|
7916
8178
|
return error instanceof Error ? error.message : String(error);
|
|
7917
8179
|
}
|
|
8180
|
+
function feedbackWebOrigin(ctx) {
|
|
8181
|
+
const port = ctx.get("webServer")?.port;
|
|
8182
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 && port <= 65535 ? `http://127.0.0.1:${String(port)}` : void 0;
|
|
8183
|
+
}
|
|
7918
8184
|
async function apply(ctx, config) {
|
|
7919
8185
|
const resolved = resolveConfig(config);
|
|
7920
8186
|
await mkdir(dirname(resolved.database), { recursive: true });
|
|
@@ -7923,23 +8189,24 @@ async function apply(ctx, config) {
|
|
|
7923
8189
|
ctx.logger.error(`stratagate-memory ingestion failed: ${renderError(error)}`);
|
|
7924
8190
|
}, async (session) => {
|
|
7925
8191
|
await ctx.sessions.flush(session);
|
|
7926
|
-
});
|
|
8192
|
+
}, () => feedbackWebOrigin(ctx));
|
|
7927
8193
|
await runtime.syncConfiguredSettings();
|
|
7928
8194
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
7929
8195
|
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
7930
8196
|
const assembled = await next();
|
|
7931
8197
|
const session = context.agent?.session;
|
|
7932
8198
|
if (!session) return assembled;
|
|
8199
|
+
const contexts = [...assembled.contexts];
|
|
7933
8200
|
try {
|
|
7934
8201
|
const text3 = await runtime.buildAutoContext(session);
|
|
7935
|
-
|
|
7936
|
-
...assembled,
|
|
7937
|
-
contexts: [...assembled.contexts, { name: "stratagate:auto-memory", text: text3 }]
|
|
7938
|
-
};
|
|
8202
|
+
contexts.push({ name: "stratagate:auto-memory", text: text3 });
|
|
7939
8203
|
} catch (error) {
|
|
7940
8204
|
ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
|
|
7941
|
-
|
|
8205
|
+
runtime.notePluginError(session, error);
|
|
7942
8206
|
}
|
|
8207
|
+
const feedbackSuggestion = runtime.takeFeedbackSuggestion(session);
|
|
8208
|
+
if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
|
|
8209
|
+
return { ...assembled, contexts };
|
|
7943
8210
|
});
|
|
7944
8211
|
ctx.on("agent/turn-stopping", ({ agent }) => {
|
|
7945
8212
|
if (!runtime.needsRecordUse(agent.session)) return;
|
|
@@ -7952,6 +8219,12 @@ async function apply(ctx, config) {
|
|
|
7952
8219
|
}));
|
|
7953
8220
|
});
|
|
7954
8221
|
registerMemoryTools(ctx, runtime);
|
|
8222
|
+
ctx.on("tools/result", (exec, result) => {
|
|
8223
|
+
if (!result.isError || !exec.agent) return;
|
|
8224
|
+
if (exec.name === "feedback_prepare" || exec.name.startsWith("memory_")) {
|
|
8225
|
+
runtime.notePluginError(exec.agent.session, result.error.message);
|
|
8226
|
+
}
|
|
8227
|
+
});
|
|
7955
8228
|
const disposeAdminRoutes = registerAdminRoutes(ctx, runtime);
|
|
7956
8229
|
ctx.on("session/event", (session, event) => runtime.acceptEvent(session, event));
|
|
7957
8230
|
ctx.logger.info(`stratagate-memory ready (${resolved.namespaceMode} namespaces, ${resolved.database})`);
|