noteconnection 1.8.0 → 1.9.0

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 (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. package/dist/src/routes/diagnostics.js +0 -51
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PathBridge = exports.BRIDGE_INBOUND_SCHEMA_LIMITS = exports.BRIDGE_INBOUND_LIMITS = exports.BRIDGE_BACKPRESSURE_LIMITS = void 0;
3
+ exports.PathBridge = exports.BRIDGE_INBOUND_SCHEMA_LIMITS = exports.BRIDGE_INBOUND_LIMITS = exports.BRIDGE_BACKPRESSURE_LIMITS = exports.PATH_BRIDGE_CAPABILITIES = exports.PATH_BRIDGE_PROTOCOL_VERSION = void 0;
4
4
  exports.resolveBridgeInboundSchemaPolicy = resolveBridgeInboundSchemaPolicy;
5
5
  exports.resolveBridgeInboundLimitConfig = resolveBridgeInboundLimitConfig;
6
6
  exports.parseBridgeInboundEnvelope = parseBridgeInboundEnvelope;
@@ -8,10 +8,21 @@ exports.computeBridgePathFingerprint = computeBridgePathFingerprint;
8
8
  exports.buildBridgePathTransportSummary = buildBridgePathTransportSummary;
9
9
  exports.validateBridgePathPayload = validateBridgePathPayload;
10
10
  const ws_1 = require("ws");
11
+ /** Versioned, host-neutral contract shared by Web, Tauri, Capacitor, and Godot. */
12
+ exports.PATH_BRIDGE_PROTOCOL_VERSION = '2.0';
13
+ exports.PATH_BRIDGE_CAPABILITIES = Object.freeze([
14
+ 'path.transport',
15
+ 'knowledge.analyze',
16
+ 'knowledge.query',
17
+ 'knowledge.readEvidence',
18
+ 'knowledge.exportBundle',
19
+ 'request.cancel',
20
+ ]);
11
21
  const PATH_REQUEST_TIMEOUT_MS = 3000;
12
22
  const PATH_PRODUCER_GRACE_MS = 30000;
13
23
  const MERMAID_RENDER_TIMEOUT_MS = 12000;
14
24
  const UNAUTHORIZED_CLIENT_TIMEOUT_MS = 5000;
25
+ const DEFAULT_HOST_OPERATION_TIMEOUT_MS = 15000;
15
26
  const BYTES_PER_MIB = 1024 * 1024;
16
27
  const DEFAULT_INBOUND_MESSAGE_LIMIT_MIB = 128;
17
28
  const LARGE_GRAPH_INBOUND_MESSAGE_LIMIT_MIB = 256;
@@ -131,6 +142,12 @@ const PATH_MUTATION_TYPES = new Set([
131
142
  const KNOWN_BRIDGE_MESSAGE_TYPES = new Set([
132
143
  'authenticate',
133
144
  'identify',
145
+ 'capabilities',
146
+ 'analyze',
147
+ 'query',
148
+ 'readEvidence',
149
+ 'exportBundle',
150
+ 'cancel',
134
151
  'requestPath',
135
152
  'pathResult',
136
153
  'pathStatus',
@@ -247,6 +264,74 @@ function validateRequestPathPayload(payload) {
247
264
  }
248
265
  return null;
249
266
  }
267
+ const BRIDGE_CORRELATION_ID_MAX_LENGTH = 128;
268
+ const BRIDGE_CAPABILITY_MAX_COUNT = 32;
269
+ function validateCorrelationId(value, field) {
270
+ if (value === undefined) {
271
+ return null;
272
+ }
273
+ if (!isNonEmptyString(value)) {
274
+ return `${field} must be a non-empty string when provided.`;
275
+ }
276
+ if (value.trim().length > BRIDGE_CORRELATION_ID_MAX_LENGTH) {
277
+ return `${field} must be at most ${BRIDGE_CORRELATION_ID_MAX_LENGTH} characters.`;
278
+ }
279
+ if (/[\u0000-\u001f\u007f]/.test(value)) {
280
+ return `${field} must not contain control characters.`;
281
+ }
282
+ return null;
283
+ }
284
+ function validateCapabilitiesPayload(payload) {
285
+ if (payload.protocolVersion !== undefined && !isNonEmptyString(payload.protocolVersion)) {
286
+ return 'capabilities payload.protocolVersion must be a non-empty string when provided.';
287
+ }
288
+ if (payload.client !== undefined && !isNonEmptyString(payload.client)) {
289
+ return 'capabilities payload.client must be a non-empty string when provided.';
290
+ }
291
+ if (!Array.isArray(payload.capabilities)) {
292
+ return 'capabilities payload.capabilities must be an array.';
293
+ }
294
+ if (payload.capabilities.length > BRIDGE_CAPABILITY_MAX_COUNT) {
295
+ return `capabilities payload.capabilities must contain at most ${BRIDGE_CAPABILITY_MAX_COUNT} entries.`;
296
+ }
297
+ if (payload.capabilities.some((capability) => !isNonEmptyString(capability))) {
298
+ return 'capabilities payload.capabilities must contain only non-empty strings.';
299
+ }
300
+ if (payload.maxPayloadBytes !== undefined && !isFiniteNumberValue(payload.maxPayloadBytes)) {
301
+ return 'capabilities payload.maxPayloadBytes must be a finite number when provided.';
302
+ }
303
+ return null;
304
+ }
305
+ function validateAnalysisRequestPayload(type, payload) {
306
+ if (!isRecord(payload)) {
307
+ return `${type} payload must be an object.`;
308
+ }
309
+ const requestIdError = validateCorrelationId(payload.requestId, `${type} payload.requestId`);
310
+ if (requestIdError) {
311
+ return requestIdError;
312
+ }
313
+ if (type !== 'cancel' && !isNonEmptyString(payload.requestId)) {
314
+ return `${type} payload.requestId must be a non-empty string.`;
315
+ }
316
+ if (payload.workspaceId !== undefined && !isNonEmptyString(payload.workspaceId)) {
317
+ return `${type} payload.workspaceId must be a non-empty string when provided.`;
318
+ }
319
+ if (payload.sourceUri !== undefined && !isNonEmptyString(payload.sourceUri)) {
320
+ return `${type} payload.sourceUri must be a non-empty string when provided.`;
321
+ }
322
+ return null;
323
+ }
324
+ function identifyPayloadCapabilitiesInvalid(payload) {
325
+ if (payload.capabilities === undefined) {
326
+ return null;
327
+ }
328
+ return validateCapabilitiesPayload({
329
+ protocolVersion: payload.protocolVersion,
330
+ client: payload.client ?? payload.tag,
331
+ capabilities: payload.capabilities,
332
+ maxPayloadBytes: payload.maxPayloadBytes,
333
+ });
334
+ }
250
335
  function validatePathStatusPayload(payload) {
251
336
  if (payload.level !== undefined && !isValidBridgeStatusLevel(payload.level)) {
252
337
  return 'pathStatus payload.level must be one of info/success/warning/error when provided.';
@@ -485,8 +570,22 @@ function validateKnownEnvelopePayload(type, payload, policy) {
485
570
  if (client !== undefined && typeof client !== 'string') {
486
571
  return `${type} client/tag must be a string when provided.`;
487
572
  }
573
+ if (identifyPayloadCapabilitiesInvalid(payload)) {
574
+ return identifyPayloadCapabilitiesInvalid(payload);
575
+ }
488
576
  }
489
577
  return null;
578
+ case 'capabilities':
579
+ if (!isRecord(payload)) {
580
+ return 'capabilities payload must be an object.';
581
+ }
582
+ return validateCapabilitiesPayload(payload);
583
+ case 'analyze':
584
+ case 'query':
585
+ case 'readEvidence':
586
+ case 'exportBundle':
587
+ case 'cancel':
588
+ return validateAnalysisRequestPayload(type, payload);
490
589
  case 'requestPath':
491
590
  if (payload !== undefined && !isRecord(payload)) {
492
591
  return 'requestPath payload must be an object when provided.';
@@ -593,6 +692,9 @@ function parseBridgeInboundEnvelope(data, policy = BRIDGE_INBOUND_SCHEMA_POLICY)
593
692
  payload: data.payload,
594
693
  token: data.token,
595
694
  client: data.client,
695
+ version: data.version,
696
+ requestId: data.requestId,
697
+ correlationId: data.correlationId,
596
698
  };
597
699
  if (envelope.token !== undefined && typeof envelope.token !== 'string') {
598
700
  return {
@@ -606,6 +708,18 @@ function parseBridgeInboundEnvelope(data, policy = BRIDGE_INBOUND_SCHEMA_POLICY)
606
708
  reason: 'Bridge message client must be a string when provided.',
607
709
  };
608
710
  }
711
+ const versionError = validateCorrelationId(envelope.version, 'Bridge message version');
712
+ if (versionError) {
713
+ return { ok: false, reason: versionError };
714
+ }
715
+ const requestIdError = validateCorrelationId(envelope.requestId, 'Bridge message requestId');
716
+ if (requestIdError) {
717
+ return { ok: false, reason: requestIdError };
718
+ }
719
+ const correlationIdError = validateCorrelationId(envelope.correlationId, 'Bridge message correlationId');
720
+ if (correlationIdError) {
721
+ return { ok: false, reason: correlationIdError };
722
+ }
609
723
  const isKnownType = KNOWN_BRIDGE_MESSAGE_TYPES.has(type);
610
724
  if (!isKnownType && policy.rejectUnknownTypes) {
611
725
  return {
@@ -828,6 +942,7 @@ class PathBridge {
828
942
  this.currentPath = null;
829
943
  this.pendingPathRequests = new Map();
830
944
  this.pendingMermaidRenderRequests = new Map();
945
+ this.pendingHostOperations = new Map();
831
946
  this.unauthorizedClientTimers = new Map();
832
947
  this.outboundQueueState = new Map();
833
948
  this.nextMermaidRenderRequestId = 1;
@@ -840,6 +955,11 @@ class PathBridge {
840
955
  : 9876;
841
956
  this.host = resolvedOptions.host || '127.0.0.1';
842
957
  this.authToken = typeof resolvedOptions.authToken === 'string' ? resolvedOptions.authToken.trim() : '';
958
+ this.hostAdapter = resolvedOptions.hostAdapter || null;
959
+ this.hostOperationTimeoutMs = Number.isFinite(Number(resolvedOptions.hostOperationTimeoutMs)) &&
960
+ Number(resolvedOptions.hostOperationTimeoutMs) > 0
961
+ ? Math.floor(Number(resolvedOptions.hostOperationTimeoutMs))
962
+ : DEFAULT_HOST_OPERATION_TIMEOUT_MS;
843
963
  this.wss = new ws_1.WebSocketServer({
844
964
  port: this.port,
845
965
  host: this.host,
@@ -918,6 +1038,7 @@ class PathBridge {
918
1038
  const wasProducer = !!meta && this.isPathProducerTag(meta.tag);
919
1039
  this.clearUnauthorizedDisconnect(ws);
920
1040
  this.clearPendingPathRequest(ws);
1041
+ this.cancelHostOperationsForClient(ws, 'client_disconnected');
921
1042
  this.clearOutboundQueueState(ws);
922
1043
  this.clients.delete(ws);
923
1044
  this.clientMeta.delete(ws);
@@ -1187,7 +1308,11 @@ class PathBridge {
1187
1308
  }
1188
1309
  let serialized = '';
1189
1310
  try {
1190
- serialized = JSON.stringify({ type, payload });
1311
+ serialized = JSON.stringify({
1312
+ type,
1313
+ payload,
1314
+ version: exports.PATH_BRIDGE_PROTOCOL_VERSION,
1315
+ });
1191
1316
  }
1192
1317
  catch (error) {
1193
1318
  const meta = this.clientMeta.get(client);
@@ -1221,6 +1346,15 @@ class PathBridge {
1221
1346
  sendStatus(client, payload) {
1222
1347
  this.sendMessage(client, 'pathStatus', payload);
1223
1348
  }
1349
+ sendCapabilities(client) {
1350
+ this.sendMessage(client, 'capabilities', {
1351
+ protocolVersion: exports.PATH_BRIDGE_PROTOCOL_VERSION,
1352
+ capabilities: [...exports.PATH_BRIDGE_CAPABILITIES],
1353
+ maxPayloadBytes: MAX_INBOUND_MESSAGE_BYTES,
1354
+ hostAdapter: Boolean(this.hostAdapter),
1355
+ hostOperationTimeoutMs: this.hostOperationTimeoutMs,
1356
+ });
1357
+ }
1224
1358
  notifyPendingPathRequests(payload) {
1225
1359
  const pendingClients = Array.from(this.pendingPathRequests.keys());
1226
1360
  pendingClients.forEach((client) => this.sendStatus(client, payload));
@@ -1239,6 +1373,17 @@ class PathBridge {
1239
1373
  clearAllPendingPathRequests() {
1240
1374
  Array.from(this.pendingPathRequests.keys()).forEach((client) => this.clearPendingPathRequest(client));
1241
1375
  }
1376
+ cancelHostOperationsForClient(client, reason) {
1377
+ Array.from(this.pendingHostOperations.entries()).forEach(([requestId, pending]) => {
1378
+ if (pending.client !== client) {
1379
+ return;
1380
+ }
1381
+ clearTimeout(pending.timer);
1382
+ pending.controller.abort(reason);
1383
+ this.pendingHostOperations.delete(requestId);
1384
+ this.hostAdapter?.cancel?.(requestId);
1385
+ });
1386
+ }
1242
1387
  invalidateCurrentPath(reason) {
1243
1388
  if (!this.currentPath) {
1244
1389
  return;
@@ -1451,6 +1596,101 @@ class PathBridge {
1451
1596
  }
1452
1597
  this.broadcast('pathStatus', status);
1453
1598
  }
1599
+ sendHostOperationResult(client, operationType, requestId, correlationId, result, error, cancelled = false) {
1600
+ this.sendMessage(client, 'operationResult', {
1601
+ type: operationType,
1602
+ requestId,
1603
+ correlationId,
1604
+ ok: !error && !cancelled,
1605
+ cancelled,
1606
+ result: error || cancelled ? undefined : result,
1607
+ error: error || (cancelled ? 'operation_cancelled' : undefined),
1608
+ });
1609
+ }
1610
+ executeHostOperation(type, payloadLike, envelope, sender) {
1611
+ if (!this.hostAdapter || !isRecord(payloadLike)) {
1612
+ this.broadcast(type, payloadLike || {});
1613
+ return;
1614
+ }
1615
+ const requestId = typeof payloadLike.requestId === 'string' ? payloadLike.requestId.trim() : '';
1616
+ if (!requestId) {
1617
+ this.sendHostOperationResult(sender, type, '', '', undefined, 'request_id_required');
1618
+ return;
1619
+ }
1620
+ if (this.pendingHostOperations.has(requestId)) {
1621
+ this.sendHostOperationResult(sender, type, requestId, requestId, undefined, 'request_in_flight');
1622
+ return;
1623
+ }
1624
+ const controller = new AbortController();
1625
+ const correlationId = typeof envelope.correlationId === 'string' && envelope.correlationId.trim()
1626
+ ? envelope.correlationId.trim()
1627
+ : requestId;
1628
+ const timer = setTimeout(() => {
1629
+ const pending = this.pendingHostOperations.get(requestId);
1630
+ if (!pending) {
1631
+ return;
1632
+ }
1633
+ this.pendingHostOperations.delete(requestId);
1634
+ pending.controller.abort();
1635
+ this.hostAdapter?.cancel?.(requestId);
1636
+ this.sendHostOperationResult(sender, type, requestId, correlationId, undefined, 'operation_timeout');
1637
+ }, this.hostOperationTimeoutMs);
1638
+ const pending = { client: sender, controller, timer };
1639
+ this.pendingHostOperations.set(requestId, pending);
1640
+ const clientTag = this.clientMeta.get(sender)?.tag || 'unknown';
1641
+ Promise.resolve()
1642
+ .then(() => this.hostAdapter.execute({
1643
+ type,
1644
+ requestId,
1645
+ correlationId,
1646
+ payload: payloadLike,
1647
+ clientTag,
1648
+ signal: controller.signal,
1649
+ }))
1650
+ .then((result) => {
1651
+ if (this.pendingHostOperations.get(requestId) !== pending) {
1652
+ return;
1653
+ }
1654
+ this.pendingHostOperations.delete(requestId);
1655
+ clearTimeout(timer);
1656
+ this.sendHostOperationResult(sender, type, requestId, correlationId, result);
1657
+ })
1658
+ .catch((error) => {
1659
+ if (this.pendingHostOperations.get(requestId) !== pending) {
1660
+ return;
1661
+ }
1662
+ this.pendingHostOperations.delete(requestId);
1663
+ clearTimeout(timer);
1664
+ const message = controller.signal.aborted ? 'operation_cancelled' : String(error?.message || error);
1665
+ this.sendHostOperationResult(sender, type, requestId, correlationId, undefined, message, controller.signal.aborted);
1666
+ });
1667
+ }
1668
+ cancelHostOperation(payloadLike, sender) {
1669
+ if (!this.hostAdapter) {
1670
+ // Preserve the pre-adapter relay contract for legacy clients,
1671
+ // including cancellation frames without a request ID.
1672
+ this.broadcast('cancel', payloadLike || {});
1673
+ return;
1674
+ }
1675
+ const payload = isRecord(payloadLike) ? payloadLike : {};
1676
+ const requestId = typeof payload.requestId === 'string' ? payload.requestId.trim() : '';
1677
+ if (!requestId) {
1678
+ this.sendHostOperationResult(sender, 'cancel', '', '', undefined, 'request_id_required');
1679
+ return;
1680
+ }
1681
+ const pending = this.pendingHostOperations.get(requestId);
1682
+ if (pending) {
1683
+ clearTimeout(pending.timer);
1684
+ pending.controller.abort();
1685
+ this.pendingHostOperations.delete(requestId);
1686
+ this.hostAdapter?.cancel?.(requestId);
1687
+ this.sendHostOperationResult(sender, 'cancel', requestId, requestId, undefined, undefined, true);
1688
+ }
1689
+ else {
1690
+ this.sendHostOperationResult(sender, 'cancel', requestId, requestId, undefined, 'operation_not_found');
1691
+ }
1692
+ this.broadcast('cancel', payloadLike || {});
1693
+ }
1454
1694
  handleMessage(envelope, sender) {
1455
1695
  const { type, payload, client } = envelope;
1456
1696
  console.log(`[PathBridge] Received: ${type}`);
@@ -1461,8 +1701,25 @@ class PathBridge {
1461
1701
  const identifyPayload = isRecord(payload) ? payload : {};
1462
1702
  const requestedTag = identifyPayload.client ?? identifyPayload.tag ?? client ?? 'unknown';
1463
1703
  this.setClientTag(sender, String(requestedTag));
1704
+ this.sendCapabilities(sender);
1464
1705
  break;
1465
1706
  }
1707
+ case 'capabilities':
1708
+ // Capability advertisements are relayed so a Web/Tauri client
1709
+ // can negotiate the same contract with Godot or a mobile peer.
1710
+ this.broadcast('capabilities', payload || {});
1711
+ break;
1712
+ case 'analyze':
1713
+ case 'query':
1714
+ case 'readEvidence':
1715
+ case 'exportBundle':
1716
+ // The optional host adapter owns domain execution while the
1717
+ // bridge retains correlation, timeout, and cancellation policy.
1718
+ this.executeHostOperation(type, payload, envelope, sender);
1719
+ break;
1720
+ case 'cancel':
1721
+ this.cancelHostOperation(payload, sender);
1722
+ break;
1466
1723
  case 'requestPath':
1467
1724
  this.handlePathRequest(sender);
1468
1725
  break;
@@ -1599,6 +1856,12 @@ class PathBridge {
1599
1856
  }
1600
1857
  close() {
1601
1858
  this.clearAllPendingPathRequests();
1859
+ Array.from(this.pendingHostOperations.entries()).forEach(([requestId, pending]) => {
1860
+ clearTimeout(pending.timer);
1861
+ pending.controller.abort();
1862
+ this.hostAdapter?.cancel?.(requestId);
1863
+ });
1864
+ this.pendingHostOperations.clear();
1602
1865
  Array.from(this.pendingMermaidRenderRequests.values()).forEach((pendingRequest) => {
1603
1866
  clearTimeout(pendingRequest.timer);
1604
1867
  pendingRequest.reject(new Error('PathBridge is closing before Mermaid render completed.'));
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeResourceReference = normalizeResourceReference;
4
+ /**
5
+ * Shared, host-neutral reference normalization used by Graph and backend
6
+ * identity generation. It intentionally has no Node or platform dependency so
7
+ * the path-core bundle can execute in a browser VM and in Godot WebView.
8
+ */
9
+ function normalizeResourceReference(reference) {
10
+ if (typeof reference !== 'string') {
11
+ throw new Error('Resource reference must be a string');
12
+ }
13
+ if (reference.includes('\0')) {
14
+ throw new Error('Resource reference must not contain NUL characters');
15
+ }
16
+ return reference.normalize('NFC').replace(/\\/g, '/').toLowerCase();
17
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const os = __importStar(require("os"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ jest.mock('child_process', () => ({ ...jest.requireActual('child_process'), spawnSync: jest.fn() }));
41
+ const { prepareDesktopGodotPack } = require('../scripts/prepare-desktop-godot-pack');
42
+ const { resolveHostGodotBinaryName } = require('../scripts/tauri-sidecar-utils');
43
+ const runtime = require('../config/godot-desktop-runtime.json');
44
+ const spawn = child_process_1.spawnSync;
45
+ describe('desktop Godot resource package', () => {
46
+ let directory;
47
+ const write = (relative, content) => {
48
+ const file = path.join(directory, relative);
49
+ fs.mkdirSync(path.dirname(file), { recursive: true });
50
+ fs.writeFileSync(file, content);
51
+ };
52
+ beforeEach(() => {
53
+ directory = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'desktop-godot-pack-'));
54
+ write('path_mode/project.godot', `config_version=5\nconfig/features=PackedStringArray("${runtime.version}", "Forward+")\n`);
55
+ write('path_mode/export_presets.cfg', '[preset.0]\nname="Windows Desktop"\n');
56
+ write('path_mode/scenes/main.tscn', '[gd_scene format=3]\n');
57
+ write('path_mode/scripts/main.gd', 'extends Node\n');
58
+ write('path_mode/shaders/main.gdshader', 'shader_type spatial;\n');
59
+ write('path_mode/assets/background.exr', 'fixture');
60
+ write(`src-tauri/bin/${resolveHostGodotBinaryName()}`, 'engine fixture');
61
+ spawn.mockImplementation((_executable, args) => {
62
+ if (args.includes('--version'))
63
+ return { status: 0, stdout: `${runtime.version}.stable.official.fixture\n`, stderr: '' };
64
+ if (args.includes('--export-pack'))
65
+ fs.writeFileSync(args[args.length - 1], 'GDPC resource fixture');
66
+ return { status: 0, stdout: 'done\n', stderr: '' };
67
+ });
68
+ });
69
+ afterEach(() => {
70
+ jest.resetAllMocks();
71
+ fs.rmSync(directory, { recursive: true, force: true });
72
+ });
73
+ test('exports only runtime roots and binds the pack to source and engine bytes', () => {
74
+ write('path_mode/.godot/editor/state', 'private editor state');
75
+ write('path_mode/tmp/debug.gd', 'debug fixture');
76
+ const report = prepareDesktopGodotPack(directory);
77
+ expect(report.engineVersion).toBe(`${runtime.version}.stable.official.fixture`);
78
+ expect(report.pack.path).toBe('output/desktop/path_mode.pck');
79
+ expect(report.pack.sha256).toMatch(/^[a-f0-9]{64}$/);
80
+ expect(report.files.map((file) => file.path)).toEqual(expect.arrayContaining(['project.godot', 'scenes/main.tscn', 'assets/background.exr']));
81
+ expect(report.files.some((file) => /editor|tmp/.test(file.path))).toBe(false);
82
+ expect(fs.readdirSync(path.join(directory, 'output/desktop')).some(name => name.startsWith('.pathmode-'))).toBe(false);
83
+ });
84
+ test('rejects an engine version that cannot consume the declared project', () => {
85
+ spawn.mockReturnValue({ status: 0, stdout: '4.3.stable.official.fixture\n', stderr: '' });
86
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/requires Godot/);
87
+ expect(spawn).toHaveBeenCalledTimes(1);
88
+ });
89
+ test('rejects SVG before invoking Godot import', () => {
90
+ write('path_mode/assets/unsupported.svg', '<svg/>');
91
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/cannot import SVG/);
92
+ expect(spawn.mock.calls.every(([, args]) => args.includes('--version'))).toBe(true);
93
+ });
94
+ test('does not accept zero exit after a Godot script error or replace an earlier pack', () => {
95
+ write('output/desktop/path_mode.pck', 'GDPC previous pack');
96
+ spawn.mockImplementation((_executable, args) => args.includes('--version')
97
+ ? { status: 0, stdout: `${runtime.version}.stable.official.fixture`, stderr: '' }
98
+ : { status: 0, stdout: '', stderr: '\u001b[31mSCRIPT ERROR: invalid exported script\u001b[0m\n' });
99
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/invalid exported script/);
100
+ expect(fs.readFileSync(path.join(directory, 'output/desktop/path_mode.pck'), 'utf8')).toBe('GDPC previous pack');
101
+ });
102
+ test('rejects successful export without a resource pack', () => {
103
+ spawn.mockImplementation((_executable, args) => ({ status: 0, stdout: args.includes('--version') ? `${runtime.version}.stable.official.fixture` : '', stderr: '' }));
104
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/ENOENT/);
105
+ });
106
+ test('rejects an invalid pack header', () => {
107
+ spawn.mockImplementation((_executable, args) => {
108
+ if (args.includes('--export-pack'))
109
+ fs.writeFileSync(args[args.length - 1], 'not a pack');
110
+ return { status: 0, stdout: args.includes('--version') ? `${runtime.version}.stable.official.fixture` : '', stderr: '' };
111
+ });
112
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/PCK resource pack/);
113
+ });
114
+ test('rejects source changes between staging and publication', () => {
115
+ spawn.mockImplementation((_executable, args) => {
116
+ if (args.includes('--export-pack')) {
117
+ fs.writeFileSync(args[args.length - 1], 'GDPC resource fixture');
118
+ write('path_mode/scripts/main.gd', 'extends Node\n# changed during export\n');
119
+ }
120
+ return { status: 0, stdout: args.includes('--version') ? `${runtime.version}.stable.official.fixture` : '', stderr: '' };
121
+ });
122
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/inputs changed/);
123
+ expect(fs.existsSync(path.join(directory, 'output/desktop/path_mode.pck'))).toBe(false);
124
+ });
125
+ test('rejects files added between staging and publication', () => {
126
+ spawn.mockImplementation((_executable, args) => {
127
+ if (args.includes('--export-pack')) {
128
+ fs.writeFileSync(args[args.length - 1], 'GDPC resource fixture');
129
+ write('path_mode/scripts/added.gd', 'extends Node\n');
130
+ }
131
+ return { status: 0, stdout: args.includes('--version') ? `${runtime.version}.stable.official.fixture` : '', stderr: '' };
132
+ });
133
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/inputs changed/);
134
+ expect(fs.existsSync(path.join(directory, 'output/desktop/path_mode.pck'))).toBe(false);
135
+ });
136
+ test('rejects a symlinked runtime root before importing external files', () => {
137
+ fs.renameSync(path.join(directory, 'path_mode/assets'), path.join(directory, 'external-assets'));
138
+ fs.symlinkSync(path.join(directory, 'external-assets'), path.join(directory, 'path_mode/assets'), process.platform === 'win32' ? 'junction' : 'dir');
139
+ expect(() => prepareDesktopGodotPack(directory)).toThrow(/must not be a symlink/);
140
+ expect(spawn.mock.calls.every(([, args]) => args.includes('--version'))).toBe(true);
141
+ });
142
+ });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vm = __importStar(require("node:vm"));
37
+ const { waitForAcceptanceGraph } = require('../scripts/verify-desktop-runtime');
38
+ const graph = { nodes: [{ id: 'Alpha' }, { id: 'Beta' }], edges: [{ source: 'Alpha', target: 'Beta' }] };
39
+ test('waits past the build reload and returns one snapshot from the ready document', async () => {
40
+ const pages = [
41
+ vm.createContext({ performance: { timeOrigin: 100 }, graphData: graph }),
42
+ vm.createContext({ performance: { timeOrigin: 200 } }),
43
+ vm.createContext({ performance: { timeOrigin: 200 }, graphData: graph }),
44
+ ];
45
+ const evaluate = jest.fn(async (expression) => {
46
+ const page = pages.shift();
47
+ if (!page)
48
+ throw new Error('Unexpected extra WebView read');
49
+ return vm.runInContext(expression, page);
50
+ });
51
+ await expect(waitForAcceptanceGraph({ evaluate }, 100)).resolves.toEqual({ nodes: ['Alpha', 'Beta'], edges: 1 });
52
+ expect(evaluate).toHaveBeenCalledTimes(3);
53
+ });
54
+ test('captures lexical graphData and its links before another CDP request can replace the document', async () => {
55
+ const page = vm.createContext({ performance: { timeOrigin: 200 } });
56
+ vm.runInContext(`const graphData = ${JSON.stringify({ nodes: graph.nodes, links: graph.edges })}`, page);
57
+ const evaluate = jest.fn(async (expression) => {
58
+ const snapshot = vm.runInContext(expression, page);
59
+ // Navigation destroys the document after this single serialized CDP response.
60
+ if (evaluate.mock.calls.length > 1)
61
+ throw new Error('Execution context was destroyed');
62
+ return snapshot;
63
+ });
64
+ await expect(waitForAcceptanceGraph({ evaluate }, 100)).resolves.toEqual({ nodes: ['Alpha', 'Beta'], edges: 1 });
65
+ expect(evaluate).toHaveBeenCalledTimes(1);
66
+ });
@@ -457,6 +457,16 @@ function buildKnowledgeRunReports(records) {
457
457
  ? knowledgeRun.scope
458
458
  : {};
459
459
  const answerReleaseReview = buildAnswerReleaseReviewSummary(knowledgeRun.answerReleaseReview ?? payload.answerReleaseReview);
460
+ const graphAnswerPlan = (knowledgeRun.graphAnswerPlan ?? payload.graphAnswerPlan);
461
+ const graphAnswerCoverage = (knowledgeRun.graphAnswerCoverage ?? payload.graphAnswerCoverage);
462
+ const answerTaskPlan = (knowledgeRun.answerTaskPlan
463
+ ?? graphAnswerPlan?.answerTaskPlan
464
+ ?? payload.answerTaskPlan);
465
+ const answerTaskCoverage = (knowledgeRun.answerTaskCoverage
466
+ ?? payload.answerTaskCoverage);
467
+ const graphAnswerClaims = Array.isArray(graphAnswerPlan?.claims)
468
+ ? graphAnswerPlan.claims.filter((claim) => Boolean(claim && typeof claim === 'object'))
469
+ : [];
460
470
  const graphContext = payload.graphContext && typeof payload.graphContext === 'object'
461
471
  ? payload.graphContext
462
472
  : {};
@@ -510,6 +520,40 @@ function buildKnowledgeRunReports(records) {
510
520
  : null,
511
521
  missingLookupCount,
512
522
  },
523
+ ...(graphAnswerPlan ? {
524
+ graphAnswerPlan: {
525
+ intent: String(graphAnswerPlan.intent || '').trim(),
526
+ depth: String(graphAnswerPlan.depth || '').trim(),
527
+ anchorAtomId: String(graphAnswerPlan.anchorAtomId || '').trim(),
528
+ claimCount: graphAnswerClaims.length,
529
+ requiredClaimCount: graphAnswerClaims.filter((claim) => claim.required === true).length,
530
+ requiredRoles: Array.isArray(graphAnswerPlan.requiredRoles)
531
+ ? graphAnswerPlan.requiredRoles.map((role) => String(role || '').trim()).filter(Boolean)
532
+ : [],
533
+ coverageScore: normalizeOptionalScore(graphAnswerCoverage?.coverageScore),
534
+ missingRequiredClaimIds: Array.isArray(graphAnswerCoverage?.missingRequiredClaimIds)
535
+ ? graphAnswerCoverage.missingRequiredClaimIds.map((claimId) => String(claimId || '').trim()).filter(Boolean)
536
+ : [],
537
+ },
538
+ } : {}),
539
+ ...(answerTaskPlan ? {
540
+ answerTask: {
541
+ primarySubject: String(answerTaskPlan.primarySubject || '').trim(),
542
+ subtaskCount: Array.isArray(answerTaskPlan.subtasks)
543
+ ? answerTaskPlan.subtasks.length
544
+ : 0,
545
+ requiredSubtaskCount: Array.isArray(answerTaskPlan.subtasks)
546
+ ? answerTaskPlan.subtasks.filter((subtask) => Boolean(subtask && typeof subtask === 'object' && subtask.required === true)).length
547
+ : 0,
548
+ missingRequiredSubtaskIds: Array.isArray(answerTaskCoverage?.missingRequiredSubtaskIds)
549
+ ? answerTaskCoverage.missingRequiredSubtaskIds.map((id) => String(id || '').trim()).filter(Boolean)
550
+ : [],
551
+ learningRouteNodeCount: Array.isArray(answerTaskPlan.learningRoute)
552
+ ? answerTaskPlan.learningRoute.length
553
+ : 0,
554
+ coverageScore: normalizeOptionalScore(answerTaskCoverage?.coverageScore),
555
+ },
556
+ } : {}),
513
557
  ...(answerReleaseReview ? { answerReleaseReview } : {}),
514
558
  };
515
559
  });