stratagate-dsh 0.3.14 → 0.3.16

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/plugin.js CHANGED
@@ -5501,6 +5501,7 @@ function agentEventCandidate(content, category, now, threadId) {
5501
5501
  };
5502
5502
  }
5503
5503
  var DERIVATION_BACKOFF_MS = 1e3;
5504
+ var GRAPH_PROJECTION_BATCH_SIZE = 8;
5504
5505
  function restrictExtractionRelations(temporal, allowedIds) {
5505
5506
  const result = { ...temporal };
5506
5507
  if (typeof result.sameEventId !== "string" || !allowedIds.has(result.sameEventId)) {
@@ -6669,6 +6670,18 @@ var StrataGate = class _StrataGate {
6669
6670
  return this.commitMutation(() => {
6670
6671
  const job = [...this.graphProjectionJobs.values()].filter(eligible).sort((left, right) => Number(left.status !== "pending") - Number(right.status !== "pending") || right.priority - left.priority || left.createdAt.localeCompare(right.createdAt))[0];
6671
6672
  if (!job) return null;
6673
+ const sourceEventIds = job.sourceEventIds;
6674
+ for (let index = GRAPH_PROJECTION_BATCH_SIZE; index < sourceEventIds.length; index += GRAPH_PROJECTION_BATCH_SIZE) {
6675
+ const id = this.graphIdFactory("gproj");
6676
+ this.graphProjectionJobs.set(id, {
6677
+ ...job,
6678
+ id,
6679
+ sourceEventIds: sourceEventIds.slice(index, index + GRAPH_PROJECTION_BATCH_SIZE),
6680
+ nodeIds: [],
6681
+ edgeIds: []
6682
+ });
6683
+ }
6684
+ job.sourceEventIds = sourceEventIds.slice(0, GRAPH_PROJECTION_BATCH_SIZE);
6672
6685
  const events = job.sourceEventIds.flatMap((id) => {
6673
6686
  const event = this.findEvent(id);
6674
6687
  return event ? [event] : [];
@@ -7554,30 +7567,30 @@ var StrataGate = class _StrataGate {
7554
7567
  return job;
7555
7568
  }
7556
7569
  queueGraphProjection(sourceEventIds, priority) {
7557
- if (!this.graphProjector) return null;
7558
- const completed = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status === "completed").flatMap((job2) => job2.sourceEventIds));
7559
- const queued = new Set([...this.graphProjectionJobs.values()].filter((job2) => job2.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job2.status !== "completed").flatMap((job2) => job2.sourceEventIds));
7570
+ if (!this.graphProjector) return;
7571
+ const completed = new Set([...this.graphProjectionJobs.values()].filter((job) => job.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job.status === "completed").flatMap((job) => job.sourceEventIds));
7572
+ const queued = new Set([...this.graphProjectionJobs.values()].filter((job) => job.projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION && job.status !== "completed").flatMap((job) => job.sourceEventIds));
7560
7573
  const ids2 = [...new Set(sourceEventIds.filter((id) => this.findEvent(id) && !completed.has(id) && !queued.has(id)))];
7561
- if (ids2.length === 0) return null;
7562
7574
  const now = toUtc8Iso(this.now());
7563
- const job = {
7564
- id: this.graphIdFactory("gproj"),
7565
- sourceEventIds: ids2,
7566
- projectorVersion: KNOWLEDGE_GRAPH_PROJECTOR_VERSION,
7567
- status: "pending",
7568
- attempts: 0,
7569
- priority,
7570
- nodeIds: [],
7571
- edgeIds: [],
7572
- nameProvenanceRecoveryVersion: 1,
7573
- reason: null,
7574
- lastError: null,
7575
- nextRetryAt: null,
7576
- createdAt: now,
7577
- updatedAt: now
7578
- };
7579
- this.graphProjectionJobs.set(job.id, job);
7580
- return job;
7575
+ for (let index = 0; index < ids2.length; index += GRAPH_PROJECTION_BATCH_SIZE) {
7576
+ const job = {
7577
+ id: this.graphIdFactory("gproj"),
7578
+ sourceEventIds: ids2.slice(index, index + GRAPH_PROJECTION_BATCH_SIZE),
7579
+ projectorVersion: KNOWLEDGE_GRAPH_PROJECTOR_VERSION,
7580
+ status: "pending",
7581
+ attempts: 0,
7582
+ priority,
7583
+ nodeIds: [],
7584
+ edgeIds: [],
7585
+ nameProvenanceRecoveryVersion: 1,
7586
+ reason: null,
7587
+ lastError: null,
7588
+ nextRetryAt: null,
7589
+ createdAt: now,
7590
+ updatedAt: now
7591
+ };
7592
+ this.graphProjectionJobs.set(job.id, job);
7593
+ }
7581
7594
  }
7582
7595
  recoverGraphNameProvenanceFailures() {
7583
7596
  for (const job of this.graphProjectionJobs.values()) {
@@ -7595,8 +7608,8 @@ var StrataGate = class _StrataGate {
7595
7608
  const score = (event) => (event.status === "active" ? 1e4 : 0) + event.weight.mentionCount * 100 + (event.scope === "project" ? 500 : 0) + (Date.parse(event.temporal.happenedStart ?? event.temporal.mentionedAt ?? event.updatedAt) || 0) / 1e12;
7596
7609
  return score(right) - score(left);
7597
7610
  });
7598
- for (let index = 0; index < candidates.length; index += 8) {
7599
- this.queueGraphProjection(candidates.slice(index, index + 8).map(({ id }) => id), candidates.length - index);
7611
+ for (let index = 0; index < candidates.length; index += GRAPH_PROJECTION_BATCH_SIZE) {
7612
+ this.queueGraphProjection(candidates.slice(index, index + GRAPH_PROJECTION_BATCH_SIZE).map(({ id }) => id), candidates.length - index);
7600
7613
  }
7601
7614
  }
7602
7615
  queueElementProjection(sourceEventIds) {
@@ -8508,6 +8521,329 @@ function parseJsonResponse(value, requiredKeys = []) {
8508
8521
  throw new ModelJsonResponseError(void 0, { response: value });
8509
8522
  }
8510
8523
 
8524
+ // src/llm.ts
8525
+ import { randomUUID as randomUUID4 } from "node:crypto";
8526
+
8527
+ // src/diagnostics.ts
8528
+ import { randomUUID as randomUUID3 } from "node:crypto";
8529
+ import { createRequire as createRequire2 } from "node:module";
8530
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
8531
+ var DAY = 864e5;
8532
+ var VERSION = (() => {
8533
+ try {
8534
+ return String(createRequire2(import.meta.url)("../package.json").version);
8535
+ } catch {
8536
+ return "unknown";
8537
+ }
8538
+ })();
8539
+ var DIAGNOSTIC_STAGES = ["summarizer", "extractor", "projector", "graphProjector", "topicProjector", "externalMemoryExtractor", "externalMemoryDecider", "profileMaintenance", "dreamingStep", "chat", "ingestion", "autoContext", "directory", "tools", "frontend", "retrieval"];
8540
+ function diagnosticText(value, limit = 1500) {
8541
+ return String(value instanceof Error ? value.message : value ?? "").slice(0, 8e3).split(/\nRaw response(?: preview| \(full\))/i)[0].replace(/\b(?:sk[-_]|gh[opasu]_|github_pat_)[A-Za-z0-9_-]{8,}\b/g, "[credential]").replace(/(["']?\b(?:api[_-]?key|token|password|passwd|secret|authorization|access[_-]?token|refresh[_-]?token)["']?\s*[:=]\s*)(["'])([\s\S]*?)\2/gi, "$1[credential]").replace(/\b(?:Bearer|Basic|Token)\s+[^\s,;}]+/gi, "[credential]").replace(/\b(?:api[_-]?key|token|password|passwd|secret|authorization)\s*[:=]\s*[^\s,;}]+/gi, "[credential]").replace(/https?:\/\/[^\s<>"']+/gi, "[url]").replace(/[A-Z]:[\\/][^\s"'<>]+/gi, "[path]").replace(/[\w.+-]+@[\w.-]+\.[a-z]{2,}/gi, "[email]").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, "").slice(0, limit).replace(/[\uD800-\uDBFF]$/, "");
8542
+ }
8543
+ function isAbortError(error) {
8544
+ return typeof error === "object" && error !== null && (error.name === "AbortError" || error.code === "ABORT_ERR");
8545
+ }
8546
+ function diagnosticCategory(error) {
8547
+ if (isAbortError(error)) return "cancelled";
8548
+ const text3 = String(error instanceof Error ? error.message : error ?? "");
8549
+ if (/timeout|timed out|超时/i.test(text3)) return "timeout";
8550
+ if (/429|rate.?limit/i.test(text3)) return "rate-limit";
8551
+ if (/401|403|unauthori|forbidden|credential/i.test(text3)) return "authentication";
8552
+ if (/token.*limit|truncat|max-tokens|context.*(window|length|limit)/i.test(text3)) return "token-limit";
8553
+ if (/json|schema|invalid|validation/i.test(text3)) return "validation";
8554
+ if (/sqlite|database|storage|EACCES|ENOSPC|write failed/i.test(text3)) return "storage";
8555
+ if (/abort|cancel/i.test(text3)) return "cancelled";
8556
+ if (/network|fetch|ECONN|ENOTFOUND/i.test(text3)) return "network";
8557
+ return "internal";
8558
+ }
8559
+ var SCHEMA2 = `
8560
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_records (
8561
+ id TEXT PRIMARY KEY, at TEXT NOT NULL, namespace TEXT NOT NULL, record_json TEXT NOT NULL
8562
+ ) STRICT;
8563
+ CREATE INDEX IF NOT EXISTS stratagate_dsh_diagnostic_scope ON stratagate_dsh_diagnostic_records(namespace, at);
8564
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_daily (
8565
+ namespace TEXT NOT NULL, day TEXT NOT NULL, stage TEXT NOT NULL, model TEXT NOT NULL,
8566
+ version TEXT NOT NULL, counters_json TEXT NOT NULL,
8567
+ PRIMARY KEY(namespace, day, stage, model, version)
8568
+ ) STRICT;
8569
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) STRICT;
8570
+ `;
8571
+ var DiagnosticsRecorder = class {
8572
+ constructor(filename) {
8573
+ this.filename = filename;
8574
+ }
8575
+ filename;
8576
+ database;
8577
+ pending = [];
8578
+ timer;
8579
+ closed = false;
8580
+ dropped = 0;
8581
+ writeFailures = 0;
8582
+ startedAt = (/* @__PURE__ */ new Date()).toISOString();
8583
+ record(scope, counters, options = {}) {
8584
+ if (this.closed) return;
8585
+ const bounded2 = Object.fromEntries(Object.entries(counters).filter(([, n]) => Number.isFinite(n) && n >= 0));
8586
+ this.pending.push({
8587
+ id: `diag_${randomUUID3()}`,
8588
+ at: (/* @__PURE__ */ new Date()).toISOString(),
8589
+ namespace: scope.namespace,
8590
+ stage: scope.stage,
8591
+ model: diagnosticText(options.model ?? "", 160),
8592
+ version: VERSION,
8593
+ counters: bounded2,
8594
+ ...scope.taskId ? { taskId: scope.taskId } : {},
8595
+ ...options.error !== void 0 ? { category: diagnosticCategory(options.error), detail: diagnosticText(options.error) } : {}
8596
+ });
8597
+ if (this.pending.length > 256) {
8598
+ this.pending.shift();
8599
+ this.dropped++;
8600
+ }
8601
+ if (!this.timer) {
8602
+ this.timer = setTimeout(() => {
8603
+ this.timer = void 0;
8604
+ this.flush();
8605
+ }, 5e3);
8606
+ this.timer.unref?.();
8607
+ }
8608
+ }
8609
+ open() {
8610
+ if (this.database) return this.database;
8611
+ const db = new DatabaseSync2(this.filename);
8612
+ try {
8613
+ db.exec("PRAGMA busy_timeout=0");
8614
+ db.exec(SCHEMA2);
8615
+ db.prepare("INSERT OR IGNORE INTO stratagate_dsh_diagnostic_meta VALUES ('startedAt', ?)").run(this.startedAt);
8616
+ this.database = db;
8617
+ return db;
8618
+ } catch (error) {
8619
+ db.close();
8620
+ throw error;
8621
+ }
8622
+ }
8623
+ flush() {
8624
+ if (!this.pending.length) return;
8625
+ try {
8626
+ const db = this.open();
8627
+ db.exec("BEGIN IMMEDIATE");
8628
+ try {
8629
+ const get = db.prepare("SELECT counters_json FROM stratagate_dsh_diagnostic_daily WHERE namespace=? AND day=? AND stage=? AND model=? AND version=?");
8630
+ const put = db.prepare("INSERT OR REPLACE INTO stratagate_dsh_diagnostic_daily VALUES (?, ?, ?, ?, ?, ?)");
8631
+ const insert = db.prepare("INSERT INTO stratagate_dsh_diagnostic_records VALUES (?, ?, ?, ?)");
8632
+ for (const item of this.pending) {
8633
+ const keys = [item.namespace, item.at.slice(0, 10), item.stage, item.model, item.version];
8634
+ const row = get.get(...keys);
8635
+ const counters = row ? JSON.parse(row.counters_json) : {};
8636
+ for (const [key, value] of Object.entries(item.counters)) counters[key] = (counters[key] ?? 0) + value;
8637
+ put.run(...keys, JSON.stringify(counters));
8638
+ insert.run(item.id, item.at, item.namespace, JSON.stringify(item));
8639
+ }
8640
+ db.prepare("DELETE FROM stratagate_dsh_diagnostic_records WHERE at < ? OR id NOT IN (SELECT id FROM stratagate_dsh_diagnostic_records ORDER BY at DESC, id DESC LIMIT 2000)").run(new Date(Date.now() - 7 * DAY).toISOString());
8641
+ db.prepare("DELETE FROM stratagate_dsh_diagnostic_daily WHERE day < ?").run(new Date(Date.now() - 90 * DAY).toISOString().slice(0, 10));
8642
+ db.exec("DELETE FROM stratagate_dsh_diagnostic_daily WHERE rowid NOT IN (SELECT rowid FROM stratagate_dsh_diagnostic_daily ORDER BY day DESC LIMIT 10000)");
8643
+ db.exec("COMMIT");
8644
+ this.pending = [];
8645
+ } catch (error) {
8646
+ db.exec("ROLLBACK");
8647
+ throw error;
8648
+ }
8649
+ } catch {
8650
+ this.writeFailures++;
8651
+ }
8652
+ }
8653
+ snapshot(namespace, options = {}) {
8654
+ this.flush();
8655
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8656
+ const since = new Date(Date.parse(today) - 6 * DAY).toISOString().slice(0, 10);
8657
+ const totals = {};
8658
+ const stages = {};
8659
+ let startedAt = this.startedAt;
8660
+ let available = false;
8661
+ let rows = [];
8662
+ let records = [];
8663
+ let totalRecords = 0;
8664
+ let corruptRollups = 0;
8665
+ let corruptRecords = 0;
8666
+ let scannedRecords = 0;
8667
+ const offset = Math.max(0, Math.min(2e3, Math.floor(options.offset ?? 0)));
8668
+ const readCounters = (text3) => {
8669
+ try {
8670
+ const value = JSON.parse(text3);
8671
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid counters");
8672
+ const entries = Object.entries(value);
8673
+ if (entries.some(([, n]) => typeof n !== "number" || !Number.isFinite(n) || n < 0)) throw new Error("Invalid counter value");
8674
+ return Object.fromEntries(entries);
8675
+ } catch {
8676
+ corruptRollups++;
8677
+ return {};
8678
+ }
8679
+ };
8680
+ try {
8681
+ const db = this.open();
8682
+ available = true;
8683
+ startedAt = db.prepare("SELECT value FROM stratagate_dsh_diagnostic_meta WHERE key='startedAt'").get().value;
8684
+ rows = db.prepare("SELECT day, stage, model, version, counters_json FROM stratagate_dsh_diagnostic_daily WHERE namespace=? AND day>=? ORDER BY day, stage, model, version LIMIT 2001").all(namespace, since);
8685
+ const where = options.id ? "namespace=? AND id=?" : "namespace=? AND at>=? AND json_extract(record_json, '$.category') IS NOT NULL";
8686
+ const values = options.id ? [namespace, options.id] : [namespace, since];
8687
+ totalRecords = Number(db.prepare(`SELECT count(*) AS count FROM stratagate_dsh_diagnostic_records WHERE ${where}`).get(...values).count);
8688
+ const raw = db.prepare(`SELECT record_json FROM stratagate_dsh_diagnostic_records WHERE ${where} ORDER BY at DESC, id DESC LIMIT 20 OFFSET ?`).all(...values, offset);
8689
+ scannedRecords = raw.length;
8690
+ for (const row of raw) {
8691
+ try {
8692
+ const item = JSON.parse(row.record_json);
8693
+ if (!item || item.namespace !== namespace || !DIAGNOSTIC_STAGES.includes(item.stage) || ["id", "at", "model", "version"].some((key) => typeof item[key] !== "string") || !item.counters || typeof item.counters !== "object" || Array.isArray(item.counters) || Object.values(item.counters).some((n) => typeof n !== "number" || !Number.isFinite(n) || n < 0)) throw new Error("Invalid diagnostic record");
8694
+ records.push({
8695
+ id: item.id,
8696
+ at: item.at,
8697
+ namespace,
8698
+ stage: item.stage,
8699
+ model: diagnosticText(item.model, 160),
8700
+ version: item.version,
8701
+ counters: item.counters,
8702
+ ...typeof item.taskId === "string" ? { taskId: item.taskId } : {},
8703
+ ...typeof item.category === "string" ? { category: diagnosticText(item.category, 80) } : {},
8704
+ ...typeof item.detail === "string" ? { detail: diagnosticText(item.detail) } : {}
8705
+ });
8706
+ } catch {
8707
+ corruptRecords++;
8708
+ }
8709
+ }
8710
+ } catch {
8711
+ available = false;
8712
+ }
8713
+ const parsedRows = rows.slice(0, 2e3).map((row) => ({ ...row, counters: readCounters(row.counters_json) }));
8714
+ for (const row of parsedRows) {
8715
+ const counters = row.counters;
8716
+ const stage = stages[row.stage] ??= {};
8717
+ for (const [key, value] of Object.entries(counters)) {
8718
+ totals[key] = (totals[key] ?? 0) + value;
8719
+ stage[key] = (stage[key] ?? 0) + value;
8720
+ }
8721
+ }
8722
+ const routes = /* @__PURE__ */ new Map();
8723
+ for (const row of parsedRows) {
8724
+ const key = JSON.stringify([row.stage, row.model, row.version]);
8725
+ const route = routes.get(key) ?? { stage: row.stage, model: row.model, version: row.version, counters: {} };
8726
+ for (const [name2, n] of Object.entries(row.counters)) route.counters[name2] = (route.counters[name2] ?? 0) + n;
8727
+ routes.set(key, route);
8728
+ }
8729
+ const rates = Object.fromEntries(Object.entries(stages).map(([stage, counts]) => [stage, {
8730
+ sampledOperations: counts.operations ?? 0,
8731
+ operationFailureRate: counts.operations ? (counts.operationsFailed ?? 0) / counts.operations : null,
8732
+ firstAttemptSuccessRate: counts.operations ? (counts.firstAttemptSucceeded ?? 0) / counts.operations : null,
8733
+ recoveredOperationRate: counts.operations ? (counts.operationsRecovered ?? 0) / counts.operations : null,
8734
+ meanOperationMs: counts.operations ? (counts.operationMs ?? 0) / counts.operations : null,
8735
+ meanModelMs: counts.modelCalls ? (counts.modelMs ?? 0) / counts.modelCalls : null
8736
+ }]));
8737
+ const chatTokens = stages.chat?.totalTokens ?? 0;
8738
+ const backgroundTokens = (totals.totalTokens ?? 0) - chatTokens;
8739
+ const totalsComplete = available && !corruptRollups && !this.dropped && totals.modelCalls && totals.totalTokenCalls === totals.modelCalls && !this.pending.length && rows.length <= 2e3;
8740
+ return {
8741
+ schemaVersion: 1,
8742
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8743
+ since,
8744
+ through: today,
8745
+ timezone: "UTC",
8746
+ startedAt,
8747
+ coverage: {
8748
+ available,
8749
+ corruptRollups,
8750
+ corruptRecords,
8751
+ processStartedAt: this.startedAt,
8752
+ pendingRecords: this.pending.length,
8753
+ droppedRecords: this.dropped,
8754
+ writeFailures: this.writeFailures,
8755
+ rollupTruncated: rows.length > 2e3,
8756
+ historicalBackfill: false,
8757
+ rawRetentionDays: 7,
8758
+ rollupRetentionDays: 90,
8759
+ totalChatCostRatio: null,
8760
+ netSavings: null,
8761
+ note: "\u7EDF\u8BA1\u4ECE\u91C7\u96C6\u542F\u7528\u540E\u5F00\u59CB\uFF1BToken \u4E3A\u9002\u914D\u5668\u62A5\u544A\u503C\uFF0C\u7F3A\u5931\u53E6\u8BA1\uFF1BmemoryInputEstimate \u4EC5\u4F30\u7B97\u5E38\u9A7B\u89C4\u5219\u3001\u5DE5\u5177\u5B9A\u4E49\u3001\u753B\u50CF\u548C\u81EA\u52A8\u4E0A\u4E0B\u6587\uFF0C\u5C1A\u672A\u5B8C\u6574\u5F52\u56E0\u5386\u53F2\u5DE5\u5177\u7ED3\u679C\u53CA\u5206\u5C42\u5BF9\u8BDD\u3002\u51C0\u6536\u76CA\u4E0E\u8D39\u7528\u5360\u6BD4\u5F85\u6D4B\u3002\u4E3B\u804A\u5929\u8986\u76D6\u5DF2\u89C2\u6D4B\u8BF7\u6C42\uFF0C\u65E0\u6CD5\u8BC1\u660E\u8D26\u5355\u5B8C\u6574\u3002"
8762
+ },
8763
+ totals,
8764
+ stages,
8765
+ rates,
8766
+ observedBackgroundTokenShare: totalsComplete && totals.totalTokens ? backgroundTokens / totals.totalTokens : null,
8767
+ definitions: "operations=\u4E00\u6B21\u8BB0\u5FC6\u6A21\u578B\u64CD\u4F5C\uFF08\u542B\u5185\u90E8\u91CD\u8BD5\uFF09\uFF1BmodelCalls=\u6D41\u5F0F\u8BF7\u6C42\u5C1D\u8BD5\uFF1Bpipeline=\u73B0\u5B58\u6301\u4E45\u4EFB\u52A1\u72B6\u6001\u3002\u540E\u53F0Token\u5360\u6BD4\u4EC5\u9488\u5BF9\u5F53\u524D\u7A7A\u95F4\u5DF2\u89C2\u6D4B\u8BF7\u6C42\uFF0C\u5E38\u9A7B\u8F93\u5165\u4F30\u7B97\u5355\u5217\uFF0C\u8D39\u7528\u4E0E\u51C0\u6536\u76CA\u672A\u6D4B\u3002",
8768
+ routes: [...routes.values()].slice(0, 40),
8769
+ omittedRoutes: Math.max(0, routes.size - 40),
8770
+ totalRecords,
8771
+ nextOffset: offset + scannedRecords < totalRecords ? offset + scannedRecords : null,
8772
+ records: records.map(({ namespace: _namespace, detail, ...record3 }) => ({ ...record3, ...options.details && detail ? { detail } : {} }))
8773
+ };
8774
+ }
8775
+ close() {
8776
+ if (this.closed) return;
8777
+ if (this.timer) clearTimeout(this.timer);
8778
+ this.timer = void 0;
8779
+ this.flush();
8780
+ this.closed = true;
8781
+ try {
8782
+ this.database?.close();
8783
+ } catch {
8784
+ }
8785
+ this.database = void 0;
8786
+ }
8787
+ };
8788
+ async function* observeModelStream(recorder, scope, options, next) {
8789
+ const start = Date.now();
8790
+ let usage;
8791
+ let finished = false;
8792
+ let failure;
8793
+ let cancelled = false;
8794
+ scope.calls = (scope.calls ?? 0) + 1;
8795
+ try {
8796
+ for await (const chunk of next()) {
8797
+ if (chunk.type === "usage") usage = chunk.usage;
8798
+ if (chunk.type === "finish") {
8799
+ finished = true;
8800
+ if (chunk.reason.kind === "error" || chunk.reason.kind === "aborted") {
8801
+ cancelled = chunk.reason.kind === "aborted";
8802
+ failure = cancelled ? Object.assign(new Error(chunk.reason.failure?.message ?? "aborted"), { name: "AbortError" }) : chunk.reason.failure.message;
8803
+ } else if (chunk.reason.kind === "max-tokens") failure = "max-tokens";
8804
+ }
8805
+ yield chunk;
8806
+ }
8807
+ } catch (error) {
8808
+ failure = error;
8809
+ cancelled = isAbortError(error);
8810
+ throw error;
8811
+ } finally {
8812
+ const valid = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
8813
+ const known = usage && valid(usage.inputTokens) && valid(usage.outputTokens);
8814
+ const counters = {
8815
+ modelCalls: 1,
8816
+ modelFailed: failure !== void 0 && !cancelled ? 1 : 0,
8817
+ modelCancelled: cancelled || !finished && failure === void 0 ? 1 : 0,
8818
+ modelMs: Date.now() - start,
8819
+ usageCalls: known ? 1 : 0,
8820
+ missingUsageCalls: known ? 0 : 1
8821
+ };
8822
+ if (scope.stage === "chat" && scope.memoryInputEstimate !== void 0) {
8823
+ counters.memoryInputEstimate = scope.memoryInputEstimate;
8824
+ counters.memoryInputEstimateCalls = 1;
8825
+ }
8826
+ if (known && usage) {
8827
+ counters.inputTokens = usage.inputTokens;
8828
+ counters.outputTokens = usage.outputTokens;
8829
+ if (valid(usage.cacheReadTokens)) {
8830
+ counters.cacheReadTokens = usage.cacheReadTokens;
8831
+ counters.cacheReadKnownCalls = 1;
8832
+ }
8833
+ if (valid(usage.cacheWriteTokens)) {
8834
+ counters.cacheWriteTokens = usage.cacheWriteTokens;
8835
+ counters.cacheWriteKnownCalls = 1;
8836
+ }
8837
+ if (valid(usage.reasoningTokens)) counters.reasoningTokens = usage.reasoningTokens;
8838
+ if (valid(usage.totalTokens) || valid(usage.cacheReadTokens) && valid(usage.cacheWriteTokens)) {
8839
+ counters.totalTokens = valid(usage.totalTokens) ? usage.totalTokens : counters.inputTokens + counters.outputTokens + counters.cacheReadTokens + counters.cacheWriteTokens;
8840
+ counters.totalTokenCalls = 1;
8841
+ }
8842
+ }
8843
+ recorder.record(scope, counters, { model: `${options.provider}/${options.model}`, ...failure !== void 0 ? { error: failure } : {} });
8844
+ }
8845
+ }
8846
+
8511
8847
  // src/llm.ts
8512
8848
  var ELEMENT_TYPES2 = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
8513
8849
  var CRITICALITIES = /* @__PURE__ */ new Set(["routine", "preference", "identity", "safety"]);
@@ -9056,6 +9392,7 @@ function parseTopicProjection(value, context) {
9056
9392
  return { topics };
9057
9393
  }
9058
9394
  var TOPIC_OUTPUT_TOKEN_BUDGET = 32768;
9395
+ var GRAPH_OUTPUT_TOKEN_BUDGET = 32768;
9059
9396
  var DshModelBridge = class {
9060
9397
  constructor(ctx, config, liveStructuredReasoningEffort) {
9061
9398
  this.ctx = ctx;
@@ -9071,6 +9408,16 @@ var DshModelBridge = class {
9071
9408
  ctx;
9072
9409
  config;
9073
9410
  liveStructuredReasoningEffort;
9411
+ diagnosticScopes = new AsyncLocalStorage();
9412
+ diagnostics;
9413
+ diagnosticNamespace = () => "@shared";
9414
+ setDiagnostics(recorder, namespace) {
9415
+ this.diagnostics = recorder;
9416
+ this.diagnosticNamespace = namespace;
9417
+ }
9418
+ currentDiagnosticScope() {
9419
+ return this.diagnosticScopes.getStore();
9420
+ }
9074
9421
  contextWindows = /* @__PURE__ */ new Map();
9075
9422
  sessions = new AsyncLocalStorage();
9076
9423
  successfulResponses = [];
@@ -9103,8 +9450,8 @@ var DshModelBridge = class {
9103
9450
  run(session, operation) {
9104
9451
  return this.sessions.run({ session, sessionId: session.id }, operation);
9105
9452
  }
9106
- runDetached(sessionId, operation, signal) {
9107
- return this.sessions.run({ sessionId, ...signal ? { signal } : {} }, operation);
9453
+ runDetached(sessionId, operation, signal, namespace) {
9454
+ return this.sessions.run({ sessionId, ...signal ? { signal } : {}, ...namespace ? { namespace } : {} }, operation);
9108
9455
  }
9109
9456
  dreamingModelOptions() {
9110
9457
  return { ...this.resolveRoute(), maxTokens: TOPIC_OUTPUT_TOKEN_BUDGET };
@@ -9385,12 +9732,47 @@ Use project scope for repository decisions, user scope for stable preferences/id
9385
9732
  return proposed;
9386
9733
  }
9387
9734
  async callStructured(kind, system, payload, validate, topicMetadata) {
9735
+ const execution = this.sessions.getStore();
9736
+ const scope = {
9737
+ namespace: execution?.namespace ?? this.diagnosticNamespace(execution?.session, String(execution?.sessionId ?? "")),
9738
+ stage: kind,
9739
+ taskId: `task_${randomUUID4()}`,
9740
+ calls: 0
9741
+ };
9742
+ const start = Date.now();
9743
+ return this.diagnosticScopes.run(scope, async () => {
9744
+ try {
9745
+ const result = await this.callStructuredInner(kind, system, payload, validate, topicMetadata);
9746
+ this.diagnostics?.record(scope, {
9747
+ operations: 1,
9748
+ operationsSucceeded: 1,
9749
+ operationsFailed: 0,
9750
+ operationsRecovered: (scope.calls ?? 0) > 1 ? 1 : 0,
9751
+ firstAttemptSucceeded: (scope.calls ?? 0) === 1 ? 1 : 0,
9752
+ retries: Math.max(0, (scope.calls ?? 0) - 1),
9753
+ operationMs: Date.now() - start
9754
+ });
9755
+ return result;
9756
+ } catch (error) {
9757
+ this.diagnostics?.record(scope, {
9758
+ operations: 1,
9759
+ operationsSucceeded: 0,
9760
+ operationsFailed: 1,
9761
+ retries: Math.max(0, (scope.calls ?? 0) - 1),
9762
+ operationMs: Date.now() - start
9763
+ }, { error });
9764
+ throw error;
9765
+ }
9766
+ });
9767
+ }
9768
+ async callStructuredInner(kind, system, payload, validate, topicMetadata) {
9388
9769
  const execution = this.sessions.getStore();
9389
9770
  if (!execution) throw new Error("StrataGate model callback ran without an execution context");
9390
9771
  const baseRoute = this.resolveRoute(execution.session);
9391
9772
  const routeKey = `${baseRoute.provider}\0${baseRoute.model}`;
9392
9773
  let useOff = await this.shouldUseOff(baseRoute);
9393
9774
  const isTopic = kind === "topicProjector";
9775
+ const isGraph = kind === "graphProjector";
9394
9776
  const topicDiagnostics = {
9395
9777
  ...topicMetadata,
9396
9778
  requestedOutputTokens: TOPIC_OUTPUT_TOKEN_BUDGET,
@@ -9417,7 +9799,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
9417
9799
  }
9418
9800
  }
9419
9801
  const message = createUserMessage({
9420
- content: [{ type: "text", text: JSON.stringify(payload) }],
9802
+ content: [{ type: "text", text: JSON.stringify(isGraph ? { ...object(payload), outputTokenBudget: GRAPH_OUTPUT_TOKEN_BUDGET } : payload) }],
9421
9803
  source: dshMessageSource()
9422
9804
  });
9423
9805
  const assembler = new BlockAssembler();
@@ -9437,7 +9819,7 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}${topicRetryFeedback}`,
9437
9819
  type: "function",
9438
9820
  function: { name: STRUCTURED_TOOLS[kind].name }
9439
9821
  },
9440
- maxTokens: kind === "dreamingStep" ? TOPIC_OUTPUT_TOKEN_BUDGET : kind === "profileMaintenance" ? Math.max(this.config.maxOutputTokens, Math.min(12e3, 512 + 2 * Array.from(JSON.stringify(payload)).length)) : isTopic ? TOPIC_OUTPUT_TOKEN_BUDGET : this.config.maxOutputTokens,
9822
+ maxTokens: kind === "dreamingStep" ? TOPIC_OUTPUT_TOKEN_BUDGET : kind === "profileMaintenance" ? Math.max(this.config.maxOutputTokens, Math.min(12e3, 512 + 2 * Array.from(JSON.stringify(payload)).length)) : isTopic ? TOPIC_OUTPUT_TOKEN_BUDGET : isGraph ? GRAPH_OUTPUT_TOKEN_BUDGET : this.config.maxOutputTokens,
9441
9823
  sessionId: execution.sessionId,
9442
9824
  purpose: "compaction"
9443
9825
  };
@@ -9449,6 +9831,18 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}${topicRetryFeedback}`,
9449
9831
  request.maxTokens = Math.min(TOPIC_OUTPUT_TOKEN_BUDGET, Math.floor(window - input - 1024));
9450
9832
  }
9451
9833
  }
9834
+ if (isGraph) {
9835
+ const input = estimateTokens(JSON.stringify({ system: request.system, tools: request.tools, messages: request.messages }));
9836
+ const window = this.contextWindows.get(routeKey);
9837
+ if (window !== void 0) {
9838
+ if (window - input - 1024 < 256) throw new Error("Graph input exceeds model context capacity; reduce the source batch");
9839
+ request.maxTokens = Math.min(GRAPH_OUTPUT_TOKEN_BUDGET, Math.floor(window - input - 1024));
9840
+ }
9841
+ request.messages = [{ ...message, content: [{ type: "text", text: JSON.stringify({
9842
+ ...object(payload),
9843
+ outputTokenBudget: request.maxTokens
9844
+ }) }] }];
9845
+ }
9452
9846
  if (isTopic) {
9453
9847
  const estimateInput = () => estimateTokens(JSON.stringify({ system: request.system, tools: request.tools, messages: request.messages }));
9454
9848
  let inputTokens = estimateInput();
@@ -9526,6 +9920,9 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}${topicRetryFeedback}`,
9526
9920
  [finish=${finish.kind}; toolCalls=${calls.length}]`;
9527
9921
  lastResponse = responseForError;
9528
9922
  try {
9923
+ if (isGraph && finish.kind === "max-tokens") {
9924
+ throw new ModelJsonResponseError("StrataGate Graph output token limit reached", { response: responseForError });
9925
+ }
9529
9926
  const expectedTool = STRUCTURED_TOOLS[kind].name;
9530
9927
  let parsed;
9531
9928
  if (calls.length !== 1 || calls[0]?.name !== expectedTool) {
@@ -9603,6 +10000,8 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
9603
10000
  return parsed;
9604
10001
  } catch (error) {
9605
10002
  if (!(error instanceof ModelJsonResponseError) && !(isTopic && error instanceof TopicProjectionError)) throw error;
10003
+ const scope = this.diagnosticScopes.getStore();
10004
+ if (scope) this.diagnostics?.record(scope, { validationFailures: 1 }, { error });
9606
10005
  let retryError = error;
9607
10006
  if (isTopic && error instanceof TopicProjectionError) {
9608
10007
  const safe = new TopicProjectionError(error.diagnostics.category, error.diagnostics.reason, { ...topicDiagnostics, ...error.diagnostics });
@@ -9704,7 +10103,9 @@ Correct the previous Topic output using these validation errors: ${JSON.stringif
9704
10103
  try {
9705
10104
  await Promise.race([
9706
10105
  (async () => {
9707
- for await (const chunk of this.ctx.llm.stream(timedRequest)) assembler.push(chunk);
10106
+ const scope = this.diagnosticScopes.getStore();
10107
+ const stream = this.diagnostics && scope ? observeModelStream(this.diagnostics, scope, timedRequest, () => this.ctx.llm.stream(timedRequest)) : this.ctx.llm.stream(timedRequest);
10108
+ for await (const chunk of stream) assembler.push(chunk);
9708
10109
  })(),
9709
10110
  timeout,
9710
10111
  aborted
@@ -9746,7 +10147,7 @@ function isOffRejection(error) {
9746
10147
  }
9747
10148
 
9748
10149
  // src/metadata.ts
9749
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
10150
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
9750
10151
  var TOPIC_BOOTSTRAP_WINDOW_MS = 10 * 6e4;
9751
10152
  var TOPIC_BOOTSTRAP_WINDOW_CALLS = 2;
9752
10153
  var METADATA_SCHEMA = `
@@ -9769,7 +10170,7 @@ CREATE TABLE IF NOT EXISTS stratagate_dsh_feedback_drafts (
9769
10170
  ) STRICT;
9770
10171
  `;
9771
10172
  function dropLegacyAgentMemoriesTable(filename) {
9772
- const database = new DatabaseSync2(filename);
10173
+ const database = new DatabaseSync3(filename);
9773
10174
  try {
9774
10175
  const legacyTable = database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_agent_memories'").get();
9775
10176
  if (!legacyTable) return;
@@ -9782,7 +10183,7 @@ function dropLegacyAgentMemoriesTable(filename) {
9782
10183
  var DshMetadataStore = class {
9783
10184
  database;
9784
10185
  constructor(filename) {
9785
- this.database = new DatabaseSync2(filename);
10186
+ this.database = new DatabaseSync3(filename);
9786
10187
  this.database.exec(METADATA_SCHEMA);
9787
10188
  }
9788
10189
  /** Database-wide, durable reservation before any historical model attempt.
@@ -9912,15 +10313,10 @@ var DshMetadataStore = class {
9912
10313
  };
9913
10314
 
9914
10315
  // src/runtime.ts
9915
- import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
9916
-
9917
- // src/feedback-policy.ts
9918
- var AGENT_FEEDBACK_ENABLED = false;
9919
-
9920
- // src/runtime.ts
10316
+ import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
9921
10317
  import { existsSync } from "node:fs";
9922
10318
  import { dirname, resolve as resolve2 } from "node:path";
9923
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
10319
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
9924
10320
  import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
9925
10321
  import { openNativePath } from "@deepseek-ai/dsh-native-command";
9926
10322
  import { validateArgs as validateArgs2 } from "@deepseek-ai/dsh-tools";
@@ -10477,7 +10873,6 @@ function graphProjectionCanRun(job, now = Date.now()) {
10477
10873
  var AUTO_EVENT_LIMIT = 4;
10478
10874
  var AUTO_ELEMENT_LIMIT = 4;
10479
10875
  var AUTO_MEMORY_TOKEN_BUDGET = 900;
10480
- var FEEDBACK_PROMPT_COOLDOWN_MS = 5 * 24 * 60 * 60 * 1e3;
10481
10876
  function projectKey(cwd) {
10482
10877
  const canonical2 = resolve2(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
10483
10878
  return createHash3("sha256").update(canonical2).digest("hex").slice(0, 20);
@@ -10560,6 +10955,8 @@ function normalizeFeedbackDraft(input, previous) {
10560
10955
  expected: replacesStructuredBody ? "" : has("expected") ? feedbackText(input.expected, 1e4) : previous?.expected ?? "",
10561
10956
  actual: replacesStructuredBody ? "" : has("actual") ? feedbackText(input.actual, 1e4) : previous?.actual ?? "",
10562
10957
  errorContext: replacesStructuredBody ? "" : has("errorContext") ? feedbackText(input.errorContext, 2e4) : previous?.errorContext ?? "",
10958
+ ...previous?.aiPrepared ? { aiPrepared: true } : {},
10959
+ ...previous?.diagnostics ? { diagnostics: previous.diagnostics } : {},
10563
10960
  ...bodyMarkdown ? { bodyMarkdown } : {},
10564
10961
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10565
10962
  };
@@ -10583,6 +10980,8 @@ var StrataGateRuntime = class {
10583
10980
  this.flushNativeSession = flushNativeSession;
10584
10981
  this.feedbackOrigin = feedbackOrigin;
10585
10982
  this.openPath = openPath;
10983
+ this.diagnostics = new DiagnosticsRecorder(config.database);
10984
+ this.models.setDiagnostics?.(this.diagnostics, (session, sessionId) => session ? this.namespaceFor(session) : this.diagnosticNamespaceForSession(sessionId ?? "") ?? "@shared");
10586
10985
  this.blockTurnSize = config.blockTurnSize;
10587
10986
  this.blockDecayLambda = config.blockDecayLambda;
10588
10987
  this.agentMemoryRetrievalWeight = config.agentMemoryRetrievalWeight ?? 1;
@@ -10596,6 +10995,9 @@ var StrataGateRuntime = class {
10596
10995
  flushNativeSession;
10597
10996
  feedbackOrigin;
10598
10997
  openPath;
10998
+ diagnostics;
10999
+ diagnosticSessionNamespaces = /* @__PURE__ */ new Map();
11000
+ diagnosticMemoryEstimates = /* @__PURE__ */ new Map();
10599
11001
  profileStorage;
10600
11002
  folder = new TurnFolder();
10601
11003
  spaces = /* @__PURE__ */ new Map();
@@ -10612,11 +11014,10 @@ var StrataGateRuntime = class {
10612
11014
  externalImportRuns = /* @__PURE__ */ new Map();
10613
11015
  dreamingRuns = /* @__PURE__ */ new Map();
10614
11016
  dreamingControllers = /* @__PURE__ */ new Map();
10615
- dreamingOwner = randomUUID3();
11017
+ dreamingOwner = randomUUID5();
10616
11018
  dreamingHost;
10617
11019
  adminJobRetryRuns = /* @__PURE__ */ new Map();
10618
11020
  feedbackDrafts = /* @__PURE__ */ new Map();
10619
- pendingFeedbackSuggestionSessions = /* @__PURE__ */ new Set();
10620
11021
  pendingTurns = /* @__PURE__ */ new Map();
10621
11022
  drainTimers = /* @__PURE__ */ new Map();
10622
11023
  drainBackoffMs = /* @__PURE__ */ new Map();
@@ -10636,7 +11037,6 @@ var StrataGateRuntime = class {
10636
11037
  blockTurnSize;
10637
11038
  blockDecayLambda;
10638
11039
  agentMemoryRetrievalWeight;
10639
- transientLastFeedbackPromptAt = null;
10640
11040
  get agentMemoryEnabled() {
10641
11041
  return this.config.agentMemoryEnabled !== false;
10642
11042
  }
@@ -10797,7 +11197,9 @@ var StrataGateRuntime = class {
10797
11197
  }
10798
11198
  }
10799
11199
  return resumed2;
10800
- }
11200
+ },
11201
+ void 0,
11202
+ namespace
10801
11203
  );
10802
11204
  await this.persistSuccessfulResponses(memory);
10803
11205
  const sessionsToSync = /* @__PURE__ */ new Set();
@@ -11170,6 +11572,12 @@ var StrataGateRuntime = class {
11170
11572
  }
11171
11573
  const memory = await this.space(session);
11172
11574
  const assessment = memory.assessRetrieval(input, new Set(batch.refs.keys()));
11575
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage: "retrieval" }, {
11576
+ assessments: 1,
11577
+ sufficient: assessment.verdict === "sufficient" ? 1 : 0,
11578
+ partial: assessment.verdict === "partial" ? 1 : 0,
11579
+ wrong: assessment.verdict === "wrong" ? 1 : 0
11580
+ });
11173
11581
  batch.assessment = assessment;
11174
11582
  return {
11175
11583
  batchId: batch.id,
@@ -11486,10 +11894,10 @@ var StrataGateRuntime = class {
11486
11894
  await Promise.allSettled(this.externalImportRuns.values());
11487
11895
  await Promise.allSettled(this.dreamingRuns.values());
11488
11896
  await this.dreamingHost?.close();
11897
+ this.diagnostics.close();
11489
11898
  await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
11490
11899
  if (this.profileStorage) await this.profileStorage.close();
11491
11900
  this.sessionsById.clear();
11492
- this.pendingFeedbackSuggestionSessions.clear();
11493
11901
  if (flushError !== void 0) throw flushError;
11494
11902
  }
11495
11903
  namespaceFor(session) {
@@ -11498,10 +11906,75 @@ var StrataGateRuntime = class {
11498
11906
  if (this.config.namespaceMode === "session") return `${prefix}:session:${String(session.id)}`;
11499
11907
  return `${prefix}:project:${projectKey(session.header.cwd)}`;
11500
11908
  }
11909
+ trackDiagnosticSession(sessionId, namespace, memoryInputEstimate) {
11910
+ this.diagnosticSessionNamespaces.set(sessionId, namespace);
11911
+ if (memoryInputEstimate !== void 0) this.diagnosticMemoryEstimates.set(sessionId, memoryInputEstimate);
11912
+ if (this.diagnosticSessionNamespaces.size > 256) {
11913
+ const oldest = this.diagnosticSessionNamespaces.keys().next().value;
11914
+ this.diagnosticSessionNamespaces.delete(oldest);
11915
+ this.diagnosticMemoryEstimates.delete(oldest);
11916
+ }
11917
+ }
11918
+ diagnosticMemoryEstimate(sessionId) {
11919
+ return this.diagnosticMemoryEstimates.get(sessionId);
11920
+ }
11921
+ diagnosticNamespaceForSession(sessionId) {
11922
+ const session = this.knownSessions.get(sessionId)?.deref();
11923
+ return session ? this.namespaceFor(session) : this.diagnosticSessionNamespaces.get(sessionId);
11924
+ }
11925
+ async diagnosticReport(namespace, options = {}) {
11926
+ const collected = this.diagnostics.snapshot(namespace, options);
11927
+ const shared = this.diagnostics.snapshot("@shared");
11928
+ const base = { ...collected, sharedOrUnattributed: {
11929
+ stages: shared.stages,
11930
+ note: "\u6570\u636E\u5E93\u5171\u4EAB\u753B\u50CF\u7EF4\u62A4\u6216\u65E0\u6CD5\u5F52\u5C5E\u7684\u6A21\u578B\u64CD\u4F5C\uFF0C\u5355\u5217\u5C55\u793A\uFF0C\u672A\u5206\u644A\u5230\u5F53\u524D\u5DE5\u4F5C\u533A\u3002"
11931
+ } };
11932
+ try {
11933
+ const snapshot = await this.adminSnapshot(namespace);
11934
+ if (!snapshot) return { ...base, pipeline: { available: false } };
11935
+ const stages = {
11936
+ summarizer: snapshot.summaryJobs,
11937
+ extractor: snapshot.extractionJobs,
11938
+ graphProjector: snapshot.graphProjectionJobs,
11939
+ topicProjector: (snapshot.memoryTopicState?.jobs ?? []).filter((job) => !job.superseded),
11940
+ externalMemoryImport: snapshot.externalMemoryImportJobs
11941
+ };
11942
+ const pipeline = Object.fromEntries(Object.entries(stages).map(([stage, jobs]) => [stage, {
11943
+ total: jobs.length,
11944
+ failed: jobs.filter((j) => j.status === "failed").length,
11945
+ succeeded: jobs.filter((j) => ["succeeded", "completed"].includes(j.status)).length,
11946
+ skipped: jobs.filter((j) => j.status === "skipped").length,
11947
+ pending: jobs.filter((j) => ["pending", "running", "processing", "extracting", "deciding"].includes(j.status)).length,
11948
+ oldestPendingUpdatedAt: jobs.filter((j) => ["pending", "running", "processing"].includes(j.status)).map((j) => j.updatedAt).sort()[0] ?? null
11949
+ }]));
11950
+ const adoptions = snapshot.usageReceipts.filter((r) => r.createdAt >= collected.since);
11951
+ const adopted = adoptions.filter((r) => r.eventIds.length > 0 || r.elementIds.length > 0);
11952
+ const sampledBlocks = snapshot.blocks.slice(-64);
11953
+ const layerTokenEstimates = Object.fromEntries([0, 1, 2, 3, 4, 5].map((level) => [
11954
+ `L${level}`,
11955
+ sampledBlocks.reduce((sum, block) => sum + estimateTokens(renderBlock(block, level)), 0)
11956
+ ]));
11957
+ return { ...base, pipeline: { available: true, stages: pipeline }, benefits: {
11958
+ retrievalReceipts: adoptions.length,
11959
+ adoptionReceipts: adopted.length,
11960
+ adoptedEvents: adopted.reduce((n, r) => n + r.eventIds.length, 0),
11961
+ blocks: snapshot.blocks.length,
11962
+ events: snapshot.events.length,
11963
+ graphNodes: snapshot.graphNodes.length,
11964
+ sampledBlocks: sampledBlocks.length,
11965
+ layerTokenEstimates,
11966
+ note: "\u5206\u5C42Token\u4E3A\u6700\u8FD1\u6700\u591A64\u4E2A\u5B58\u50A8\u5757\u7684\u5185\u5BB9\u4F53\u79EF\u4F30\u7B97\uFF0C\u5404\u4F1A\u8BDD\u5B9E\u9645\u5C42\u7EA7\u4E0D\u540C\uFF1B\u91C7\u7528\u6B21\u6570\u4E0E\u5185\u5BB9\u538B\u7F29\u6BD4\u4F8B\u4E0D\u7B49\u4E8E\u7B54\u6848\u8D28\u91CF\u6216\u8D26\u5355\u51C0\u6536\u76CA\u3002"
11967
+ } };
11968
+ } catch {
11969
+ return { ...base, pipeline: { available: false } };
11970
+ }
11971
+ }
11501
11972
  async prepareFeedback(session, input) {
11502
11973
  const namespace = this.namespaceFor(session);
11503
- const previous = this.loadFeedbackDraft(namespace);
11974
+ const previous = input.newDraft ? null : this.loadFeedbackDraft(namespace);
11504
11975
  const draft = normalizeFeedbackDraft(input, previous);
11976
+ draft.aiPrepared = true;
11977
+ draft.diagnostics = await this.diagnosticReport(namespace);
11505
11978
  this.saveFeedbackDraft(namespace, draft);
11506
11979
  const feedbackUrl = feedbackDraftUrl(namespace, this.feedbackOrigin());
11507
11980
  return {
@@ -11510,7 +11983,7 @@ var StrataGateRuntime = class {
11510
11983
  submitted: false,
11511
11984
  namespace,
11512
11985
  feedbackUrl,
11513
- message: `\u53CD\u9988\u8349\u7A3F\u5DF2\u7ECF\u51C6\u5907\u597D\u4E86\uFF0C\u8FD8\u6CA1\u6709\u63D0\u4EA4\u5230 GitHub\u3002
11986
+ message: `\u53CD\u9988\u8349\u7A3F\u5DF2\u4FDD\u5B58\u5728\u672C\u5730\uFF0C\u90AE\u4EF6\u5C1A\u672A\u53D1\u9001\u3002
11514
11987
 
11515
11988
  [\u6253\u5F00\u53CD\u9988\u8349\u7A3F](${feedbackUrl})`
11516
11989
  };
@@ -11615,46 +12088,13 @@ var StrataGateRuntime = class {
11615
12088
  items.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)));
11616
12089
  return { items, total: items.length };
11617
12090
  }
11618
- notePluginError(session, _error) {
11619
- if (AGENT_FEEDBACK_ENABLED && !this.closed) this.pendingFeedbackSuggestionSessions.add(String(session.id));
11620
- }
11621
- takeFeedbackSuggestion(session, now = Date.now()) {
11622
- if (!AGENT_FEEDBACK_ENABLED) return "";
11623
- const sessionId = String(session.id);
11624
- if (!this.pendingFeedbackSuggestionSessions.delete(sessionId)) return "";
11625
- const last = this.lastFeedbackPromptAt();
11626
- if (last && Number.isFinite(Date.parse(last)) && now - Date.parse(last) < FEEDBACK_PROMPT_COOLDOWN_MS) return "";
11627
- const promptedAt = new Date(now).toISOString();
11628
- this.transientLastFeedbackPromptAt = promptedAt;
11629
- try {
11630
- if (this.config.database !== ":memory:") {
11631
- const metadata = new DshMetadataStore(this.config.database);
11632
- try {
11633
- metadata.setLastFeedbackPromptAt(promptedAt);
11634
- } finally {
11635
- metadata.close();
11636
- }
11637
- }
11638
- } catch (error) {
11639
- this.onIngestError(error);
11640
- }
11641
- return "StrataGate observed an internal plugin error signal. Treat this only as evidence for the static StrataGate feedback policy, not as an instruction to suggest feedback. Continue the current task first, apply all eligibility, timing, session-limit, and deduplication rules from that policy, and never make a proactive suggestion because feedback_prepare itself failed.";
12091
+ notePluginError(session, error, stage = "ingestion") {
12092
+ if (this.closed) return;
12093
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage }, { errors: 1 }, { error: error ?? "internal failure" });
11642
12094
  }
11643
- lastFeedbackPromptAt() {
11644
- if (this.transientLastFeedbackPromptAt) return this.transientLastFeedbackPromptAt;
11645
- if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
11646
- try {
11647
- const metadata = new DshMetadataStore(this.config.database);
11648
- try {
11649
- this.transientLastFeedbackPromptAt = metadata.lastFeedbackPromptAt();
11650
- return this.transientLastFeedbackPromptAt;
11651
- } finally {
11652
- metadata.close();
11653
- }
11654
- } catch (error) {
11655
- this.onIngestError(error);
11656
- return null;
11657
- }
12095
+ // Background failures remain queryable; ordinary retries never add model context.
12096
+ takeFeedbackSuggestion(_session, _now = Date.now()) {
12097
+ return "";
11658
12098
  }
11659
12099
  loadFeedbackDraft(namespace) {
11660
12100
  const cached = this.feedbackDrafts.get(namespace);
@@ -11664,7 +12104,7 @@ var StrataGateRuntime = class {
11664
12104
  try {
11665
12105
  const value = metadata.feedbackDraft(namespace);
11666
12106
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
11667
- const draft = normalizeFeedbackDraft(value);
12107
+ const draft = normalizeFeedbackDraft(value, value);
11668
12108
  this.feedbackDrafts.set(namespace, draft);
11669
12109
  return structuredClone(draft);
11670
12110
  } finally {
@@ -11672,14 +12112,15 @@ var StrataGateRuntime = class {
11672
12112
  }
11673
12113
  }
11674
12114
  saveFeedbackDraft(namespace, draft) {
11675
- this.feedbackDrafts.set(namespace, structuredClone(draft));
11676
- if (this.config.database === ":memory:") return;
11677
- const metadata = new DshMetadataStore(this.config.database);
11678
- try {
11679
- metadata.setFeedbackDraft(namespace, draft);
11680
- } finally {
11681
- metadata.close();
12115
+ if (this.config.database !== ":memory:") {
12116
+ const metadata = new DshMetadataStore(this.config.database);
12117
+ try {
12118
+ metadata.setFeedbackDraft(namespace, draft);
12119
+ } finally {
12120
+ metadata.close();
12121
+ }
11682
12122
  }
12123
+ this.feedbackDrafts.set(namespace, structuredClone(draft));
11683
12124
  }
11684
12125
  async adminNamespaces() {
11685
12126
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
@@ -11934,7 +12375,7 @@ var StrataGateRuntime = class {
11934
12375
  } finally {
11935
12376
  if (owned) await memory.close();
11936
12377
  }
11937
- const action = await this.models.runDetached(`dreaming:${run.id}`, () => this.models.dreamingStep(payload), controller.signal);
12378
+ const action = await this.models.runDetached(`dreaming:${run.id}`, () => this.models.dreamingStep(payload), controller.signal, namespace);
11938
12379
  if (controller.signal.aborted) return;
11939
12380
  const repeated = run.trace.filter((entry) => entry.tool === action.tool && JSON.stringify(entry.args) === JSON.stringify(action.args)).length;
11940
12381
  if (repeated >= 3) {
@@ -12028,7 +12469,7 @@ var StrataGateRuntime = class {
12028
12469
  }
12029
12470
  try {
12030
12471
  if (job.status === "extracting") {
12031
- const recovered = await this.models.runDetached(`admin-import-recovery:${jobId}`, () => this.models.externalMemoryExtractor({ text: job.text, importedAt: job.importedAt }));
12472
+ const recovered = await this.models.runDetached(`admin-import-recovery:${jobId}`, () => this.models.externalMemoryExtractor({ text: job.text, importedAt: job.importedAt }), void 0, namespace);
12032
12473
  const responses2 = this.takeSuccessfulResponses();
12033
12474
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeExternalMemoryFallback(jobId, recovered));
12034
12475
  await this.persistExternalImportResponses(namespace, responses2);
@@ -12043,7 +12484,7 @@ var StrataGateRuntime = class {
12043
12484
  decision = await this.models.runDetached(`admin-import:${jobId}`, () => this.models.externalMemoryDecider({
12044
12485
  candidate: structuredClone(work.candidate),
12045
12486
  matches: structuredClone(work.matches)
12046
- }));
12487
+ }), void 0, namespace);
12047
12488
  responses = this.takeSuccessfulResponses();
12048
12489
  }
12049
12490
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeNextExternalMemoryImport(
@@ -12184,7 +12625,7 @@ var StrataGateRuntime = class {
12184
12625
  const remembered = this.workspaceNames.get(namespace);
12185
12626
  if (remembered) return remembered;
12186
12627
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
12187
- const database = new DatabaseSync3(this.config.database, { readOnly: true });
12628
+ const database = new DatabaseSync4(this.config.database, { readOnly: true });
12188
12629
  try {
12189
12630
  if (!database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_workspaces'").get()) {
12190
12631
  return null;
@@ -12314,7 +12755,7 @@ var StrataGateRuntime = class {
12314
12755
  if (!this.models.isReady(session)) {
12315
12756
  throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
12316
12757
  }
12317
- const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
12758
+ const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry, void 0, namespace);
12318
12759
  await this.persistSuccessfulResponses(memory);
12319
12760
  const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
12320
12761
  if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
@@ -12689,6 +13130,12 @@ var StrataGateRuntime = class {
12689
13130
  }
12690
13131
  sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
12691
13132
  this.latestBatchIds.set(key, id);
13133
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage: "retrieval" }, {
13134
+ batches: 1,
13135
+ emptyBatches: evidence.length === 0 ? 1 : 0,
13136
+ returnedRefs: refs.size,
13137
+ resultTokensEstimate: estimateTokens(JSON.stringify(output))
13138
+ });
12692
13139
  return output;
12693
13140
  }
12694
13141
  requireBatch(session, batchId, operation) {
@@ -13278,6 +13725,7 @@ ${profile}
13278
13725
  throw new Error("\u672A\u627E\u5230\u672C\u76EE\u5F55\u6240\u5C5E\u4F1A\u8BDD\u7684\u5DE5\u4F5C\u533A\u8DEF\u5F84\uFF0C\u8BF7\u5148\u6253\u5F00\u8BE5\u5DE5\u4F5C\u533A\u7684\u666E\u901A\u4F1A\u8BDD\u540E\u91CD\u8BD5");
13279
13726
  }
13280
13727
  install(scoped, binding) {
13728
+ this.runtime.trackDiagnosticSession(String(binding.agent.id), binding.namespace);
13281
13729
  if (typeof scoped.tools.restrict !== "function" || typeof scoped.tools.guard !== "function" || typeof DshTools.parameterSchemaSpecToJsonSchema !== "function" || !binding.agent.session.surface?.nodes || typeof binding.agent.whenIdle !== "function") throw new Error("\u5F53\u524D DSH \u7F3A\u5C11\u5B89\u5168\u7684 Dreaming Agent \u63A5\u53E3\uFF0C\u8BF7\u5347\u7EA7\u5BBF\u4E3B\u540E\u91CD\u8BD5");
13282
13730
  const modelTools = TOOLS.map((tool) => ({
13283
13731
  name: tool.name,
@@ -13536,6 +13984,11 @@ ${profile}
13536
13984
 
13537
13985
  // src/tools.ts
13538
13986
  import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
13987
+
13988
+ // src/feedback-policy.ts
13989
+ var AGENT_FEEDBACK_ENABLED = true;
13990
+
13991
+ // src/tools.ts
13539
13992
  var jsonOutput = {
13540
13993
  schema: { type: "json" },
13541
13994
  render: (_args, value) => [{
@@ -13576,17 +14029,19 @@ function registerMemoryTools(ctx, runtime) {
13576
14029
  }));
13577
14030
  if (AGENT_FEEDBACK_ENABLED) ctx.tools.register(defineTool2({
13578
14031
  name: "feedback_prepare",
13579
- description: 'This tool is provided by the StrataGate plugin. Create or revise a local StrataGate feedback draft when the user directly requests it, or after a proactive suggestion permitted by the StrataGate feedback policy and the user explicitly agrees. A direct user request is already authorization. Use only facts known from the current conversation; leave unknown fields empty and never invent versions, logs, Block counts, or diagnostics. Never submit anything to GitHub. After success, briefly say the draft is local and not submitted, then render feedbackUrl as a Markdown link labeled "\u6253\u5F00\u53CD\u9988\u8349\u7A3F". Then ask exactly once: "\u8981\u4E0D\u8981\u987A\u4FBF\u8BA9\u6211\u5C1D\u8BD5\u4FEE\u590D\u8FD9\u4E2A\u95EE\u9898\uFF0C\u5E76\u63D0\u4EA4\u4E00\u4E2A PR\uFF1F" If the user does not respond or declines, do not ask again. Do not use a popup or other additional UI. Do not print draft fields or an Issue-content table, and do not direct the user through Settings manually.',
14032
+ description: "This tool is provided by the StrataGate plugin. \u6574\u7406 StrataGate \u63D2\u4EF6\u7684\u6545\u969C\u3001\u4F7F\u7528\u7591\u95EE\u548C\u6539\u8FDB\u5EFA\u8BAE\uFF0C\u751F\u6210\u672C\u5730\u90AE\u4EF6\u53CD\u9988\u8349\u7A3F\u5E76\u81EA\u52A8\u9644\u5E26\u8FD0\u884C\u6458\u8981\u3002\u7528\u6237\u76F4\u63A5\u8981\u6C42\u53EF\u8C03\u7528\uFF1B\u4E3B\u52A8\u5EFA\u8BAE\u987B\u5148\u83B7\u540C\u610F\u3002\u4F9D\u636E\u5F53\u524D\u4F1A\u8BDD\u4E8B\u5B9E\u586B\u5199\uFF0C\u672A\u77E5\u7559\u7A7A\uFF0C\u53BB\u9664\u51ED\u636E\u3002\u7701\u7565\u5B57\u6BB5\u4FDD\u7559\u539F\u503C\uFF0C\u7A7A\u5B57\u7B26\u4E32\u6216\u7A7A\u6570\u7EC4\u6E05\u7A7A\uFF1B\u65B0\u95EE\u9898\u4F20 new_draft=true\u3002\u6210\u529F\u540E\u8BF4\u660E\u8349\u7A3F\u5728\u672C\u5730\u3001\u90AE\u4EF6\u5C1A\u672A\u53D1\u9001\uFF0C\u5E76\u5C06 feedbackUrl \u663E\u793A\u4E3A\u201C\u6253\u5F00\u53CD\u9988\u8349\u7A3F\u201D\u94FE\u63A5\uFF0C\u4F9B\u7528\u6237\u68C0\u67E5\u540E\u624B\u52A8\u53D1\u90AE\u4EF6\u3002",
13580
14033
  parameters: {
13581
- title: { type: "string" },
13582
- description: { type: "string" },
13583
- reproduction: { type: "array", items: { type: "string" } },
13584
- expected: { type: "string" },
13585
- actual: { type: "string" },
13586
- error_context: { type: "string" }
14034
+ new_draft: { type: "boolean", description: "\u65B0\u95EE\u9898\u4E3A true\uFF1B\u4FEE\u6539\u5DF2\u6709\u8349\u7A3F\u65F6\u7701\u7565\u3002" },
14035
+ title: { type: "string", description: "\u7B80\u77ED\u95EE\u9898\u6807\u9898\u3002" },
14036
+ description: { type: "string", description: "\u5DF2\u77E5\u95EE\u9898\u3001\u5EFA\u8BAE\u6216\u7591\u95EE\u3002" },
14037
+ reproduction: { type: "array", items: { type: "string" }, description: "\u5B9E\u9645\u53D1\u751F\u8FC7\u7684\u590D\u73B0\u6B65\u9AA4\u3002" },
14038
+ expected: { type: "string", description: "\u5DF2\u77E5\u9884\u671F\u7ED3\u679C\u3002" },
14039
+ actual: { type: "string", description: "\u5B9E\u9645\u89C2\u5BDF\u5230\u7684\u7ED3\u679C\u3002" },
14040
+ error_context: { type: "string", description: "\u5F53\u524D\u4F1A\u8BDD\u7684\u76F8\u5173\u9519\u8BEF\uFF1B\u672C\u5730\u8BCA\u65AD\u7531\u7A0B\u5E8F\u81EA\u52A8\u8865\u5145\u3002" }
13587
14041
  },
13588
14042
  output: jsonOutput,
13589
14043
  execute: async (args, exec) => runtime.prepareFeedback(sessionOf(exec), {
14044
+ ...args.new_draft !== void 0 ? { newDraft: args.new_draft } : {},
13590
14045
  ...args.title !== void 0 ? { title: args.title } : {},
13591
14046
  ...args.description !== void 0 ? { description: args.description } : {},
13592
14047
  ...args.reproduction !== void 0 ? { reproduction: args.reproduction } : {},
@@ -13595,6 +14050,19 @@ function registerMemoryTools(ctx, runtime) {
13595
14050
  ...args.error_context !== void 0 ? { errorContext: args.error_context } : {}
13596
14051
  })
13597
14052
  }));
14053
+ if (AGENT_FEEDBACK_ENABLED) ctx.tools.register(defineTool2({
14054
+ name: "stratagate_diagnostics",
14055
+ description: "This tool is provided by the StrataGate plugin. \u53EA\u8BFB\u67E5\u8BE2 StrataGate \u63D2\u4EF6\u5F53\u524D\u8BB0\u5FC6\u7A7A\u95F4\u7684\u8FD0\u884C\u8BCA\u65AD\uFF1A\u8FD1\u671F\u5931\u8D25\u3001\u4EFB\u52A1\u72B6\u6001\u3001\u8017\u65F6\u3001Token \u7528\u91CF\u4E0E\u91C7\u96C6\u8986\u76D6\u5EA6\u3002\u9ED8\u8BA4\u7ED9\u6458\u8981\uFF1B\u7528\u8FD4\u56DE\u7684\u8BCA\u65AD id \u8BFB\u53D6\u8131\u654F\u9519\u8BEF\u8BE6\u60C5\uFF0Coffset \u5206\u9875\u3002\u7528\u4E8E\u6392\u67E5\u6216\u56DE\u5E94\u7528\u6237\u7591\u95EE\uFF1B\u53CD\u9988\u8349\u7A3F\u4F1A\u81EA\u52A8\u9644\u5E26\u6458\u8981\u3002",
14056
+ parameters: {
14057
+ id: { type: "string", description: "\u6458\u8981\u4E2D\u8FD4\u56DE\u7684\u8BCA\u65AD ID\uFF1B\u63D0\u4F9B\u65F6\u8BFB\u53D6\u8BE5\u6761\u8BE6\u60C5\u3002" },
14058
+ offset: { type: "integer", description: "\u4F7F\u7528\u8FD4\u56DE\u7684 nextOffset \u7EE7\u7EED\u8BFB\u53D6\u8FD1\u671F\u5F02\u5E38\u3002" }
14059
+ },
14060
+ output: jsonOutput,
14061
+ execute: async (args, exec) => runtime.diagnosticReport(runtime.namespaceFor(sessionOf(exec)), {
14062
+ ...args.id !== void 0 ? { id: args.id, details: true } : {},
14063
+ ...args.offset !== void 0 ? { offset: args.offset } : {}
14064
+ })
14065
+ }));
13598
14066
  ctx.tools.register(defineTool2({
13599
14067
  name: "memory_list_topics",
13600
14068
  description: "This tool is provided by the StrataGate plugin. \u6D4F\u89C8\u5F53\u524D\u8BB0\u5FC6\u7A7A\u95F4\u7684\u4E3B\u9898\u76EE\u5F55\u3002\u53EF\u6309\u5206\u7C7B\u5206\u9875\uFF0C\u6216\u7528 query \u67E5\u4E3B\u9898\u540D\u79F0\u53CA\u7B80\u4ECB\u3002\u672A\u6574\u7406\u7684\u4E8B\u4EF6\u4E5F\u6709\u5165\u53E3\u3002\u76EE\u5F55\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u521B\u5EFA\u8BC1\u636E\u6279\u6B21\uFF0C\u4E0D\u5F3A\u5316\u8BB0\u5FC6\uFF1B\u9700\u8981\u4E3B\u9898\u8109\u7EDC\u65F6\u8C03\u7528 memory_expand_topic\uFF1B\u9700\u8981\u4E8B\u5B9E\u8BC1\u636E\u53EF\u76F4\u63A5 memory_search_events(topic_id)\uFF0C\u5E76\u8BC4\u4F30\u6765\u6E90\u4E8B\u4EF6\u3002",
@@ -13773,7 +14241,7 @@ function registerMemoryTools(ctx, runtime) {
13773
14241
 
13774
14242
  // src/web.ts
13775
14243
  import { createHash as createHash4 } from "node:crypto";
13776
- import { createRequire as createRequire2 } from "node:module";
14244
+ import { createRequire as createRequire3 } from "node:module";
13777
14245
 
13778
14246
  // src/graph-clustering.ts
13779
14247
  import createGraph from "ngraph.graph";
@@ -13887,7 +14355,7 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
13887
14355
 
13888
14356
  // src/web.ts
13889
14357
  var LEGACY_THREAD_ID = "__legacy__";
13890
- var nodeRequire2 = createRequire2(import.meta.url);
14358
+ var nodeRequire2 = createRequire3(import.meta.url);
13891
14359
  function currentPluginVersion() {
13892
14360
  try {
13893
14361
  const value = nodeRequire2("../package.json");
@@ -14646,7 +15114,8 @@ async function feedback(runtime, req, url) {
14646
15114
  if (req.method === "GET") {
14647
15115
  const namespace2 = url.searchParams.get("namespace")?.trim() ?? "";
14648
15116
  if (!namespace2) throw new AdminHttpError(400, "namespace is required");
14649
- return runtime.adminFeedbackDraft(namespace2);
15117
+ const result = runtime.adminFeedbackDraft(namespace2);
15118
+ return { ...result, diagnostics: result.draft?.diagnostics ?? await runtime.diagnosticReport?.(namespace2) };
14650
15119
  }
14651
15120
  if (req.method !== "PUT") throw new AdminHttpError(405, "StrataGate feedback requires GET or PUT");
14652
15121
  let suppliedBody = req.body;
@@ -15298,6 +15767,19 @@ async function handleAdminRequest(runtime, req, res) {
15298
15767
  const path = url.pathname.replace(/\/$/, "");
15299
15768
  if (path === "/api/stratagate/feedback") {
15300
15769
  sendJson(res, 200, await feedback(runtime, req, url));
15770
+ } else if (path === "/api/stratagate/diagnostics") {
15771
+ const namespace = url.searchParams.get("namespace")?.trim() ?? "";
15772
+ if (!namespace) throw new AdminHttpError(400, "namespace is required");
15773
+ if (req.method === "POST") {
15774
+ const category = url.searchParams.get("category") ?? "";
15775
+ if (!["timeout", "network", "request-failed"].includes(category)) throw new AdminHttpError(400, "Unknown frontend diagnostic category");
15776
+ runtime.diagnostics.record({ namespace, stage: "frontend" }, { errors: 1 }, { error: category });
15777
+ sendJson(res, 200, { recorded: true });
15778
+ return;
15779
+ }
15780
+ if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate diagnostics require GET or POST");
15781
+ const id = url.searchParams.get("id") ?? void 0;
15782
+ sendJson(res, 200, await runtime.diagnosticReport(namespace, { ...id ? { id, details: true } : {}, offset: numeric(url.searchParams.get("offset"), 0, 0, 2e3) }));
15301
15783
  } else if (path === "/api/stratagate/profile") {
15302
15784
  sendJson(res, 200, await persistentProfile(runtime, req), true);
15303
15785
  } else if (path === "/api/stratagate/settings") {
@@ -15464,7 +15946,7 @@ import {
15464
15946
  writeFile
15465
15947
  } from "node:fs/promises";
15466
15948
  import { basename, dirname as dirname2, join } from "node:path";
15467
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
15949
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
15468
15950
  import { encodeSeqRanges, KNOWN_SESSION_EVENT_TYPES } from "@deepseek-ai/dsh-session";
15469
15951
  var LEGACY_EVENT_TYPE = "stratagate/memory-citations";
15470
15952
  var COMPAT_EVENT_TYPE = "feedback/record";
@@ -15685,7 +16167,7 @@ async function exists(path) {
15685
16167
  }
15686
16168
  async function publishExclusive(path, bytes) {
15687
16169
  await mkdir(dirname2(path), { recursive: true });
15688
- const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID4()}.tmp`);
16170
+ const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID6()}.tmp`);
15689
16171
  const handle = await open(temporary, "wx", 384);
15690
16172
  try {
15691
16173
  await handle.writeFile(bytes);
@@ -15810,15 +16292,8 @@ Memory writing:
15810
16292
  - Current city (currentCity) also belongs in the always-on Profile. Travel or business-trip location can be temporary while its saved value persists across sessions until the user updates or clears it; do not expire it automatically. A user request to remember their current trip city belongs here, not in retrieval-dependent memory_remember. Future trip plans, completed trips, and historical locations do not establish current whereabouts. Never copy currentCity into defaultLocation or homeCity. For local tasks use an explicit task location first, otherwise currentCity when set, then defaultLocation. Follow the same Profile consent rule for updates and clearing.
15811
16293
  - Use memory_remember for durable project facts, past decisions, corrections, experiences, and context-specific preferences. Record one self-contained, grounded fact per call, with necessary project, time, and scope. Never record speculation, secrets, credentials, or transient task state.`;
15812
16294
  var FEEDBACK_PROTOCOL = `[StrataGate feedback policy]
15813
- The feedback_prepare tool creates a local draft for the user to review; it never submits the draft.
15814
-
15815
- - Consider proactively suggesting a feedback draft only when the current conversation contains a clear error signal: a tool call threw or returned an error; a result is clearly contrary to expectations and the user expresses confusion or dissatisfaction; or the same problem remains after the user retries it. Normal use, casual conversation, general complaints, and suspected problems without clear error evidence are not eligible.
15816
- - An eligible error does not require a suggestion. Suggest only when the problem remains unresolved, recurs, or materially interferes with the user's task. Do not suggest for a minor failure that recovered automatically without affecting the task.
15817
- - Always troubleshoot, solve the current problem, or offer a workaround first. If it cannot be solved, explain the blocker first. Only at the end of that turn may you add one unobtrusive sentence in the user's language, such as: "\u5982\u679C\u4F60\u613F\u610F\uFF0C\u6211\u53EF\u4EE5\u628A\u8FD9\u6B21\u5F02\u5E38\u6574\u7406\u6210\u53CD\u9988\u8349\u7A3F\uFF0C\u4F9B\u4F60\u68C0\u67E5\u540E\u81EA\u884C\u63D0\u4EA4\u3002" Do not use a popup, heading, interactive-question tool, or interruption for the suggestion.
15818
- - Across the entire current conversation, make at most one proactive feedback suggestion in total, across all namespaces and problems. The suggestion consumes this allowance as soon as it is sent, whether the user accepts, declines, or does not reply. Use the conversation history to remember this; do not expose internal tracking or claim a cross-conversation, daily, or persistent limit.
15819
- - A user's direct request to create or revise feedback is not a proactive suggestion, does not consume that allowance, and is not restricted by it. Necessary clarification for that request is also allowed. If the user says they are not interested, stop immediately and do not ask again.
15820
- - Deduplicate a problem by namespace plus its substantive characteristics; changed wording or another retry does not make it a new problem. Never proactively suggest feedback for a problem that was already proactively suggested or already has a draft. A draft created at the user's request still makes that problem ineligible for a later proactive suggestion.
15821
- - After a proactive suggestion, call feedback_prepare only if the user explicitly agrees. A direct request to create feedback is already authorization, so do not ask again. A failure of feedback_prepare itself must never trigger another proactive feedback suggestion.`;
16295
+ \u9488\u5BF9 StrataGate \u63D2\u4EF6\u7684\u6545\u969C\u3001\u4F7F\u7528\u7591\u95EE\u548C\u6539\u8FDB\u5EFA\u8BAE\uFF0C\u7528\u6237\u8981\u6C42\u65F6\u7528 feedback_prepare \u751F\u6210\u672C\u5730\u90AE\u4EF6\u53CD\u9988\u8349\u7A3F\u3002\u9700\u8981\u8FD0\u884C\u8BC1\u636E\u65F6\u7528 stratagate_diagnostics\uFF0C\u8349\u7A3F\u4F1A\u81EA\u52A8\u9644\u5E26\u672C\u5730\u8BCA\u65AD\u6458\u8981\u4F9B\u7528\u6237\u68C0\u67E5\u3002
16296
+ \u4EC5\u5728\u5F53\u524D\u4F1A\u8BDD\u5DF2\u6709\u660E\u786E\u7684 StrataGate \u9519\u8BEF\u8BC1\u636E\uFF0C\u4E14\u95EE\u9898\u6301\u7EED\u5F71\u54CD\u5F53\u524D\u4EFB\u52A1\u65F6\uFF0C\u5148\u5904\u7406\u95EE\u9898\uFF0C\u518D\u4E8E\u56DE\u590D\u672B\u5C3E\u8F7B\u58F0\u5EFA\u8BAE\u6574\u7406\u53CD\u9988\u3002\u6574\u4E2A\u4F1A\u8BDD\u6700\u591A\u4E3B\u52A8\u5EFA\u8BAE\u4E00\u6B21\uFF1B\u7528\u6237\u660E\u786E\u540C\u610F\u540E\u8C03\u7528\u3002\u7528\u6237\u76F4\u63A5\u8981\u6C42\u53EF\u76F4\u63A5\u6267\u884C\uFF1B\u62D2\u7EDD\u6216\u672A\u56DE\u5E94\u5C31\u7ED3\u675F\u5EFA\u8BAE\u3002\u6309\u8BB0\u5FC6\u7A7A\u95F4\u4E0E\u5B9E\u8D28\u95EE\u9898\u53BB\u91CD\u3002\u666E\u901A\u540E\u53F0\u91CD\u8BD5\u4FDD\u6301\u5B89\u9759\uFF1B\u53CD\u9988\u5DE5\u5177\u81EA\u8EAB\u5931\u8D25\u7EE7\u7EED\u539F\u4EFB\u52A1\u3002`;
15822
16297
  function renderError(error) {
15823
16298
  return error instanceof Error ? error.message : String(error);
15824
16299
  }
@@ -15899,27 +16374,48 @@ async function apply(ctx, config) {
15899
16374
  autoMemory = { name: "stratagate:auto-memory", text: text3 };
15900
16375
  } catch (error) {
15901
16376
  ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
15902
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16377
+ runtime.notePluginError(session, error, "autoContext");
15903
16378
  }
15904
16379
  try {
15905
16380
  const directory = await runtime.buildMemoryDirectory(session);
15906
16381
  if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
15907
16382
  } catch (error) {
15908
16383
  ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
15909
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16384
+ runtime.notePluginError(session, error, "directory");
15910
16385
  }
15911
16386
  if (autoMemory) contexts.push(autoMemory);
15912
- const feedbackSuggestion = AGENT_FEEDBACK_ENABLED ? runtime.takeFeedbackSuggestion(session) : "";
15913
- if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
16387
+ const toolSchemas = ctx.tools.schemas(context.agent).filter(({ name: name2 }) => name2.startsWith("memory_") || name2 === "feedback_prepare" || name2 === "stratagate_diagnostics");
16388
+ runtime.trackDiagnosticSession(String(session.id), runtime.namespaceFor(session), estimateTokens(JSON.stringify({
16389
+ sections: assembled.sections.filter(({ name: name2 }) => name2.startsWith("tool:stratagate-")),
16390
+ contexts: contexts.filter(({ name: name2 }) => name2.startsWith("stratagate:")),
16391
+ tools: toolSchemas
16392
+ })));
15914
16393
  return { ...assembled, contexts };
15915
16394
  });
15916
16395
  installRetrievalFinalization(ctx, runtime);
15917
16396
  registerMemoryTools(ctx, runtime);
15918
- ctx.on("tools/result", (exec, result) => {
15919
- if (!AGENT_FEEDBACK_ENABLED || !result.isError || !exec.agent) return;
15920
- if (exec.name === "feedback_prepare" || exec.name.startsWith("memory_")) {
15921
- runtime.notePluginError(exec.agent.session, result.error.message);
16397
+ ctx.on("llm/stream", async function* (options, next) {
16398
+ if (models.currentDiagnosticScope()) {
16399
+ yield* next();
16400
+ return;
15922
16401
  }
16402
+ const namespace = runtime.diagnosticNamespaceForSession(String(options.sessionId ?? ""));
16403
+ if (!namespace || options.purpose) {
16404
+ yield* next();
16405
+ return;
16406
+ }
16407
+ const stage = String(options.sessionId).startsWith("stratagate-dreaming:") ? "dreamingStep" : "chat";
16408
+ const memoryInputEstimate = runtime.diagnosticMemoryEstimate(String(options.sessionId));
16409
+ yield* observeModelStream(runtime.diagnostics, { namespace, stage, ...memoryInputEstimate !== void 0 ? { memoryInputEstimate } : {} }, options, next);
16410
+ });
16411
+ ctx.on("tools/result", (exec, result) => {
16412
+ if (!exec.agent || !(exec.name === "feedback_prepare" || exec.name === "stratagate_diagnostics" || exec.name.startsWith("memory_"))) return;
16413
+ const namespace = runtime.namespaceFor(exec.agent.session);
16414
+ runtime.diagnostics.record(
16415
+ { namespace, stage: "tools" },
16416
+ { toolCalls: 1, toolFailures: result.isError ? 1 : 0 },
16417
+ result.isError ? { error: result.error.message } : {}
16418
+ );
15923
16419
  });
15924
16420
  const disposeAdminRoutes = registerAdminRoutes(ctx, runtime);
15925
16421
  ctx.on("session/event", (session, event) => runtime.acceptEvent(session, event));