tree-pruner.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. "use strict";
  2. function buildMaps(nodes, edges) {
  3. const byId = new Map(nodes.map((node) => [node.id, node]));
  4. const parent = new Map();
  5. const children = new Map();
  6. for (const edge of edges) {
  7. if (edge.type !== "hierarchy") continue;
  8. parent.set(edge.to, edge.from);
  9. if (!children.has(edge.from)) children.set(edge.from, []);
  10. children.get(edge.from).push(edge.to);
  11. }
  12. return { byId, parent, children };
  13. }
  14. function getAncestors(focusId, maps) {
  15. const path = [];
  16. let cursor = focusId;
  17. const seen = new Set();
  18. while (cursor && !seen.has(cursor)) {
  19. seen.add(cursor);
  20. const node = maps.byId.get(cursor);
  21. if (!node) break;
  22. path.push(node);
  23. cursor = maps.parent.get(cursor) || null;
  24. }
  25. return path.reverse();
  26. }
  27. function summarizeNode(node) {
  28. return `[${node.status}] ${node.name}`;
  29. }
  30. function pruneConversationContext(graph, focusNodeId) {
  31. const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
  32. const edges = Array.isArray(graph.edges) ? graph.edges : [];
  33. const maps = buildMaps(nodes, edges);
  34. const focus = maps.byId.get(focusNodeId);
  35. if (!focus) {
  36. return {
  37. focusNodeId,
  38. markdown: "未找到焦点节点,无法生成上下文。",
  39. segments: {
  40. ancestorPath: [],
  41. directChildren: [],
  42. siblingSummaries: [],
  43. foldedSummary: "无",
  44. },
  45. };
  46. }
  47. const ancestorPath = getAncestors(focusNodeId, maps);
  48. const directChildren = (maps.children.get(focusNodeId) || []).map((id) => maps.byId.get(id)).filter(Boolean);
  49. const parentId = maps.parent.get(focusNodeId) || null;
  50. const siblingSummaries = parentId
  51. ? (maps.children.get(parentId) || [])
  52. .filter((id) => id !== focusNodeId)
  53. .map((id) => maps.byId.get(id))
  54. .filter(Boolean)
  55. .map(summarizeNode)
  56. : [];
  57. const keepIds = new Set([
  58. ...ancestorPath.map((node) => node.id),
  59. focusNodeId,
  60. ...directChildren.map((node) => node.id),
  61. ]);
  62. const foldedCount = nodes.filter((node) => !keepIds.has(node.id) && node.status === "completed").length;
  63. const foldedSummary = foldedCount > 0 ? `已折叠 ${foldedCount} 个已完成远端节点` : "无";
  64. const markdownLines = [];
  65. markdownLines.push("## Focused Context");
  66. markdownLines.push("");
  67. markdownLines.push("### Ancestor Path");
  68. if (ancestorPath.length === 0) {
  69. markdownLines.push("- (empty)");
  70. } else {
  71. for (const node of ancestorPath) markdownLines.push(`- ${summarizeNode(node)}`);
  72. }
  73. markdownLines.push("");
  74. markdownLines.push("### Direct Children");
  75. if (directChildren.length === 0) {
  76. markdownLines.push("- (empty)");
  77. } else {
  78. for (const node of directChildren) markdownLines.push(`- ${summarizeNode(node)}`);
  79. }
  80. markdownLines.push("");
  81. markdownLines.push("### Sibling Branches");
  82. if (siblingSummaries.length === 0) {
  83. markdownLines.push("- (empty)");
  84. } else {
  85. for (const summary of siblingSummaries) markdownLines.push(`- ${summary}`);
  86. }
  87. markdownLines.push("");
  88. markdownLines.push("### Folded History");
  89. markdownLines.push(`- ${foldedSummary}`);
  90. return {
  91. focusNodeId,
  92. markdown: markdownLines.join("\n"),
  93. segments: {
  94. ancestorPath,
  95. directChildren,
  96. siblingSummaries,
  97. foldedSummary,
  98. },
  99. };
  100. }
  101. module.exports = {
  102. pruneConversationContext,
  103. };