stratagate-dsh 0.3.15 → 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
@@ -8521,6 +8521,329 @@ function parseJsonResponse(value, requiredKeys = []) {
8521
8521
  throw new ModelJsonResponseError(void 0, { response: value });
8522
8522
  }
8523
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
+
8524
8847
  // src/llm.ts
8525
8848
  var ELEMENT_TYPES2 = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
8526
8849
  var CRITICALITIES = /* @__PURE__ */ new Set(["routine", "preference", "identity", "safety"]);
@@ -9085,6 +9408,16 @@ var DshModelBridge = class {
9085
9408
  ctx;
9086
9409
  config;
9087
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
+ }
9088
9421
  contextWindows = /* @__PURE__ */ new Map();
9089
9422
  sessions = new AsyncLocalStorage();
9090
9423
  successfulResponses = [];
@@ -9117,8 +9450,8 @@ var DshModelBridge = class {
9117
9450
  run(session, operation) {
9118
9451
  return this.sessions.run({ session, sessionId: session.id }, operation);
9119
9452
  }
9120
- runDetached(sessionId, operation, signal) {
9121
- 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);
9122
9455
  }
9123
9456
  dreamingModelOptions() {
9124
9457
  return { ...this.resolveRoute(), maxTokens: TOPIC_OUTPUT_TOKEN_BUDGET };
@@ -9399,6 +9732,40 @@ Use project scope for repository decisions, user scope for stable preferences/id
9399
9732
  return proposed;
9400
9733
  }
9401
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) {
9402
9769
  const execution = this.sessions.getStore();
9403
9770
  if (!execution) throw new Error("StrataGate model callback ran without an execution context");
9404
9771
  const baseRoute = this.resolveRoute(execution.session);
@@ -9633,6 +10000,8 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
9633
10000
  return parsed;
9634
10001
  } catch (error) {
9635
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 });
9636
10005
  let retryError = error;
9637
10006
  if (isTopic && error instanceof TopicProjectionError) {
9638
10007
  const safe = new TopicProjectionError(error.diagnostics.category, error.diagnostics.reason, { ...topicDiagnostics, ...error.diagnostics });
@@ -9734,7 +10103,9 @@ Correct the previous Topic output using these validation errors: ${JSON.stringif
9734
10103
  try {
9735
10104
  await Promise.race([
9736
10105
  (async () => {
9737
- 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);
9738
10109
  })(),
9739
10110
  timeout,
9740
10111
  aborted
@@ -9776,7 +10147,7 @@ function isOffRejection(error) {
9776
10147
  }
9777
10148
 
9778
10149
  // src/metadata.ts
9779
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
10150
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
9780
10151
  var TOPIC_BOOTSTRAP_WINDOW_MS = 10 * 6e4;
9781
10152
  var TOPIC_BOOTSTRAP_WINDOW_CALLS = 2;
9782
10153
  var METADATA_SCHEMA = `
@@ -9799,7 +10170,7 @@ CREATE TABLE IF NOT EXISTS stratagate_dsh_feedback_drafts (
9799
10170
  ) STRICT;
9800
10171
  `;
9801
10172
  function dropLegacyAgentMemoriesTable(filename) {
9802
- const database = new DatabaseSync2(filename);
10173
+ const database = new DatabaseSync3(filename);
9803
10174
  try {
9804
10175
  const legacyTable = database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_agent_memories'").get();
9805
10176
  if (!legacyTable) return;
@@ -9812,7 +10183,7 @@ function dropLegacyAgentMemoriesTable(filename) {
9812
10183
  var DshMetadataStore = class {
9813
10184
  database;
9814
10185
  constructor(filename) {
9815
- this.database = new DatabaseSync2(filename);
10186
+ this.database = new DatabaseSync3(filename);
9816
10187
  this.database.exec(METADATA_SCHEMA);
9817
10188
  }
9818
10189
  /** Database-wide, durable reservation before any historical model attempt.
@@ -9942,15 +10313,10 @@ var DshMetadataStore = class {
9942
10313
  };
9943
10314
 
9944
10315
  // src/runtime.ts
9945
- import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
9946
-
9947
- // src/feedback-policy.ts
9948
- var AGENT_FEEDBACK_ENABLED = false;
9949
-
9950
- // src/runtime.ts
10316
+ import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
9951
10317
  import { existsSync } from "node:fs";
9952
10318
  import { dirname, resolve as resolve2 } from "node:path";
9953
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
10319
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
9954
10320
  import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
9955
10321
  import { openNativePath } from "@deepseek-ai/dsh-native-command";
9956
10322
  import { validateArgs as validateArgs2 } from "@deepseek-ai/dsh-tools";
@@ -10507,7 +10873,6 @@ function graphProjectionCanRun(job, now = Date.now()) {
10507
10873
  var AUTO_EVENT_LIMIT = 4;
10508
10874
  var AUTO_ELEMENT_LIMIT = 4;
10509
10875
  var AUTO_MEMORY_TOKEN_BUDGET = 900;
10510
- var FEEDBACK_PROMPT_COOLDOWN_MS = 5 * 24 * 60 * 60 * 1e3;
10511
10876
  function projectKey(cwd) {
10512
10877
  const canonical2 = resolve2(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
10513
10878
  return createHash3("sha256").update(canonical2).digest("hex").slice(0, 20);
@@ -10590,6 +10955,8 @@ function normalizeFeedbackDraft(input, previous) {
10590
10955
  expected: replacesStructuredBody ? "" : has("expected") ? feedbackText(input.expected, 1e4) : previous?.expected ?? "",
10591
10956
  actual: replacesStructuredBody ? "" : has("actual") ? feedbackText(input.actual, 1e4) : previous?.actual ?? "",
10592
10957
  errorContext: replacesStructuredBody ? "" : has("errorContext") ? feedbackText(input.errorContext, 2e4) : previous?.errorContext ?? "",
10958
+ ...previous?.aiPrepared ? { aiPrepared: true } : {},
10959
+ ...previous?.diagnostics ? { diagnostics: previous.diagnostics } : {},
10593
10960
  ...bodyMarkdown ? { bodyMarkdown } : {},
10594
10961
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10595
10962
  };
@@ -10613,6 +10980,8 @@ var StrataGateRuntime = class {
10613
10980
  this.flushNativeSession = flushNativeSession;
10614
10981
  this.feedbackOrigin = feedbackOrigin;
10615
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");
10616
10985
  this.blockTurnSize = config.blockTurnSize;
10617
10986
  this.blockDecayLambda = config.blockDecayLambda;
10618
10987
  this.agentMemoryRetrievalWeight = config.agentMemoryRetrievalWeight ?? 1;
@@ -10626,6 +10995,9 @@ var StrataGateRuntime = class {
10626
10995
  flushNativeSession;
10627
10996
  feedbackOrigin;
10628
10997
  openPath;
10998
+ diagnostics;
10999
+ diagnosticSessionNamespaces = /* @__PURE__ */ new Map();
11000
+ diagnosticMemoryEstimates = /* @__PURE__ */ new Map();
10629
11001
  profileStorage;
10630
11002
  folder = new TurnFolder();
10631
11003
  spaces = /* @__PURE__ */ new Map();
@@ -10642,11 +11014,10 @@ var StrataGateRuntime = class {
10642
11014
  externalImportRuns = /* @__PURE__ */ new Map();
10643
11015
  dreamingRuns = /* @__PURE__ */ new Map();
10644
11016
  dreamingControllers = /* @__PURE__ */ new Map();
10645
- dreamingOwner = randomUUID3();
11017
+ dreamingOwner = randomUUID5();
10646
11018
  dreamingHost;
10647
11019
  adminJobRetryRuns = /* @__PURE__ */ new Map();
10648
11020
  feedbackDrafts = /* @__PURE__ */ new Map();
10649
- pendingFeedbackSuggestionSessions = /* @__PURE__ */ new Set();
10650
11021
  pendingTurns = /* @__PURE__ */ new Map();
10651
11022
  drainTimers = /* @__PURE__ */ new Map();
10652
11023
  drainBackoffMs = /* @__PURE__ */ new Map();
@@ -10666,7 +11037,6 @@ var StrataGateRuntime = class {
10666
11037
  blockTurnSize;
10667
11038
  blockDecayLambda;
10668
11039
  agentMemoryRetrievalWeight;
10669
- transientLastFeedbackPromptAt = null;
10670
11040
  get agentMemoryEnabled() {
10671
11041
  return this.config.agentMemoryEnabled !== false;
10672
11042
  }
@@ -10827,7 +11197,9 @@ var StrataGateRuntime = class {
10827
11197
  }
10828
11198
  }
10829
11199
  return resumed2;
10830
- }
11200
+ },
11201
+ void 0,
11202
+ namespace
10831
11203
  );
10832
11204
  await this.persistSuccessfulResponses(memory);
10833
11205
  const sessionsToSync = /* @__PURE__ */ new Set();
@@ -11200,6 +11572,12 @@ var StrataGateRuntime = class {
11200
11572
  }
11201
11573
  const memory = await this.space(session);
11202
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
+ });
11203
11581
  batch.assessment = assessment;
11204
11582
  return {
11205
11583
  batchId: batch.id,
@@ -11516,10 +11894,10 @@ var StrataGateRuntime = class {
11516
11894
  await Promise.allSettled(this.externalImportRuns.values());
11517
11895
  await Promise.allSettled(this.dreamingRuns.values());
11518
11896
  await this.dreamingHost?.close();
11897
+ this.diagnostics.close();
11519
11898
  await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
11520
11899
  if (this.profileStorage) await this.profileStorage.close();
11521
11900
  this.sessionsById.clear();
11522
- this.pendingFeedbackSuggestionSessions.clear();
11523
11901
  if (flushError !== void 0) throw flushError;
11524
11902
  }
11525
11903
  namespaceFor(session) {
@@ -11528,10 +11906,75 @@ var StrataGateRuntime = class {
11528
11906
  if (this.config.namespaceMode === "session") return `${prefix}:session:${String(session.id)}`;
11529
11907
  return `${prefix}:project:${projectKey(session.header.cwd)}`;
11530
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
+ }
11531
11972
  async prepareFeedback(session, input) {
11532
11973
  const namespace = this.namespaceFor(session);
11533
- const previous = this.loadFeedbackDraft(namespace);
11974
+ const previous = input.newDraft ? null : this.loadFeedbackDraft(namespace);
11534
11975
  const draft = normalizeFeedbackDraft(input, previous);
11976
+ draft.aiPrepared = true;
11977
+ draft.diagnostics = await this.diagnosticReport(namespace);
11535
11978
  this.saveFeedbackDraft(namespace, draft);
11536
11979
  const feedbackUrl = feedbackDraftUrl(namespace, this.feedbackOrigin());
11537
11980
  return {
@@ -11540,7 +11983,7 @@ var StrataGateRuntime = class {
11540
11983
  submitted: false,
11541
11984
  namespace,
11542
11985
  feedbackUrl,
11543
- 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
11544
11987
 
11545
11988
  [\u6253\u5F00\u53CD\u9988\u8349\u7A3F](${feedbackUrl})`
11546
11989
  };
@@ -11645,46 +12088,13 @@ var StrataGateRuntime = class {
11645
12088
  items.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)));
11646
12089
  return { items, total: items.length };
11647
12090
  }
11648
- notePluginError(session, _error) {
11649
- if (AGENT_FEEDBACK_ENABLED && !this.closed) this.pendingFeedbackSuggestionSessions.add(String(session.id));
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" });
11650
12094
  }
11651
- takeFeedbackSuggestion(session, now = Date.now()) {
11652
- if (!AGENT_FEEDBACK_ENABLED) return "";
11653
- const sessionId = String(session.id);
11654
- if (!this.pendingFeedbackSuggestionSessions.delete(sessionId)) return "";
11655
- const last = this.lastFeedbackPromptAt();
11656
- if (last && Number.isFinite(Date.parse(last)) && now - Date.parse(last) < FEEDBACK_PROMPT_COOLDOWN_MS) return "";
11657
- const promptedAt = new Date(now).toISOString();
11658
- this.transientLastFeedbackPromptAt = promptedAt;
11659
- try {
11660
- if (this.config.database !== ":memory:") {
11661
- const metadata = new DshMetadataStore(this.config.database);
11662
- try {
11663
- metadata.setLastFeedbackPromptAt(promptedAt);
11664
- } finally {
11665
- metadata.close();
11666
- }
11667
- }
11668
- } catch (error) {
11669
- this.onIngestError(error);
11670
- }
11671
- 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.";
11672
- }
11673
- lastFeedbackPromptAt() {
11674
- if (this.transientLastFeedbackPromptAt) return this.transientLastFeedbackPromptAt;
11675
- if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
11676
- try {
11677
- const metadata = new DshMetadataStore(this.config.database);
11678
- try {
11679
- this.transientLastFeedbackPromptAt = metadata.lastFeedbackPromptAt();
11680
- return this.transientLastFeedbackPromptAt;
11681
- } finally {
11682
- metadata.close();
11683
- }
11684
- } catch (error) {
11685
- this.onIngestError(error);
11686
- return null;
11687
- }
12095
+ // Background failures remain queryable; ordinary retries never add model context.
12096
+ takeFeedbackSuggestion(_session, _now = Date.now()) {
12097
+ return "";
11688
12098
  }
11689
12099
  loadFeedbackDraft(namespace) {
11690
12100
  const cached = this.feedbackDrafts.get(namespace);
@@ -11694,7 +12104,7 @@ var StrataGateRuntime = class {
11694
12104
  try {
11695
12105
  const value = metadata.feedbackDraft(namespace);
11696
12106
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
11697
- const draft = normalizeFeedbackDraft(value);
12107
+ const draft = normalizeFeedbackDraft(value, value);
11698
12108
  this.feedbackDrafts.set(namespace, draft);
11699
12109
  return structuredClone(draft);
11700
12110
  } finally {
@@ -11702,14 +12112,15 @@ var StrataGateRuntime = class {
11702
12112
  }
11703
12113
  }
11704
12114
  saveFeedbackDraft(namespace, draft) {
11705
- this.feedbackDrafts.set(namespace, structuredClone(draft));
11706
- if (this.config.database === ":memory:") return;
11707
- const metadata = new DshMetadataStore(this.config.database);
11708
- try {
11709
- metadata.setFeedbackDraft(namespace, draft);
11710
- } finally {
11711
- 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
+ }
11712
12122
  }
12123
+ this.feedbackDrafts.set(namespace, structuredClone(draft));
11713
12124
  }
11714
12125
  async adminNamespaces() {
11715
12126
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
@@ -11964,7 +12375,7 @@ var StrataGateRuntime = class {
11964
12375
  } finally {
11965
12376
  if (owned) await memory.close();
11966
12377
  }
11967
- 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);
11968
12379
  if (controller.signal.aborted) return;
11969
12380
  const repeated = run.trace.filter((entry) => entry.tool === action.tool && JSON.stringify(entry.args) === JSON.stringify(action.args)).length;
11970
12381
  if (repeated >= 3) {
@@ -12058,7 +12469,7 @@ var StrataGateRuntime = class {
12058
12469
  }
12059
12470
  try {
12060
12471
  if (job.status === "extracting") {
12061
- 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);
12062
12473
  const responses2 = this.takeSuccessfulResponses();
12063
12474
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeExternalMemoryFallback(jobId, recovered));
12064
12475
  await this.persistExternalImportResponses(namespace, responses2);
@@ -12073,7 +12484,7 @@ var StrataGateRuntime = class {
12073
12484
  decision = await this.models.runDetached(`admin-import:${jobId}`, () => this.models.externalMemoryDecider({
12074
12485
  candidate: structuredClone(work.candidate),
12075
12486
  matches: structuredClone(work.matches)
12076
- }));
12487
+ }), void 0, namespace);
12077
12488
  responses = this.takeSuccessfulResponses();
12078
12489
  }
12079
12490
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeNextExternalMemoryImport(
@@ -12214,7 +12625,7 @@ var StrataGateRuntime = class {
12214
12625
  const remembered = this.workspaceNames.get(namespace);
12215
12626
  if (remembered) return remembered;
12216
12627
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
12217
- const database = new DatabaseSync3(this.config.database, { readOnly: true });
12628
+ const database = new DatabaseSync4(this.config.database, { readOnly: true });
12218
12629
  try {
12219
12630
  if (!database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_workspaces'").get()) {
12220
12631
  return null;
@@ -12344,7 +12755,7 @@ var StrataGateRuntime = class {
12344
12755
  if (!this.models.isReady(session)) {
12345
12756
  throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
12346
12757
  }
12347
- 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);
12348
12759
  await this.persistSuccessfulResponses(memory);
12349
12760
  const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
12350
12761
  if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
@@ -12719,6 +13130,12 @@ var StrataGateRuntime = class {
12719
13130
  }
12720
13131
  sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
12721
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
+ });
12722
13139
  return output;
12723
13140
  }
12724
13141
  requireBatch(session, batchId, operation) {
@@ -13308,6 +13725,7 @@ ${profile}
13308
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");
13309
13726
  }
13310
13727
  install(scoped, binding) {
13728
+ this.runtime.trackDiagnosticSession(String(binding.agent.id), binding.namespace);
13311
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");
13312
13730
  const modelTools = TOOLS.map((tool) => ({
13313
13731
  name: tool.name,
@@ -13566,6 +13984,11 @@ ${profile}
13566
13984
 
13567
13985
  // src/tools.ts
13568
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
13569
13992
  var jsonOutput = {
13570
13993
  schema: { type: "json" },
13571
13994
  render: (_args, value) => [{
@@ -13606,17 +14029,19 @@ function registerMemoryTools(ctx, runtime) {
13606
14029
  }));
13607
14030
  if (AGENT_FEEDBACK_ENABLED) ctx.tools.register(defineTool2({
13608
14031
  name: "feedback_prepare",
13609
- 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",
13610
14033
  parameters: {
13611
- title: { type: "string" },
13612
- description: { type: "string" },
13613
- reproduction: { type: "array", items: { type: "string" } },
13614
- expected: { type: "string" },
13615
- actual: { type: "string" },
13616
- 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" }
13617
14041
  },
13618
14042
  output: jsonOutput,
13619
14043
  execute: async (args, exec) => runtime.prepareFeedback(sessionOf(exec), {
14044
+ ...args.new_draft !== void 0 ? { newDraft: args.new_draft } : {},
13620
14045
  ...args.title !== void 0 ? { title: args.title } : {},
13621
14046
  ...args.description !== void 0 ? { description: args.description } : {},
13622
14047
  ...args.reproduction !== void 0 ? { reproduction: args.reproduction } : {},
@@ -13625,6 +14050,19 @@ function registerMemoryTools(ctx, runtime) {
13625
14050
  ...args.error_context !== void 0 ? { errorContext: args.error_context } : {}
13626
14051
  })
13627
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
+ }));
13628
14066
  ctx.tools.register(defineTool2({
13629
14067
  name: "memory_list_topics",
13630
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",
@@ -13803,7 +14241,7 @@ function registerMemoryTools(ctx, runtime) {
13803
14241
 
13804
14242
  // src/web.ts
13805
14243
  import { createHash as createHash4 } from "node:crypto";
13806
- import { createRequire as createRequire2 } from "node:module";
14244
+ import { createRequire as createRequire3 } from "node:module";
13807
14245
 
13808
14246
  // src/graph-clustering.ts
13809
14247
  import createGraph from "ngraph.graph";
@@ -13917,7 +14355,7 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
13917
14355
 
13918
14356
  // src/web.ts
13919
14357
  var LEGACY_THREAD_ID = "__legacy__";
13920
- var nodeRequire2 = createRequire2(import.meta.url);
14358
+ var nodeRequire2 = createRequire3(import.meta.url);
13921
14359
  function currentPluginVersion() {
13922
14360
  try {
13923
14361
  const value = nodeRequire2("../package.json");
@@ -14676,7 +15114,8 @@ async function feedback(runtime, req, url) {
14676
15114
  if (req.method === "GET") {
14677
15115
  const namespace2 = url.searchParams.get("namespace")?.trim() ?? "";
14678
15116
  if (!namespace2) throw new AdminHttpError(400, "namespace is required");
14679
- return runtime.adminFeedbackDraft(namespace2);
15117
+ const result = runtime.adminFeedbackDraft(namespace2);
15118
+ return { ...result, diagnostics: result.draft?.diagnostics ?? await runtime.diagnosticReport?.(namespace2) };
14680
15119
  }
14681
15120
  if (req.method !== "PUT") throw new AdminHttpError(405, "StrataGate feedback requires GET or PUT");
14682
15121
  let suppliedBody = req.body;
@@ -15328,6 +15767,19 @@ async function handleAdminRequest(runtime, req, res) {
15328
15767
  const path = url.pathname.replace(/\/$/, "");
15329
15768
  if (path === "/api/stratagate/feedback") {
15330
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) }));
15331
15783
  } else if (path === "/api/stratagate/profile") {
15332
15784
  sendJson(res, 200, await persistentProfile(runtime, req), true);
15333
15785
  } else if (path === "/api/stratagate/settings") {
@@ -15494,7 +15946,7 @@ import {
15494
15946
  writeFile
15495
15947
  } from "node:fs/promises";
15496
15948
  import { basename, dirname as dirname2, join } from "node:path";
15497
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
15949
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
15498
15950
  import { encodeSeqRanges, KNOWN_SESSION_EVENT_TYPES } from "@deepseek-ai/dsh-session";
15499
15951
  var LEGACY_EVENT_TYPE = "stratagate/memory-citations";
15500
15952
  var COMPAT_EVENT_TYPE = "feedback/record";
@@ -15715,7 +16167,7 @@ async function exists(path) {
15715
16167
  }
15716
16168
  async function publishExclusive(path, bytes) {
15717
16169
  await mkdir(dirname2(path), { recursive: true });
15718
- const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID4()}.tmp`);
16170
+ const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID6()}.tmp`);
15719
16171
  const handle = await open(temporary, "wx", 384);
15720
16172
  try {
15721
16173
  await handle.writeFile(bytes);
@@ -15840,15 +16292,8 @@ Memory writing:
15840
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.
15841
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.`;
15842
16294
  var FEEDBACK_PROTOCOL = `[StrataGate feedback policy]
15843
- The feedback_prepare tool creates a local draft for the user to review; it never submits the draft.
15844
-
15845
- - 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.
15846
- - 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.
15847
- - 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.
15848
- - 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.
15849
- - 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.
15850
- - 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.
15851
- - 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`;
15852
16297
  function renderError(error) {
15853
16298
  return error instanceof Error ? error.message : String(error);
15854
16299
  }
@@ -15929,27 +16374,48 @@ async function apply(ctx, config) {
15929
16374
  autoMemory = { name: "stratagate:auto-memory", text: text3 };
15930
16375
  } catch (error) {
15931
16376
  ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
15932
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16377
+ runtime.notePluginError(session, error, "autoContext");
15933
16378
  }
15934
16379
  try {
15935
16380
  const directory = await runtime.buildMemoryDirectory(session);
15936
16381
  if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
15937
16382
  } catch (error) {
15938
16383
  ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
15939
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16384
+ runtime.notePluginError(session, error, "directory");
15940
16385
  }
15941
16386
  if (autoMemory) contexts.push(autoMemory);
15942
- const feedbackSuggestion = AGENT_FEEDBACK_ENABLED ? runtime.takeFeedbackSuggestion(session) : "";
15943
- 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
+ })));
15944
16393
  return { ...assembled, contexts };
15945
16394
  });
15946
16395
  installRetrievalFinalization(ctx, runtime);
15947
16396
  registerMemoryTools(ctx, runtime);
15948
- ctx.on("tools/result", (exec, result) => {
15949
- if (!AGENT_FEEDBACK_ENABLED || !result.isError || !exec.agent) return;
15950
- if (exec.name === "feedback_prepare" || exec.name.startsWith("memory_")) {
15951
- 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;
16401
+ }
16402
+ const namespace = runtime.diagnosticNamespaceForSession(String(options.sessionId ?? ""));
16403
+ if (!namespace || options.purpose) {
16404
+ yield* next();
16405
+ return;
15952
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
+ );
15953
16419
  });
15954
16420
  const disposeAdminRoutes = registerAdminRoutes(ctx, runtime);
15955
16421
  ctx.on("session/event", (session, event) => runtime.acceptEvent(session, event));