stratagate-dsh 0.3.15 → 0.3.17

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
@@ -8241,6 +8241,7 @@ var StrataGate = class _StrataGate {
8241
8241
  // src/dsh-compatibility.ts
8242
8242
  import { readFileSync } from "node:fs";
8243
8243
  import { createRequire } from "node:module";
8244
+ import { satisfies } from "semver";
8244
8245
  var nodeRequire = createRequire(import.meta.url);
8245
8246
  var CORE_RUNTIME_PACKAGES = [
8246
8247
  "@deepseek-ai/cordis",
@@ -8260,93 +8261,26 @@ var SESSION_FORMAT_PACKAGES = [
8260
8261
  "@deepseek-ai/dsh-session-format-v0-to-v1"
8261
8262
  ];
8262
8263
  var RUNTIME_PACKAGES = [...CORE_RUNTIME_PACKAGES, ...SESSION_FORMAT_PACKAGES];
8263
- var SUPPORTED_RUNTIME_FAMILIES = [
8264
- {
8265
- cli: "0.1.2-rc.1",
8266
- versions: {
8267
- "@deepseek-ai/cordis": "4.0.2",
8268
- "@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
8269
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
8270
- "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
8271
- "@deepseek-ai/dsh-native-command": "0.1.2-rc.1",
8272
- "@deepseek-ai/dsh-session": "0.1.2-rc.1",
8273
- "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
8274
- "@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
8275
- "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
8276
- "@deepseek-ai/schemastery": "3.18.2"
8277
- }
8278
- },
8279
- {
8280
- cli: "0.1.5-rc.1",
8281
- versions: {
8282
- "@deepseek-ai/cordis": "4.0.2",
8283
- "@deepseek-ai/dsh-agent-default-model": "0.1.5-rc.2",
8284
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.2",
8285
- "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
8286
- "@deepseek-ai/dsh-native-command": "0.1.5-rc.2",
8287
- "@deepseek-ai/dsh-session": "0.1.5-rc.2",
8288
- "@deepseek-ai/dsh-settings": "0.1.5-rc.2",
8289
- "@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
8290
- "@deepseek-ai/dsh-tools": "0.1.5-rc.2",
8291
- "@deepseek-ai/schemastery": "3.18.2"
8292
- }
8293
- },
8294
- {
8295
- cli: "0.1.6-alpha.1",
8296
- versions: {
8297
- "@deepseek-ai/cordis": "4.0.2",
8298
- "@deepseek-ai/dsh-agent-default-model": "0.1.6-alpha.1",
8299
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.6-alpha.1",
8300
- "@deepseek-ai/dsh-llm": "0.1.6-alpha.1",
8301
- "@deepseek-ai/dsh-native-command": "0.1.6-alpha.1",
8302
- "@deepseek-ai/dsh-session": "0.1.6-alpha.1",
8303
- "@deepseek-ai/dsh-settings": "0.1.6-alpha.1",
8304
- "@deepseek-ai/dsh-system-prompt": "0.1.6-alpha.1",
8305
- "@deepseek-ai/dsh-tools": "0.1.6-alpha.1",
8306
- "@deepseek-ai/schemastery": "3.18.2"
8307
- }
8308
- },
8309
- {
8310
- cli: "0.1.7-rc.1",
8311
- versions: {
8312
- "@deepseek-ai/cordis": "4.0.4",
8313
- "@deepseek-ai/dsh-agent-default-model": "0.1.7-rc.1",
8314
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
8315
- "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
8316
- "@deepseek-ai/dsh-native-command": "0.1.7-rc.1",
8317
- "@deepseek-ai/dsh-session": "0.1.7-rc.1",
8318
- "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
8319
- "@deepseek-ai/dsh-system-prompt": "0.1.7-rc.1",
8320
- "@deepseek-ai/dsh-tools": "0.1.7-rc.1",
8321
- "@deepseek-ai/schemastery": "3.18.4"
8322
- }
8323
- },
8324
- {
8325
- cli: "0.1.7-rc.2",
8326
- versions: {
8327
- "@deepseek-ai/cordis": "4.0.4",
8328
- "@deepseek-ai/dsh-agent-default-model": "0.1.7-rc.2",
8329
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
8330
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
8331
- "@deepseek-ai/dsh-native-command": "0.1.7-rc.2",
8332
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
8333
- "@deepseek-ai/dsh-settings": "0.1.7-rc.2",
8334
- "@deepseek-ai/dsh-system-prompt": "0.1.7-rc.2",
8335
- "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
8336
- "@deepseek-ai/schemastery": "3.18.4"
8337
- }
8338
- }
8339
- ];
8340
8264
  var STRATAGATE_MESSAGE_SOURCE_KIND = "plugin:stratagate-memory";
8341
- function isDsh020Version(version) {
8342
- const match = /^0\.2\.0(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/.exec(version);
8343
- if (!match) return false;
8344
- const prerelease = match[1]?.split(".") ?? [];
8345
- const build = match[2]?.split(".") ?? [];
8346
- return [...prerelease, ...build].every((part) => part.length > 0) && prerelease.every((part) => !/^\d+$/.test(part) || part === "0" || !part.startsWith("0"));
8265
+ var DSH_RUNTIME_RANGE = "0.1.2-rc.1 || >=0.1.5-rc.2 <0.3.0-0";
8266
+ var MODERN_API_RANGE = ">=0.1.7-rc.1";
8267
+ var SESSION_FORMAT_RUNTIME_RANGE = ">=0.2.0-0";
8268
+ var SEMVER_OPTIONS = { includePrerelease: true };
8269
+ function assertDshMethods(scope, value, methods) {
8270
+ const target = value;
8271
+ const missing = methods.filter((method) => typeof target?.[method] !== "function");
8272
+ if (missing.length) {
8273
+ throw new Error(`StrataGate cannot use this DSH host: missing required interface ${missing.map((method) => `${scope}.${method}`).join(", ")}. Use a compatible host or plugin version.`);
8274
+ }
8275
+ }
8276
+ function assertDshHostContract(ctx) {
8277
+ assertDshMethods("tools", ctx.tools, ["register", "get"]);
8278
+ assertDshMethods("systemPrompt", ctx.systemPrompt, ["section"]);
8279
+ assertDshMethods("llm", ctx.llm, ["stream", "listProviders"]);
8280
+ assertDshMethods("agentDefaultModel", ctx.agentDefaultModel, ["currentSelection"]);
8347
8281
  }
8348
8282
  function buildDshMessageSource(version, form) {
8349
- const source = /^0\.1\.7(?:-|$)/.test(version) || isDsh020Version(version) ? { kind: STRATAGATE_MESSAGE_SOURCE_KIND } : { kind: "plugin", plugin: "stratagate-memory" };
8283
+ const source = satisfies(version, MODERN_API_RANGE, SEMVER_OPTIONS) ? { kind: STRATAGATE_MESSAGE_SOURCE_KIND } : { kind: "plugin", plugin: "stratagate-memory" };
8350
8284
  return form ? { ...source, form } : source;
8351
8285
  }
8352
8286
  function installedVersion(packageName) {
@@ -8377,17 +8311,17 @@ function isCompatiblePatch(version, major, minor, minimumPatch) {
8377
8311
  return !!match && Number(match[1]) === major && Number(match[2]) === minor && Number(match[3]) >= minimumPatch;
8378
8312
  }
8379
8313
  function classifyDshRuntime(packageVersions) {
8380
- const family = SUPPORTED_RUNTIME_FAMILIES.find(({ versions }) => CORE_RUNTIME_PACKAGES.every((name2) => packageVersions[name2] === versions[name2]));
8381
- if (family) return { cliVersion: family.cli, packageVersions };
8382
8314
  const dshVersion = packageVersions["@deepseek-ai/dsh-session"];
8383
8315
  const sameDshFamily = CORE_RUNTIME_PACKAGES.filter((name2) => name2.startsWith("@deepseek-ai/dsh-")).every((name2) => packageVersions[name2] === dshVersion);
8384
- const sameSessionFormatFamily = !isDsh020Version(dshVersion) || SESSION_FORMAT_PACKAGES.every((name2) => packageVersions[name2] === dshVersion);
8385
- if ((/^0\.1\.7-rc\.[1-9]\d*$/.test(dshVersion) || dshVersion === "0.1.7" || isDsh020Version(dshVersion)) && sameDshFamily && sameSessionFormatFamily && isCompatiblePatch(packageVersions["@deepseek-ai/cordis"], 4, 0, 4) && isCompatiblePatch(packageVersions["@deepseek-ai/schemastery"], 3, 18, 4)) {
8386
- return { cliVersion: dshVersion, packageVersions };
8316
+ const sameSessionFormatFamily = !satisfies(dshVersion, SESSION_FORMAT_RUNTIME_RANGE, SEMVER_OPTIONS) || SESSION_FORMAT_PACKAGES.every((name2) => packageVersions[name2] === dshVersion);
8317
+ const modernApi = satisfies(dshVersion, MODERN_API_RANGE, SEMVER_OPTIONS);
8318
+ const compatibleVendors = modernApi ? isCompatiblePatch(packageVersions["@deepseek-ai/cordis"], 4, 0, 4) && isCompatiblePatch(packageVersions["@deepseek-ai/schemastery"], 3, 18, 4) : packageVersions["@deepseek-ai/cordis"] === "4.0.2" && packageVersions["@deepseek-ai/schemastery"] === "3.18.2";
8319
+ if (satisfies(dshVersion, DSH_RUNTIME_RANGE, SEMVER_OPTIONS) && sameDshFamily && sameSessionFormatFamily && compatibleVendors) {
8320
+ return { cliVersion: dshVersion === "0.1.5-rc.2" ? "0.1.5-rc.1" : dshVersion, packageVersions };
8387
8321
  }
8388
8322
  const found = RUNTIME_PACKAGES.map((name2) => `${name2}@${packageVersions[name2]}`).join(", ");
8389
8323
  throw new Error(
8390
- `StrataGate cannot start because this DSH profile resolves an unsupported or mixed core runtime. Resolved: ${found}. Supported hosts are @deepseek-ai/dsh@0.1.2-rc.1 (internal DSH packages 0.1.2-rc.1) and @deepseek-ai/dsh@0.1.5-rc.1 (its real dependency tree uses internal DSH packages 0.1.5-rc.2), and @deepseek-ai/dsh@0.1.6-alpha.1 (internal DSH packages 0.1.6-alpha.1), and the coherent DSH 0.1.7 family from rc.1 through the 0.1.7 release, and the complete DSH 0.2.0 family (all alpha, beta, rc, and stable versions; >=0.2.0-0 <0.2.1-0) (Cordis 4.0.x from 4.0.4, Schemastery 3.18.x from 3.18.4). Reinstall or update stratagate-dsh through \`dsh plugin --profile <name> add <package>\` so the host supplies its peers.`
8324
+ `StrataGate cannot start because this DSH profile resolves an unsupported or mixed core runtime. Resolved: ${found}. Supported DSH versions: ${DSH_RUNTIME_RANGE} (including prereleases). Core DSH packages must share one version; DSH 0.2.0 and later also require matching Session Format packages. Modern hosts require Cordis 4.0.x from 4.0.4 and Schemastery 3.18.x from 3.18.4; older hosts use 4.0.2 and 3.18.2. Reinstall or update stratagate-dsh through \`dsh plugin --profile <name> add <package>\` so the host supplies its peers.`
8391
8325
  );
8392
8326
  }
8393
8327
  function assertCompatibleDshRuntime() {
@@ -8521,6 +8455,329 @@ function parseJsonResponse(value, requiredKeys = []) {
8521
8455
  throw new ModelJsonResponseError(void 0, { response: value });
8522
8456
  }
8523
8457
 
8458
+ // src/llm.ts
8459
+ import { randomUUID as randomUUID4 } from "node:crypto";
8460
+
8461
+ // src/diagnostics.ts
8462
+ import { randomUUID as randomUUID3 } from "node:crypto";
8463
+ import { createRequire as createRequire2 } from "node:module";
8464
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
8465
+ var DAY = 864e5;
8466
+ var VERSION = (() => {
8467
+ try {
8468
+ return String(createRequire2(import.meta.url)("../package.json").version);
8469
+ } catch {
8470
+ return "unknown";
8471
+ }
8472
+ })();
8473
+ var DIAGNOSTIC_STAGES = ["summarizer", "extractor", "projector", "graphProjector", "topicProjector", "externalMemoryExtractor", "externalMemoryDecider", "profileMaintenance", "dreamingStep", "chat", "ingestion", "autoContext", "directory", "tools", "frontend", "retrieval"];
8474
+ function diagnosticText(value, limit = 1500) {
8475
+ 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]$/, "");
8476
+ }
8477
+ function isAbortError(error) {
8478
+ return typeof error === "object" && error !== null && (error.name === "AbortError" || error.code === "ABORT_ERR");
8479
+ }
8480
+ function diagnosticCategory(error) {
8481
+ if (isAbortError(error)) return "cancelled";
8482
+ const text3 = String(error instanceof Error ? error.message : error ?? "");
8483
+ if (/timeout|timed out|超时/i.test(text3)) return "timeout";
8484
+ if (/429|rate.?limit/i.test(text3)) return "rate-limit";
8485
+ if (/401|403|unauthori|forbidden|credential/i.test(text3)) return "authentication";
8486
+ if (/token.*limit|truncat|max-tokens|context.*(window|length|limit)/i.test(text3)) return "token-limit";
8487
+ if (/json|schema|invalid|validation/i.test(text3)) return "validation";
8488
+ if (/sqlite|database|storage|EACCES|ENOSPC|write failed/i.test(text3)) return "storage";
8489
+ if (/abort|cancel/i.test(text3)) return "cancelled";
8490
+ if (/network|fetch|ECONN|ENOTFOUND/i.test(text3)) return "network";
8491
+ return "internal";
8492
+ }
8493
+ var SCHEMA2 = `
8494
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_records (
8495
+ id TEXT PRIMARY KEY, at TEXT NOT NULL, namespace TEXT NOT NULL, record_json TEXT NOT NULL
8496
+ ) STRICT;
8497
+ CREATE INDEX IF NOT EXISTS stratagate_dsh_diagnostic_scope ON stratagate_dsh_diagnostic_records(namespace, at);
8498
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_daily (
8499
+ namespace TEXT NOT NULL, day TEXT NOT NULL, stage TEXT NOT NULL, model TEXT NOT NULL,
8500
+ version TEXT NOT NULL, counters_json TEXT NOT NULL,
8501
+ PRIMARY KEY(namespace, day, stage, model, version)
8502
+ ) STRICT;
8503
+ CREATE TABLE IF NOT EXISTS stratagate_dsh_diagnostic_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) STRICT;
8504
+ `;
8505
+ var DiagnosticsRecorder = class {
8506
+ constructor(filename) {
8507
+ this.filename = filename;
8508
+ }
8509
+ filename;
8510
+ database;
8511
+ pending = [];
8512
+ timer;
8513
+ closed = false;
8514
+ dropped = 0;
8515
+ writeFailures = 0;
8516
+ startedAt = (/* @__PURE__ */ new Date()).toISOString();
8517
+ record(scope, counters, options = {}) {
8518
+ if (this.closed) return;
8519
+ const bounded2 = Object.fromEntries(Object.entries(counters).filter(([, n]) => Number.isFinite(n) && n >= 0));
8520
+ this.pending.push({
8521
+ id: `diag_${randomUUID3()}`,
8522
+ at: (/* @__PURE__ */ new Date()).toISOString(),
8523
+ namespace: scope.namespace,
8524
+ stage: scope.stage,
8525
+ model: diagnosticText(options.model ?? "", 160),
8526
+ version: VERSION,
8527
+ counters: bounded2,
8528
+ ...scope.taskId ? { taskId: scope.taskId } : {},
8529
+ ...options.error !== void 0 ? { category: diagnosticCategory(options.error), detail: diagnosticText(options.error) } : {}
8530
+ });
8531
+ if (this.pending.length > 256) {
8532
+ this.pending.shift();
8533
+ this.dropped++;
8534
+ }
8535
+ if (!this.timer) {
8536
+ this.timer = setTimeout(() => {
8537
+ this.timer = void 0;
8538
+ this.flush();
8539
+ }, 5e3);
8540
+ this.timer.unref?.();
8541
+ }
8542
+ }
8543
+ open() {
8544
+ if (this.database) return this.database;
8545
+ const db = new DatabaseSync2(this.filename);
8546
+ try {
8547
+ db.exec("PRAGMA busy_timeout=0");
8548
+ db.exec(SCHEMA2);
8549
+ db.prepare("INSERT OR IGNORE INTO stratagate_dsh_diagnostic_meta VALUES ('startedAt', ?)").run(this.startedAt);
8550
+ this.database = db;
8551
+ return db;
8552
+ } catch (error) {
8553
+ db.close();
8554
+ throw error;
8555
+ }
8556
+ }
8557
+ flush() {
8558
+ if (!this.pending.length) return;
8559
+ try {
8560
+ const db = this.open();
8561
+ db.exec("BEGIN IMMEDIATE");
8562
+ try {
8563
+ const get = db.prepare("SELECT counters_json FROM stratagate_dsh_diagnostic_daily WHERE namespace=? AND day=? AND stage=? AND model=? AND version=?");
8564
+ const put = db.prepare("INSERT OR REPLACE INTO stratagate_dsh_diagnostic_daily VALUES (?, ?, ?, ?, ?, ?)");
8565
+ const insert = db.prepare("INSERT INTO stratagate_dsh_diagnostic_records VALUES (?, ?, ?, ?)");
8566
+ for (const item of this.pending) {
8567
+ const keys = [item.namespace, item.at.slice(0, 10), item.stage, item.model, item.version];
8568
+ const row = get.get(...keys);
8569
+ const counters = row ? JSON.parse(row.counters_json) : {};
8570
+ for (const [key, value] of Object.entries(item.counters)) counters[key] = (counters[key] ?? 0) + value;
8571
+ put.run(...keys, JSON.stringify(counters));
8572
+ insert.run(item.id, item.at, item.namespace, JSON.stringify(item));
8573
+ }
8574
+ 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());
8575
+ db.prepare("DELETE FROM stratagate_dsh_diagnostic_daily WHERE day < ?").run(new Date(Date.now() - 90 * DAY).toISOString().slice(0, 10));
8576
+ 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)");
8577
+ db.exec("COMMIT");
8578
+ this.pending = [];
8579
+ } catch (error) {
8580
+ db.exec("ROLLBACK");
8581
+ throw error;
8582
+ }
8583
+ } catch {
8584
+ this.writeFailures++;
8585
+ }
8586
+ }
8587
+ snapshot(namespace, options = {}) {
8588
+ this.flush();
8589
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8590
+ const since = new Date(Date.parse(today) - 6 * DAY).toISOString().slice(0, 10);
8591
+ const totals = {};
8592
+ const stages = {};
8593
+ let startedAt = this.startedAt;
8594
+ let available = false;
8595
+ let rows = [];
8596
+ let records = [];
8597
+ let totalRecords = 0;
8598
+ let corruptRollups = 0;
8599
+ let corruptRecords = 0;
8600
+ let scannedRecords = 0;
8601
+ const offset = Math.max(0, Math.min(2e3, Math.floor(options.offset ?? 0)));
8602
+ const readCounters = (text3) => {
8603
+ try {
8604
+ const value = JSON.parse(text3);
8605
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid counters");
8606
+ const entries = Object.entries(value);
8607
+ if (entries.some(([, n]) => typeof n !== "number" || !Number.isFinite(n) || n < 0)) throw new Error("Invalid counter value");
8608
+ return Object.fromEntries(entries);
8609
+ } catch {
8610
+ corruptRollups++;
8611
+ return {};
8612
+ }
8613
+ };
8614
+ try {
8615
+ const db = this.open();
8616
+ available = true;
8617
+ startedAt = db.prepare("SELECT value FROM stratagate_dsh_diagnostic_meta WHERE key='startedAt'").get().value;
8618
+ 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);
8619
+ const where = options.id ? "namespace=? AND id=?" : "namespace=? AND at>=? AND json_extract(record_json, '$.category') IS NOT NULL";
8620
+ const values = options.id ? [namespace, options.id] : [namespace, since];
8621
+ totalRecords = Number(db.prepare(`SELECT count(*) AS count FROM stratagate_dsh_diagnostic_records WHERE ${where}`).get(...values).count);
8622
+ 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);
8623
+ scannedRecords = raw.length;
8624
+ for (const row of raw) {
8625
+ try {
8626
+ const item = JSON.parse(row.record_json);
8627
+ 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");
8628
+ records.push({
8629
+ id: item.id,
8630
+ at: item.at,
8631
+ namespace,
8632
+ stage: item.stage,
8633
+ model: diagnosticText(item.model, 160),
8634
+ version: item.version,
8635
+ counters: item.counters,
8636
+ ...typeof item.taskId === "string" ? { taskId: item.taskId } : {},
8637
+ ...typeof item.category === "string" ? { category: diagnosticText(item.category, 80) } : {},
8638
+ ...typeof item.detail === "string" ? { detail: diagnosticText(item.detail) } : {}
8639
+ });
8640
+ } catch {
8641
+ corruptRecords++;
8642
+ }
8643
+ }
8644
+ } catch {
8645
+ available = false;
8646
+ }
8647
+ const parsedRows = rows.slice(0, 2e3).map((row) => ({ ...row, counters: readCounters(row.counters_json) }));
8648
+ for (const row of parsedRows) {
8649
+ const counters = row.counters;
8650
+ const stage = stages[row.stage] ??= {};
8651
+ for (const [key, value] of Object.entries(counters)) {
8652
+ totals[key] = (totals[key] ?? 0) + value;
8653
+ stage[key] = (stage[key] ?? 0) + value;
8654
+ }
8655
+ }
8656
+ const routes = /* @__PURE__ */ new Map();
8657
+ for (const row of parsedRows) {
8658
+ const key = JSON.stringify([row.stage, row.model, row.version]);
8659
+ const route = routes.get(key) ?? { stage: row.stage, model: row.model, version: row.version, counters: {} };
8660
+ for (const [name2, n] of Object.entries(row.counters)) route.counters[name2] = (route.counters[name2] ?? 0) + n;
8661
+ routes.set(key, route);
8662
+ }
8663
+ const rates = Object.fromEntries(Object.entries(stages).map(([stage, counts]) => [stage, {
8664
+ sampledOperations: counts.operations ?? 0,
8665
+ operationFailureRate: counts.operations ? (counts.operationsFailed ?? 0) / counts.operations : null,
8666
+ firstAttemptSuccessRate: counts.operations ? (counts.firstAttemptSucceeded ?? 0) / counts.operations : null,
8667
+ recoveredOperationRate: counts.operations ? (counts.operationsRecovered ?? 0) / counts.operations : null,
8668
+ meanOperationMs: counts.operations ? (counts.operationMs ?? 0) / counts.operations : null,
8669
+ meanModelMs: counts.modelCalls ? (counts.modelMs ?? 0) / counts.modelCalls : null
8670
+ }]));
8671
+ const chatTokens = stages.chat?.totalTokens ?? 0;
8672
+ const backgroundTokens = (totals.totalTokens ?? 0) - chatTokens;
8673
+ const totalsComplete = available && !corruptRollups && !this.dropped && totals.modelCalls && totals.totalTokenCalls === totals.modelCalls && !this.pending.length && rows.length <= 2e3;
8674
+ return {
8675
+ schemaVersion: 1,
8676
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8677
+ since,
8678
+ through: today,
8679
+ timezone: "UTC",
8680
+ startedAt,
8681
+ coverage: {
8682
+ available,
8683
+ corruptRollups,
8684
+ corruptRecords,
8685
+ processStartedAt: this.startedAt,
8686
+ pendingRecords: this.pending.length,
8687
+ droppedRecords: this.dropped,
8688
+ writeFailures: this.writeFailures,
8689
+ rollupTruncated: rows.length > 2e3,
8690
+ historicalBackfill: false,
8691
+ rawRetentionDays: 7,
8692
+ rollupRetentionDays: 90,
8693
+ totalChatCostRatio: null,
8694
+ netSavings: null,
8695
+ 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"
8696
+ },
8697
+ totals,
8698
+ stages,
8699
+ rates,
8700
+ observedBackgroundTokenShare: totalsComplete && totals.totalTokens ? backgroundTokens / totals.totalTokens : null,
8701
+ 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",
8702
+ routes: [...routes.values()].slice(0, 40),
8703
+ omittedRoutes: Math.max(0, routes.size - 40),
8704
+ totalRecords,
8705
+ nextOffset: offset + scannedRecords < totalRecords ? offset + scannedRecords : null,
8706
+ records: records.map(({ namespace: _namespace, detail, ...record3 }) => ({ ...record3, ...options.details && detail ? { detail } : {} }))
8707
+ };
8708
+ }
8709
+ close() {
8710
+ if (this.closed) return;
8711
+ if (this.timer) clearTimeout(this.timer);
8712
+ this.timer = void 0;
8713
+ this.flush();
8714
+ this.closed = true;
8715
+ try {
8716
+ this.database?.close();
8717
+ } catch {
8718
+ }
8719
+ this.database = void 0;
8720
+ }
8721
+ };
8722
+ async function* observeModelStream(recorder, scope, options, next) {
8723
+ const start = Date.now();
8724
+ let usage;
8725
+ let finished = false;
8726
+ let failure;
8727
+ let cancelled = false;
8728
+ scope.calls = (scope.calls ?? 0) + 1;
8729
+ try {
8730
+ for await (const chunk of next()) {
8731
+ if (chunk.type === "usage") usage = chunk.usage;
8732
+ if (chunk.type === "finish") {
8733
+ finished = true;
8734
+ if (chunk.reason.kind === "error" || chunk.reason.kind === "aborted") {
8735
+ cancelled = chunk.reason.kind === "aborted";
8736
+ failure = cancelled ? Object.assign(new Error(chunk.reason.failure?.message ?? "aborted"), { name: "AbortError" }) : chunk.reason.failure.message;
8737
+ } else if (chunk.reason.kind === "max-tokens") failure = "max-tokens";
8738
+ }
8739
+ yield chunk;
8740
+ }
8741
+ } catch (error) {
8742
+ failure = error;
8743
+ cancelled = isAbortError(error);
8744
+ throw error;
8745
+ } finally {
8746
+ const valid = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
8747
+ const known = usage && valid(usage.inputTokens) && valid(usage.outputTokens);
8748
+ const counters = {
8749
+ modelCalls: 1,
8750
+ modelFailed: failure !== void 0 && !cancelled ? 1 : 0,
8751
+ modelCancelled: cancelled || !finished && failure === void 0 ? 1 : 0,
8752
+ modelMs: Date.now() - start,
8753
+ usageCalls: known ? 1 : 0,
8754
+ missingUsageCalls: known ? 0 : 1
8755
+ };
8756
+ if (scope.stage === "chat" && scope.memoryInputEstimate !== void 0) {
8757
+ counters.memoryInputEstimate = scope.memoryInputEstimate;
8758
+ counters.memoryInputEstimateCalls = 1;
8759
+ }
8760
+ if (known && usage) {
8761
+ counters.inputTokens = usage.inputTokens;
8762
+ counters.outputTokens = usage.outputTokens;
8763
+ if (valid(usage.cacheReadTokens)) {
8764
+ counters.cacheReadTokens = usage.cacheReadTokens;
8765
+ counters.cacheReadKnownCalls = 1;
8766
+ }
8767
+ if (valid(usage.cacheWriteTokens)) {
8768
+ counters.cacheWriteTokens = usage.cacheWriteTokens;
8769
+ counters.cacheWriteKnownCalls = 1;
8770
+ }
8771
+ if (valid(usage.reasoningTokens)) counters.reasoningTokens = usage.reasoningTokens;
8772
+ if (valid(usage.totalTokens) || valid(usage.cacheReadTokens) && valid(usage.cacheWriteTokens)) {
8773
+ counters.totalTokens = valid(usage.totalTokens) ? usage.totalTokens : counters.inputTokens + counters.outputTokens + counters.cacheReadTokens + counters.cacheWriteTokens;
8774
+ counters.totalTokenCalls = 1;
8775
+ }
8776
+ }
8777
+ recorder.record(scope, counters, { model: `${options.provider}/${options.model}`, ...failure !== void 0 ? { error: failure } : {} });
8778
+ }
8779
+ }
8780
+
8524
8781
  // src/llm.ts
8525
8782
  var ELEMENT_TYPES2 = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
8526
8783
  var CRITICALITIES = /* @__PURE__ */ new Set(["routine", "preference", "identity", "safety"]);
@@ -9085,6 +9342,16 @@ var DshModelBridge = class {
9085
9342
  ctx;
9086
9343
  config;
9087
9344
  liveStructuredReasoningEffort;
9345
+ diagnosticScopes = new AsyncLocalStorage();
9346
+ diagnostics;
9347
+ diagnosticNamespace = () => "@shared";
9348
+ setDiagnostics(recorder, namespace) {
9349
+ this.diagnostics = recorder;
9350
+ this.diagnosticNamespace = namespace;
9351
+ }
9352
+ currentDiagnosticScope() {
9353
+ return this.diagnosticScopes.getStore();
9354
+ }
9088
9355
  contextWindows = /* @__PURE__ */ new Map();
9089
9356
  sessions = new AsyncLocalStorage();
9090
9357
  successfulResponses = [];
@@ -9117,8 +9384,8 @@ var DshModelBridge = class {
9117
9384
  run(session, operation) {
9118
9385
  return this.sessions.run({ session, sessionId: session.id }, operation);
9119
9386
  }
9120
- runDetached(sessionId, operation, signal) {
9121
- return this.sessions.run({ sessionId, ...signal ? { signal } : {} }, operation);
9387
+ runDetached(sessionId, operation, signal, namespace) {
9388
+ return this.sessions.run({ sessionId, ...signal ? { signal } : {}, ...namespace ? { namespace } : {} }, operation);
9122
9389
  }
9123
9390
  dreamingModelOptions() {
9124
9391
  return { ...this.resolveRoute(), maxTokens: TOPIC_OUTPUT_TOKEN_BUDGET };
@@ -9399,6 +9666,40 @@ Use project scope for repository decisions, user scope for stable preferences/id
9399
9666
  return proposed;
9400
9667
  }
9401
9668
  async callStructured(kind, system, payload, validate, topicMetadata) {
9669
+ const execution = this.sessions.getStore();
9670
+ const scope = {
9671
+ namespace: execution?.namespace ?? this.diagnosticNamespace(execution?.session, String(execution?.sessionId ?? "")),
9672
+ stage: kind,
9673
+ taskId: `task_${randomUUID4()}`,
9674
+ calls: 0
9675
+ };
9676
+ const start = Date.now();
9677
+ return this.diagnosticScopes.run(scope, async () => {
9678
+ try {
9679
+ const result = await this.callStructuredInner(kind, system, payload, validate, topicMetadata);
9680
+ this.diagnostics?.record(scope, {
9681
+ operations: 1,
9682
+ operationsSucceeded: 1,
9683
+ operationsFailed: 0,
9684
+ operationsRecovered: (scope.calls ?? 0) > 1 ? 1 : 0,
9685
+ firstAttemptSucceeded: (scope.calls ?? 0) === 1 ? 1 : 0,
9686
+ retries: Math.max(0, (scope.calls ?? 0) - 1),
9687
+ operationMs: Date.now() - start
9688
+ });
9689
+ return result;
9690
+ } catch (error) {
9691
+ this.diagnostics?.record(scope, {
9692
+ operations: 1,
9693
+ operationsSucceeded: 0,
9694
+ operationsFailed: 1,
9695
+ retries: Math.max(0, (scope.calls ?? 0) - 1),
9696
+ operationMs: Date.now() - start
9697
+ }, { error });
9698
+ throw error;
9699
+ }
9700
+ });
9701
+ }
9702
+ async callStructuredInner(kind, system, payload, validate, topicMetadata) {
9402
9703
  const execution = this.sessions.getStore();
9403
9704
  if (!execution) throw new Error("StrataGate model callback ran without an execution context");
9404
9705
  const baseRoute = this.resolveRoute(execution.session);
@@ -9633,6 +9934,8 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
9633
9934
  return parsed;
9634
9935
  } catch (error) {
9635
9936
  if (!(error instanceof ModelJsonResponseError) && !(isTopic && error instanceof TopicProjectionError)) throw error;
9937
+ const scope = this.diagnosticScopes.getStore();
9938
+ if (scope) this.diagnostics?.record(scope, { validationFailures: 1 }, { error });
9636
9939
  let retryError = error;
9637
9940
  if (isTopic && error instanceof TopicProjectionError) {
9638
9941
  const safe = new TopicProjectionError(error.diagnostics.category, error.diagnostics.reason, { ...topicDiagnostics, ...error.diagnostics });
@@ -9734,7 +10037,9 @@ Correct the previous Topic output using these validation errors: ${JSON.stringif
9734
10037
  try {
9735
10038
  await Promise.race([
9736
10039
  (async () => {
9737
- for await (const chunk of this.ctx.llm.stream(timedRequest)) assembler.push(chunk);
10040
+ const scope = this.diagnosticScopes.getStore();
10041
+ const stream = this.diagnostics && scope ? observeModelStream(this.diagnostics, scope, timedRequest, () => this.ctx.llm.stream(timedRequest)) : this.ctx.llm.stream(timedRequest);
10042
+ for await (const chunk of stream) assembler.push(chunk);
9738
10043
  })(),
9739
10044
  timeout,
9740
10045
  aborted
@@ -9776,7 +10081,7 @@ function isOffRejection(error) {
9776
10081
  }
9777
10082
 
9778
10083
  // src/metadata.ts
9779
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
10084
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
9780
10085
  var TOPIC_BOOTSTRAP_WINDOW_MS = 10 * 6e4;
9781
10086
  var TOPIC_BOOTSTRAP_WINDOW_CALLS = 2;
9782
10087
  var METADATA_SCHEMA = `
@@ -9799,7 +10104,7 @@ CREATE TABLE IF NOT EXISTS stratagate_dsh_feedback_drafts (
9799
10104
  ) STRICT;
9800
10105
  `;
9801
10106
  function dropLegacyAgentMemoriesTable(filename) {
9802
- const database = new DatabaseSync2(filename);
10107
+ const database = new DatabaseSync3(filename);
9803
10108
  try {
9804
10109
  const legacyTable = database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_agent_memories'").get();
9805
10110
  if (!legacyTable) return;
@@ -9812,7 +10117,7 @@ function dropLegacyAgentMemoriesTable(filename) {
9812
10117
  var DshMetadataStore = class {
9813
10118
  database;
9814
10119
  constructor(filename) {
9815
- this.database = new DatabaseSync2(filename);
10120
+ this.database = new DatabaseSync3(filename);
9816
10121
  this.database.exec(METADATA_SCHEMA);
9817
10122
  }
9818
10123
  /** Database-wide, durable reservation before any historical model attempt.
@@ -9942,15 +10247,10 @@ var DshMetadataStore = class {
9942
10247
  };
9943
10248
 
9944
10249
  // 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
10250
+ import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
9951
10251
  import { existsSync } from "node:fs";
9952
10252
  import { dirname, resolve as resolve2 } from "node:path";
9953
- import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
10253
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
9954
10254
  import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
9955
10255
  import { openNativePath } from "@deepseek-ai/dsh-native-command";
9956
10256
  import { validateArgs as validateArgs2 } from "@deepseek-ai/dsh-tools";
@@ -10507,7 +10807,6 @@ function graphProjectionCanRun(job, now = Date.now()) {
10507
10807
  var AUTO_EVENT_LIMIT = 4;
10508
10808
  var AUTO_ELEMENT_LIMIT = 4;
10509
10809
  var AUTO_MEMORY_TOKEN_BUDGET = 900;
10510
- var FEEDBACK_PROMPT_COOLDOWN_MS = 5 * 24 * 60 * 60 * 1e3;
10511
10810
  function projectKey(cwd) {
10512
10811
  const canonical2 = resolve2(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
10513
10812
  return createHash3("sha256").update(canonical2).digest("hex").slice(0, 20);
@@ -10590,6 +10889,8 @@ function normalizeFeedbackDraft(input, previous) {
10590
10889
  expected: replacesStructuredBody ? "" : has("expected") ? feedbackText(input.expected, 1e4) : previous?.expected ?? "",
10591
10890
  actual: replacesStructuredBody ? "" : has("actual") ? feedbackText(input.actual, 1e4) : previous?.actual ?? "",
10592
10891
  errorContext: replacesStructuredBody ? "" : has("errorContext") ? feedbackText(input.errorContext, 2e4) : previous?.errorContext ?? "",
10892
+ ...previous?.aiPrepared ? { aiPrepared: true } : {},
10893
+ ...previous?.diagnostics ? { diagnostics: previous.diagnostics } : {},
10593
10894
  ...bodyMarkdown ? { bodyMarkdown } : {},
10594
10895
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10595
10896
  };
@@ -10613,6 +10914,8 @@ var StrataGateRuntime = class {
10613
10914
  this.flushNativeSession = flushNativeSession;
10614
10915
  this.feedbackOrigin = feedbackOrigin;
10615
10916
  this.openPath = openPath;
10917
+ this.diagnostics = new DiagnosticsRecorder(config.database);
10918
+ this.models.setDiagnostics?.(this.diagnostics, (session, sessionId) => session ? this.namespaceFor(session) : this.diagnosticNamespaceForSession(sessionId ?? "") ?? "@shared");
10616
10919
  this.blockTurnSize = config.blockTurnSize;
10617
10920
  this.blockDecayLambda = config.blockDecayLambda;
10618
10921
  this.agentMemoryRetrievalWeight = config.agentMemoryRetrievalWeight ?? 1;
@@ -10626,6 +10929,9 @@ var StrataGateRuntime = class {
10626
10929
  flushNativeSession;
10627
10930
  feedbackOrigin;
10628
10931
  openPath;
10932
+ diagnostics;
10933
+ diagnosticSessionNamespaces = /* @__PURE__ */ new Map();
10934
+ diagnosticMemoryEstimates = /* @__PURE__ */ new Map();
10629
10935
  profileStorage;
10630
10936
  folder = new TurnFolder();
10631
10937
  spaces = /* @__PURE__ */ new Map();
@@ -10642,11 +10948,10 @@ var StrataGateRuntime = class {
10642
10948
  externalImportRuns = /* @__PURE__ */ new Map();
10643
10949
  dreamingRuns = /* @__PURE__ */ new Map();
10644
10950
  dreamingControllers = /* @__PURE__ */ new Map();
10645
- dreamingOwner = randomUUID3();
10951
+ dreamingOwner = randomUUID5();
10646
10952
  dreamingHost;
10647
10953
  adminJobRetryRuns = /* @__PURE__ */ new Map();
10648
10954
  feedbackDrafts = /* @__PURE__ */ new Map();
10649
- pendingFeedbackSuggestionSessions = /* @__PURE__ */ new Set();
10650
10955
  pendingTurns = /* @__PURE__ */ new Map();
10651
10956
  drainTimers = /* @__PURE__ */ new Map();
10652
10957
  drainBackoffMs = /* @__PURE__ */ new Map();
@@ -10666,7 +10971,6 @@ var StrataGateRuntime = class {
10666
10971
  blockTurnSize;
10667
10972
  blockDecayLambda;
10668
10973
  agentMemoryRetrievalWeight;
10669
- transientLastFeedbackPromptAt = null;
10670
10974
  get agentMemoryEnabled() {
10671
10975
  return this.config.agentMemoryEnabled !== false;
10672
10976
  }
@@ -10827,7 +11131,9 @@ var StrataGateRuntime = class {
10827
11131
  }
10828
11132
  }
10829
11133
  return resumed2;
10830
- }
11134
+ },
11135
+ void 0,
11136
+ namespace
10831
11137
  );
10832
11138
  await this.persistSuccessfulResponses(memory);
10833
11139
  const sessionsToSync = /* @__PURE__ */ new Set();
@@ -11200,6 +11506,12 @@ var StrataGateRuntime = class {
11200
11506
  }
11201
11507
  const memory = await this.space(session);
11202
11508
  const assessment = memory.assessRetrieval(input, new Set(batch.refs.keys()));
11509
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage: "retrieval" }, {
11510
+ assessments: 1,
11511
+ sufficient: assessment.verdict === "sufficient" ? 1 : 0,
11512
+ partial: assessment.verdict === "partial" ? 1 : 0,
11513
+ wrong: assessment.verdict === "wrong" ? 1 : 0
11514
+ });
11203
11515
  batch.assessment = assessment;
11204
11516
  return {
11205
11517
  batchId: batch.id,
@@ -11516,10 +11828,10 @@ var StrataGateRuntime = class {
11516
11828
  await Promise.allSettled(this.externalImportRuns.values());
11517
11829
  await Promise.allSettled(this.dreamingRuns.values());
11518
11830
  await this.dreamingHost?.close();
11831
+ this.diagnostics.close();
11519
11832
  await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
11520
11833
  if (this.profileStorage) await this.profileStorage.close();
11521
11834
  this.sessionsById.clear();
11522
- this.pendingFeedbackSuggestionSessions.clear();
11523
11835
  if (flushError !== void 0) throw flushError;
11524
11836
  }
11525
11837
  namespaceFor(session) {
@@ -11528,10 +11840,75 @@ var StrataGateRuntime = class {
11528
11840
  if (this.config.namespaceMode === "session") return `${prefix}:session:${String(session.id)}`;
11529
11841
  return `${prefix}:project:${projectKey(session.header.cwd)}`;
11530
11842
  }
11843
+ trackDiagnosticSession(sessionId, namespace, memoryInputEstimate) {
11844
+ this.diagnosticSessionNamespaces.set(sessionId, namespace);
11845
+ if (memoryInputEstimate !== void 0) this.diagnosticMemoryEstimates.set(sessionId, memoryInputEstimate);
11846
+ if (this.diagnosticSessionNamespaces.size > 256) {
11847
+ const oldest = this.diagnosticSessionNamespaces.keys().next().value;
11848
+ this.diagnosticSessionNamespaces.delete(oldest);
11849
+ this.diagnosticMemoryEstimates.delete(oldest);
11850
+ }
11851
+ }
11852
+ diagnosticMemoryEstimate(sessionId) {
11853
+ return this.diagnosticMemoryEstimates.get(sessionId);
11854
+ }
11855
+ diagnosticNamespaceForSession(sessionId) {
11856
+ const session = this.knownSessions.get(sessionId)?.deref();
11857
+ return session ? this.namespaceFor(session) : this.diagnosticSessionNamespaces.get(sessionId);
11858
+ }
11859
+ async diagnosticReport(namespace, options = {}) {
11860
+ const collected = this.diagnostics.snapshot(namespace, options);
11861
+ const shared = this.diagnostics.snapshot("@shared");
11862
+ const base = { ...collected, sharedOrUnattributed: {
11863
+ stages: shared.stages,
11864
+ 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"
11865
+ } };
11866
+ try {
11867
+ const snapshot = await this.adminSnapshot(namespace);
11868
+ if (!snapshot) return { ...base, pipeline: { available: false } };
11869
+ const stages = {
11870
+ summarizer: snapshot.summaryJobs,
11871
+ extractor: snapshot.extractionJobs,
11872
+ graphProjector: snapshot.graphProjectionJobs,
11873
+ topicProjector: (snapshot.memoryTopicState?.jobs ?? []).filter((job) => !job.superseded),
11874
+ externalMemoryImport: snapshot.externalMemoryImportJobs
11875
+ };
11876
+ const pipeline = Object.fromEntries(Object.entries(stages).map(([stage, jobs]) => [stage, {
11877
+ total: jobs.length,
11878
+ failed: jobs.filter((j) => j.status === "failed").length,
11879
+ succeeded: jobs.filter((j) => ["succeeded", "completed"].includes(j.status)).length,
11880
+ skipped: jobs.filter((j) => j.status === "skipped").length,
11881
+ pending: jobs.filter((j) => ["pending", "running", "processing", "extracting", "deciding"].includes(j.status)).length,
11882
+ oldestPendingUpdatedAt: jobs.filter((j) => ["pending", "running", "processing"].includes(j.status)).map((j) => j.updatedAt).sort()[0] ?? null
11883
+ }]));
11884
+ const adoptions = snapshot.usageReceipts.filter((r) => r.createdAt >= collected.since);
11885
+ const adopted = adoptions.filter((r) => r.eventIds.length > 0 || r.elementIds.length > 0);
11886
+ const sampledBlocks = snapshot.blocks.slice(-64);
11887
+ const layerTokenEstimates = Object.fromEntries([0, 1, 2, 3, 4, 5].map((level) => [
11888
+ `L${level}`,
11889
+ sampledBlocks.reduce((sum, block) => sum + estimateTokens(renderBlock(block, level)), 0)
11890
+ ]));
11891
+ return { ...base, pipeline: { available: true, stages: pipeline }, benefits: {
11892
+ retrievalReceipts: adoptions.length,
11893
+ adoptionReceipts: adopted.length,
11894
+ adoptedEvents: adopted.reduce((n, r) => n + r.eventIds.length, 0),
11895
+ blocks: snapshot.blocks.length,
11896
+ events: snapshot.events.length,
11897
+ graphNodes: snapshot.graphNodes.length,
11898
+ sampledBlocks: sampledBlocks.length,
11899
+ layerTokenEstimates,
11900
+ 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"
11901
+ } };
11902
+ } catch {
11903
+ return { ...base, pipeline: { available: false } };
11904
+ }
11905
+ }
11531
11906
  async prepareFeedback(session, input) {
11532
11907
  const namespace = this.namespaceFor(session);
11533
- const previous = this.loadFeedbackDraft(namespace);
11908
+ const previous = input.newDraft ? null : this.loadFeedbackDraft(namespace);
11534
11909
  const draft = normalizeFeedbackDraft(input, previous);
11910
+ draft.aiPrepared = true;
11911
+ draft.diagnostics = await this.diagnosticReport(namespace);
11535
11912
  this.saveFeedbackDraft(namespace, draft);
11536
11913
  const feedbackUrl = feedbackDraftUrl(namespace, this.feedbackOrigin());
11537
11914
  return {
@@ -11540,7 +11917,7 @@ var StrataGateRuntime = class {
11540
11917
  submitted: false,
11541
11918
  namespace,
11542
11919
  feedbackUrl,
11543
- message: `\u53CD\u9988\u8349\u7A3F\u5DF2\u7ECF\u51C6\u5907\u597D\u4E86\uFF0C\u8FD8\u6CA1\u6709\u63D0\u4EA4\u5230 GitHub\u3002
11920
+ message: `\u53CD\u9988\u8349\u7A3F\u5DF2\u4FDD\u5B58\u5728\u672C\u5730\uFF0C\u90AE\u4EF6\u5C1A\u672A\u53D1\u9001\u3002
11544
11921
 
11545
11922
  [\u6253\u5F00\u53CD\u9988\u8349\u7A3F](${feedbackUrl})`
11546
11923
  };
@@ -11645,46 +12022,13 @@ var StrataGateRuntime = class {
11645
12022
  items.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)));
11646
12023
  return { items, total: items.length };
11647
12024
  }
11648
- notePluginError(session, _error) {
11649
- if (AGENT_FEEDBACK_ENABLED && !this.closed) this.pendingFeedbackSuggestionSessions.add(String(session.id));
12025
+ notePluginError(session, error, stage = "ingestion") {
12026
+ if (this.closed) return;
12027
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage }, { errors: 1 }, { error: error ?? "internal failure" });
11650
12028
  }
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
- }
12029
+ // Background failures remain queryable; ordinary retries never add model context.
12030
+ takeFeedbackSuggestion(_session, _now = Date.now()) {
12031
+ return "";
11688
12032
  }
11689
12033
  loadFeedbackDraft(namespace) {
11690
12034
  const cached = this.feedbackDrafts.get(namespace);
@@ -11694,7 +12038,7 @@ var StrataGateRuntime = class {
11694
12038
  try {
11695
12039
  const value = metadata.feedbackDraft(namespace);
11696
12040
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
11697
- const draft = normalizeFeedbackDraft(value);
12041
+ const draft = normalizeFeedbackDraft(value, value);
11698
12042
  this.feedbackDrafts.set(namespace, draft);
11699
12043
  return structuredClone(draft);
11700
12044
  } finally {
@@ -11702,14 +12046,15 @@ var StrataGateRuntime = class {
11702
12046
  }
11703
12047
  }
11704
12048
  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();
12049
+ if (this.config.database !== ":memory:") {
12050
+ const metadata = new DshMetadataStore(this.config.database);
12051
+ try {
12052
+ metadata.setFeedbackDraft(namespace, draft);
12053
+ } finally {
12054
+ metadata.close();
12055
+ }
11712
12056
  }
12057
+ this.feedbackDrafts.set(namespace, structuredClone(draft));
11713
12058
  }
11714
12059
  async adminNamespaces() {
11715
12060
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
@@ -11964,7 +12309,7 @@ var StrataGateRuntime = class {
11964
12309
  } finally {
11965
12310
  if (owned) await memory.close();
11966
12311
  }
11967
- const action = await this.models.runDetached(`dreaming:${run.id}`, () => this.models.dreamingStep(payload), controller.signal);
12312
+ const action = await this.models.runDetached(`dreaming:${run.id}`, () => this.models.dreamingStep(payload), controller.signal, namespace);
11968
12313
  if (controller.signal.aborted) return;
11969
12314
  const repeated = run.trace.filter((entry) => entry.tool === action.tool && JSON.stringify(entry.args) === JSON.stringify(action.args)).length;
11970
12315
  if (repeated >= 3) {
@@ -12058,7 +12403,7 @@ var StrataGateRuntime = class {
12058
12403
  }
12059
12404
  try {
12060
12405
  if (job.status === "extracting") {
12061
- const recovered = await this.models.runDetached(`admin-import-recovery:${jobId}`, () => this.models.externalMemoryExtractor({ text: job.text, importedAt: job.importedAt }));
12406
+ const recovered = await this.models.runDetached(`admin-import-recovery:${jobId}`, () => this.models.externalMemoryExtractor({ text: job.text, importedAt: job.importedAt }), void 0, namespace);
12062
12407
  const responses2 = this.takeSuccessfulResponses();
12063
12408
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeExternalMemoryFallback(jobId, recovered));
12064
12409
  await this.persistExternalImportResponses(namespace, responses2);
@@ -12073,7 +12418,7 @@ var StrataGateRuntime = class {
12073
12418
  decision = await this.models.runDetached(`admin-import:${jobId}`, () => this.models.externalMemoryDecider({
12074
12419
  candidate: structuredClone(work.candidate),
12075
12420
  matches: structuredClone(work.matches)
12076
- }));
12421
+ }), void 0, namespace);
12077
12422
  responses = this.takeSuccessfulResponses();
12078
12423
  }
12079
12424
  await this.retryExternalImportWrite(namespace, (memory) => memory.completeNextExternalMemoryImport(
@@ -12214,7 +12559,7 @@ var StrataGateRuntime = class {
12214
12559
  const remembered = this.workspaceNames.get(namespace);
12215
12560
  if (remembered) return remembered;
12216
12561
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
12217
- const database = new DatabaseSync3(this.config.database, { readOnly: true });
12562
+ const database = new DatabaseSync4(this.config.database, { readOnly: true });
12218
12563
  try {
12219
12564
  if (!database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'stratagate_dsh_workspaces'").get()) {
12220
12565
  return null;
@@ -12344,7 +12689,7 @@ var StrataGateRuntime = class {
12344
12689
  if (!this.models.isReady(session)) {
12345
12690
  throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
12346
12691
  }
12347
- const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
12692
+ const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry, void 0, namespace);
12348
12693
  await this.persistSuccessfulResponses(memory);
12349
12694
  const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
12350
12695
  if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
@@ -12719,6 +13064,12 @@ var StrataGateRuntime = class {
12719
13064
  }
12720
13065
  sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
12721
13066
  this.latestBatchIds.set(key, id);
13067
+ this.diagnostics.record({ namespace: this.namespaceFor(session), stage: "retrieval" }, {
13068
+ batches: 1,
13069
+ emptyBatches: evidence.length === 0 ? 1 : 0,
13070
+ returnedRefs: refs.size,
13071
+ resultTokensEstimate: estimateTokens(JSON.stringify(output))
13072
+ });
12722
13073
  return output;
12723
13074
  }
12724
13075
  requireBatch(session, batchId, operation) {
@@ -13308,6 +13659,7 @@ ${profile}
13308
13659
  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
13660
  }
13310
13661
  install(scoped, binding) {
13662
+ this.runtime.trackDiagnosticSession(String(binding.agent.id), binding.namespace);
13311
13663
  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
13664
  const modelTools = TOOLS.map((tool) => ({
13313
13665
  name: tool.name,
@@ -13566,6 +13918,11 @@ ${profile}
13566
13918
 
13567
13919
  // src/tools.ts
13568
13920
  import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
13921
+
13922
+ // src/feedback-policy.ts
13923
+ var AGENT_FEEDBACK_ENABLED = true;
13924
+
13925
+ // src/tools.ts
13569
13926
  var jsonOutput = {
13570
13927
  schema: { type: "json" },
13571
13928
  render: (_args, value) => [{
@@ -13606,17 +13963,19 @@ function registerMemoryTools(ctx, runtime) {
13606
13963
  }));
13607
13964
  if (AGENT_FEEDBACK_ENABLED) ctx.tools.register(defineTool2({
13608
13965
  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.',
13966
+ 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
13967
  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" }
13968
+ new_draft: { type: "boolean", description: "\u65B0\u95EE\u9898\u4E3A true\uFF1B\u4FEE\u6539\u5DF2\u6709\u8349\u7A3F\u65F6\u7701\u7565\u3002" },
13969
+ title: { type: "string", description: "\u7B80\u77ED\u95EE\u9898\u6807\u9898\u3002" },
13970
+ description: { type: "string", description: "\u5DF2\u77E5\u95EE\u9898\u3001\u5EFA\u8BAE\u6216\u7591\u95EE\u3002" },
13971
+ reproduction: { type: "array", items: { type: "string" }, description: "\u5B9E\u9645\u53D1\u751F\u8FC7\u7684\u590D\u73B0\u6B65\u9AA4\u3002" },
13972
+ expected: { type: "string", description: "\u5DF2\u77E5\u9884\u671F\u7ED3\u679C\u3002" },
13973
+ actual: { type: "string", description: "\u5B9E\u9645\u89C2\u5BDF\u5230\u7684\u7ED3\u679C\u3002" },
13974
+ 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
13975
  },
13618
13976
  output: jsonOutput,
13619
13977
  execute: async (args, exec) => runtime.prepareFeedback(sessionOf(exec), {
13978
+ ...args.new_draft !== void 0 ? { newDraft: args.new_draft } : {},
13620
13979
  ...args.title !== void 0 ? { title: args.title } : {},
13621
13980
  ...args.description !== void 0 ? { description: args.description } : {},
13622
13981
  ...args.reproduction !== void 0 ? { reproduction: args.reproduction } : {},
@@ -13625,6 +13984,19 @@ function registerMemoryTools(ctx, runtime) {
13625
13984
  ...args.error_context !== void 0 ? { errorContext: args.error_context } : {}
13626
13985
  })
13627
13986
  }));
13987
+ if (AGENT_FEEDBACK_ENABLED) ctx.tools.register(defineTool2({
13988
+ name: "stratagate_diagnostics",
13989
+ 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",
13990
+ parameters: {
13991
+ id: { type: "string", description: "\u6458\u8981\u4E2D\u8FD4\u56DE\u7684\u8BCA\u65AD ID\uFF1B\u63D0\u4F9B\u65F6\u8BFB\u53D6\u8BE5\u6761\u8BE6\u60C5\u3002" },
13992
+ offset: { type: "integer", description: "\u4F7F\u7528\u8FD4\u56DE\u7684 nextOffset \u7EE7\u7EED\u8BFB\u53D6\u8FD1\u671F\u5F02\u5E38\u3002" }
13993
+ },
13994
+ output: jsonOutput,
13995
+ execute: async (args, exec) => runtime.diagnosticReport(runtime.namespaceFor(sessionOf(exec)), {
13996
+ ...args.id !== void 0 ? { id: args.id, details: true } : {},
13997
+ ...args.offset !== void 0 ? { offset: args.offset } : {}
13998
+ })
13999
+ }));
13628
14000
  ctx.tools.register(defineTool2({
13629
14001
  name: "memory_list_topics",
13630
14002
  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 +14175,7 @@ function registerMemoryTools(ctx, runtime) {
13803
14175
 
13804
14176
  // src/web.ts
13805
14177
  import { createHash as createHash4 } from "node:crypto";
13806
- import { createRequire as createRequire2 } from "node:module";
14178
+ import { createRequire as createRequire3 } from "node:module";
13807
14179
 
13808
14180
  // src/graph-clustering.ts
13809
14181
  import createGraph from "ngraph.graph";
@@ -13917,7 +14289,7 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
13917
14289
 
13918
14290
  // src/web.ts
13919
14291
  var LEGACY_THREAD_ID = "__legacy__";
13920
- var nodeRequire2 = createRequire2(import.meta.url);
14292
+ var nodeRequire2 = createRequire3(import.meta.url);
13921
14293
  function currentPluginVersion() {
13922
14294
  try {
13923
14295
  const value = nodeRequire2("../package.json");
@@ -14676,7 +15048,8 @@ async function feedback(runtime, req, url) {
14676
15048
  if (req.method === "GET") {
14677
15049
  const namespace2 = url.searchParams.get("namespace")?.trim() ?? "";
14678
15050
  if (!namespace2) throw new AdminHttpError(400, "namespace is required");
14679
- return runtime.adminFeedbackDraft(namespace2);
15051
+ const result = runtime.adminFeedbackDraft(namespace2);
15052
+ return { ...result, diagnostics: result.draft?.diagnostics ?? await runtime.diagnosticReport?.(namespace2) };
14680
15053
  }
14681
15054
  if (req.method !== "PUT") throw new AdminHttpError(405, "StrataGate feedback requires GET or PUT");
14682
15055
  let suppliedBody = req.body;
@@ -15328,6 +15701,19 @@ async function handleAdminRequest(runtime, req, res) {
15328
15701
  const path = url.pathname.replace(/\/$/, "");
15329
15702
  if (path === "/api/stratagate/feedback") {
15330
15703
  sendJson(res, 200, await feedback(runtime, req, url));
15704
+ } else if (path === "/api/stratagate/diagnostics") {
15705
+ const namespace = url.searchParams.get("namespace")?.trim() ?? "";
15706
+ if (!namespace) throw new AdminHttpError(400, "namespace is required");
15707
+ if (req.method === "POST") {
15708
+ const category = url.searchParams.get("category") ?? "";
15709
+ if (!["timeout", "network", "request-failed"].includes(category)) throw new AdminHttpError(400, "Unknown frontend diagnostic category");
15710
+ runtime.diagnostics.record({ namespace, stage: "frontend" }, { errors: 1 }, { error: category });
15711
+ sendJson(res, 200, { recorded: true });
15712
+ return;
15713
+ }
15714
+ if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate diagnostics require GET or POST");
15715
+ const id = url.searchParams.get("id") ?? void 0;
15716
+ sendJson(res, 200, await runtime.diagnosticReport(namespace, { ...id ? { id, details: true } : {}, offset: numeric(url.searchParams.get("offset"), 0, 0, 2e3) }));
15331
15717
  } else if (path === "/api/stratagate/profile") {
15332
15718
  sendJson(res, 200, await persistentProfile(runtime, req), true);
15333
15719
  } else if (path === "/api/stratagate/settings") {
@@ -15494,7 +15880,7 @@ import {
15494
15880
  writeFile
15495
15881
  } from "node:fs/promises";
15496
15882
  import { basename, dirname as dirname2, join } from "node:path";
15497
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
15883
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
15498
15884
  import { encodeSeqRanges, KNOWN_SESSION_EVENT_TYPES } from "@deepseek-ai/dsh-session";
15499
15885
  var LEGACY_EVENT_TYPE = "stratagate/memory-citations";
15500
15886
  var COMPAT_EVENT_TYPE = "feedback/record";
@@ -15504,6 +15890,13 @@ var V0_ZSTD = "session.jsonl.zstd";
15504
15890
  var V1_PLAIN = "session.v1.jsonl";
15505
15891
  var V1_ZSTD = "session.v1.jsonl.zstd";
15506
15892
  var RECEIPT_FILENAME = "stratagate-legacy-citations-v1.json";
15893
+ function assertDshMigrationContract(value) {
15894
+ const modules = value;
15895
+ assertDshMethods("releasedV0SessionFormatCodec", modules?.releasedV0SessionFormatCodec, ["createDecoder"]);
15896
+ assertDshMethods("releasedV1SessionFormatCodec", modules?.releasedV1SessionFormatCodec, ["decodeHeader", "createDecoder"]);
15897
+ assertDshMethods("sessionFormatV0ToV1", modules?.sessionFormatV0ToV1, ["migrateHeader", "createStage", "validateTargetHeader"]);
15898
+ assertDshMethods("sessionFormat", modules, ["restoreReleasedV1Artifact"]);
15899
+ }
15507
15900
  function errorText(error) {
15508
15901
  return error instanceof Error ? error.message : String(error);
15509
15902
  }
@@ -15676,6 +16069,7 @@ function prepareLegacyCitationGeneration(sourceBytes, compression, modules) {
15676
16069
  async function loadMigrationModules() {
15677
16070
  try {
15678
16071
  const edge = await import("@deepseek-ai/dsh-session-format-v0-to-v1");
16072
+ assertDshMigrationContract(edge);
15679
16073
  return {
15680
16074
  releasedV0SessionFormatCodec: edge.releasedV0SessionFormatCodec,
15681
16075
  releasedV1SessionFormatCodec: edge.releasedV1SessionFormatCodec,
@@ -15715,7 +16109,7 @@ async function exists(path) {
15715
16109
  }
15716
16110
  async function publishExclusive(path, bytes) {
15717
16111
  await mkdir(dirname2(path), { recursive: true });
15718
- const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID4()}.tmp`);
16112
+ const temporary = join(dirname2(path), `.${basename(path)}.stratagate-${randomUUID6()}.tmp`);
15719
16113
  const handle = await open(temporary, "wx", 384);
15720
16114
  try {
15721
16115
  await handle.writeFile(bytes);
@@ -15840,15 +16234,8 @@ Memory writing:
15840
16234
  - 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
16235
  - 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
16236
  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.`;
16237
+ \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
16238
+ \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
16239
  function renderError(error) {
15853
16240
  return error instanceof Error ? error.message : String(error);
15854
16241
  }
@@ -15858,6 +16245,7 @@ function feedbackWebOrigin(ctx) {
15858
16245
  }
15859
16246
  async function apply(ctx, config) {
15860
16247
  const compatibility = assertCompatibleDshRuntime();
16248
+ assertDshHostContract(ctx);
15861
16249
  const resolved = resolveConfig(config);
15862
16250
  const legacyMigration = await migrateLegacyCitationSessions(resolved.sessionRoot);
15863
16251
  if (legacyMigration.failures.length > 0) {
@@ -15929,27 +16317,48 @@ async function apply(ctx, config) {
15929
16317
  autoMemory = { name: "stratagate:auto-memory", text: text3 };
15930
16318
  } catch (error) {
15931
16319
  ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
15932
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16320
+ runtime.notePluginError(session, error, "autoContext");
15933
16321
  }
15934
16322
  try {
15935
16323
  const directory = await runtime.buildMemoryDirectory(session);
15936
16324
  if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
15937
16325
  } catch (error) {
15938
16326
  ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
15939
- if (AGENT_FEEDBACK_ENABLED) runtime.notePluginError(session, error);
16327
+ runtime.notePluginError(session, error, "directory");
15940
16328
  }
15941
16329
  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 });
16330
+ const toolSchemas = ctx.tools.schemas(context.agent).filter(({ name: name2 }) => name2.startsWith("memory_") || name2 === "feedback_prepare" || name2 === "stratagate_diagnostics");
16331
+ runtime.trackDiagnosticSession(String(session.id), runtime.namespaceFor(session), estimateTokens(JSON.stringify({
16332
+ sections: assembled.sections.filter(({ name: name2 }) => name2.startsWith("tool:stratagate-")),
16333
+ contexts: contexts.filter(({ name: name2 }) => name2.startsWith("stratagate:")),
16334
+ tools: toolSchemas
16335
+ })));
15944
16336
  return { ...assembled, contexts };
15945
16337
  });
15946
16338
  installRetrievalFinalization(ctx, runtime);
15947
16339
  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);
16340
+ ctx.on("llm/stream", async function* (options, next) {
16341
+ if (models.currentDiagnosticScope()) {
16342
+ yield* next();
16343
+ return;
16344
+ }
16345
+ const namespace = runtime.diagnosticNamespaceForSession(String(options.sessionId ?? ""));
16346
+ if (!namespace || options.purpose) {
16347
+ yield* next();
16348
+ return;
15952
16349
  }
16350
+ const stage = String(options.sessionId).startsWith("stratagate-dreaming:") ? "dreamingStep" : "chat";
16351
+ const memoryInputEstimate = runtime.diagnosticMemoryEstimate(String(options.sessionId));
16352
+ yield* observeModelStream(runtime.diagnostics, { namespace, stage, ...memoryInputEstimate !== void 0 ? { memoryInputEstimate } : {} }, options, next);
16353
+ });
16354
+ ctx.on("tools/result", (exec, result) => {
16355
+ if (!exec.agent || !(exec.name === "feedback_prepare" || exec.name === "stratagate_diagnostics" || exec.name.startsWith("memory_"))) return;
16356
+ const namespace = runtime.namespaceFor(exec.agent.session);
16357
+ runtime.diagnostics.record(
16358
+ { namespace, stage: "tools" },
16359
+ { toolCalls: 1, toolFailures: result.isError ? 1 : 0 },
16360
+ result.isError ? { error: result.error.message } : {}
16361
+ );
15953
16362
  });
15954
16363
  const disposeAdminRoutes = registerAdminRoutes(ctx, runtime);
15955
16364
  ctx.on("session/event", (session, event) => runtime.acceptEvent(session, event));