@spotpatch/runtime 1.8.0 → 1.10.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.
package/dist/index.cjs CHANGED
@@ -18,12 +18,17 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
23
  UI_MARKER_ATTRIBUTE: () => UI_MARKER_ATTRIBUTE,
24
- bootstrapSpotPatch: () => bootstrapSpotPatch
24
+ bootstrapSpotPatch: () => bootstrapSpotPatch,
25
+ createDataFlowRuntime: () => createDataFlowRuntime,
26
+ getDataFlowRuntime: () => getDataFlowRuntime,
27
+ installDataFlowPrelude: () => installDataFlowPrelude,
28
+ mergeComponentDataFlowReport: () => mergeComponentDataFlowReport,
29
+ mergePageDataFlowReport: () => mergePageDataFlowReport
25
30
  });
26
- module.exports = __toCommonJS(index_exports);
31
+ module.exports = __toCommonJS(src_exports);
27
32
 
28
33
  // src/controller/runtime-controller.ts
29
34
  var import_react_adapter = require("@spotpatch/react-adapter");
@@ -348,15 +353,36 @@ function parseJson(text2) {
348
353
  throw new RuntimeApiError();
349
354
  }
350
355
  }
351
- async function readJsonEnvelope(response) {
356
+ async function readJsonEnvelope(response, maximumBytes = MAX_JSON_RESPONSE_BYTES) {
352
357
  const payload = parseJson(
353
- await readBoundedText(response, MAX_JSON_RESPONSE_BYTES)
358
+ await readBoundedText(response, maximumBytes)
354
359
  );
355
360
  if (!response.ok) {
356
361
  throw new RuntimeApiError(readFailureCode(payload));
357
362
  }
358
363
  return readSuccessData(payload);
359
364
  }
365
+ function isStringArray(value) {
366
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
367
+ }
368
+ function isReportCompleteness(value) {
369
+ return isRecord2(value) && typeof value.complete === "boolean" && typeof value.visitedModules === "number" && typeof value.visitedCallsites === "number" && typeof value.frontierCount === "number";
370
+ }
371
+ function isDataFlowCapability(value) {
372
+ return isRecord2(value) && typeof value.enabled === "boolean" && (value.staticAnalysis === "available" || value.staticAnalysis === "partial" || value.staticAnalysis === "unavailable") && value.runtimeObservation === "dispatch-only" && value.responseShape === "consumed-fields-only" && value.aiAssistance === "disabled" && Array.isArray(value.reasons);
373
+ }
374
+ function isDataFlowDependency(value) {
375
+ return isRecord2(value) && typeof value.id === "string" && typeof value.kind === "string" && typeof value.direction === "string" && typeof value.execution === "string" && typeof value.proof === "string" && typeof value.association === "string" && Array.isArray(value.parameters) && isRecord2(value.response) && isStringArray(value.response.consumedFields) && isStringArray(value.evidenceIds) && isStringArray(value.observationIds);
376
+ }
377
+ function isDataFlowReportBase(value) {
378
+ return isRecord2(value) && value.schemaVersion === import_shared3.DATA_FLOW_SCHEMA_VERSION && typeof value.reportId === "string" && isRecord2(value.baseline) && typeof value.baseline.registryEpoch === "string" && typeof value.baseline.analyzerVersion === "string" && isStringArray(value.baseline.analyzedSourceVersions) && isDataFlowCapability(value.capability) && Array.isArray(value.dependencies) && value.dependencies.every(isDataFlowDependency) && Array.isArray(value.evidence) && Array.isArray(value.diagnostics) && isReportCompleteness(value.completeness);
379
+ }
380
+ function isComponentDataFlowReport(value) {
381
+ return isDataFlowReportBase(value) && isRecord2(value) && isRecord2(value.component) && isRecord2(value.component.source) && typeof value.component.source.fileId === "string" && typeof value.component.source.sourceVersion === "string";
382
+ }
383
+ function isPageDataFlowReport(value) {
384
+ return isDataFlowReportBase(value);
385
+ }
360
386
  function parseCapability(value, expected) {
361
387
  if (!isAgentCapabilitySnapshot(value) || value.providerProfileId !== expected.providerProfileId || value.modelProfileId !== expected.modelProfileId) {
362
388
  throw new RuntimeApiError();
@@ -408,7 +434,7 @@ function createRuntimeApi(options) {
408
434
  }
409
435
  pendingRequests.clear();
410
436
  }
411
- async function requestJson(endpoint, method, body) {
437
+ async function requestJson(endpoint, method, body, maximumResponseBytes = MAX_JSON_RESPONSE_BYTES) {
412
438
  const abortController = new AbortController();
413
439
  pendingRequests.add(abortController);
414
440
  try {
@@ -421,7 +447,7 @@ function createRuntimeApi(options) {
421
447
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
422
448
  signal: abortController.signal
423
449
  });
424
- return await readJsonEnvelope(response);
450
+ return await readJsonEnvelope(response, maximumResponseBytes);
425
451
  } finally {
426
452
  pendingRequests.delete(abortController);
427
453
  }
@@ -515,6 +541,30 @@ function createRuntimeApi(options) {
515
541
  }
516
542
  return Object.freeze({ ...data });
517
543
  },
544
+ async componentDataFlowReport(request) {
545
+ const data = await requestJson(
546
+ import_shared3.SPOTPATCH_ENDPOINTS.dataFlowComponentReport,
547
+ "POST",
548
+ request,
549
+ options.dataFlowReportMaxBytes
550
+ );
551
+ if (!isComponentDataFlowReport(data)) {
552
+ throw new RuntimeApiError();
553
+ }
554
+ return deepFreeze(data);
555
+ },
556
+ async pageDataFlowReport(request) {
557
+ const data = await requestJson(
558
+ import_shared3.SPOTPATCH_ENDPOINTS.dataFlowPageReport,
559
+ "POST",
560
+ request,
561
+ options.dataFlowReportMaxBytes
562
+ );
563
+ if (!isPageDataFlowReport(data)) {
564
+ throw new RuntimeApiError();
565
+ }
566
+ return deepFreeze(data);
567
+ },
518
568
  async openEditor(request) {
519
569
  const data = await requestJson(import_shared3.SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
520
570
  if (!isRecord2(data) || data.editor !== "auto" && data.editor !== "vscode" && data.editor !== "cursor") {
@@ -2455,6 +2505,12 @@ function createBrandMark(document, content) {
2455
2505
  return svg;
2456
2506
  }
2457
2507
 
2508
+ // src/ui/data-flow-panel-contract.ts
2509
+ var DATA_FLOW_EXTENSION_KEY = /* @__PURE__ */ Symbol.for("spotpatch.data-flow.extension.v1");
2510
+ function getDataFlowExtension(target2 = globalThis) {
2511
+ return target2[DATA_FLOW_EXTENSION_KEY];
2512
+ }
2513
+
2458
2514
  // src/ui/dialog-placement.ts
2459
2515
  var VIEWPORT_MARGIN = 16;
2460
2516
  var TARGET_GAP = 14;
@@ -2522,6 +2578,12 @@ function calculateDialogPlacement({
2522
2578
  });
2523
2579
  }
2524
2580
 
2581
+ // src/ui/external-handoff-contract.ts
2582
+ var EXTERNAL_HANDOFF_EXTENSION_KEY = /* @__PURE__ */ Symbol.for("spotpatch.external-handoff.v1");
2583
+ function getExternalHandoffExtension() {
2584
+ return globalThis[EXTERNAL_HANDOFF_EXTENSION_KEY];
2585
+ }
2586
+
2525
2587
  // src/ui/localization.ts
2526
2588
  var import_shared6 = require("@spotpatch/shared");
2527
2589
  var STATUS_EN = Object.freeze({
@@ -2554,6 +2616,8 @@ var STATUS_ZH = Object.freeze({
2554
2616
  reverted: "\u5DF2\u64A4\u9500",
2555
2617
  failed: "\u5931\u8D25"
2556
2618
  });
2619
+ var EXTERNAL_HANDOFF_ERROR_EN = "The external Agent handoff request failed.";
2620
+ var EXTERNAL_HANDOFF_ERROR_ZH = "\u5916\u90E8 Agent \u4EA4\u63A5\u8BF7\u6C42\u5931\u8D25\u3002";
2557
2621
  var ERROR_MESSAGES_EN = Object.freeze({
2558
2622
  [import_shared6.ERROR_CODES.INVALID_REQUEST]: "The Agent request was rejected as invalid.",
2559
2623
  [import_shared6.ERROR_CODES.INVALID_TOKEN]: "The local SpotPatch session expired.",
@@ -2562,6 +2626,9 @@ var ERROR_MESSAGES_EN = Object.freeze({
2562
2626
  [import_shared6.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The selected source is outside the project.",
2563
2627
  [import_shared6.ERROR_CODES.SOURCE_TOO_LARGE]: "The selected source exceeds the safety limit.",
2564
2628
  [import_shared6.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request failed.",
2629
+ [import_shared6.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is disabled.",
2630
+ [import_shared6.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source changed. Select the component again.",
2631
+ [import_shared6.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "Component data-flow analysis was cancelled.",
2565
2632
  [import_shared6.ERROR_CODES.AI_DISABLED]: "AI execution is disabled in Vite configuration.",
2566
2633
  [import_shared6.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The provider Key environment variable is missing on the Vite process.",
2567
2634
  [import_shared6.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The provider rejected authentication. Check the server-side Key.",
@@ -2572,6 +2639,24 @@ var ERROR_MESSAGES_EN = Object.freeze({
2572
2639
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "Another write Agent job is still active.",
2573
2640
  [import_shared6.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent stopped at a configured time, turn, output, or size limit.",
2574
2641
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
2642
+ [import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: EXTERNAL_HANDOFF_ERROR_EN,
2643
+ [import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: EXTERNAL_HANDOFF_ERROR_EN,
2644
+ [import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: EXTERNAL_HANDOFF_ERROR_EN,
2645
+ [import_shared6.ERROR_CODES.HANDOFF_SOURCE_STALE]: EXTERNAL_HANDOFF_ERROR_EN,
2646
+ [import_shared6.ERROR_CODES.HANDOFF_NOT_FOUND]: EXTERNAL_HANDOFF_ERROR_EN,
2647
+ [import_shared6.ERROR_CODES.HANDOFF_EXPIRED]: EXTERNAL_HANDOFF_ERROR_EN,
2648
+ [import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID]: EXTERNAL_HANDOFF_ERROR_EN,
2649
+ [import_shared6.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: EXTERNAL_HANDOFF_ERROR_EN,
2650
+ [import_shared6.ERROR_CODES.BRIDGE_UNAUTHORIZED]: EXTERNAL_HANDOFF_ERROR_EN,
2651
+ [import_shared6.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: EXTERNAL_HANDOFF_ERROR_EN,
2652
+ [import_shared6.ERROR_CODES.BRIDGE_BUSY]: EXTERNAL_HANDOFF_ERROR_EN,
2653
+ [import_shared6.ERROR_CODES.EXTERNAL_AGENT_BUSY]: EXTERNAL_HANDOFF_ERROR_EN,
2654
+ [import_shared6.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: EXTERNAL_HANDOFF_ERROR_EN,
2655
+ [import_shared6.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: EXTERNAL_HANDOFF_ERROR_EN,
2656
+ [import_shared6.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: EXTERNAL_HANDOFF_ERROR_EN,
2657
+ [import_shared6.ERROR_CODES.SESSION_NOT_FOUND]: EXTERNAL_HANDOFF_ERROR_EN,
2658
+ [import_shared6.ERROR_CODES.SESSION_AMBIGUOUS]: EXTERNAL_HANDOFF_ERROR_EN,
2659
+ [import_shared6.ERROR_CODES.SESSION_CLOSED]: EXTERNAL_HANDOFF_ERROR_EN,
2575
2660
  [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "Confirm inclusion of local changes before running AI.",
2576
2661
  [import_shared6.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "Vite root must be an initialized Git repository root.",
2577
2662
  [import_shared6.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "Finish the active merge, rebase, cherry-pick, or revert.",
@@ -2597,6 +2682,9 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2597
2682
  [import_shared6.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "\u9009\u4E2D\u6E90\u7801\u4F4D\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\u4E4B\u5916\u3002",
2598
2683
  [import_shared6.ERROR_CODES.SOURCE_TOO_LARGE]: "\u9009\u4E2D\u6E90\u7801\u8D85\u8FC7\u5B89\u5168\u5927\u5C0F\u9650\u5236\u3002",
2599
2684
  [import_shared6.ERROR_CODES.EDITOR_OPEN_FAILED]: "\u7F16\u8F91\u5668\u6253\u5F00\u8BF7\u6C42\u5931\u8D25\u3002",
2685
+ [import_shared6.ERROR_CODES.DATA_FLOW_DISABLED]: "\u7EC4\u4EF6\u6570\u636E\u94FE\u8DEF\u5206\u6790\u672A\u542F\u7528\u3002",
2686
+ [import_shared6.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "\u9009\u4E2D\u6E90\u7801\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u7EC4\u4EF6\u3002",
2687
+ [import_shared6.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "\u7EC4\u4EF6\u6570\u636E\u94FE\u8DEF\u5206\u6790\u5DF2\u53D6\u6D88\u3002",
2600
2688
  [import_shared6.ERROR_CODES.AI_DISABLED]: "Vite \u914D\u7F6E\u672A\u542F\u7528 AI \u6267\u884C\u3002",
2601
2689
  [import_shared6.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "\u542F\u52A8 Vite \u7684\u8FDB\u7A0B\u4E2D\u7F3A\u5C11\u6A21\u578B\u670D\u52A1 Key \u73AF\u5883\u53D8\u91CF\u3002",
2602
2690
  [import_shared6.ERROR_CODES.PROVIDER_AUTH_FAILED]: "\u6A21\u578B\u670D\u52A1\u9274\u6743\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u7AEF Key\u3002",
@@ -2607,6 +2695,24 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2607
2695
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u4E00\u4E2A\u5199\u5165\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\u3002",
2608
2696
  [import_shared6.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "Agent \u8FBE\u5230\u65F6\u95F4\u3001\u8F6E\u6B21\u3001\u8F93\u51FA\u6216\u53D8\u66F4\u89C4\u6A21\u9650\u5236\u3002",
2609
2697
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "Agent \u4EFB\u52A1\u5DF2\u53D6\u6D88\u3002",
2698
+ [import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: EXTERNAL_HANDOFF_ERROR_ZH,
2699
+ [import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: EXTERNAL_HANDOFF_ERROR_ZH,
2700
+ [import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: EXTERNAL_HANDOFF_ERROR_ZH,
2701
+ [import_shared6.ERROR_CODES.HANDOFF_SOURCE_STALE]: EXTERNAL_HANDOFF_ERROR_ZH,
2702
+ [import_shared6.ERROR_CODES.HANDOFF_NOT_FOUND]: EXTERNAL_HANDOFF_ERROR_ZH,
2703
+ [import_shared6.ERROR_CODES.HANDOFF_EXPIRED]: EXTERNAL_HANDOFF_ERROR_ZH,
2704
+ [import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID]: EXTERNAL_HANDOFF_ERROR_ZH,
2705
+ [import_shared6.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: EXTERNAL_HANDOFF_ERROR_ZH,
2706
+ [import_shared6.ERROR_CODES.BRIDGE_UNAUTHORIZED]: EXTERNAL_HANDOFF_ERROR_ZH,
2707
+ [import_shared6.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: EXTERNAL_HANDOFF_ERROR_ZH,
2708
+ [import_shared6.ERROR_CODES.BRIDGE_BUSY]: EXTERNAL_HANDOFF_ERROR_ZH,
2709
+ [import_shared6.ERROR_CODES.EXTERNAL_AGENT_BUSY]: EXTERNAL_HANDOFF_ERROR_ZH,
2710
+ [import_shared6.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: EXTERNAL_HANDOFF_ERROR_ZH,
2711
+ [import_shared6.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: EXTERNAL_HANDOFF_ERROR_ZH,
2712
+ [import_shared6.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: EXTERNAL_HANDOFF_ERROR_ZH,
2713
+ [import_shared6.ERROR_CODES.SESSION_NOT_FOUND]: EXTERNAL_HANDOFF_ERROR_ZH,
2714
+ [import_shared6.ERROR_CODES.SESSION_AMBIGUOUS]: EXTERNAL_HANDOFF_ERROR_ZH,
2715
+ [import_shared6.ERROR_CODES.SESSION_CLOSED]: EXTERNAL_HANDOFF_ERROR_ZH,
2610
2716
  [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u660E\u786E\u540C\u610F\u5C06\u5F53\u524D\u672C\u5730\u4FEE\u6539\u7EB3\u5165\u9694\u79BB\u57FA\u7EBF\u3002",
2611
2717
  [import_shared6.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "Vite \u6839\u76EE\u5F55\u4E0D\u662F\u5DF2\u521D\u59CB\u5316 Git \u4ED3\u5E93\u7684\u9876\u5C42\u76EE\u5F55\u3002",
2612
2718
  [import_shared6.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "\u8BF7\u5148\u5B8C\u6210\u5F53\u524D merge\u3001rebase\u3001cherry-pick \u6216 revert\uFF0C\u518D\u8FD0\u884C AI\u3002",
@@ -3031,6 +3137,15 @@ var DIALOG_FALLBACK_HEIGHT = Object.freeze({
3031
3137
  previewing: 560,
3032
3138
  selected: DIALOG_MAX_HEIGHT
3033
3139
  });
3140
+ function resolveStyleNonce(document) {
3141
+ const nonces = new Set(
3142
+ [...document.querySelectorAll("script[nonce]")].map((script) => script.nonce?.trim() ?? "").filter(Boolean)
3143
+ );
3144
+ if (nonces.size !== 1) {
3145
+ return void 0;
3146
+ }
3147
+ return nonces.values().next().value;
3148
+ }
3034
3149
  function createStyles(document) {
3035
3150
  const style = document.createElement("style");
3036
3151
  style.textContent = `
@@ -3741,7 +3856,18 @@ function summaryLine(summary, prefix) {
3741
3856
  const line = summary.split("\n").find((candidate) => candidate.startsWith(`${prefix}: `));
3742
3857
  return line?.slice(prefix.length + 2).trim();
3743
3858
  }
3744
- function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto") {
3859
+ function createUnavailableDataFlowPanel(document, changesRoot, diagnosticsRoot) {
3860
+ changesRoot.append(diagnosticsRoot);
3861
+ return Object.freeze({
3862
+ root: changesRoot,
3863
+ refreshButton: createButton(document, ""),
3864
+ styles: document.createElement("style"),
3865
+ dispose: () => void 0,
3866
+ render: () => void 0,
3867
+ resetView: () => void 0
3868
+ });
3869
+ }
3870
+ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", dataFlowEnabled = false, externalAgentEnabled = false, framework = "vite", sessionId = "") {
3745
3871
  const localizer = createUiLocalizer(document, localePreference);
3746
3872
  let messages = localizer.messages();
3747
3873
  const host = document.createElement("spotpatch-root");
@@ -3857,7 +3983,29 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3857
3983
  summary.className = "spotpatch-summary";
3858
3984
  diagnostics.append(diagnosticsLabel, summary);
3859
3985
  const agentPanel = createAgentPanel(document, ai, localizer);
3860
- selectionPanel.append(targetsPanel, diagnostics, agentPanel.root);
3986
+ const changesPanel = createMarkedElement(document, "div");
3987
+ const externalHandoffPanel = externalAgentEnabled ? getExternalHandoffExtension()?.createPanel(
3988
+ document,
3989
+ framework,
3990
+ localizer.locale,
3991
+ sessionId,
3992
+ localizer.subscribe,
3993
+ placeDialog
3994
+ ) : void 0;
3995
+ changesPanel.append(
3996
+ targetsPanel,
3997
+ agentPanel.root,
3998
+ ...externalHandoffPanel === void 0 ? [] : [externalHandoffPanel.root]
3999
+ );
4000
+ const dataFlowPanel = getDataFlowExtension()?.createPanel(
4001
+ document,
4002
+ dataFlowEnabled,
4003
+ localizer.locale,
4004
+ changesPanel,
4005
+ diagnostics,
4006
+ placeDialog
4007
+ ) ?? createUnavailableDataFlowPanel(document, changesPanel, diagnostics);
4008
+ selectionPanel.append(dataFlowPanel.root);
3861
4009
  const previewPanel = createMarkedElement(document, "div");
3862
4010
  previewPanel.className = "spotpatch-preview-panel";
3863
4011
  previewPanel.hidden = true;
@@ -3900,6 +4048,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3900
4048
  agentPanel.testButton,
3901
4049
  addTargetButton,
3902
4050
  agentPanel.runButton,
4051
+ ...externalHandoffPanel === void 0 ? [] : [externalHandoffPanel.sendButton],
3903
4052
  previewButton,
3904
4053
  agentPanel.cancelButton,
3905
4054
  agentPanel.applyButton,
@@ -3915,8 +4064,19 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3915
4064
  liveRegion.className = "spotpatch-live";
3916
4065
  liveRegion.setAttribute("aria-live", "polite");
3917
4066
  liveRegion.setAttribute("aria-atomic", "true");
3918
- shadowRoot.append(
4067
+ const styles2 = [
3919
4068
  createStyles(document),
4069
+ dataFlowPanel.styles,
4070
+ ...externalHandoffPanel === void 0 ? [] : [externalHandoffPanel.styles]
4071
+ ];
4072
+ const styleNonce = resolveStyleNonce(document);
4073
+ if (styleNonce !== void 0) {
4074
+ for (const style of styles2) {
4075
+ style.nonce = styleNonce;
4076
+ }
4077
+ }
4078
+ shadowRoot.append(
4079
+ ...styles2,
3920
4080
  selectionHighlights,
3921
4081
  highlight,
3922
4082
  dialog,
@@ -3934,6 +4094,13 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3934
4094
  let currentTargets = [];
3935
4095
  let currentMaximum = 0;
3936
4096
  let currentEditorFeedbackState = "idle";
4097
+ let currentDataFlowState = Object.freeze({
4098
+ component: Object.freeze({
4099
+ status: dataFlowEnabled ? "idle" : "disabled"
4100
+ }),
4101
+ page: Object.freeze({ status: dataFlowEnabled ? "idle" : "disabled" }),
4102
+ observationCount: 0
4103
+ });
3937
4104
  function renderEditorStatus(state) {
3938
4105
  currentEditorFeedbackState = state;
3939
4106
  editorFeedback.dataset.state = state;
@@ -4191,6 +4358,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4191
4358
  openEditorButton.disabled = !canOpenEditor;
4192
4359
  previewButton.disabled = !canPreview;
4193
4360
  agentPanel.setContextReady(canPreview);
4361
+ externalHandoffPanel?.setContextReady(canPreview);
4194
4362
  updateContextOverview(summaryText);
4195
4363
  placeDialog();
4196
4364
  }
@@ -4207,6 +4375,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4207
4375
  previewButton.hidden = !selected;
4208
4376
  secondaryActions.hidden = !selected;
4209
4377
  agentPanel.setSelectionVisible(selected);
4378
+ externalHandoffPanel?.setSelectionVisible(selected);
4210
4379
  copyButton.hidden = !previewing;
4211
4380
  backButton.hidden = !previewing;
4212
4381
  title.textContent = previewing ? messages.dialog.previewTitle : messages.dialog.editTitle;
@@ -4235,6 +4404,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4235
4404
  previewButton.textContent = messages.actions.preview;
4236
4405
  copyButton.textContent = messages.actions.copy;
4237
4406
  backButton.textContent = messages.actions.back;
4407
+ dataFlowPanel.render(currentDataFlowState);
4238
4408
  triggerButton.title = messages.trigger.title(shortcut);
4239
4409
  triggerButton.textContent = currentStatus === "inspecting" ? messages.trigger.stop : messages.trigger.select;
4240
4410
  renderPanelStatus(currentStatus);
@@ -4273,6 +4443,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4273
4443
  repositoryLink,
4274
4444
  previewButton,
4275
4445
  copyButton,
4446
+ dataFlowRefreshButton: dataFlowPanel.refreshButton,
4276
4447
  backButton,
4277
4448
  closeButton,
4278
4449
  agentProviderSelect: agentPanel.providerSelect,
@@ -4286,6 +4457,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4286
4457
  agentApplyButton: agentPanel.applyButton,
4287
4458
  agentRevertButton: agentPanel.revertButton,
4288
4459
  agentResetButton: agentPanel.resetButton,
4460
+ ...externalHandoffPanel === void 0 ? {} : { externalHandoffPanel },
4289
4461
  renderStatus(status) {
4290
4462
  const inspecting = status === "inspecting";
4291
4463
  triggerButton.setAttribute("aria-pressed", String(inspecting));
@@ -4293,6 +4465,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4293
4465
  renderPanelStatus(status);
4294
4466
  },
4295
4467
  renderEditorStatus,
4468
+ renderDataFlow(state) {
4469
+ currentDataFlowState = state;
4470
+ dataFlowPanel.render(state);
4471
+ placeDialog();
4472
+ },
4296
4473
  showHighlight(rect, label) {
4297
4474
  currentRect = rect;
4298
4475
  highlight.hidden = false;
@@ -4329,6 +4506,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4329
4506
  currentCanPreview = enabled;
4330
4507
  previewButton.disabled = !enabled;
4331
4508
  agentPanel.setContextReady(enabled);
4509
+ externalHandoffPanel?.setContextReady(enabled);
4332
4510
  },
4333
4511
  hideSelection() {
4334
4512
  dialog.hidden = true;
@@ -4353,8 +4531,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4353
4531
  currentCanPreview = false;
4354
4532
  agentPanel.setContextReady(false);
4355
4533
  agentPanel.setSelectionVisible(false);
4534
+ externalHandoffPanel?.setContextReady(false);
4535
+ externalHandoffPanel?.setSelectionVisible(false);
4356
4536
  agentPanel.setEditingEnabled(true);
4357
4537
  agentPanel.resetJob();
4538
+ dataFlowPanel.resetView();
4358
4539
  },
4359
4540
  hideSelectionTemporarily() {
4360
4541
  dialog.hidden = true;
@@ -4384,6 +4565,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4384
4565
  });
4385
4566
  openEditorButton.disabled = !currentCanOpenEditor;
4386
4567
  previewButton.disabled = !enabled || !currentCanPreview;
4568
+ externalHandoffPanel?.setContextReady(enabled && currentCanPreview);
4387
4569
  agentPanel.setEditingEnabled(enabled);
4388
4570
  placeDialog();
4389
4571
  },
@@ -4423,6 +4605,8 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4423
4605
  diagnostics.removeEventListener("toggle", placeDialog);
4424
4606
  localeButton.removeEventListener("click", localizer.toggle);
4425
4607
  unsubscribeLocale();
4608
+ dataFlowPanel.dispose();
4609
+ externalHandoffPanel?.dispose();
4426
4610
  agentPanel.dispose();
4427
4611
  host.remove();
4428
4612
  }
@@ -4913,9 +5097,19 @@ function resolveBrowserDependencies(dependencies) {
4913
5097
  }
4914
5098
  function createController(config, dependencies = {}) {
4915
5099
  const browser = resolveBrowserDependencies(dependencies);
4916
- const view = dependencies.view ?? createRuntimeView(browser.document, config.shortcut, config.ai, config.locale);
5100
+ const view = dependencies.view ?? createRuntimeView(
5101
+ browser.document,
5102
+ config.shortcut,
5103
+ config.ai,
5104
+ config.locale,
5105
+ config.dataFlow.enabled,
5106
+ config.externalAgent.enabled,
5107
+ config.framework,
5108
+ config.sessionId
5109
+ );
4917
5110
  const api = dependencies.api ?? createRuntimeApi({
4918
5111
  apiBase: config.apiBase,
5112
+ dataFlowReportMaxBytes: config.dataFlow.limits.reportMaxBytes,
4919
5113
  fetch: browser.window.fetch.bind(browser.window),
4920
5114
  sessionToken: config.sessionToken
4921
5115
  });
@@ -4927,6 +5121,9 @@ function createController(config, dependencies = {}) {
4927
5121
  const restoredSelection = selectionSession.load();
4928
5122
  const sourceResolver = createSourceResolver({
4929
5123
  adapter: dependencies.reactAdapter ?? (0, import_react_adapter.createReact18Adapter)({
5124
+ ...config.dataFlow.enabled ? {
5125
+ getComponentRegistration: (component) => getDataFlowExtension()?.getComponentRegistration(component)
5126
+ } : {},
4930
5127
  maxComponentDepth: config.budget.maxComponentDepth
4931
5128
  }),
4932
5129
  onAdapterError() {
@@ -4950,6 +5147,8 @@ function createController(config, dependencies = {}) {
4950
5147
  apiStatus: marker === void 0 ? "not-required" : target2.code === void 0 ? "failed" : "connected",
4951
5148
  code: target2.code,
4952
5149
  collectionStatus: "ready",
5150
+ dataFlowReport: void 0,
5151
+ dataFlowStatus: "idle",
4953
5152
  element: void 0,
4954
5153
  elementContext: target2.element,
4955
5154
  instruction: target2.instruction,
@@ -4964,6 +5163,9 @@ function createController(config, dependencies = {}) {
4964
5163
  let workflowSelectionActive = false;
4965
5164
  let sessionRevision = 0;
4966
5165
  let editorRequestRevision = 0;
5166
+ let dataFlowRequestRevision = 0;
5167
+ let pageDataFlowReport;
5168
+ let pageDataFlowStatus = "idle";
4967
5169
  let addingTarget = false;
4968
5170
  let previewPrompt = "";
4969
5171
  let previousFocus;
@@ -4978,6 +5180,90 @@ function createController(config, dependencies = {}) {
4978
5180
  function activeTarget() {
4979
5181
  return targets.find((target2) => target2.id === activeTargetId) ?? targets.at(-1);
4980
5182
  }
5183
+ function dataFlowRequest(target2) {
5184
+ const react2 = target2.resolution.react;
5185
+ if (react2.componentSourceId !== void 0 && react2.sourceVersion !== void 0) {
5186
+ return Object.freeze({
5187
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5188
+ componentSourceId: react2.componentSourceId,
5189
+ sourceVersion: react2.sourceVersion
5190
+ });
5191
+ }
5192
+ const marker = target2.marker;
5193
+ return marker === void 0 ? void 0 : Object.freeze({
5194
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5195
+ fileId: marker.fileId,
5196
+ line: marker.line,
5197
+ column: marker.column
5198
+ });
5199
+ }
5200
+ function renderDataFlowView() {
5201
+ const dataFlowClient = getDataFlowExtension();
5202
+ const observations = dataFlowClient?.observations(browser.window.location.pathname) ?? [];
5203
+ const current = activeTarget();
5204
+ view.renderDataFlow({
5205
+ component: Object.freeze({
5206
+ status: config.dataFlow.enabled ? current?.dataFlowStatus ?? "idle" : "disabled",
5207
+ ...current?.dataFlowReport === void 0 ? {} : {
5208
+ report: dataFlowClient?.mergeComponentReport(
5209
+ current.dataFlowReport,
5210
+ observations
5211
+ ) ?? current.dataFlowReport
5212
+ }
5213
+ }),
5214
+ page: Object.freeze({
5215
+ status: config.dataFlow.enabled ? pageDataFlowStatus : "disabled",
5216
+ ...pageDataFlowReport === void 0 ? {} : {
5217
+ report: dataFlowClient?.mergePageReport(pageDataFlowReport, observations) ?? pageDataFlowReport
5218
+ }
5219
+ }),
5220
+ observationCount: observations.length
5221
+ });
5222
+ }
5223
+ async function loadDataFlowReports() {
5224
+ if (!config.dataFlow.enabled) {
5225
+ renderDataFlowView();
5226
+ return;
5227
+ }
5228
+ const current = activeTarget();
5229
+ const currentRequest = current === void 0 ? void 0 : dataFlowRequest(current);
5230
+ const pageTargets = targets.flatMap((target2) => {
5231
+ const request = dataFlowRequest(target2);
5232
+ return request === void 0 ? [] : [request];
5233
+ });
5234
+ const revision = ++dataFlowRequestRevision;
5235
+ const selectionRevision = sessionRevision;
5236
+ if (current !== void 0) current.dataFlowStatus = "loading";
5237
+ pageDataFlowStatus = pageTargets.length === 0 ? "idle" : "loading";
5238
+ renderDataFlowView();
5239
+ const [componentResult, pageResult] = await Promise.allSettled([
5240
+ currentRequest === void 0 ? Promise.resolve(void 0) : api.componentDataFlowReport(currentRequest),
5241
+ pageTargets.length === 0 ? Promise.resolve(void 0) : api.pageDataFlowReport({
5242
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5243
+ targets: pageTargets
5244
+ })
5245
+ ]);
5246
+ if (!mounted || revision !== dataFlowRequestRevision || selectionRevision !== sessionRevision) {
5247
+ return;
5248
+ }
5249
+ if (current !== void 0 && targets.includes(current)) {
5250
+ if (componentResult.status === "fulfilled") {
5251
+ current.dataFlowReport = componentResult.value;
5252
+ current.dataFlowStatus = componentResult.value === void 0 ? "idle" : "ready";
5253
+ } else {
5254
+ current.dataFlowReport = void 0;
5255
+ current.dataFlowStatus = "error";
5256
+ }
5257
+ }
5258
+ if (pageResult.status === "fulfilled") {
5259
+ pageDataFlowReport = pageResult.value;
5260
+ pageDataFlowStatus = pageResult.value === void 0 ? "idle" : "ready";
5261
+ } else {
5262
+ pageDataFlowReport = void 0;
5263
+ pageDataFlowStatus = "error";
5264
+ }
5265
+ renderDataFlowView();
5266
+ }
4981
5267
  function snapshotTarget(target2) {
4982
5268
  if (target2.elementContext === void 0 || target2.styles === void 0) {
4983
5269
  return void 0;
@@ -5142,10 +5428,14 @@ ${summary}`;
5142
5428
  sessionRevision += 1;
5143
5429
  api.cancelPending();
5144
5430
  agentWorkflow.disposeSelection();
5431
+ externalHandoffWorkflow?.cancelPending();
5145
5432
  clearCollectionTimers();
5146
5433
  resizeObserver?.disconnect();
5147
5434
  targets = [];
5148
5435
  activeTargetId = void 0;
5436
+ dataFlowRequestRevision += 1;
5437
+ pageDataFlowReport = void 0;
5438
+ pageDataFlowStatus = "idle";
5149
5439
  selectionOpen = false;
5150
5440
  workflowSelectionActive = false;
5151
5441
  addingTarget = false;
@@ -5200,6 +5490,7 @@ ${summary}`;
5200
5490
  workflowSelectionActive = true;
5201
5491
  }
5202
5492
  refreshSelectionView(true);
5493
+ void loadDataFlowReports();
5203
5494
  view.focusTargetInstruction(activeTargetId);
5204
5495
  persistSelection();
5205
5496
  return;
@@ -5256,6 +5547,13 @@ ${summary}`;
5256
5547
  },
5257
5548
  view
5258
5549
  });
5550
+ const externalHandoffWorkflow = config.externalAgent.enabled && view.externalHandoffPanel !== void 0 ? getExternalHandoffExtension()?.createWorkflow(
5551
+ browser.window.fetch.bind(browser.window),
5552
+ view.externalHandoffPanel,
5553
+ selectedAnnotation,
5554
+ config.sessionToken,
5555
+ browser.window
5556
+ ) : void 0;
5259
5557
  async function loadSourceContext(target2, revision) {
5260
5558
  const marker = target2.marker;
5261
5559
  if (marker === void 0) {
@@ -5350,6 +5648,7 @@ ${summary}`;
5350
5648
  transition({ type: "SELECT" });
5351
5649
  view.hideHighlight();
5352
5650
  refreshSelectionView(true);
5651
+ void loadDataFlowReports();
5353
5652
  view.focusTargetInstruction(duplicate.id);
5354
5653
  view.announce(view.messages().announcements.duplicate);
5355
5654
  return;
@@ -5381,7 +5680,9 @@ ${summary}`;
5381
5680
  styles: void 0,
5382
5681
  instruction: "",
5383
5682
  apiStatus: marker === void 0 ? "not-required" : "loading",
5384
- collectionStatus: "loading"
5683
+ collectionStatus: "loading",
5684
+ dataFlowReport: void 0,
5685
+ dataFlowStatus: "idle"
5385
5686
  };
5386
5687
  targets.push(target2);
5387
5688
  activeTargetId = target2.id;
@@ -5391,6 +5692,7 @@ ${summary}`;
5391
5692
  view.hideHighlight();
5392
5693
  resizeObserver?.observe(element2);
5393
5694
  refreshSelectionView(true);
5695
+ void loadDataFlowReports();
5394
5696
  selectionOpen = true;
5395
5697
  view.focusTargetInstruction(target2.id);
5396
5698
  scheduleBrowserContextCollection(target2, revision);
@@ -5495,11 +5797,15 @@ ${summary}`;
5495
5797
  }
5496
5798
  view.hideSelectionTemporarily();
5497
5799
  view.hideSelectionHighlights();
5800
+ pageDataFlowReport = void 0;
5801
+ pageDataFlowStatus = "idle";
5802
+ renderDataFlowView();
5498
5803
  selectionSession.clear();
5499
5804
  view.announce(view.messages().announcements.allTargetsRemoved);
5500
5805
  return;
5501
5806
  }
5502
5807
  refreshSelectionView();
5808
+ void loadDataFlowReports();
5503
5809
  persistSelection();
5504
5810
  view.announce(view.messages().announcements.targetRemoved);
5505
5811
  }
@@ -5662,16 +5968,21 @@ ${summary}`;
5662
5968
  }
5663
5969
  activeTargetId = targetId;
5664
5970
  refreshSelectionView();
5971
+ void loadDataFlowReports();
5665
5972
  view.focusTargetInstruction(targetId);
5666
5973
  }
5667
5974
  function handleReselect() {
5668
5975
  beginReselect();
5669
5976
  }
5977
+ function handleDataFlowRefresh() {
5978
+ void loadDataFlowReports();
5979
+ }
5670
5980
  function mount() {
5671
5981
  if (mounted) {
5672
5982
  return;
5673
5983
  }
5674
5984
  mounted = true;
5985
+ externalHandoffWorkflow?.mount();
5675
5986
  view.renderStatus(state.status);
5676
5987
  browser.document.addEventListener("pointermove", handlePointerMove, true);
5677
5988
  browser.document.addEventListener("click", handleClick, true);
@@ -5685,6 +5996,7 @@ ${summary}`;
5685
5996
  view.targetList.addEventListener("input", handleTargetListInput);
5686
5997
  view.targetList.addEventListener("keydown", handleTargetListKeydown);
5687
5998
  view.openEditorButton.addEventListener("click", handleOpenEditorButtonClick);
5999
+ view.dataFlowRefreshButton.addEventListener("click", handleDataFlowRefresh);
5688
6000
  view.previewButton.addEventListener("click", handlePreview);
5689
6001
  view.copyButton.addEventListener("click", handleCopy);
5690
6002
  view.backButton.addEventListener("click", handleBack);
@@ -5737,6 +6049,7 @@ ${summary}`;
5737
6049
  api.cancelPending();
5738
6050
  api.dispose();
5739
6051
  agentWorkflow.disposeSelection();
6052
+ externalHandoffWorkflow?.dispose();
5740
6053
  sourceResolver.dispose();
5741
6054
  return;
5742
6055
  }
@@ -5755,6 +6068,7 @@ ${summary}`;
5755
6068
  view.targetList.removeEventListener("input", handleTargetListInput);
5756
6069
  view.targetList.removeEventListener("keydown", handleTargetListKeydown);
5757
6070
  view.openEditorButton.removeEventListener("click", handleOpenEditorButtonClick);
6071
+ view.dataFlowRefreshButton.removeEventListener("click", handleDataFlowRefresh);
5758
6072
  view.previewButton.removeEventListener("click", handlePreview);
5759
6073
  view.copyButton.removeEventListener("click", handleCopy);
5760
6074
  view.backButton.removeEventListener("click", handleBack);
@@ -5799,6 +6113,7 @@ ${summary}`;
5799
6113
  api.cancelPending();
5800
6114
  api.dispose();
5801
6115
  agentWorkflow.disposeSelection();
6116
+ externalHandoffWorkflow?.dispose();
5802
6117
  sourceResolver.dispose();
5803
6118
  view.dispose();
5804
6119
  state = INITIAL_RUNTIME_STATE;
@@ -5815,9 +6130,551 @@ function bootstrapSpotPatch(config) {
5815
6130
  target2[RUNTIME_INSTANCE_KEY] = controller;
5816
6131
  controller.mount();
5817
6132
  }
6133
+
6134
+ // src/data-flow/data-flow-runtime.ts
6135
+ var import_data_flow_runtime = require("@spotpatch/shared/data-flow-runtime");
6136
+ var RUNTIME_KEY = /* @__PURE__ */ Symbol.for(
6137
+ "spotpatch.data-flow.runtime.v1"
6138
+ );
6139
+ var REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for("react.memo");
6140
+ var REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for("react.forward_ref");
6141
+ function positiveNow(target2) {
6142
+ return target2.performance?.now() ?? Date.now();
6143
+ }
6144
+ function createOpaqueSequence(prefix) {
6145
+ let sequence = 0;
6146
+ const randomPrefix = (() => {
6147
+ try {
6148
+ const bytes = new Uint8Array(8);
6149
+ globalThis.crypto.getRandomValues(bytes);
6150
+ return Array.from(bytes, (value) => value.toString(36).padStart(2, "0")).join("");
6151
+ } catch {
6152
+ return "local";
6153
+ }
6154
+ })();
6155
+ return () => {
6156
+ sequence += 1;
6157
+ return `${prefix}_${randomPrefix}_${sequence.toString(36)}`;
6158
+ };
6159
+ }
6160
+ function freezeUrl(value, baseUrl) {
6161
+ try {
6162
+ const url = new URL(value, baseUrl);
6163
+ return Object.freeze({
6164
+ origin: url.origin,
6165
+ pathname: url.pathname,
6166
+ queryKeys: Object.freeze(
6167
+ [...new Set(url.searchParams.keys())].sort().slice(0, import_data_flow_runtime.DATA_FLOW_URL_QUERY_KEY_LIMIT)
6168
+ )
6169
+ });
6170
+ } catch {
6171
+ return Object.freeze({
6172
+ pathname: value.split(/[?#]/u, 1)[0] ?? "{invalid}",
6173
+ queryKeys: Object.freeze([])
6174
+ });
6175
+ }
6176
+ }
6177
+ function readFetchUrl(input) {
6178
+ if (typeof input === "string") return input;
6179
+ if (input instanceof URL) return input.toString();
6180
+ return input.url;
6181
+ }
6182
+ function readFetchMethod(input, init) {
6183
+ if (init?.method !== void 0) return init.method.toUpperCase();
6184
+ return typeof Request !== "undefined" && input instanceof Request ? input.method.toUpperCase() : "GET";
6185
+ }
6186
+ function isSpotPatchInternalUrl(value, baseUrl) {
6187
+ try {
6188
+ const pathname = new URL(value, baseUrl).pathname;
6189
+ return pathname === import_data_flow_runtime.SPOTPATCH_API_BASE || pathname.startsWith(`${import_data_flow_runtime.SPOTPATCH_API_BASE}/`);
6190
+ } catch {
6191
+ return false;
6192
+ }
6193
+ }
6194
+ function approximateBytes(value) {
6195
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
6196
+ }
6197
+ function createRingStore(limits, target2) {
6198
+ const entries = [];
6199
+ let totalBytes = 0;
6200
+ function removeExpired(now) {
6201
+ while (entries[0] !== void 0 && now - entries[0].recordedAt > limits.observationTtlMs) {
6202
+ const removed = entries.shift();
6203
+ if (removed !== void 0) totalBytes -= removed.bytes;
6204
+ }
6205
+ }
6206
+ return Object.freeze({
6207
+ add(observation) {
6208
+ const now = positiveNow(target2);
6209
+ removeExpired(now);
6210
+ const bytes = approximateBytes(observation);
6211
+ if (bytes > limits.observationMaxBytes) return;
6212
+ entries.push(Object.freeze({ bytes, observation, recordedAt: now }));
6213
+ totalBytes += bytes;
6214
+ while (entries.length > limits.observationMaxEntries || totalBytes > limits.observationMaxBytes) {
6215
+ const removed = entries.shift();
6216
+ if (removed !== void 0) totalBytes -= removed.bytes;
6217
+ }
6218
+ },
6219
+ clear() {
6220
+ entries.length = 0;
6221
+ totalBytes = 0;
6222
+ },
6223
+ values() {
6224
+ removeExpired(positiveNow(target2));
6225
+ return Object.freeze(entries.map(({ observation }) => observation));
6226
+ }
6227
+ });
6228
+ }
6229
+ function ownPropertyDescriptor(target2, key) {
6230
+ try {
6231
+ return Object.getOwnPropertyDescriptor(target2, key);
6232
+ } catch {
6233
+ return void 0;
6234
+ }
6235
+ }
6236
+ function nestedReactWrapperComponent(candidate) {
6237
+ const marker = ownPropertyDescriptor(candidate, "$$typeof")?.value;
6238
+ const key = marker === REACT_MEMO_TYPE ? "type" : marker === REACT_FORWARD_REF_TYPE ? "render" : void 0;
6239
+ if (key === void 0) return void 0;
6240
+ const nested = ownPropertyDescriptor(candidate, key)?.value;
6241
+ return typeof nested === "object" && nested !== null || typeof nested === "function" ? nested : void 0;
6242
+ }
6243
+ function installWritableDataProperty(target2, key, replacement, original = ownPropertyDescriptor(target2, key)) {
6244
+ if (original === void 0 || !("value" in original) || !original.writable) {
6245
+ return void 0;
6246
+ }
6247
+ try {
6248
+ Object.defineProperty(target2, key, { ...original, value: replacement });
6249
+ } catch {
6250
+ return void 0;
6251
+ }
6252
+ return () => {
6253
+ try {
6254
+ if (ownPropertyDescriptor(target2, key)?.value === replacement) {
6255
+ Object.defineProperty(target2, key, original);
6256
+ }
6257
+ } catch {
6258
+ }
6259
+ };
6260
+ }
6261
+ function recordWithoutAffectingHost(record2) {
6262
+ try {
6263
+ record2();
6264
+ } catch {
6265
+ }
6266
+ }
6267
+ function createDataFlowRuntime(config, target2 = globalThis) {
6268
+ const componentRegistry = /* @__PURE__ */ new WeakMap();
6269
+ const xhrMetadata = /* @__PURE__ */ new WeakMap();
6270
+ const store = createRingStore(config.limits, target2);
6271
+ const nextInvocationId = createOpaqueSequence("invocation");
6272
+ const nextObservationId = createOpaqueSequence("observation");
6273
+ const pageEpoch = createOpaqueSequence("page")();
6274
+ const nextRouteEpoch = createOpaqueSequence("route");
6275
+ let routeEpoch = nextRouteEpoch();
6276
+ let routeKey;
6277
+ let currentInvocation;
6278
+ let currentRequestFrame;
6279
+ let disposed = false;
6280
+ const originalFetch = (() => {
6281
+ try {
6282
+ return target2.fetch;
6283
+ } catch {
6284
+ return void 0;
6285
+ }
6286
+ })();
6287
+ function spotPatchFetch(input, init) {
6288
+ if (originalFetch === void 0) {
6289
+ throw new TypeError("Fetch is unavailable.");
6290
+ }
6291
+ const result = Reflect.apply(originalFetch, this, [input, init]);
6292
+ recordWithoutAffectingHost(() => {
6293
+ const frame = currentRequestFrame;
6294
+ const token = frame?.invocationToken;
6295
+ const rawUrl = readFetchUrl(input);
6296
+ if (isSpotPatchInternalUrl(
6297
+ rawUrl,
6298
+ target2.location?.href ?? "http://spotpatch.invalid/"
6299
+ )) {
6300
+ return;
6301
+ }
6302
+ store.add(
6303
+ Object.freeze({
6304
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6305
+ id: nextObservationId(),
6306
+ pageEpoch,
6307
+ routeEpoch,
6308
+ ...frame === void 0 ? {} : {
6309
+ requestCallsiteId: frame.requestCallsiteId,
6310
+ sourceVersion: frame.sourceVersion
6311
+ },
6312
+ ...token === void 0 ? {} : {
6313
+ invocationId: token.invocationId,
6314
+ componentSourceId: token.componentSourceId,
6315
+ triggerCallsiteId: token.triggerCallsiteId
6316
+ },
6317
+ transport: "fetch",
6318
+ method: readFetchMethod(input, init),
6319
+ url: freezeUrl(rawUrl, target2.location?.href ?? "http://spotpatch.invalid/"),
6320
+ outcome: "dispatched",
6321
+ freshness: "current",
6322
+ diagnosticIds: Object.freeze([])
6323
+ })
6324
+ );
6325
+ });
6326
+ return result;
6327
+ }
6328
+ const restoreFetch = originalFetch === void 0 ? void 0 : installWritableDataProperty(target2, "fetch", spotPatchFetch);
6329
+ const xhrPrototype = (() => {
6330
+ try {
6331
+ return target2.XMLHttpRequest?.prototype;
6332
+ } catch {
6333
+ return void 0;
6334
+ }
6335
+ })();
6336
+ const originalOpenDescriptor = xhrPrototype === void 0 ? void 0 : ownPropertyDescriptor(xhrPrototype, "open");
6337
+ const originalSendDescriptor = xhrPrototype === void 0 ? void 0 : ownPropertyDescriptor(xhrPrototype, "send");
6338
+ function spotPatchOpen(method, url, ...rest) {
6339
+ const original = originalOpenDescriptor?.value;
6340
+ if (typeof original === "function") {
6341
+ Reflect.apply(original, this, [method, url, ...rest]);
6342
+ }
6343
+ recordWithoutAffectingHost(() => {
6344
+ xhrMetadata.set(this, Object.freeze({ method, url: String(url) }));
6345
+ });
6346
+ }
6347
+ function spotPatchSend(body) {
6348
+ const original = originalSendDescriptor?.value;
6349
+ if (typeof original === "function") Reflect.apply(original, this, [body]);
6350
+ recordWithoutAffectingHost(() => {
6351
+ const metadata = xhrMetadata.get(this);
6352
+ if (metadata === void 0 || isSpotPatchInternalUrl(
6353
+ metadata.url,
6354
+ target2.location?.href ?? "http://spotpatch.invalid/"
6355
+ )) {
6356
+ return;
6357
+ }
6358
+ const frame = currentRequestFrame;
6359
+ const token = frame?.invocationToken;
6360
+ store.add(
6361
+ Object.freeze({
6362
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6363
+ id: nextObservationId(),
6364
+ pageEpoch,
6365
+ routeEpoch,
6366
+ ...frame === void 0 ? {} : {
6367
+ requestCallsiteId: frame.requestCallsiteId,
6368
+ sourceVersion: frame.sourceVersion
6369
+ },
6370
+ ...token === void 0 ? {} : {
6371
+ invocationId: token.invocationId,
6372
+ componentSourceId: token.componentSourceId,
6373
+ triggerCallsiteId: token.triggerCallsiteId
6374
+ },
6375
+ transport: "xhr",
6376
+ method: metadata.method.toUpperCase(),
6377
+ url: freezeUrl(
6378
+ metadata.url,
6379
+ target2.location?.href ?? "http://spotpatch.invalid/"
6380
+ ),
6381
+ outcome: "dispatched",
6382
+ freshness: "current",
6383
+ diagnosticIds: Object.freeze([])
6384
+ })
6385
+ );
6386
+ });
6387
+ }
6388
+ const restoreXhrOpen = xhrPrototype === void 0 || typeof originalOpenDescriptor?.value !== "function" ? void 0 : installWritableDataProperty(
6389
+ xhrPrototype,
6390
+ "open",
6391
+ spotPatchOpen,
6392
+ originalOpenDescriptor
6393
+ );
6394
+ const restoreXhrSend = xhrPrototype === void 0 || typeof originalSendDescriptor?.value !== "function" ? void 0 : installWritableDataProperty(
6395
+ xhrPrototype,
6396
+ "send",
6397
+ spotPatchSend,
6398
+ originalSendDescriptor
6399
+ );
6400
+ const runtime = Object.freeze({
6401
+ beginInvocation(metadata) {
6402
+ return Object.freeze({
6403
+ invocationId: nextInvocationId(),
6404
+ componentSourceId: metadata.componentSourceId,
6405
+ triggerCallsiteId: metadata.triggerCallsiteId,
6406
+ sourceVersion: metadata.sourceVersion
6407
+ });
6408
+ },
6409
+ bindInvocation(token, callback) {
6410
+ return function boundInvocation(...args) {
6411
+ return runtime.withInvocation(token, () => Reflect.apply(callback, this, args));
6412
+ };
6413
+ },
6414
+ bindTrigger(metadata, callback) {
6415
+ if (typeof callback !== "function") return callback;
6416
+ const callable = callback;
6417
+ return function boundTrigger(...args) {
6418
+ const token = runtime.beginInvocation(metadata);
6419
+ return runtime.withInvocation(token, () => Reflect.apply(callable, this, args));
6420
+ };
6421
+ },
6422
+ captureInvocation: () => currentInvocation,
6423
+ clear: store.clear,
6424
+ createTrpcLink() {
6425
+ return () => (options) => {
6426
+ recordWithoutAffectingHost(() => {
6427
+ const operation = options.op.path;
6428
+ const operationType = options.op.type;
6429
+ if (typeof operation !== "string" || operation.length === 0 || operation.length > 512 || operationType !== "query" && operationType !== "mutation" && operationType !== "subscription") {
6430
+ return;
6431
+ }
6432
+ const frame = currentRequestFrame;
6433
+ const token = frame?.invocationToken;
6434
+ store.add(
6435
+ Object.freeze({
6436
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6437
+ id: nextObservationId(),
6438
+ pageEpoch,
6439
+ routeEpoch,
6440
+ ...frame === void 0 ? {} : {
6441
+ requestCallsiteId: frame.requestCallsiteId,
6442
+ sourceVersion: frame.sourceVersion
6443
+ },
6444
+ ...token === void 0 ? {} : {
6445
+ invocationId: token.invocationId,
6446
+ componentSourceId: token.componentSourceId,
6447
+ triggerCallsiteId: token.triggerCallsiteId
6448
+ },
6449
+ transport: "trpc",
6450
+ method: operationType.toUpperCase(),
6451
+ operation,
6452
+ url: Object.freeze({
6453
+ pathname: operation,
6454
+ queryKeys: Object.freeze([])
6455
+ }),
6456
+ outcome: "dispatched",
6457
+ freshness: "current",
6458
+ diagnosticIds: Object.freeze([])
6459
+ })
6460
+ );
6461
+ });
6462
+ return options.next(options.op);
6463
+ };
6464
+ },
6465
+ dispose() {
6466
+ if (disposed) return;
6467
+ disposed = true;
6468
+ store.clear();
6469
+ restoreFetch?.();
6470
+ restoreXhrOpen?.();
6471
+ restoreXhrSend?.();
6472
+ },
6473
+ getComponentRegistration: (component) => componentRegistry.get(component),
6474
+ getCurrentRequestFrame: () => currentRequestFrame,
6475
+ observations: () => Object.freeze(
6476
+ store.values().map(
6477
+ (observation) => observation.routeEpoch === routeEpoch ? observation : Object.freeze({ ...observation, freshness: "stale-route" })
6478
+ )
6479
+ ),
6480
+ registerComponent(component, componentSourceId, registeredSourceVersion) {
6481
+ const registration = Object.freeze({
6482
+ componentSourceId,
6483
+ sourceVersion: registeredSourceVersion
6484
+ });
6485
+ const pending = [component];
6486
+ const registered = /* @__PURE__ */ new Set();
6487
+ while (pending.length > 0) {
6488
+ const candidate = pending.pop();
6489
+ if (candidate === void 0 || registered.has(candidate)) continue;
6490
+ registered.add(candidate);
6491
+ componentRegistry.set(candidate, registration);
6492
+ const nested = nestedReactWrapperComponent(candidate);
6493
+ if (nested !== void 0) pending.push(nested);
6494
+ }
6495
+ },
6496
+ updateRoute(nextRouteKey) {
6497
+ if (routeKey === void 0) {
6498
+ routeKey = nextRouteKey;
6499
+ } else if (routeKey !== nextRouteKey) {
6500
+ routeKey = nextRouteKey;
6501
+ routeEpoch = nextRouteEpoch();
6502
+ }
6503
+ },
6504
+ withInvocation(token, callback) {
6505
+ const parent = currentInvocation;
6506
+ currentInvocation = token;
6507
+ try {
6508
+ return callback();
6509
+ } finally {
6510
+ currentInvocation = parent;
6511
+ }
6512
+ },
6513
+ withRequestFrame(token, metadata, callback) {
6514
+ const parent = currentRequestFrame;
6515
+ currentRequestFrame = Object.freeze({
6516
+ requestCallsiteId: metadata.requestCallsiteId,
6517
+ sourceVersion: metadata.sourceVersion,
6518
+ ...token === void 0 ? {} : { invocationToken: token }
6519
+ });
6520
+ try {
6521
+ return callback();
6522
+ } finally {
6523
+ currentRequestFrame = parent;
6524
+ }
6525
+ }
6526
+ });
6527
+ return runtime;
6528
+ }
6529
+ function installDataFlowPrelude(config, target2 = globalThis) {
6530
+ if (!config.enabled) return void 0;
6531
+ const runtime = target2[RUNTIME_KEY] ?? createDataFlowRuntime(config, target2);
6532
+ target2[RUNTIME_KEY] = runtime;
6533
+ return runtime;
6534
+ }
6535
+ function getDataFlowRuntime(target2 = globalThis) {
6536
+ return target2[RUNTIME_KEY];
6537
+ }
6538
+
6539
+ // src/data-flow/report-merger.ts
6540
+ var import_shared10 = require("@spotpatch/shared");
6541
+ function observationMatchesDependency(observation, dependency) {
6542
+ const origin = dependency.origin;
6543
+ const transportMatches = dependency.kind === "rpc" ? observation.transport === "trpc" && dependency.operation !== void 0 && observation.operation === dependency.operation : dependency.kind === "http" ? observation.transport === "fetch" || observation.transport === "xhr" : false;
6544
+ if (!transportMatches || observation.freshness !== "current" || origin === void 0 || observation.requestCallsiteId !== origin.requestCallsiteId || observation.sourceVersion !== origin.sourceVersion || dependency.method !== void 0 && observation.method.toUpperCase() !== dependency.method.toUpperCase() || dependency.url !== void 0 && observation.url.pathname !== dependency.url.pathname || dependency.url?.origin !== void 0 && observation.url.origin !== dependency.url.origin || origin.componentSourceId !== void 0 && observation.componentSourceId !== void 0 && observation.componentSourceId !== origin.componentSourceId || origin.triggerCallsiteId !== void 0 && observation.triggerCallsiteId !== void 0 && observation.triggerCallsiteId !== origin.triggerCallsiteId) {
6545
+ return false;
6546
+ }
6547
+ return dependency.association !== "transitive" || observation.componentSourceId === origin.componentSourceId && observation.triggerCallsiteId === origin.triggerCallsiteId;
6548
+ }
6549
+ function runtimeEvidence(observation) {
6550
+ return Object.freeze({
6551
+ id: observation.id,
6552
+ kind: "runtime-observation",
6553
+ summaryKey: "dataFlow.evidence.runtimeDispatch"
6554
+ });
6555
+ }
6556
+ function mergeDependencies(dependencies, observations) {
6557
+ const matchedObservationIds = /* @__PURE__ */ new Set();
6558
+ const merged = dependencies.map((dependency) => {
6559
+ const matches = observations.filter(
6560
+ (observation) => observationMatchesDependency(observation, dependency)
6561
+ );
6562
+ for (const observation of matches) matchedObservationIds.add(observation.id);
6563
+ if (matches.length === 0) return dependency;
6564
+ const observedOrigins = [
6565
+ ...new Set(
6566
+ matches.flatMap(({ url }) => url.origin === void 0 ? [] : [url.origin])
6567
+ )
6568
+ ];
6569
+ const observedOrigin = observedOrigins.length === 1 ? observedOrigins[0] : void 0;
6570
+ return Object.freeze({
6571
+ ...dependency,
6572
+ ...dependency.url === void 0 || dependency.url.origin !== void 0 || observedOrigin === void 0 ? {} : {
6573
+ url: Object.freeze({
6574
+ ...dependency.url,
6575
+ origin: observedOrigin
6576
+ })
6577
+ },
6578
+ execution: "observed",
6579
+ observationIds: Object.freeze([
6580
+ .../* @__PURE__ */ new Set([...dependency.observationIds, ...matches.map(({ id }) => id)])
6581
+ ]),
6582
+ evidenceIds: Object.freeze([
6583
+ .../* @__PURE__ */ new Set([...dependency.evidenceIds, ...matches.map(({ id }) => id)])
6584
+ ])
6585
+ });
6586
+ });
6587
+ return Object.freeze({
6588
+ dependencies: Object.freeze(merged),
6589
+ matchedObservationIds
6590
+ });
6591
+ }
6592
+ function appendRuntimeEvidence(evidence, observations, matchedIds) {
6593
+ const existing = new Set(evidence.map(({ id }) => id));
6594
+ return Object.freeze([
6595
+ ...evidence,
6596
+ ...observations.flatMap(
6597
+ (observation) => matchedIds.has(observation.id) && !existing.has(observation.id) ? [runtimeEvidence(observation)] : []
6598
+ )
6599
+ ]);
6600
+ }
6601
+ function mergeComponentDataFlowReport(report, observations) {
6602
+ const componentObservations = observations.filter(
6603
+ (observation) => observation.componentSourceId === void 0 || observation.componentSourceId === report.component.componentSourceId
6604
+ );
6605
+ const merged = mergeDependencies(report.dependencies, componentObservations);
6606
+ return (0, import_shared10.limitDataFlowReportCollections)(
6607
+ Object.freeze({
6608
+ ...report,
6609
+ dependencies: merged.dependencies,
6610
+ evidence: appendRuntimeEvidence(
6611
+ report.evidence,
6612
+ componentObservations,
6613
+ merged.matchedObservationIds
6614
+ )
6615
+ }),
6616
+ { mode: "observation" }
6617
+ );
6618
+ }
6619
+ function unassignedDependency(observation) {
6620
+ const isRpc = observation.transport === "trpc";
6621
+ return Object.freeze({
6622
+ id: observation.id,
6623
+ kind: isRpc ? "rpc" : "http",
6624
+ direction: observation.method === "GET" || observation.method === "HEAD" || observation.method === "QUERY" || observation.method === "SUBSCRIPTION" ? "read" : "write",
6625
+ execution: "observed",
6626
+ proof: "unavailable",
6627
+ association: "unassigned",
6628
+ method: observation.method,
6629
+ ...isRpc ? observation.operation === void 0 ? {} : { operation: observation.operation } : { url: observation.url },
6630
+ parameters: Object.freeze(
6631
+ (isRpc ? [] : observation.url.queryKeys).map(
6632
+ (path) => Object.freeze({
6633
+ path,
6634
+ position: "query",
6635
+ sensitive: (0, import_shared10.isSensitiveName)(path),
6636
+ valueState: "not-collected",
6637
+ evidenceIds: Object.freeze([observation.id])
6638
+ })
6639
+ )
6640
+ ),
6641
+ response: Object.freeze({
6642
+ consumedFields: Object.freeze([])
6643
+ }),
6644
+ suppliedBindings: Object.freeze([]),
6645
+ locationIds: Object.freeze([]),
6646
+ evidenceIds: Object.freeze([observation.id]),
6647
+ observationIds: Object.freeze([observation.id])
6648
+ });
6649
+ }
6650
+ function mergePageDataFlowReport(report, observations) {
6651
+ const currentObservations = observations.filter(
6652
+ ({ freshness }) => freshness === "current"
6653
+ );
6654
+ const merged = mergeDependencies(report.dependencies, currentObservations);
6655
+ const unassigned = currentObservations.filter(({ id }) => !merged.matchedObservationIds.has(id)).map(unassignedDependency);
6656
+ const allObservationIds = new Set(currentObservations.map(({ id }) => id));
6657
+ return (0, import_shared10.limitDataFlowReportCollections)(
6658
+ Object.freeze({
6659
+ ...report,
6660
+ dependencies: Object.freeze([...merged.dependencies, ...unassigned]),
6661
+ evidence: appendRuntimeEvidence(
6662
+ report.evidence,
6663
+ currentObservations,
6664
+ allObservationIds
6665
+ )
6666
+ }),
6667
+ { mode: "observation" }
6668
+ );
6669
+ }
5818
6670
  // Annotate the CommonJS export names for ESM import in node:
5819
6671
  0 && (module.exports = {
5820
6672
  UI_MARKER_ATTRIBUTE,
5821
- bootstrapSpotPatch
6673
+ bootstrapSpotPatch,
6674
+ createDataFlowRuntime,
6675
+ getDataFlowRuntime,
6676
+ installDataFlowPrelude,
6677
+ mergeComponentDataFlowReport,
6678
+ mergePageDataFlowReport
5822
6679
  });
5823
6680
  //# sourceMappingURL=index.cjs.map