aifastdb-devplan 1.6.1 → 1.6.3

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.
Files changed (97) hide show
  1. package/dist/dev-plan-document-store.d.ts +13 -1
  2. package/dist/dev-plan-document-store.d.ts.map +1 -1
  3. package/dist/dev-plan-document-store.js +119 -0
  4. package/dist/dev-plan-document-store.js.map +1 -1
  5. package/dist/dev-plan-factory.d.ts.map +1 -1
  6. package/dist/dev-plan-factory.js +3 -1
  7. package/dist/dev-plan-factory.js.map +1 -1
  8. package/dist/dev-plan-graph-store.d.ts +341 -9
  9. package/dist/dev-plan-graph-store.d.ts.map +1 -1
  10. package/dist/dev-plan-graph-store.js +2414 -210
  11. package/dist/dev-plan-graph-store.js.map +1 -1
  12. package/dist/dev-plan-interface.d.ts +119 -1
  13. package/dist/dev-plan-interface.d.ts.map +1 -1
  14. package/dist/dev-plan-migrate.d.ts +1 -0
  15. package/dist/dev-plan-migrate.d.ts.map +1 -1
  16. package/dist/dev-plan-migrate.js +28 -2
  17. package/dist/dev-plan-migrate.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp-server/index.js +652 -0
  22. package/dist/mcp-server/index.js.map +1 -1
  23. package/dist/shard-config.d.ts +64 -0
  24. package/dist/shard-config.d.ts.map +1 -0
  25. package/dist/shard-config.js +109 -0
  26. package/dist/shard-config.js.map +1 -0
  27. package/dist/types.d.ts +305 -2
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/visualize/graph-canvas/api-compat.d.ts.map +1 -1
  31. package/dist/visualize/graph-canvas/api-compat.js +22 -12
  32. package/dist/visualize/graph-canvas/api-compat.js.map +1 -1
  33. package/dist/visualize/graph-canvas/core.d.ts.map +1 -1
  34. package/dist/visualize/graph-canvas/core.js +296 -4
  35. package/dist/visualize/graph-canvas/core.js.map +1 -1
  36. package/dist/visualize/graph-canvas/interaction.d.ts.map +1 -1
  37. package/dist/visualize/graph-canvas/interaction.js +11 -0
  38. package/dist/visualize/graph-canvas/interaction.js.map +1 -1
  39. package/dist/visualize/graph-canvas/layout-worker.d.ts.map +1 -1
  40. package/dist/visualize/graph-canvas/layout-worker.js +45 -9
  41. package/dist/visualize/graph-canvas/layout-worker.js.map +1 -1
  42. package/dist/visualize/graph-canvas/renderer.d.ts.map +1 -1
  43. package/dist/visualize/graph-canvas/renderer.js +164 -33
  44. package/dist/visualize/graph-canvas/renderer.js.map +1 -1
  45. package/dist/visualize/graph-canvas/styles.d.ts.map +1 -1
  46. package/dist/visualize/graph-canvas/styles.js +146 -121
  47. package/dist/visualize/graph-canvas/styles.js.map +1 -1
  48. package/dist/visualize/graph-canvas/viewport.d.ts.map +1 -1
  49. package/dist/visualize/graph-canvas/viewport.js +10 -0
  50. package/dist/visualize/graph-canvas/viewport.js.map +1 -1
  51. package/dist/visualize/server.js +371 -32
  52. package/dist/visualize/server.js.map +1 -1
  53. package/dist/visualize/template-core.d.ts +9 -0
  54. package/dist/visualize/template-core.d.ts.map +1 -0
  55. package/dist/visualize/template-core.js +721 -0
  56. package/dist/visualize/template-core.js.map +1 -0
  57. package/dist/visualize/template-data-loading.d.ts +7 -0
  58. package/dist/visualize/template-data-loading.d.ts.map +1 -0
  59. package/dist/visualize/template-data-loading.js +677 -0
  60. package/dist/visualize/template-data-loading.js.map +1 -0
  61. package/dist/visualize/template-detail-panel.d.ts +14 -0
  62. package/dist/visualize/template-detail-panel.d.ts.map +1 -0
  63. package/dist/visualize/template-detail-panel.js +624 -0
  64. package/dist/visualize/template-detail-panel.js.map +1 -0
  65. package/dist/visualize/template-graph-3d.d.ts +7 -0
  66. package/dist/visualize/template-graph-3d.d.ts.map +1 -0
  67. package/dist/visualize/template-graph-3d.js +1114 -0
  68. package/dist/visualize/template-graph-3d.js.map +1 -0
  69. package/dist/visualize/template-graph-vis.d.ts +8 -0
  70. package/dist/visualize/template-graph-vis.d.ts.map +1 -0
  71. package/dist/visualize/template-graph-vis.js +1215 -0
  72. package/dist/visualize/template-graph-vis.js.map +1 -0
  73. package/dist/visualize/template-html.d.ts +9 -0
  74. package/dist/visualize/template-html.d.ts.map +1 -0
  75. package/dist/visualize/template-html.js +635 -0
  76. package/dist/visualize/template-html.js.map +1 -0
  77. package/dist/visualize/template-md-viewer.d.ts +11 -0
  78. package/dist/visualize/template-md-viewer.d.ts.map +1 -0
  79. package/dist/visualize/template-md-viewer.js +806 -0
  80. package/dist/visualize/template-md-viewer.js.map +1 -0
  81. package/dist/visualize/template-pages.d.ts +7 -0
  82. package/dist/visualize/template-pages.d.ts.map +1 -0
  83. package/dist/visualize/template-pages.js +1892 -0
  84. package/dist/visualize/template-pages.js.map +1 -0
  85. package/dist/visualize/template-stats-modal.d.ts +7 -0
  86. package/dist/visualize/template-stats-modal.d.ts.map +1 -0
  87. package/dist/visualize/template-stats-modal.js +466 -0
  88. package/dist/visualize/template-stats-modal.js.map +1 -0
  89. package/dist/visualize/template-styles.d.ts +9 -0
  90. package/dist/visualize/template-styles.d.ts.map +1 -0
  91. package/dist/visualize/template-styles.js +623 -0
  92. package/dist/visualize/template-styles.js.map +1 -0
  93. package/dist/visualize/template.d.ts +15 -3
  94. package/dist/visualize/template.d.ts.map +1 -1
  95. package/dist/visualize/template.js +44 -3475
  96. package/dist/visualize/template.js.map +1 -1
  97. package/package.json +2 -2
@@ -276,12 +276,15 @@ function startServer(projectName, basePath, port) {
276
276
  const includeModules = url.searchParams.get('includeModules') !== 'false';
277
277
  const includeNodeDegree = url.searchParams.get('includeNodeDegree') !== 'false';
278
278
  const enableBackendDegreeFallback = url.searchParams.get('enableBackendDegreeFallback') !== 'false';
279
+ // 可视化页面默认不渲染 Prompt 节点(通过顶部统计栏点击查看 Prompt 列表)
280
+ const includePrompts = url.searchParams.get('includePrompts') === 'true';
279
281
  if (store.exportGraph) {
280
282
  const graph = store.exportGraph({
281
283
  includeDocuments,
282
284
  includeModules,
283
285
  includeNodeDegree,
284
286
  enableBackendDegreeFallback,
287
+ includePrompts,
285
288
  });
286
289
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
287
290
  res.end(JSON.stringify(graph));
@@ -296,8 +299,40 @@ function startServer(projectName, basePath, port) {
296
299
  // 每次请求重新创建 store,确保读取最新数据
297
300
  const store = createFreshStore(projectName, basePath);
298
301
  const progress = store.getProgress();
302
+ // 附加模块和文档计数(分层加载模式下 graph.nodes 不含全部类型,需从此处获取真实数量)
303
+ const sections = store.listSections();
304
+ const modules = store.listModules();
305
+ // 附加 Prompt 计数
306
+ let promptCount = 0;
307
+ try {
308
+ if (typeof store.listPrompts === 'function') {
309
+ promptCount = store.listPrompts().length;
310
+ }
311
+ }
312
+ catch (e) { /* listPrompts 不支持时忽略 */ }
313
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
314
+ res.end(JSON.stringify({
315
+ ...progress,
316
+ moduleCount: modules.length,
317
+ docCount: sections.length,
318
+ promptCount,
319
+ }));
320
+ break;
321
+ }
322
+ case '/api/prompts': {
323
+ // 列出所有 Prompt 日志
324
+ const store = createFreshStore(projectName, basePath);
325
+ let prompts = [];
326
+ try {
327
+ if (typeof store.listPrompts === 'function') {
328
+ prompts = store.listPrompts();
329
+ }
330
+ }
331
+ catch (e) {
332
+ prompts = [];
333
+ }
299
334
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
300
- res.end(JSON.stringify(progress));
335
+ res.end(JSON.stringify({ prompts, count: prompts.length }));
301
336
  break;
302
337
  }
303
338
  case '/api/stats': {
@@ -400,6 +435,228 @@ function startServer(projectName, basePath, port) {
400
435
  res.end(JSON.stringify(stats));
401
436
  break;
402
437
  }
438
+ case '/api/memories': {
439
+ // 列出所有记忆(用于记忆浏览页面)
440
+ const memStore = createFreshStore(projectName, basePath);
441
+ let memories = [];
442
+ if (typeof memStore.listMemories === 'function') {
443
+ const memoryType = url.searchParams.get('memoryType') || undefined;
444
+ memories = memStore.listMemories({
445
+ memoryType,
446
+ });
447
+ }
448
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
449
+ res.end(JSON.stringify({ memories }));
450
+ break;
451
+ }
452
+ case '/api/memories/generate': {
453
+ // 从文档/任务中生成记忆候选项
454
+ const genStore = createFreshStore(projectName, basePath);
455
+ if (typeof genStore.generateMemoryCandidates !== 'function') {
456
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
457
+ res.end(JSON.stringify({ error: 'generateMemoryCandidates not supported (requires graph engine)' }));
458
+ break;
459
+ }
460
+ const genSource = url.searchParams.get('source') || 'both';
461
+ const genTaskId = url.searchParams.get('taskId') || undefined;
462
+ const genSection = url.searchParams.get('section') || undefined;
463
+ const rawLimit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit'), 10) : 50;
464
+ const genLimit = rawLimit <= 0 ? 99999 : rawLimit;
465
+ const result = genStore.generateMemoryCandidates({
466
+ source: genSource,
467
+ taskId: genTaskId,
468
+ section: genSection,
469
+ limit: genLimit,
470
+ });
471
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
472
+ res.end(JSON.stringify(result));
473
+ break;
474
+ }
475
+ case '/api/memories/save': {
476
+ // 保存一条记忆(POST)
477
+ if (req.method !== 'POST') {
478
+ res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
479
+ res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
480
+ break;
481
+ }
482
+ const saveBody = await readRequestBody(req);
483
+ const saveStore = createFreshStore(projectName, basePath);
484
+ if (typeof saveStore.saveMemory !== 'function') {
485
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
486
+ res.end(JSON.stringify({ error: 'saveMemory not supported (requires graph engine)' }));
487
+ break;
488
+ }
489
+ try {
490
+ const saved = saveStore.saveMemory({
491
+ projectName,
492
+ memoryType: saveBody.memoryType || 'insight',
493
+ content: saveBody.content || '',
494
+ tags: saveBody.tags || [],
495
+ relatedTaskId: saveBody.relatedTaskId || undefined,
496
+ sourceId: saveBody.sourceId || undefined,
497
+ importance: saveBody.importance ?? 0.5,
498
+ });
499
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
500
+ res.end(JSON.stringify({ status: 'saved', memory: saved }));
501
+ }
502
+ catch (e) {
503
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
504
+ res.end(JSON.stringify({ error: e.message || String(e) }));
505
+ }
506
+ break;
507
+ }
508
+ case '/api/memories/relate': {
509
+ // Phase-44: 建立记忆间关系(POST)
510
+ if (req.method !== 'POST') {
511
+ res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
512
+ res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
513
+ break;
514
+ }
515
+ const relBody = await readRequestBody(req);
516
+ const relStore = createFreshStore(projectName, basePath);
517
+ try {
518
+ // 使用 graph 的 put_relation 或 applyMutations 建立关系
519
+ const graph = relStore.graph;
520
+ if (!graph) {
521
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
522
+ res.end(JSON.stringify({ error: 'Graph engine not available' }));
523
+ break;
524
+ }
525
+ const fromId = relBody.fromId;
526
+ const toId = relBody.toId;
527
+ const relationType = relBody.relationType || 'MEMORY_RELATES';
528
+ const weight = relBody.weight ?? 0.5;
529
+ // 尝试使用 applyMutations (Phase-44),回退到 putRelation
530
+ if (typeof graph.applyMutations === 'function') {
531
+ graph.applyMutations([{
532
+ type: 'PutRelation',
533
+ relation: {
534
+ source_id: fromId,
535
+ target_id: toId,
536
+ relation_type: relationType,
537
+ weight: weight,
538
+ }
539
+ }]);
540
+ }
541
+ else if (typeof graph.putRelation === 'function') {
542
+ graph.putRelation({
543
+ source_id: fromId,
544
+ target_id: toId,
545
+ relation_type: relationType,
546
+ weight: weight,
547
+ });
548
+ }
549
+ else {
550
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
551
+ res.end(JSON.stringify({ error: 'No relation creation method available' }));
552
+ break;
553
+ }
554
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
555
+ res.end(JSON.stringify({ status: 'created', from: fromId, to: toId, relationType, weight }));
556
+ }
557
+ catch (e) {
558
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
559
+ res.end(JSON.stringify({ error: e.message || String(e) }));
560
+ }
561
+ break;
562
+ }
563
+ case '/api/memories/clear': {
564
+ // 批量清除所有记忆(POST)
565
+ if (req.method !== 'POST') {
566
+ res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
567
+ res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
568
+ break;
569
+ }
570
+ const clearBody = await readRequestBody(req);
571
+ if (clearBody.confirm !== true) {
572
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
573
+ res.end(JSON.stringify({ error: 'Safety guard: confirm must be true' }));
574
+ break;
575
+ }
576
+ const clearStore = createFreshStore(projectName, basePath);
577
+ if (typeof clearStore.clearAllMemories !== 'function') {
578
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
579
+ res.end(JSON.stringify({ error: 'clearAllMemories not supported (requires graph engine)' }));
580
+ break;
581
+ }
582
+ try {
583
+ const clearResult = clearStore.clearAllMemories(clearBody.memoryType || undefined);
584
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
585
+ res.end(JSON.stringify({ status: 'cleared', ...clearResult, memoryType: clearBody.memoryType || 'all' }));
586
+ }
587
+ catch (e) {
588
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
589
+ res.end(JSON.stringify({ error: e.message || String(e) }));
590
+ }
591
+ break;
592
+ }
593
+ case '/api/memories/graph': {
594
+ // 导出仅记忆相关的图数据(用于记忆页面 3D 可视化)
595
+ const memGraphStore = createFreshStore(projectName, basePath);
596
+ if (!memGraphStore.exportGraph) {
597
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
598
+ res.end(JSON.stringify({ error: 'exportGraph not supported (requires graph engine)' }));
599
+ break;
600
+ }
601
+ try {
602
+ // 导出完整图(含记忆),然后过滤出记忆相关节点和边
603
+ const fullGraph = memGraphStore.exportGraph({
604
+ includeDocuments: true,
605
+ includeModules: true,
606
+ includeNodeDegree: false,
607
+ enableBackendDegreeFallback: false,
608
+ includePrompts: false,
609
+ });
610
+ if (!fullGraph) {
611
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
612
+ res.end(JSON.stringify({ error: 'exportGraph returned null' }));
613
+ break;
614
+ }
615
+ // 收集记忆节点 ID
616
+ const memoryNodeIds = new Set();
617
+ const memoryNodes = [];
618
+ const contextNodes = [];
619
+ const contextNodeIds = new Set();
620
+ for (const node of fullGraph.nodes) {
621
+ if (node.type === 'memory') {
622
+ memoryNodeIds.add(node.id);
623
+ memoryNodes.push(node);
624
+ }
625
+ }
626
+ // 收集与记忆相关的边和上下文节点(任务、文档、模块)
627
+ const memEdges = [];
628
+ for (const edge of fullGraph.edges) {
629
+ const fromIsMem = memoryNodeIds.has(edge.from);
630
+ const toIsMem = memoryNodeIds.has(edge.to);
631
+ if (fromIsMem || toIsMem) {
632
+ memEdges.push(edge);
633
+ // 添加非记忆端的上下文节点
634
+ const otherId = fromIsMem ? edge.to : edge.from;
635
+ if (!memoryNodeIds.has(otherId) && !contextNodeIds.has(otherId)) {
636
+ contextNodeIds.add(otherId);
637
+ const ctxNode = fullGraph.nodes.find((n) => n.id === otherId);
638
+ if (ctxNode)
639
+ contextNodes.push(ctxNode);
640
+ }
641
+ }
642
+ }
643
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
644
+ res.end(JSON.stringify({
645
+ nodes: [...memoryNodes, ...contextNodes],
646
+ edges: memEdges,
647
+ stats: {
648
+ memoryCount: memoryNodes.length,
649
+ contextCount: contextNodes.length,
650
+ edgeCount: memEdges.length,
651
+ },
652
+ }));
653
+ }
654
+ catch (e) {
655
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
656
+ res.end(JSON.stringify({ error: e.message || String(e) }));
657
+ }
658
+ break;
659
+ }
403
660
  case '/api/docs': {
404
661
  // 列出所有文档片段(不含内容,用于文档浏览页面左侧列表)
405
662
  const store = createFreshStore(projectName, basePath);
@@ -855,19 +1112,48 @@ function startServer(projectName, basePath, port) {
855
1112
  break;
856
1113
  }
857
1114
  case '/api/graph/paged': {
858
- // ── Phase-8C T8C.3: Paginated graph data API ──
859
- // GET /api/graph/paged?offset=0&limit=5000&sortBy=center
860
- // Returns a page of { nodes, edges, total, hasMore }
861
- // sortBy=center: viewport-priority (nodes sorted by distance to center)
862
- // sortBy=default: natural order
863
- const store = createFreshStore(projectName, basePath);
1115
+ // ── Phase-9 T9.2: True pagination push-down to Rust layer ──
1116
+ // GET /api/graph/paged?offset=0&limit=5000
1117
+ // Returns a page of { nodes, edges, totalNodes, totalEdges, hasMore }
1118
+ // Now calls Rust SocialGraphV2.exportGraphPaginated() via NAPI
1119
+ // instead of full-loading + in-memory slicing.
1120
+ const pagedStore = createFreshStore(projectName, basePath);
864
1121
  const offset = parseInt(url.searchParams.get('offset') || '0', 10);
865
1122
  const limit = parseInt(url.searchParams.get('limit') || '5000', 10);
866
- const sortBy = url.searchParams.get('sortBy') || 'default';
867
1123
  const includeDocuments = url.searchParams.get('includeDocuments') !== 'false';
868
1124
  const includeModules = url.searchParams.get('includeModules') !== 'false';
869
- if (store.exportGraph) {
870
- const fullGraph = store.exportGraph({
1125
+ // Phase-10 T10.1: Support entityTypes query param for tiered loading
1126
+ const entityTypesParam = url.searchParams.get('entityTypes');
1127
+ const entityTypes = entityTypesParam ? entityTypesParam.split(',').map(t => t.trim()).filter(Boolean) : undefined;
1128
+ // Use the new pagination push-down method if available
1129
+ if (pagedStore.exportGraphPaginated) {
1130
+ try {
1131
+ const result = pagedStore.exportGraphPaginated(offset, limit, {
1132
+ includeDocuments,
1133
+ includeModules,
1134
+ includeNodeDegree: true,
1135
+ entityTypes,
1136
+ });
1137
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1138
+ res.end(JSON.stringify({
1139
+ nodes: result.nodes,
1140
+ edges: result.edges,
1141
+ total: result.totalNodes,
1142
+ totalEdges: result.totalEdges,
1143
+ offset: result.offset,
1144
+ limit: result.limit,
1145
+ hasMore: result.hasMore,
1146
+ nextOffset: Math.min(offset + limit, result.totalNodes),
1147
+ }));
1148
+ }
1149
+ catch (err) {
1150
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1151
+ res.end(JSON.stringify({ error: `分页导出失败: ${err?.message || err}` }));
1152
+ }
1153
+ }
1154
+ else if (pagedStore.exportGraph) {
1155
+ // Fallback: full load + in-memory slicing (pre-Phase-9 behavior)
1156
+ const fullGraph = pagedStore.exportGraph({
871
1157
  includeDocuments,
872
1158
  includeModules,
873
1159
  includeNodeDegree: true,
@@ -878,31 +1164,10 @@ function startServer(projectName, basePath, port) {
878
1164
  res.end(JSON.stringify({ error: '图数据导出失败' }));
879
1165
  break;
880
1166
  }
881
- let allNodes = fullGraph.nodes || [];
1167
+ const allNodes = fullGraph.nodes || [];
882
1168
  const allEdges = fullGraph.edges || [];
883
- // T8C.4: Viewport-priority sorting — sort by distance from center
884
- if (sortBy === 'center') {
885
- // Calculate centroid of all nodes
886
- let cx = 0, cy = 0;
887
- for (const n of allNodes) {
888
- cx += (n.x || 0);
889
- cy += (n.y || 0);
890
- }
891
- if (allNodes.length > 0) {
892
- cx /= allNodes.length;
893
- cy /= allNodes.length;
894
- }
895
- // Sort by distance to centroid (closest first)
896
- allNodes = [...allNodes].sort((a, b) => {
897
- const da = (a.x - cx) ** 2 + (a.y - cy) ** 2;
898
- const db = (b.x - cx) ** 2 + (b.y - cy) ** 2;
899
- return da - db;
900
- });
901
- }
902
- // Slice the page
903
1169
  const pageNodes = allNodes.slice(offset, offset + limit);
904
1170
  const pageNodeIds = new Set(pageNodes.map((n) => n.id));
905
- // Include only edges where both endpoints are in this page
906
1171
  const pageEdges = allEdges.filter((e) => pageNodeIds.has(e.from) && pageNodeIds.has(e.to));
907
1172
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
908
1173
  res.end(JSON.stringify({
@@ -922,6 +1187,80 @@ function startServer(projectName, basePath, port) {
922
1187
  }
923
1188
  break;
924
1189
  }
1190
+ case '/api/graph/binary': {
1191
+ // ── Phase-9 T9.3: Binary compact export endpoint ──
1192
+ // GET /api/graph/binary
1193
+ // Returns ArrayBuffer with compact binary format (5x smaller than JSON)
1194
+ // Client can parse directly as TypedArray, no JSON.parse needed.
1195
+ const binaryStore = createFreshStore(projectName, basePath);
1196
+ if (binaryStore.exportGraphCompact) {
1197
+ try {
1198
+ const buf = binaryStore.exportGraphCompact();
1199
+ if (buf && buf.length > 0) {
1200
+ res.writeHead(200, {
1201
+ 'Content-Type': 'application/octet-stream',
1202
+ 'Content-Length': String(buf.length),
1203
+ 'X-Node-Count': String(new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(8, true)),
1204
+ 'X-Edge-Count': String(new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(12, true)),
1205
+ });
1206
+ res.end(buf);
1207
+ }
1208
+ else {
1209
+ // Fallback: return empty binary
1210
+ const emptyBuf = Buffer.alloc(16);
1211
+ emptyBuf.writeUInt32LE(0x41494647, 0); // magic
1212
+ emptyBuf.writeUInt32LE(1, 4); // version
1213
+ emptyBuf.writeUInt32LE(0, 8); // node_count
1214
+ emptyBuf.writeUInt32LE(0, 12); // edge_count
1215
+ res.writeHead(200, {
1216
+ 'Content-Type': 'application/octet-stream',
1217
+ 'Content-Length': '16',
1218
+ 'X-Node-Count': '0',
1219
+ 'X-Edge-Count': '0',
1220
+ });
1221
+ res.end(emptyBuf);
1222
+ }
1223
+ }
1224
+ catch (err) {
1225
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1226
+ res.end(JSON.stringify({ error: `二进制导出失败: ${err?.message || err}` }));
1227
+ }
1228
+ }
1229
+ else {
1230
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1231
+ res.end(JSON.stringify({ error: '当前引擎不支持二进制导出' }));
1232
+ }
1233
+ break;
1234
+ }
1235
+ case '/api/graph/clusters': {
1236
+ // ── Phase-9 T9.4: Server-side aggregation endpoint ──
1237
+ // GET /api/graph/clusters
1238
+ // Returns pre-aggregated entity group summaries.
1239
+ // Ideal for low-zoom cluster views — no need to transfer all nodes.
1240
+ const clusterStore = createFreshStore(projectName, basePath);
1241
+ if (clusterStore.getEntityGroupSummary) {
1242
+ try {
1243
+ const agg = clusterStore.getEntityGroupSummary();
1244
+ if (agg) {
1245
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1246
+ res.end(JSON.stringify(agg));
1247
+ }
1248
+ else {
1249
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
1250
+ res.end(JSON.stringify({ groups: [], totalEntities: 0, totalRelations: 0 }));
1251
+ }
1252
+ }
1253
+ catch (err) {
1254
+ res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
1255
+ res.end(JSON.stringify({ error: `聚合查询失败: ${err?.message || err}` }));
1256
+ }
1257
+ }
1258
+ else {
1259
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
1260
+ res.end(JSON.stringify({ error: '当前引擎不支持聚合查询' }));
1261
+ }
1262
+ break;
1263
+ }
925
1264
  case '/graph-canvas.js': {
926
1265
  // Serve the GraphCanvas engine as a JavaScript file
927
1266
  res.writeHead(200, {