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/dist/index.js CHANGED
@@ -179,7 +179,10 @@ function summarizeToolJson(value) {
179
179
  }
180
180
  }
181
181
  function removeStandaloneFillers(paragraph) {
182
- return paragraph.split(/(?<=[。!?!?])|\n+/u).map((piece) => piece.trim()).filter((piece) => piece && !isFillerSentence(piece)).join("\n");
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: condenseTranscript(messages),
252
- l4Readable: formatReadableTranscript(messages),
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
- if (level === 3) return block.l3Condensed;
2667
- if (level === 4) return block.l4Readable;
2668
- return block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n");
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, String(value), (/* @__PURE__ */ new Date()).toISOString());
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.48";
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: block.l3Condensed },
7170
- { level: 4, content: block.l4Readable },
7171
- { level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
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: block.l3Condensed || block.l2Keypoints.join("\n") || block.l1Summary },
7180
- { level: 4, content: block.l4Readable || block.l3Condensed },
7181
- { level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
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.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
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/settings") {
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
- return {
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
- return assembled;
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})`);