stratagate-dsh 0.2.69 → 0.2.71

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
@@ -1314,8 +1314,9 @@ function normalizeRetrievalAssessment(input, batchEvidenceRefs) {
1314
1314
  }
1315
1315
 
1316
1316
  // packages/core/src/storage.ts
1317
- var STRATAGATE_STORAGE_SCHEMA_VERSION = 10;
1317
+ var STRATAGATE_STORAGE_SCHEMA_VERSION = 11;
1318
1318
  var KNOWLEDGE_GRAPH_PROJECTOR_VERSION = 1;
1319
+ var DERIVATION_MAX_ATTEMPTS = 3;
1319
1320
  var StorageConflictError = class extends Error {
1320
1321
  constructor(namespace, expectedRevision, actualRevision) {
1321
1322
  super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
@@ -1448,6 +1449,11 @@ function normalizeSnapshot(value) {
1448
1449
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1449
1450
  externalMemoryImportJobs: []
1450
1451
  };
1452
+ } else if (schemaVersion === 10) {
1453
+ snapshot = {
1454
+ ...structuredClone(value),
1455
+ schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
1456
+ };
1451
1457
  } else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
1452
1458
  snapshot = structuredClone(value);
1453
1459
  } else {
@@ -1467,8 +1473,19 @@ function normalizeSnapshot(value) {
1467
1473
  if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
1468
1474
  }
1469
1475
  if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
1476
+ for (const job of snapshot.graphProjectionJobs) {
1477
+ if (job.nextRetryAt === void 0) job.nextRetryAt = null;
1478
+ }
1479
+ const sourceBlockMap = new Map(snapshot.blocks.map((block) => [block.id, block]));
1470
1480
  for (const event of snapshot.events) {
1471
1481
  event.temporal = { ...event.temporal, eventType: normalizeStandardEventType(event.temporal.eventType) };
1482
+ if (event.formedTurn === void 0) {
1483
+ const sourceBlock = sourceBlockMap.get(event.sourceBlockId);
1484
+ const reliableSource = sourceBlock && !sourceBlock.threadId?.startsWith("external-import:") && Number.isSafeInteger(sourceBlock.endTurn) && sourceBlock.endTurn >= 0;
1485
+ if (reliableSource) event.formedTurn = sourceBlock.endTurn;
1486
+ } else if (!Number.isSafeInteger(event.formedTurn) || event.formedTurn < 0) {
1487
+ throw new TypeError("Invalid StrataGate snapshot: Event formedTurn must be a non-negative integer");
1488
+ }
1472
1489
  }
1473
1490
  for (const block of snapshot.blocks) {
1474
1491
  if (block.processingStatus !== "pending" && block.processingStatus !== "ready") {
@@ -1715,6 +1732,7 @@ CREATE TABLE IF NOT EXISTS events (
1715
1732
  tags_json TEXT NOT NULL,
1716
1733
  quotes_json TEXT NOT NULL,
1717
1734
  source_block_id TEXT NOT NULL,
1735
+ formed_turn INTEGER,
1718
1736
  temporal_json TEXT NOT NULL,
1719
1737
  scope TEXT NOT NULL,
1720
1738
  criticality TEXT NOT NULL,
@@ -2020,6 +2038,7 @@ var SqliteStorage = class {
2020
2038
  quotes: parseJson(row.quotes_json, "events.quotes_json"),
2021
2039
  sourceMessageIds: sourcesByEvent.get(row.id) ?? [],
2022
2040
  sourceBlockId: row.source_block_id,
2041
+ ...row.formed_turn === null ? {} : { formedTurn: row.formed_turn },
2023
2042
  temporal: (() => {
2024
2043
  const temporal = parseJson(row.temporal_json, "events.temporal_json");
2025
2044
  return { ...temporal, eventType: normalizeStandardEventType(temporal.eventType) };
@@ -2192,8 +2211,7 @@ var SqliteStorage = class {
2192
2211
  externalMemoryImportJobs,
2193
2212
  successfulModelResponses
2194
2213
  };
2195
- assertValidSnapshot(snapshot);
2196
- return { snapshot: cloneSnapshot(snapshot), revision: space.revision };
2214
+ return { snapshot: cloneSnapshot(normalizeSnapshot(snapshot)), revision: space.revision };
2197
2215
  }
2198
2216
  async save(namespace, snapshot, expectedRevision) {
2199
2217
  this.assertOpen();
@@ -2331,10 +2349,10 @@ var SqliteStorage = class {
2331
2349
  const insertEvent = this.database.prepare(`
2332
2350
  INSERT INTO events (
2333
2351
  namespace, id, position, title, summary, narrative, tags_json, quotes_json, source_block_id,
2334
- temporal_json, scope, criticality, confidence, status, superseded_by,
2352
+ formed_turn, temporal_json, scope, criticality, confidence, status, superseded_by,
2335
2353
  mention_count, last_adopted_turn, last_retrieved_at, pinned, floor_weight, forced_cap,
2336
2354
  created_at, updated_at
2337
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2355
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2338
2356
  ON CONFLICT (namespace, id) DO UPDATE SET
2339
2357
  position = excluded.position,
2340
2358
  title = excluded.title,
@@ -2343,6 +2361,7 @@ var SqliteStorage = class {
2343
2361
  tags_json = excluded.tags_json,
2344
2362
  quotes_json = excluded.quotes_json,
2345
2363
  source_block_id = excluded.source_block_id,
2364
+ formed_turn = excluded.formed_turn,
2346
2365
  temporal_json = excluded.temporal_json,
2347
2366
  scope = excluded.scope,
2348
2367
  criticality = excluded.criticality,
@@ -2373,6 +2392,7 @@ var SqliteStorage = class {
2373
2392
  JSON.stringify(event.tags),
2374
2393
  JSON.stringify(event.quotes),
2375
2394
  event.sourceBlockId,
2395
+ event.formedTurn ?? null,
2376
2396
  JSON.stringify(event.temporal),
2377
2397
  event.scope,
2378
2398
  event.criticality,
@@ -2641,6 +2661,23 @@ var SqliteStorage = class {
2641
2661
  if (!blockColumns.some(({ name: name2 }) => name2 === "processing_status")) {
2642
2662
  this.database.exec("ALTER TABLE blocks ADD COLUMN processing_status TEXT NOT NULL DEFAULT 'ready' CHECK (processing_status IN ('pending', 'ready'))");
2643
2663
  }
2664
+ const eventColumns = this.database.prepare("PRAGMA table_info('events')").all();
2665
+ if (!eventColumns.some(({ name: name2 }) => name2 === "formed_turn")) {
2666
+ this.database.exec("ALTER TABLE events ADD COLUMN formed_turn INTEGER");
2667
+ }
2668
+ this.database.exec(`
2669
+ UPDATE events
2670
+ SET formed_turn = (
2671
+ SELECT blocks.end_turn FROM blocks
2672
+ WHERE blocks.namespace = events.namespace AND blocks.id = events.source_block_id
2673
+ )
2674
+ WHERE formed_turn IS NULL AND EXISTS (
2675
+ SELECT 1 FROM blocks
2676
+ WHERE blocks.namespace = events.namespace
2677
+ AND blocks.id = events.source_block_id
2678
+ AND (blocks.thread_id IS NULL OR blocks.thread_id NOT LIKE 'external-import:%')
2679
+ )
2680
+ `);
2644
2681
  const extractionColumns = this.database.prepare("PRAGMA table_info('extraction_jobs')").all();
2645
2682
  if (!extractionColumns.some(({ name: name2 }) => name2 === "next_retry_at")) {
2646
2683
  this.database.exec("ALTER TABLE extraction_jobs ADD COLUMN next_retry_at TEXT");
@@ -2734,7 +2771,6 @@ var EXTERNAL_MEMORY_AUTO_APPLY_CONFIDENCE = 0.85;
2734
2771
  function externalMemoryFingerprint(value) {
2735
2772
  return `${normalizeSearchText(value.title)}\0${normalizeSearchText(value.summary)}`;
2736
2773
  }
2737
- var DERIVATION_MAX_ATTEMPTS = 3;
2738
2774
  var DERIVATION_BACKOFF_MS = 1e3;
2739
2775
  var StrataGate = class _StrataGate {
2740
2776
  blockTurnSizeValue;
@@ -2919,6 +2955,7 @@ var StrataGate = class _StrataGate {
2919
2955
  ...job,
2920
2956
  status: "failed",
2921
2957
  lastError: "Graph projection was interrupted before completion.",
2958
+ nextRetryAt: memory.retryAt(job.attempts),
2922
2959
  updatedAt: now
2923
2960
  });
2924
2961
  }
@@ -3077,6 +3114,7 @@ var StrataGate = class _StrataGate {
3077
3114
  job.status = "pending";
3078
3115
  job.attempts = 0;
3079
3116
  job.lastError = null;
3117
+ job.nextRetryAt = null;
3080
3118
  job.updatedAt = toUtc8Iso(this.now());
3081
3119
  });
3082
3120
  const batch = await this.claimGraphProjection(jobId);
@@ -3675,7 +3713,7 @@ var StrataGate = class _StrataGate {
3675
3713
  }
3676
3714
  async claimNextElementProjection() {
3677
3715
  return this.commitMutation(() => {
3678
- const job = [...this.elementProjectionJobs.values()].find((candidate) => candidate.status === "pending" || candidate.status === "failed");
3716
+ const job = [...this.elementProjectionJobs.values()].filter((candidate) => candidate.attempts < DERIVATION_MAX_ATTEMPTS && (candidate.status === "pending" || candidate.status === "failed")).sort((left, right) => Number(left.status !== "pending") - Number(right.status !== "pending") || left.createdAt.localeCompare(right.createdAt))[0];
3679
3717
  if (!job) return null;
3680
3718
  const events = job.sourceEventIds.flatMap((id) => this.events.find((event) => event.id === id) ?? []);
3681
3719
  if (events.length === 0) {
@@ -3729,7 +3767,8 @@ var StrataGate = class _StrataGate {
3729
3767
  }
3730
3768
  async claimGraphProjection(jobId) {
3731
3769
  return this.commitMutation(() => {
3732
- const job = [...this.graphProjectionJobs.values()].filter((candidate) => (jobId === void 0 || candidate.id === jobId) && (candidate.status === "pending" || candidate.status === "failed")).sort((left, right) => right.priority - left.priority || left.createdAt.localeCompare(right.createdAt))[0];
3770
+ const now = this.now().getTime();
3771
+ const job = [...this.graphProjectionJobs.values()].filter((candidate) => (jobId === void 0 || candidate.id === jobId) && candidate.attempts < DERIVATION_MAX_ATTEMPTS && (candidate.status === "pending" || candidate.status === "failed" && candidate.nextRetryAt !== null && Date.parse(candidate.nextRetryAt) <= now)).sort((left, right) => Number(left.status !== "pending") - Number(right.status !== "pending") || right.priority - left.priority || left.createdAt.localeCompare(right.createdAt))[0];
3733
3772
  if (!job) return null;
3734
3773
  const events = job.sourceEventIds.flatMap((id) => this.events.find((event) => event.id === id) ?? []);
3735
3774
  if (events.length === 0) throw new Error(`Graph projection ${job.id} has no available source events`);
@@ -3744,14 +3783,14 @@ var StrataGate = class _StrataGate {
3744
3783
  (event.temporal.participants ?? []).join(" ")
3745
3784
  ].join(" ")).join(" "));
3746
3785
  const relevantNodes = this.graphNodes.filter((node) => [node.name, ...node.aliases].some((name2) => eventText.includes(normalizeSearchText(name2)))).concat([...this.graphNodes].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 24));
3747
- const existingNodes = [...new Map(relevantNodes.map((node) => [node.id, node])).values()].slice(0, 80);
3786
+ const existingNodes = [...new Map(relevantNodes.map((node) => [node.id, node])).values()].slice(0, 32);
3748
3787
  const nodeIds = new Set(existingNodes.map(({ id }) => id));
3749
3788
  return {
3750
3789
  jobId: job.id,
3751
3790
  projectorVersion: job.projectorVersion,
3752
3791
  events: structuredClone(events),
3753
3792
  existingNodes: structuredClone(existingNodes),
3754
- existingEdges: structuredClone(this.graphEdges.filter(({ fromNodeId, toNodeId }) => nodeIds.has(fromNodeId) && nodeIds.has(toNodeId)).slice(-120))
3793
+ existingEdges: structuredClone(this.graphEdges.filter(({ fromNodeId, toNodeId }) => nodeIds.has(fromNodeId) && nodeIds.has(toNodeId)).slice(-60))
3755
3794
  };
3756
3795
  });
3757
3796
  }
@@ -3777,6 +3816,7 @@ var StrataGate = class _StrataGate {
3777
3816
  job.edgeIds = touched.edgeIds;
3778
3817
  job.reason = typeof result.reason === "string" ? result.reason.trim().replace(/\s+/g, " ").slice(0, 500) || null : null;
3779
3818
  job.lastError = null;
3819
+ job.nextRetryAt = null;
3780
3820
  job.updatedAt = toUtc8Iso(this.now());
3781
3821
  return touched;
3782
3822
  });
@@ -3787,6 +3827,7 @@ var StrataGate = class _StrataGate {
3787
3827
  if (job.status === "completed") return;
3788
3828
  job.status = "failed";
3789
3829
  job.lastError = errorMessage(error);
3830
+ job.nextRetryAt = this.retryAt(job.attempts);
3790
3831
  job.updatedAt = toUtc8Iso(this.now());
3791
3832
  });
3792
3833
  }
@@ -4052,8 +4093,10 @@ var StrataGate = class _StrataGate {
4052
4093
  const sourceMessageIds = requestedRefs.length > 0 ? requestedRefs : sourceBlock.l5Raw.map((message) => message.id);
4053
4094
  const now = toUtc8Iso(this.now());
4054
4095
  const criticality = input.criticality ?? "routine";
4096
+ const formedTurn = sourceBlock.threadId?.startsWith("external-import:") ? this.currentTurn : sourceBlock.endTurn;
4055
4097
  const event = {
4056
4098
  id: input.id ?? this.idFactory("evt"),
4099
+ formedTurn,
4057
4100
  title: input.title.trim(),
4058
4101
  summary: input.summary.trim(),
4059
4102
  narrative: input.narrative?.trim() || input.summary.trim(),
@@ -4072,7 +4115,7 @@ var StrataGate = class _StrataGate {
4072
4115
  supersededBy: null,
4073
4116
  weight: {
4074
4117
  mentionCount: 1,
4075
- lastAdoptedTurn: this.currentTurn,
4118
+ lastAdoptedTurn: formedTurn,
4076
4119
  lastRetrievedAt: null,
4077
4120
  pinned: false,
4078
4121
  floorWeight: criticalityFloor(criticality),
@@ -4169,6 +4212,7 @@ var StrataGate = class _StrataGate {
4169
4212
  edgeIds: [],
4170
4213
  reason: null,
4171
4214
  lastError: null,
4215
+ nextRetryAt: null,
4172
4216
  createdAt: now,
4173
4217
  updatedAt: now
4174
4218
  };
@@ -4991,6 +5035,48 @@ function renderBlocksForDiagnostics(blocks, finish) {
4991
5035
  const rendered = blocks.map(renderBlockForDiagnostics).join("\n\n");
4992
5036
  return rendered || `[no model blocks; finish=${finish}]`;
4993
5037
  }
5038
+ function compactGraphProjectionContext(context) {
5039
+ const compactValue = (value) => Array.isArray(value) ? value.slice(0, 12).map((entry) => entry.slice(0, 160)) : value.slice(0, 400);
5040
+ return {
5041
+ jobId: context.jobId,
5042
+ projectorVersion: context.projectorVersion,
5043
+ events: context.events.map((event) => ({
5044
+ id: event.id,
5045
+ title: event.title,
5046
+ summary: event.summary,
5047
+ narrative: event.narrative,
5048
+ tags: event.tags,
5049
+ quotes: event.quotes,
5050
+ temporal: event.temporal,
5051
+ scope: event.scope,
5052
+ criticality: event.criticality,
5053
+ status: event.status
5054
+ })),
5055
+ existingNodes: context.existingNodes.slice(0, 32).map((node) => ({
5056
+ id: node.id,
5057
+ name: node.name,
5058
+ type: node.type,
5059
+ aliases: node.aliases.slice(0, 12),
5060
+ tags: node.tags?.slice(0, 12),
5061
+ currentState: node.currentState.slice(0, 600),
5062
+ facts: node.facts.filter(({ status }) => status === "active" || status === "disputed").slice(-12).map((fact) => ({
5063
+ key: fact.key,
5064
+ value: compactValue(fact.value),
5065
+ status: fact.status,
5066
+ sourceEventIds: fact.sourceEventIds.slice(-16)
5067
+ })),
5068
+ status: node.status,
5069
+ sourceEventIds: node.sourceEventIds.slice(-16)
5070
+ })),
5071
+ existingEdges: context.existingEdges.slice(-60).map((edge) => ({
5072
+ fromNodeId: edge.fromNodeId,
5073
+ toNodeId: edge.toNodeId,
5074
+ relation: edge.relation,
5075
+ status: edge.status,
5076
+ sourceEventIds: edge.sourceEventIds.slice(-16)
5077
+ }))
5078
+ };
5079
+ }
4994
5080
  var DshModelBridge = class {
4995
5081
  constructor(ctx, config) {
4996
5082
  this.ctx = ctx;
@@ -5104,8 +5190,8 @@ var DshModelBridge = class {
5104
5190
  const eventIds = new Set(context.events.map((event) => event.id));
5105
5191
  const raw = object(await this.callStructured(
5106
5192
  "graphProjector",
5107
- `Project the supplied Events into the current Knowledge Graph, then call ${STRUCTURED_TOOLS.graphProjector.name} exactly once. Events are the sole source of truth; never use legacy Element data. Return stable entity nodes for people, projects, organizations, tools, and places. Use aliases to merge spelling/case/separator variants. Give every returned node 1-6 concise semantic role tags such as benchmark, evaluation, memory-plugin, parser, or development-tool; tags describe the node's specific role and never replace its person/project/organization/tool/place type. Reuse stable tag wording when possible. Put attributes in node facts and every relationship in a directed edge using fromRef/toRef\u2014never encode a relationship as a fact string. Prefer concise canonical Chinese relation labels such as \u4F7F\u7528\u3001\u5C5E\u4E8E\u3001\u521B\u5EFA\u3001\u53C2\u4E0E\u3001\u8D21\u732E\u3001\u4F9D\u8D56\u3001\u4F4D\u4E8E\u3001\u76F8\u5173. Every node, fact, and edge must cite only supplied Event ids. Do not return text.`,
5108
- context
5193
+ `Project the supplied Events into the current Knowledge Graph, then call ${STRUCTURED_TOOLS.graphProjector.name} exactly once. Events are the sole source of truth; never use legacy Element data. Return only nodes and edges touched by the supplied Events; never echo unchanged historical graph records. Return at most 24 nodes and 32 edges. Use stable entity nodes for people, projects, organizations, tools, and places. Use aliases to merge spelling/case/separator variants. Give every returned node 1-6 concise semantic role tags such as benchmark, evaluation, memory-plugin, parser, or development-tool; tags describe the node's specific role and never replace its person/project/organization/tool/place type. Reuse stable tag wording when possible. Put attributes in node facts and every relationship in a directed edge using fromRef/toRef\u2014never encode a relationship as a fact string. Prefer concise canonical Chinese relation labels such as \u4F7F\u7528\u3001\u5C5E\u4E8E\u3001\u521B\u5EFA\u3001\u53C2\u4E0E\u3001\u8D21\u732E\u3001\u4F9D\u8D56\u3001\u4F4D\u4E8E\u3001\u76F8\u5173. Every node, fact, and edge must cite only supplied Event ids. Do not return text.`,
5194
+ compactGraphProjectionContext(context)
5109
5195
  ));
5110
5196
  const nodes = (Array.isArray(raw.nodes) ? raw.nodes : []).flatMap((candidate) => {
5111
5197
  const item = object(candidate);
@@ -5134,7 +5220,7 @@ var DshModelBridge = class {
5134
5220
  ...typeof item.confidence === "number" ? { confidence: item.confidence } : {},
5135
5221
  sourceEventIds
5136
5222
  }];
5137
- });
5223
+ }).slice(0, 24);
5138
5224
  const refs = new Set(nodes.map(({ ref }) => ref));
5139
5225
  const edges = (Array.isArray(raw.edges) ? raw.edges : []).flatMap((candidate) => {
5140
5226
  const item = object(candidate);
@@ -5153,7 +5239,7 @@ var DshModelBridge = class {
5153
5239
  ...typeof item.confidence === "number" ? { confidence: item.confidence } : {},
5154
5240
  sourceEventIds
5155
5241
  }];
5156
- });
5242
+ }).slice(0, 32);
5157
5243
  return { reason: text2(raw.reason, "Projected Event evidence into the Knowledge Graph."), nodes, edges };
5158
5244
  };
5159
5245
  externalMemoryDecider = async (context) => {
@@ -5197,7 +5283,9 @@ var DshModelBridge = class {
5197
5283
  let useOff = await this.shouldUseOff(baseRoute);
5198
5284
  let lastError;
5199
5285
  let lastResponse = "";
5286
+ let attemptsUsed = 0;
5200
5287
  for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
5288
+ attemptsUsed = attempt;
5201
5289
  const message = createUserMessage({
5202
5290
  content: [{ type: "text", text: JSON.stringify(payload) }],
5203
5291
  source: { kind: "plugin", plugin: "stratagate-memory" }
@@ -5306,13 +5394,14 @@ ${JSON_RETRY_INSTRUCTION}`,
5306
5394
  `StrataGate ${STRUCTURED_TOOLS[kind].name} call was truncated before valid arguments`,
5307
5395
  { cause: error, response: responseForError }
5308
5396
  ) : error;
5397
+ if (kind === "graphProjector" && finish.kind === "max-tokens") break;
5309
5398
  if (attempt < JSON_RESPONSE_ATTEMPTS) {
5310
5399
  this.ctx.logger.warn(`stratagate-memory model returned an invalid structured tool call; retrying (${attempt}/${JSON_RESPONSE_ATTEMPTS})`);
5311
5400
  }
5312
5401
  }
5313
5402
  }
5314
5403
  throw new ModelJsonResponseError(
5315
- `StrataGate model did not produce a valid ${STRUCTURED_TOOLS[kind].name} call after ${JSON_RESPONSE_ATTEMPTS} attempts`,
5404
+ `StrataGate model did not produce a valid ${STRUCTURED_TOOLS[kind].name} call after ${attemptsUsed} attempt${attemptsUsed === 1 ? "" : "s"}`,
5316
5405
  { cause: lastError, response: lastResponse }
5317
5406
  );
5318
5407
  }
@@ -5715,6 +5804,9 @@ var DRAIN_BASE_BACKOFF_MS = 2e3;
5715
5804
  var DRAIN_MAX_BACKOFF_MS = 6e4;
5716
5805
  var BACKGROUND_WORKER_INITIAL_DELAY_MS = 250;
5717
5806
  var BACKGROUND_WORKER_INTERVAL_MS = 3e3;
5807
+ function graphProjectionCanRun(job, now = Date.now()) {
5808
+ return job.attempts < DERIVATION_MAX_ATTEMPTS && (job.status === "pending" || job.status === "failed" && job.nextRetryAt != null && Date.parse(job.nextRetryAt) <= now);
5809
+ }
5718
5810
  var AUTO_EVENT_LIMIT = 4;
5719
5811
  var AUTO_ELEMENT_LIMIT = 4;
5720
5812
  var AUTO_MEMORY_TOKEN_BUDGET = 900;
@@ -5849,7 +5941,7 @@ var StrataGateRuntime = class {
5849
5941
  const runnable = [
5850
5942
  ...memory.listSummaryJobs(),
5851
5943
  ...memory.listExtractionJobs()
5852
- ].some((job) => job.status === "pending" || job.status === "failed" && job.nextRetryAt !== null && Date.parse(job.nextRetryAt) <= Date.now()) || memory.listGraphProjectionJobs().some(({ status }) => status === "pending");
5944
+ ].some((job) => job.status === "pending" || job.status === "failed" && job.nextRetryAt !== null && Date.parse(job.nextRetryAt) <= Date.now()) || memory.listGraphProjectionJobs().some((job) => graphProjectionCanRun(job));
5853
5945
  if (!runnable) return;
5854
5946
  const hasActiveSessionWork = [
5855
5947
  ...this.derivationTimers.keys(),
@@ -7127,7 +7219,7 @@ var StrataGateRuntime = class {
7127
7219
  const namespace = this.namespaceFor(session);
7128
7220
  if (this.closed || this.migrationTimers.has(namespace)) return;
7129
7221
  if (typeof memory.listGraphProjectionJobs !== "function") return;
7130
- const pending = memory.listGraphProjectionJobs().some(({ status }) => status === "pending" || status === "failed");
7222
+ const pending = memory.listGraphProjectionJobs().some((job) => graphProjectionCanRun(job));
7131
7223
  if (!pending) return;
7132
7224
  const timer = setTimeout(() => {
7133
7225
  this.migrationTimers.delete(namespace);
@@ -7833,6 +7925,10 @@ function currentPluginVersion() {
7833
7925
  return "unknown";
7834
7926
  }
7835
7927
  var STRATAGATE_DSH_VERSION = currentPluginVersion();
7928
+ function graphProjectionIsProcessing(job) {
7929
+ if (job.status === "running") return true;
7930
+ return job.attempts < DERIVATION_MAX_ATTEMPTS && (job.status === "pending" || job.status === "failed" && job.nextRetryAt !== null);
7931
+ }
7836
7932
  function installedPackageVersion(names) {
7837
7933
  for (const name2 of names) {
7838
7934
  try {
@@ -7923,7 +8019,8 @@ function appendWeightSegment(points, event, fromTurn, toTurn, mentionCount) {
7923
8019
  if (toTurn < fromTurn) return;
7924
8020
  if (toTurn === fromTurn) return;
7925
8021
  const span = toTurn - fromTurn;
7926
- const step = Math.max(1, Math.ceil(span / 32));
8022
+ const samples = Math.min(256, Math.max(12, Math.ceil(span * 12)));
8023
+ const step = span / samples;
7927
8024
  for (let turn = fromTurn; turn < toTurn; turn += step) {
7928
8025
  points.push({ turn, weight: sampledEventWeight(event, turn, fromTurn, mentionCount), kind: "sample" });
7929
8026
  }
@@ -7931,65 +8028,116 @@ function appendWeightSegment(points, event, fromTurn, toTurn, mentionCount) {
7931
8028
  }
7932
8029
  function eventWeightTrajectory(snapshot, event) {
7933
8030
  const currentTurn = snapshot.currentTurn;
8031
+ const formedTurn = Number.isSafeInteger(event.formedTurn) && event.formedTurn >= 0 ? event.formedTurn : null;
8032
+ const sourceBlock = snapshot.blocks.find(({ id }) => id === event.sourceBlockId);
8033
+ const sourceBlockEndTurn = Number.isSafeInteger(sourceBlock?.endTurn) && sourceBlock.endTurn >= 0 ? sourceBlock.endTurn : null;
7934
8034
  const effectiveAdoptions = Math.max(0, event.weight.mentionCount - 1);
7935
8035
  const adoptionReceipts = snapshot.usageReceipts.filter(({ eventIds }) => eventIds.includes(event.id));
7936
8036
  const adoptionTurns = adoptionReceipts.map(({ audit: audit2 }) => audit2?.turn).filter((turn) => Number.isSafeInteger(turn) && turn >= 0).sort((left, right) => left - right);
7937
- const historyComplete = adoptionReceipts.length === effectiveAdoptions && adoptionTurns.length === adoptionReceipts.length && (effectiveAdoptions === 0 || adoptionTurns.at(-1) === event.weight.lastAdoptedTurn);
8037
+ const confirmedAdoptionTurns = [...adoptionTurns];
8038
+ if (effectiveAdoptions > 0 && Number.isSafeInteger(event.weight.lastAdoptedTurn) && event.weight.lastAdoptedTurn >= 0) {
8039
+ confirmedAdoptionTurns.push(event.weight.lastAdoptedTurn);
8040
+ }
8041
+ confirmedAdoptionTurns.sort((left, right) => left - right);
8042
+ const groupedAdoptions = [...new Set(confirmedAdoptionTurns)].map((turn) => ({
8043
+ turn,
8044
+ adoptionCount: Math.max(1, adoptionTurns.filter((value) => value === turn).length)
8045
+ }));
8046
+ const earliestKnownAdoptionTurn = groupedAdoptions[0]?.turn ?? null;
8047
+ const trajectoryStartTurn = formedTurn ?? sourceBlockEndTurn ?? earliestKnownAdoptionTurn ?? currentTurn;
8048
+ const formationTurnSource = formedTurn !== null ? "formedTurn" : sourceBlockEndTurn !== null ? "sourceBlock" : null;
8049
+ const historyComplete = adoptionReceipts.length === effectiveAdoptions && adoptionTurns.length === adoptionReceipts.length && (effectiveAdoptions === 0 || adoptionTurns.at(-1) === event.weight.lastAdoptedTurn) && adoptionTurns.every((turn) => turn >= trajectoryStartTurn) && (effectiveAdoptions > 0 || event.weight.lastAdoptedTurn === trajectoryStartTurn);
8050
+ const adoptionHistoryComplete = adoptionReceipts.length === effectiveAdoptions && adoptionReceipts.every((receipt) => Boolean(receipt.createdAt) && Number.isFinite(Date.parse(receipt.createdAt)) && Boolean(receipt.audit?.sessionId?.trim()) && Number.isSafeInteger(receipt.audit?.turn) && receipt.audit.turn >= 0) && (formedTurn === null || adoptionReceipts.every((receipt) => receipt.audit.turn >= formedTurn));
8051
+ const adoptionHistory = adoptionReceipts.filter((receipt) => Boolean(receipt.createdAt) && Number.isFinite(Date.parse(receipt.createdAt)) && Boolean(receipt.audit?.sessionId?.trim()) && Number.isSafeInteger(receipt.audit?.turn) && receipt.audit.turn >= 0 && (formedTurn === null || receipt.audit.turn >= formedTurn)).map((receipt) => ({
8052
+ receiptId: receipt.id,
8053
+ createdAt: receipt.createdAt,
8054
+ sessionId: receipt.audit.sessionId,
8055
+ turn: receipt.audit.turn,
8056
+ ...formedTurn === null ? {} : { relativeTurn: receipt.audit.turn - formedTurn },
8057
+ evidenceRefs: receipt.audit?.evidenceRefs ?? []
8058
+ })).sort((left, right) => left.turn - right.turn || left.createdAt.localeCompare(right.createdAt));
7938
8059
  const canInterpolate = event.status !== "forgotten" && event.status !== "archived" && event.weight.forcedCap === null && !event.weight.pinned;
7939
- const points = [];
7940
- if (canInterpolate && effectiveAdoptions === 0) {
7941
- appendWeightSegment(points, event, event.weight.lastAdoptedTurn, currentTurn, 1);
7942
- if (points.length > 0) {
7943
- const first = points[0];
7944
- points[0] = { turn: first.turn, weight: first.weight, kind: "creation", label: "\u521B\u5EFA" };
7945
- }
7946
- } else if (canInterpolate && historyComplete && adoptionTurns.length > 0) {
7947
- let anchorTurn = adoptionTurns[0];
7948
- let mentionCount = 2;
7949
- points.push({ turn: anchorTurn, weight: 1, kind: "adoption", label: "\u91C7\u7528" });
7950
- for (const [index, turn] of adoptionTurns.slice(1).entries()) {
7951
- appendWeightSegment(points, event, anchorTurn, turn, mentionCount);
7952
- points.push({ turn, weight: 1, kind: "adoption", label: index === adoptionTurns.length - 2 ? "\u518D\u6B21\u91C7\u7528" : "\u91C7\u7528" });
8060
+ const segments = [];
8061
+ const originPoint = formationTurnSource ? { turn: trajectoryStartTurn, weight: 1, kind: "creation", label: formationTurnSource === "formedTurn" ? "\u5F62\u6210" : "\u6765\u6E90" } : null;
8062
+ if (canInterpolate && historyComplete && originPoint) {
8063
+ const lifecycle = [originPoint];
8064
+ let anchorTurn = trajectoryStartTurn;
8065
+ let mentionCount = 1;
8066
+ for (const { turn, adoptionCount } of groupedAdoptions) {
8067
+ if (turn < trajectoryStartTurn || turn > currentTurn) continue;
8068
+ appendWeightSegment(lifecycle, event, anchorTurn, turn, mentionCount);
8069
+ lifecycle.push({ turn, weight: 1, kind: "adoption", adoptionCount, label: adoptionCount > 1 ? `\u91C7\u7EB3 \xD7${adoptionCount}` : "\u91C7\u7EB3" });
7953
8070
  anchorTurn = turn;
7954
- mentionCount += 1;
8071
+ mentionCount += adoptionCount;
7955
8072
  }
7956
- appendWeightSegment(points, event, anchorTurn, currentTurn, mentionCount);
8073
+ appendWeightSegment(lifecycle, event, anchorTurn, currentTurn, mentionCount);
8074
+ segments.push({ certainty: "known", points: lifecycle });
7957
8075
  } else if (canInterpolate) {
7958
- appendWeightSegment(points, event, event.weight.lastAdoptedTurn, currentTurn, event.weight.mentionCount);
7959
- if (effectiveAdoptions > 0 && points.length > 0) {
7960
- const first = points[0];
7961
- points[0] = { turn: first.turn, weight: first.weight, kind: "adoption", label: "\u6700\u8FD1\u91C7\u7528" };
7962
- }
8076
+ let anchorPoint = originPoint;
8077
+ let mentionCount = 1;
8078
+ const visibleAdoptions = groupedAdoptions.filter(({ turn }) => turn >= trajectoryStartTurn && turn <= currentTurn);
8079
+ for (const { turn, adoptionCount } of visibleAdoptions) {
8080
+ if (anchorPoint && turn > anchorPoint.turn) {
8081
+ const incomplete = [anchorPoint];
8082
+ appendWeightSegment(incomplete, event, anchorPoint.turn, turn, mentionCount);
8083
+ incomplete.push({ turn, weight: 1, kind: "adoption", adoptionCount, label: adoptionCount > 1 ? `\u91C7\u7EB3 \xD7${adoptionCount}` : "\u91C7\u7EB3" });
8084
+ segments.push({ certainty: "incomplete", points: incomplete });
8085
+ } else {
8086
+ segments.push({ certainty: "known", points: [{ turn, weight: 1, kind: "adoption", adoptionCount, label: adoptionCount > 1 ? `\u91C7\u7EB3 \xD7${adoptionCount}` : "\u91C7\u7EB3" }] });
8087
+ }
8088
+ anchorPoint = { turn, weight: 1, kind: "adoption", adoptionCount, label: adoptionCount > 1 ? `\u91C7\u7EB3 \xD7${adoptionCount}` : "\u91C7\u7EB3" };
8089
+ mentionCount += adoptionCount;
8090
+ }
8091
+ if (anchorPoint && anchorPoint.turn <= currentTurn) {
8092
+ const latestIsReliable = effectiveAdoptions > 0 && anchorPoint.turn === event.weight.lastAdoptedTurn && visibleAdoptions.at(-1)?.turn === event.weight.lastAdoptedTurn;
8093
+ const tail = [anchorPoint];
8094
+ appendWeightSegment(tail, event, anchorPoint.turn, currentTurn, latestIsReliable ? event.weight.mentionCount : mentionCount);
8095
+ if (tail.length > 1) segments.push({ certainty: latestIsReliable ? "known" : "incomplete", points: tail });
8096
+ } else if (originPoint && originPoint.turn <= currentTurn) {
8097
+ const tail = [originPoint];
8098
+ appendWeightSegment(tail, event, originPoint.turn, currentTurn, 1);
8099
+ if (tail.length > 1) segments.push({ certainty: historyComplete ? "known" : "incomplete", points: tail });
8100
+ else segments.push({ certainty: "known", points: [originPoint] });
8101
+ }
8102
+ } else if (originPoint) {
8103
+ segments.push({ certainty: "known", points: [originPoint] });
7963
8104
  }
7964
8105
  const currentWeight = memoryWeightAt(event, currentTurn);
7965
- if (points.length === 0 || points.at(-1)?.turn !== currentTurn) {
7966
- points.push({ turn: currentTurn, weight: currentWeight, kind: "current", label: "\u5F53\u524D" });
7967
- } else if (points.at(-1)?.kind === "creation" || points.at(-1)?.kind === "adoption") {
7968
- points[points.length - 1] = {
7969
- ...points[points.length - 1],
7970
- weight: currentWeight,
7971
- label: `${points[points.length - 1].label} \xB7 \u5F53\u524D`
7972
- };
8106
+ const lastSegment = segments.at(-1);
8107
+ if (!lastSegment || lastSegment.points.at(-1)?.turn !== currentTurn) {
8108
+ segments.push({ certainty: "known", points: [{ turn: currentTurn, weight: currentWeight, kind: "current", label: "\u5F53\u524D" }] });
8109
+ } else if (lastSegment.points.at(-1)?.kind === "sample") {
8110
+ lastSegment.points[lastSegment.points.length - 1] = { ...lastSegment.points[lastSegment.points.length - 1], weight: currentWeight, kind: "current", label: "\u5F53\u524D" };
7973
8111
  } else {
7974
- points[points.length - 1] = { ...points[points.length - 1], weight: currentWeight, kind: "current", label: "\u5F53\u524D" };
8112
+ segments.push({ certainty: "known", points: [{ turn: currentTurn, weight: currentWeight, kind: "current", label: "\u5F53\u524D" }] });
7975
8113
  }
8114
+ const points = segments.flatMap(({ points: segmentPoints }) => segmentPoints);
7976
8115
  return {
7977
8116
  scale: "conversation_turn",
8117
+ formedTurn,
8118
+ trajectoryStartTurn,
8119
+ formationTurnSource,
7978
8120
  currentTurn,
7979
8121
  currentWeight,
7980
8122
  effectiveAdoptions,
8123
+ floorWeight: event.weight.floorWeight,
8124
+ criticality: event.criticality,
7981
8125
  latestAdoptionTurn: effectiveAdoptions > 0 ? event.weight.lastAdoptedTurn : null,
7982
8126
  turnsSinceLatestAdoption: effectiveAdoptions > 0 ? Math.max(0, currentTurn - event.weight.lastAdoptedTurn) : null,
7983
8127
  lastRetrievedAt: event.weight.lastRetrievedAt,
7984
8128
  recordedAdoptionTurns: adoptionTurns,
7985
8129
  historyComplete,
8130
+ adoptionHistoryComplete,
8131
+ adoptionHistory,
8132
+ segments,
7986
8133
  points,
7987
- note: !canInterpolate ? "\u56FA\u5B9A\u3001\u5C01\u5B58\u6216\u72B6\u6001\u4E0A\u9650\u7684\u53D8\u66F4\u8F6E\u6B21\u6CA1\u6709\u5386\u53F2\u8BB0\u5F55\uFF0C\u56E0\u6B64\u4EC5\u663E\u793A\u5F53\u524D\u771F\u5B9E\u6743\u91CD\u3002" : effectiveAdoptions > 0 && !historyComplete ? "\u90E8\u5206\u65E9\u671F\u91C7\u7528\u8BB0\u5F55\u7F3A\u5C11\u8F6E\u6B21\uFF1B\u66F2\u7EBF\u4EC5\u4ECE\u6700\u8FD1\u4E00\u6B21\u53EF\u786E\u8BA4\u7684\u5F3A\u5316\u5F00\u59CB\u3002" : effectiveAdoptions > 0 ? "\u91C7\u7528\u8F6E\u6B21\u6765\u81EA\u4F7F\u7528\u56DE\u6267\uFF1B\u521B\u5EFA\u8F6E\u6B21\u672A\u5355\u72EC\u4FDD\u5B58\uFF0C\u56E0\u6B64\u4E0D\u63A8\u6D4B\u521B\u5EFA\u8282\u70B9\u3002" : "\u5C1A\u672A\u53D1\u751F\u6709\u6548\u91C7\u7528\uFF1B\u521B\u5EFA\u951A\u70B9\u6765\u81EA Event \u5F53\u524D\u6743\u91CD\u72B6\u6001\u3002"
8134
+ note: !canInterpolate ? "\u56FA\u5B9A\u3001\u5C01\u5B58\u6216\u72B6\u6001\u4E0A\u9650\u7684\u53D8\u66F4\u8F6E\u6B21\u6CA1\u6709\u5386\u53F2\u8BB0\u5F55\uFF0C\u56E0\u6B64\u4EC5\u663E\u793A\u5F53\u524D\u771F\u5B9E\u6743\u91CD\u3002" : !historyComplete ? "\u65E7\u7248\u672C\u672A\u8BB0\u5F55\u90E8\u5206\u91C7\u7EB3\u7684\u5177\u4F53\u8F6E\u6B21\uFF1B\u786E\u5B9A\u8282\u70B9\u4E0E\u533A\u95F4\u7167\u5E38\u663E\u793A\uFF0C\u7F3A\u5931\u533A\u95F4\u4EC5\u4F5C\u8D8B\u52BF\u8FDE\u63A5\u3002" : effectiveAdoptions > 0 ? "\u91C7\u7EB3\u8F6E\u6B21\u6765\u81EA\u771F\u5B9E\u4F7F\u7528\u56DE\u6267\u3002" : "\u5C1A\u672A\u88AB Agent \u91C7\u7EB3\u3002"
7988
8135
  };
7989
8136
  }
7990
8137
  function eventSummary(event, snapshot) {
7991
8138
  return {
7992
8139
  id: event.id,
8140
+ formedTurn: event.formedTurn,
7993
8141
  title: event.title,
7994
8142
  summary: event.summary,
7995
8143
  narrative: event.narrative,
@@ -8100,7 +8248,7 @@ async function overview(runtime, cachedEntries) {
8100
8248
  kind: "graph-projection",
8101
8249
  status: job.status,
8102
8250
  attempts: job.attempts,
8103
- nextRetryAt: null,
8251
+ nextRetryAt: job.nextRetryAt,
8104
8252
  lastError: job.lastError?.slice(0, 500) ?? null,
8105
8253
  lastErrorFull: job.lastError,
8106
8254
  updatedAt: job.updatedAt,
@@ -8112,7 +8260,7 @@ async function overview(runtime, cachedEntries) {
8112
8260
  };
8113
8261
  };
8114
8262
  const failedJobs = snapshot.summaryJobs.filter(({ status }) => status === "failed").length + snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
8115
- const processingJobs = snapshot.summaryJobs.filter(({ status, nextRetryAt }) => status === "pending" || status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.extractionJobs.filter(({ status, nextRetryAt }) => status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
8263
+ const processingJobs = snapshot.summaryJobs.filter(({ status, nextRetryAt }) => status === "pending" || status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.extractionJobs.filter(({ status, nextRetryAt }) => status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.graphProjectionJobs.filter(graphProjectionIsProcessing).length;
8116
8264
  const failedJobDetails = [
8117
8265
  ...snapshot.summaryJobs.filter(({ status }) => status === "failed").map((job) => describeBlockJob("block-summary", job)),
8118
8266
  ...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => describeBlockJob("event-extraction", job)),
@@ -8121,7 +8269,7 @@ async function overview(runtime, cachedEntries) {
8121
8269
  const processingJobDetails = [
8122
8270
  ...snapshot.summaryJobs.filter(({ status, nextRetryAt }) => status === "pending" || status === "running" || status === "failed" && nextRetryAt !== null).map((job) => describeBlockJob("block-summary", job)),
8123
8271
  ...snapshot.extractionJobs.filter(({ status, nextRetryAt }) => status === "running" || status === "failed" && nextRetryAt !== null).map((job) => describeBlockJob("event-extraction", job)),
8124
- ...snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").map(describeGraphJob)
8272
+ ...snapshot.graphProjectionJobs.filter(graphProjectionIsProcessing).map(describeGraphJob)
8125
8273
  ];
8126
8274
  const timestamps = [
8127
8275
  ...snapshot.blocks.map(({ createdAt }) => createdAt),
@@ -8454,7 +8602,7 @@ async function memories(runtime, url) {
8454
8602
  return time(right).localeCompare(time(left));
8455
8603
  }).map((event) => ({
8456
8604
  ...eventSummary(event, snapshot),
8457
- relatedNodes: snapshot.graphNodes.filter(({ id, sourceEventIds }) => sourceEventIds.includes(event.id) || (event.temporal.participantNodeIds ?? []).includes(id)).map(({ id, name: name2, type }) => ({ id, name: name2, type })),
8605
+ relatedNodes: snapshot.graphNodes.filter(({ id }) => (event.temporal.participantNodeIds ?? []).includes(id)).map(({ id, name: name2, type, aliases }) => ({ id, name: name2, type, aliases })),
8458
8606
  relatedElements: snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.includes(event.id)).map(({ id, name: name2 }) => ({ id, name: name2 }))
8459
8607
  }));
8460
8608
  else if (kind === "graph") {
@@ -8645,6 +8793,9 @@ async function sources(runtime, url) {
8645
8793
  return {
8646
8794
  namespace,
8647
8795
  events: events.map((event) => eventSummary(event, eventId ? snapshot : void 0)),
8796
+ ...eventId ? {
8797
+ relatedNodes: snapshot.graphNodes.filter(({ id }) => events[0]?.temporal.participantNodeIds?.includes(id)).map(({ id, name: name2, type, aliases }) => ({ id, name: name2, type, aliases }))
8798
+ } : {},
8648
8799
  elements: elements.map(elementSummary),
8649
8800
  blocks: events.map((event) => snapshot.blocks.find(({ id }) => id === event.sourceBlockId)).filter((block) => Boolean(block)).map((block) => ({ id: block.id, title: block.l0Title, createdAt: block.createdAt })),
8650
8801
  messages: sourceMessages(snapshot, ids)