stratagate-dsh 0.2.26 → 0.2.29
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/CHANGELOG.md +18 -0
- package/README.md +4 -4
- package/dist/client.js +354 -931
- package/dist/index.js +120 -2
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +4 -4
- package/package.json +56 -10
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
|
-
|
|
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.29";
|
|
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 {
|