stratagate-dsh 0.2.26 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -364,6 +364,7 @@ function applyGraphProjection(options) {
364
364
  const sources2 = validSources(proposal.sourceEventIds);
365
365
  if (!ref || !name2 || !NODE_TYPES.has(proposal.type) || sources2.length === 0) continue;
366
366
  const aliases = strings(proposal.aliases, 20).filter((alias) => normalizeSearchText(alias) !== normalizeSearchText(name2));
367
+ const tags = strings(proposal.tags, 12);
367
368
  let node = options.nodes.find((candidate) => sameEntity(candidate, { ...proposal, name: name2, aliases }));
368
369
  if (!node) {
369
370
  node = {
@@ -382,6 +383,7 @@ function applyGraphProjection(options) {
382
383
  options.nodes.push(node);
383
384
  }
384
385
  node.aliases = [.../* @__PURE__ */ new Set([...node.aliases, ...aliases])];
386
+ if (tags.length > 0) node.tags = [.../* @__PURE__ */ new Set([...node.tags ?? [], ...tags])].slice(0, 12);
385
387
  node.status = STATUSES.has(proposal.status ?? "active") ? proposal.status ?? "active" : "active";
386
388
  node.confidence = confidence(proposal.confidence);
387
389
  node.sourceEventIds = [.../* @__PURE__ */ new Set([...node.sourceEventIds, ...sources2])];
@@ -2626,6 +2628,7 @@ var StrataGate = class _StrataGate {
2626
2628
  const ranked = bm25Rank(candidates, query, (node) => weightedSearchTokens([
2627
2629
  [node.name, 6],
2628
2630
  [node.aliases.join(" "), 5],
2631
+ [(node.tags ?? []).join(" "), 5],
2629
2632
  [node.type, 2],
2630
2633
  [node.currentState, 4],
2631
2634
  [node.facts.map((fact) => `${fact.key} ${Array.isArray(fact.value) ? fact.value.join(" ") : fact.value}`).join(" "), 4],
@@ -3281,6 +3284,7 @@ var GRAPH_NODE = {
3281
3284
  name: { type: "string", required: true },
3282
3285
  type: { type: "string", enum: ["person", "project", "organization", "tool", "place"], required: true },
3283
3286
  aliases: STRING_ARRAY,
3287
+ tags: { ...STRING_ARRAY, required: true },
3284
3288
  state: { type: "string" },
3285
3289
  facts: { type: "array", items: GRAPH_FACT },
3286
3290
  status: { type: "string", enum: ["active", "superseded", "disputed", "archived"] },
@@ -3443,7 +3447,7 @@ var DshModelBridge = class {
3443
3447
  const eventIds = new Set(context.events.map((event) => event.id));
3444
3448
  const raw = object(await this.callStructured(
3445
3449
  "graphProjector",
3446
- `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. 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.`,
3450
+ `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.`,
3447
3451
  context
3448
3452
  ));
3449
3453
  const nodes = (Array.isArray(raw.nodes) ? raw.nodes : []).flatMap((candidate) => {
@@ -3464,6 +3468,7 @@ var DshModelBridge = class {
3464
3468
  name: text2(item.name),
3465
3469
  type,
3466
3470
  aliases: strings2(item.aliases),
3471
+ tags: strings2(item.tags).slice(0, 12),
3467
3472
  ...text2(item.state) ? { state: text2(item.state) } : {},
3468
3473
  facts,
3469
3474
  ...typeof item.status === "string" ? { status: item.status } : {},
@@ -4720,7 +4725,119 @@ function registerMemoryTools(ctx, runtime) {
4720
4725
 
4721
4726
  // src/web.ts
4722
4727
  import { createRequire } from "node:module";
4723
- var STRATAGATE_DSH_VERSION = "0.2.22";
4728
+
4729
+ // src/graph-clustering.ts
4730
+ import createGraph from "ngraph.graph";
4731
+ import { detectClusters } from "ngraph.leiden";
4732
+ var MAX_CLUSTER_SIZE = 12;
4733
+ var LEIDEN_RESOLUTION = 0.55;
4734
+ function pairKey(left, right) {
4735
+ return left < right ? `${left}\0${right}` : `${right}\0${left}`;
4736
+ }
4737
+ function pairIds(key) {
4738
+ const separator = key.indexOf("\0");
4739
+ return [key.slice(0, separator), key.slice(separator + 1)];
4740
+ }
4741
+ function semanticTags(node) {
4742
+ return Array.isArray(node.tags) ? [...new Set(node.tags.map((tag) => tag.trim()).filter(Boolean))] : [];
4743
+ }
4744
+ function stableClusterId(nodeIds) {
4745
+ let hash = 2166136261;
4746
+ for (const character of [...nodeIds].sort().join("\0")) {
4747
+ hash ^= character.charCodeAt(0);
4748
+ hash = Math.imul(hash, 16777619);
4749
+ }
4750
+ return `cluster_${(hash >>> 0).toString(36)}`;
4751
+ }
4752
+ function clusterLabel(members, weightedDegree) {
4753
+ const counts = /* @__PURE__ */ new Map();
4754
+ for (const node of members) for (const tag of semanticTags(node)) {
4755
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
4756
+ }
4757
+ const orderedTags = [...counts].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([tag]) => tag);
4758
+ const sharedTags = [...counts].filter(([, count]) => members.length === 1 || count > 1).sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).slice(0, 2).map(([tag]) => tag);
4759
+ if (sharedTags.length > 0) return { label: sharedTags.join(" / "), tags: orderedTags.slice(0, 8) };
4760
+ const names = [...members].sort((left, right) => (weightedDegree.get(right.id) ?? 0) - (weightedDegree.get(left.id) ?? 0) || right.sourceEventIds.length - left.sourceEventIds.length || left.name.localeCompare(right.name)).slice(0, 2).map(({ name: name2 }) => name2);
4761
+ return { label: names.join(" / ") || "\u672A\u547D\u540D\u4E3B\u9898", tags: orderedTags.slice(0, 8) };
4762
+ }
4763
+ function clusterKnowledgeGraph(rawNodes, rawEdges) {
4764
+ const nodes = rawNodes.filter((node) => node.status !== "archived");
4765
+ if (nodes.length === 0) return [];
4766
+ const nodeMap = new Map(nodes.map((node) => [node.id, node]));
4767
+ const structuralWeights = /* @__PURE__ */ new Map();
4768
+ const addStructuralWeight = (left, right, weight) => {
4769
+ if (left === right || !nodeMap.has(left) || !nodeMap.has(right) || weight <= 0) return;
4770
+ const key = pairKey(left, right);
4771
+ structuralWeights.set(key, (structuralWeights.get(key) ?? 0) + weight);
4772
+ };
4773
+ for (const edge of rawEdges.filter((edge2) => edge2.status === "active")) {
4774
+ addStructuralWeight(edge.fromNodeId, edge.toNodeId, 3 * Math.max(0.25, Number(edge.confidence) || 0.8));
4775
+ }
4776
+ const eventOwners = /* @__PURE__ */ new Map();
4777
+ for (const node of nodes) for (const eventId of new Set(node.sourceEventIds)) {
4778
+ const owners = eventOwners.get(eventId) ?? [];
4779
+ owners.push(node.id);
4780
+ eventOwners.set(eventId, owners);
4781
+ }
4782
+ for (const owners of eventOwners.values()) {
4783
+ const eventWeight = 1.4 / Math.sqrt(Math.max(1, owners.length - 1));
4784
+ for (let left = 0; left < owners.length; left += 1) for (let right = left + 1; right < owners.length; right += 1) {
4785
+ addStructuralWeight(owners[left], owners[right], eventWeight);
4786
+ }
4787
+ }
4788
+ const graph = createGraph({ multigraph: false });
4789
+ for (const node of nodes) graph.addNode(node.id);
4790
+ const weightedDegree = new Map(nodes.map((node) => [node.id, 0]));
4791
+ for (const [key, structuralWeight] of structuralWeights) {
4792
+ const [leftId, rightId] = pairIds(key);
4793
+ const left = nodeMap.get(leftId);
4794
+ const right = nodeMap.get(rightId);
4795
+ const leftTags = new Set(semanticTags(left));
4796
+ const rightTags = new Set(semanticTags(right));
4797
+ const overlap = [...leftTags].filter((tag) => rightTags.has(tag)).length;
4798
+ const union = (/* @__PURE__ */ new Set([...leftTags, ...rightTags])).size;
4799
+ const tagBoost = overlap > 0 && union > 0 ? 0.85 * overlap / union : 0;
4800
+ const typeBoost = left.type === right.type ? 0.15 : 0;
4801
+ const weight = structuralWeight + tagBoost + typeBoost;
4802
+ graph.addLink(leftId, rightId, { weight });
4803
+ weightedDegree.set(leftId, (weightedDegree.get(leftId) ?? 0) + weight);
4804
+ weightedDegree.set(rightId, (weightedDegree.get(rightId) ?? 0) + weight);
4805
+ }
4806
+ const partition = detectClusters(graph, {
4807
+ quality: "cpm",
4808
+ resolution: LEIDEN_RESOLUTION,
4809
+ randomSeed: 42,
4810
+ refine: true,
4811
+ maxCommunitySize: MAX_CLUSTER_SIZE,
4812
+ linkWeight: (link) => link.data.weight
4813
+ });
4814
+ const connectedGroups = [];
4815
+ const isolatedIds = [];
4816
+ for (const community of partition.getCommunities().values()) {
4817
+ const ids = community.map(String).sort();
4818
+ const connected = ids.filter((id) => (weightedDegree.get(id) ?? 0) > 0);
4819
+ const isolated = ids.filter((id) => (weightedDegree.get(id) ?? 0) === 0);
4820
+ if (connected.length > 0) connectedGroups.push(connected);
4821
+ isolatedIds.push(...isolated);
4822
+ }
4823
+ for (let index = 0; index < isolatedIds.length; index += MAX_CLUSTER_SIZE) {
4824
+ connectedGroups.push(isolatedIds.slice(index, index + MAX_CLUSTER_SIZE).sort());
4825
+ }
4826
+ return connectedGroups.map((nodeIds) => {
4827
+ const members = nodeIds.map((id) => nodeMap.get(id));
4828
+ const semantic = clusterLabel(members, weightedDegree);
4829
+ const isolated = members.every((node) => (weightedDegree.get(node.id) ?? 0) === 0);
4830
+ return {
4831
+ id: stableClusterId(nodeIds),
4832
+ label: isolated ? "\u672A\u8FDE\u63A5\u8282\u70B9" : semantic.label,
4833
+ nodeIds,
4834
+ tags: semantic.tags
4835
+ };
4836
+ }).sort((left, right) => right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label));
4837
+ }
4838
+
4839
+ // src/web.ts
4840
+ var STRATAGATE_DSH_VERSION = "0.2.27";
4724
4841
  var LEGACY_THREAD_ID = "__legacy__";
4725
4842
  var nodeRequire = createRequire(import.meta.url);
4726
4843
  function installedPackageVersion(names) {
@@ -5064,6 +5181,7 @@ async function memories(runtime, url) {
5064
5181
  supportingEvents: node.sourceEventIds.flatMap((id) => eventMap.get(id) ?? []).map(eventSummary)
5065
5182
  })),
5066
5183
  edges: snapshot.graphEdges,
5184
+ clusters: clusterKnowledgeGraph(snapshot.graphNodes, snapshot.graphEdges),
5067
5185
  migration: (() => {
5068
5186
  const projected = new Set(snapshot.graphProjectionJobs.filter(({ status, projectorVersion }) => status === "completed" && projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION).flatMap(({ sourceEventIds }) => sourceEventIds)).size;
5069
5187
  return {