@agent-scope/runtime 1.1.0 → 1.3.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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { ComponentType, ComponentNode, PageReport, ConsoleEntry, CapturedError, HookState, SuspenseBoundaryInfo } from '@agent-scope/core';
2
- export { CapturedError, ComponentNode, PageReport, SuspenseBoundaryInfo } from '@agent-scope/core';
1
+ import { ComponentType, ComponentNode, LightweightComponentNode, PageReport, CapturedError, SuspenseBoundaryInfo, ConsoleEntry, HookState } from '@agent-scope/core';
2
+ export { CapturedError, ComponentNode, LightweightComponentNode, PageReport, SuspenseBoundaryInfo } from '@agent-scope/core';
3
3
 
4
4
  /**
5
5
  * devtools-hook.ts
@@ -167,6 +167,26 @@ declare function walkFiber(fiber: Fiber | null, options?: WalkOptions): Componen
167
167
  */
168
168
  declare function walkFiberRoot(fiberRoot: any, options?: WalkOptions): ComponentNode | null;
169
169
 
170
+ /**
171
+ * Walk a fiber and all its descendants, producing a `LightweightComponentNode`
172
+ * tree. Props, state values, context, source, renderCount and renderDuration
173
+ * are deliberately omitted — only structural / identification fields are
174
+ * included.
175
+ *
176
+ * @param fiber - The starting fiber.
177
+ * @param options - Walk configuration (same as `walkFiber`).
178
+ * @returns The `LightweightComponentNode` for `fiber`, or `null` if it should
179
+ * be skipped.
180
+ */
181
+ declare function walkFiberLightweight(fiber: Fiber | null, options?: WalkOptions): LightweightComponentNode | null;
182
+ /**
183
+ * Walk a fiber root (as returned by the DevTools hook) and return the root
184
+ * `LightweightComponentNode`, or `null` if the tree is empty.
185
+ *
186
+ * Mirrors `walkFiberRoot` but uses the lightweight walker.
187
+ */
188
+ declare function walkFiberRootLightweight(fiberRoot: any, options?: WalkOptions): LightweightComponentNode | null;
189
+
170
190
  /**
171
191
  * capture.ts
172
192
  *
@@ -185,9 +205,29 @@ interface CaptureOptions extends WalkOptions {
185
205
  * Default: 10 000.
186
206
  */
187
207
  reactTimeout?: number;
208
+ /**
209
+ * When `true`, perform a lightweight capture that omits props, state,
210
+ * context, source, renderCount, and renderDuration.
211
+ *
212
+ * Produces a `LightweightCaptureResult` instead of a full `CaptureResult`.
213
+ * Expected size reduction: ~99% (e.g. 300 MB → ~3 MB for 2 500 nodes).
214
+ *
215
+ * Default: `false`.
216
+ */
217
+ lightweight?: boolean;
188
218
  }
189
- /** The fields populated by `capture()`. The rest can be added by callers. */
219
+ /** The fields populated by a full `capture()`. */
190
220
  type CaptureResult = Pick<PageReport, "url" | "timestamp" | "capturedIn" | "tree" | "consoleEntries" | "errors" | "suspenseBoundaries">;
221
+ /** The fields populated by a lightweight `capture({ lightweight: true })`. */
222
+ interface LightweightCaptureResult {
223
+ url: string;
224
+ timestamp: number;
225
+ capturedIn: number;
226
+ tree: LightweightComponentNode;
227
+ consoleEntries: PageReport["consoleEntries"];
228
+ errors: CapturedError[];
229
+ suspenseBoundaries: SuspenseBoundaryInfo[];
230
+ }
191
231
  /**
192
232
  * Capture a snapshot of the current React component tree.
193
233
  *
@@ -200,11 +240,18 @@ type CaptureResult = Pick<PageReport, "url" | "timestamp" | "capturedIn" | "tree
200
240
  * Also detects error boundaries that have caught errors and Suspense
201
241
  * boundaries with their current suspension status.
202
242
  *
203
- * @returns A partial `PageReport` containing `url`, `timestamp`, `capturedIn`,
204
- * `tree`, `consoleEntries`, `errors`, and `suspenseBoundaries`.
243
+ * @param options.lightweight - When `true`, returns a `LightweightCaptureResult`
244
+ * with a minimal tree (no props/state/context/source/renderCount/renderDuration).
245
+ *
246
+ * @returns A `CaptureResult` (or `LightweightCaptureResult` when lightweight is
247
+ * true) containing `url`, `timestamp`, `capturedIn`, `tree`,
248
+ * `consoleEntries`, `errors`, and `suspenseBoundaries`.
205
249
  * @throws If no React renderer registers within `reactTimeout` ms, or if the
206
250
  * fiber tree is empty.
207
251
  */
252
+ declare function capture(options: CaptureOptions & {
253
+ lightweight: true;
254
+ }): Promise<LightweightCaptureResult>;
208
255
  declare function capture(options?: CaptureOptions): Promise<CaptureResult>;
209
256
 
210
257
  /**
@@ -422,4 +469,4 @@ declare class ScopeRuntime {
422
469
  /** Create and return a new ScopeRuntime instance */
423
470
  declare function createRuntime(options?: RuntimeOptions): ScopeRuntime;
424
471
 
425
- export { type CaptureOptions, type CaptureResult, type Fiber, type ProfilingSnapshot, type ReactRenderer, type RuntimeOptions, type ScopeDevToolsHook, ScopeRuntime, type WalkOptions, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberRoot };
472
+ export { type CaptureOptions, type CaptureResult, type Fiber, type LightweightCaptureResult, type ProfilingSnapshot, type ReactRenderer, type RuntimeOptions, type ScopeDevToolsHook, ScopeRuntime, type WalkOptions, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberLightweight, walkFiberRoot, walkFiberRootLightweight };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ComponentType, ComponentNode, PageReport, ConsoleEntry, CapturedError, HookState, SuspenseBoundaryInfo } from '@agent-scope/core';
2
- export { CapturedError, ComponentNode, PageReport, SuspenseBoundaryInfo } from '@agent-scope/core';
1
+ import { ComponentType, ComponentNode, LightweightComponentNode, PageReport, CapturedError, SuspenseBoundaryInfo, ConsoleEntry, HookState } from '@agent-scope/core';
2
+ export { CapturedError, ComponentNode, LightweightComponentNode, PageReport, SuspenseBoundaryInfo } from '@agent-scope/core';
3
3
 
4
4
  /**
5
5
  * devtools-hook.ts
@@ -167,6 +167,26 @@ declare function walkFiber(fiber: Fiber | null, options?: WalkOptions): Componen
167
167
  */
168
168
  declare function walkFiberRoot(fiberRoot: any, options?: WalkOptions): ComponentNode | null;
169
169
 
170
+ /**
171
+ * Walk a fiber and all its descendants, producing a `LightweightComponentNode`
172
+ * tree. Props, state values, context, source, renderCount and renderDuration
173
+ * are deliberately omitted — only structural / identification fields are
174
+ * included.
175
+ *
176
+ * @param fiber - The starting fiber.
177
+ * @param options - Walk configuration (same as `walkFiber`).
178
+ * @returns The `LightweightComponentNode` for `fiber`, or `null` if it should
179
+ * be skipped.
180
+ */
181
+ declare function walkFiberLightweight(fiber: Fiber | null, options?: WalkOptions): LightweightComponentNode | null;
182
+ /**
183
+ * Walk a fiber root (as returned by the DevTools hook) and return the root
184
+ * `LightweightComponentNode`, or `null` if the tree is empty.
185
+ *
186
+ * Mirrors `walkFiberRoot` but uses the lightweight walker.
187
+ */
188
+ declare function walkFiberRootLightweight(fiberRoot: any, options?: WalkOptions): LightweightComponentNode | null;
189
+
170
190
  /**
171
191
  * capture.ts
172
192
  *
@@ -185,9 +205,29 @@ interface CaptureOptions extends WalkOptions {
185
205
  * Default: 10 000.
186
206
  */
187
207
  reactTimeout?: number;
208
+ /**
209
+ * When `true`, perform a lightweight capture that omits props, state,
210
+ * context, source, renderCount, and renderDuration.
211
+ *
212
+ * Produces a `LightweightCaptureResult` instead of a full `CaptureResult`.
213
+ * Expected size reduction: ~99% (e.g. 300 MB → ~3 MB for 2 500 nodes).
214
+ *
215
+ * Default: `false`.
216
+ */
217
+ lightweight?: boolean;
188
218
  }
189
- /** The fields populated by `capture()`. The rest can be added by callers. */
219
+ /** The fields populated by a full `capture()`. */
190
220
  type CaptureResult = Pick<PageReport, "url" | "timestamp" | "capturedIn" | "tree" | "consoleEntries" | "errors" | "suspenseBoundaries">;
221
+ /** The fields populated by a lightweight `capture({ lightweight: true })`. */
222
+ interface LightweightCaptureResult {
223
+ url: string;
224
+ timestamp: number;
225
+ capturedIn: number;
226
+ tree: LightweightComponentNode;
227
+ consoleEntries: PageReport["consoleEntries"];
228
+ errors: CapturedError[];
229
+ suspenseBoundaries: SuspenseBoundaryInfo[];
230
+ }
191
231
  /**
192
232
  * Capture a snapshot of the current React component tree.
193
233
  *
@@ -200,11 +240,18 @@ type CaptureResult = Pick<PageReport, "url" | "timestamp" | "capturedIn" | "tree
200
240
  * Also detects error boundaries that have caught errors and Suspense
201
241
  * boundaries with their current suspension status.
202
242
  *
203
- * @returns A partial `PageReport` containing `url`, `timestamp`, `capturedIn`,
204
- * `tree`, `consoleEntries`, `errors`, and `suspenseBoundaries`.
243
+ * @param options.lightweight - When `true`, returns a `LightweightCaptureResult`
244
+ * with a minimal tree (no props/state/context/source/renderCount/renderDuration).
245
+ *
246
+ * @returns A `CaptureResult` (or `LightweightCaptureResult` when lightweight is
247
+ * true) containing `url`, `timestamp`, `capturedIn`, `tree`,
248
+ * `consoleEntries`, `errors`, and `suspenseBoundaries`.
205
249
  * @throws If no React renderer registers within `reactTimeout` ms, or if the
206
250
  * fiber tree is empty.
207
251
  */
252
+ declare function capture(options: CaptureOptions & {
253
+ lightweight: true;
254
+ }): Promise<LightweightCaptureResult>;
208
255
  declare function capture(options?: CaptureOptions): Promise<CaptureResult>;
209
256
 
210
257
  /**
@@ -422,4 +469,4 @@ declare class ScopeRuntime {
422
469
  /** Create and return a new ScopeRuntime instance */
423
470
  declare function createRuntime(options?: RuntimeOptions): ScopeRuntime;
424
471
 
425
- export { type CaptureOptions, type CaptureResult, type Fiber, type ProfilingSnapshot, type ReactRenderer, type RuntimeOptions, type ScopeDevToolsHook, ScopeRuntime, type WalkOptions, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberRoot };
472
+ export { type CaptureOptions, type CaptureResult, type Fiber, type LightweightCaptureResult, type ProfilingSnapshot, type ReactRenderer, type RuntimeOptions, type ScopeDevToolsHook, ScopeRuntime, type WalkOptions, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberLightweight, walkFiberRoot, walkFiberRootLightweight };
package/dist/index.js CHANGED
@@ -695,6 +695,131 @@ function walkFiberRoot(fiberRoot, options = {}) {
695
695
  children
696
696
  };
697
697
  }
698
+ var HookLayout2 = 4;
699
+ function isLightweightEffectNode(node) {
700
+ const ms = node.memoizedState;
701
+ if (ms === null || typeof ms !== "object") return false;
702
+ const obj = ms;
703
+ return typeof obj.create === "function" && "deps" in obj && typeof obj.tag === "number";
704
+ }
705
+ function isLightweightRefNode(node) {
706
+ if (node.queue != null) return false;
707
+ const ms = node.memoizedState;
708
+ if (ms === null || typeof ms !== "object" || Array.isArray(ms)) return false;
709
+ const keys = Object.keys(ms);
710
+ return keys.length === 1 && keys[0] === "current";
711
+ }
712
+ function isLightweightMemoTuple(node) {
713
+ if (node.queue != null) return false;
714
+ const ms = node.memoizedState;
715
+ if (!Array.isArray(ms) || ms.length !== 2) return false;
716
+ return ms[1] === null || Array.isArray(ms[1]);
717
+ }
718
+ function isLightweightStateOrReducer(node) {
719
+ return node.queue != null && typeof node.queue === "object" && typeof node.queue.dispatch === "function";
720
+ }
721
+ function isLightweightReducer(node) {
722
+ if (!isLightweightStateOrReducer(node)) return false;
723
+ const q = node.queue;
724
+ if (typeof q.reducer === "function") return true;
725
+ const lrr = q.lastRenderedReducer;
726
+ if (typeof lrr !== "function") return false;
727
+ const name = lrr.name ?? "";
728
+ return name !== "basicStateReducer" && name !== "";
729
+ }
730
+ function classifyHookType(node) {
731
+ if (isLightweightEffectNode(node)) {
732
+ const ms = node.memoizedState;
733
+ return ms.tag & HookLayout2 ? "useLayoutEffect" : "useEffect";
734
+ }
735
+ if (isLightweightRefNode(node)) return "useRef";
736
+ if (isLightweightMemoTuple(node)) {
737
+ const [val] = node.memoizedState;
738
+ return typeof val === "function" ? "useCallback" : "useMemo";
739
+ }
740
+ if (isLightweightStateOrReducer(node)) {
741
+ return isLightweightReducer(node) ? "useReducer" : "useState";
742
+ }
743
+ return "custom";
744
+ }
745
+ function countAndClassifyHooks(fiber) {
746
+ const hookTypes = [];
747
+ let node = fiber.memoizedState ?? null;
748
+ if (node === null || typeof node !== "object" || !("next" in node)) {
749
+ return { hookCount: 0, hookTypes: [] };
750
+ }
751
+ while (node !== null) {
752
+ hookTypes.push(classifyHookType(node));
753
+ node = node.next ?? null;
754
+ }
755
+ return { hookCount: hookTypes.length, hookTypes };
756
+ }
757
+ function walkFiberLightweight(fiber, options = {}) {
758
+ if (fiber === null || fiber === void 0) return null;
759
+ const includeHost = options.includeHostElements ?? false;
760
+ return walkFiberLightweightInner(fiber, includeHost, /* @__PURE__ */ new Set(), 0);
761
+ }
762
+ function walkFiberLightweightInner(fiber, includeHost, visited, depth) {
763
+ if (fiber === null || fiber === void 0) return null;
764
+ if (visited.has(fiber)) return null;
765
+ if (shouldSkip(fiber, includeHost)) return null;
766
+ visited.add(fiber);
767
+ const id = typeof fiber._debugID === "number" ? fiber._debugID : nextId();
768
+ const { hookCount, hookTypes } = countAndClassifyHooks(fiber);
769
+ const children = collectLightweightChildren(fiber, includeHost, visited, depth + 1);
770
+ const node = {
771
+ id,
772
+ name: extractName(fiber),
773
+ type: classifyType(fiber),
774
+ hookCount,
775
+ hookTypes,
776
+ childCount: children.length,
777
+ depth,
778
+ children
779
+ };
780
+ return node;
781
+ }
782
+ function collectLightweightChildren(fiber, includeHost, visited, childDepth) {
783
+ const nodes = [];
784
+ let current = fiber.child ?? null;
785
+ while (current !== null) {
786
+ if (visited.has(current)) {
787
+ current = current.sibling ?? null;
788
+ continue;
789
+ }
790
+ if (shouldSkip(current, includeHost)) {
791
+ const promoted = collectLightweightChildren(current, includeHost, visited, childDepth);
792
+ nodes.push(...promoted);
793
+ } else {
794
+ const node = walkFiberLightweightInner(current, includeHost, visited, childDepth);
795
+ if (node !== null) {
796
+ nodes.push(node);
797
+ }
798
+ }
799
+ current = current.sibling ?? null;
800
+ }
801
+ return nodes;
802
+ }
803
+ function walkFiberRootLightweight(fiberRoot, options = {}) {
804
+ if (!fiberRoot) return null;
805
+ const hostRootFiber = fiberRoot.current ?? null;
806
+ if (!hostRootFiber) return null;
807
+ const includeHost = options.includeHostElements ?? false;
808
+ const visited = /* @__PURE__ */ new Set();
809
+ const children = collectLightweightChildren(hostRootFiber, includeHost, visited, 0);
810
+ if (children.length === 0) return null;
811
+ if (children.length === 1) return children[0] ?? null;
812
+ return {
813
+ id: nextId(),
814
+ name: "Root",
815
+ type: "function",
816
+ hookCount: 0,
817
+ hookTypes: [],
818
+ childCount: children.length,
819
+ depth: 0,
820
+ children
821
+ };
822
+ }
698
823
 
699
824
  // src/suspense-detector.ts
700
825
  var SuspenseComponent2 = 13;
@@ -786,18 +911,35 @@ async function capture(options = {}) {
786
911
  const walkOptions = {
787
912
  includeHostElements: options.includeHostElements ?? false
788
913
  };
789
- const tree = walkFiberRoot(fiberRoot, walkOptions);
790
- if (!tree) {
791
- throw new Error(
792
- "capture(): Fiber tree is empty. Make sure React has rendered at least one component."
793
- );
794
- }
795
914
  const hostRootFiber = fiberRoot.current ?? null;
796
915
  const rootChild = hostRootFiber?.child ?? null;
797
916
  const errors = detectErrors(rootChild);
798
917
  const suspenseBoundaries = detectSuspenseBoundaries(rootChild);
799
918
  const capturedIn = Date.now() - startTime;
800
919
  const consoleEntries = getConsoleEntries();
920
+ if (options.lightweight) {
921
+ const tree2 = walkFiberRootLightweight(fiberRoot, walkOptions);
922
+ if (!tree2) {
923
+ throw new Error(
924
+ "capture(): Fiber tree is empty. Make sure React has rendered at least one component."
925
+ );
926
+ }
927
+ return {
928
+ url,
929
+ timestamp: startTime,
930
+ capturedIn,
931
+ tree: tree2,
932
+ consoleEntries,
933
+ errors,
934
+ suspenseBoundaries
935
+ };
936
+ }
937
+ const tree = walkFiberRoot(fiberRoot, walkOptions);
938
+ if (!tree) {
939
+ throw new Error(
940
+ "capture(): Fiber tree is empty. Make sure React has rendered at least one component."
941
+ );
942
+ }
801
943
  return {
802
944
  url,
803
945
  timestamp: startTime,
@@ -852,6 +994,6 @@ function createRuntime(options) {
852
994
  return new ScopeRuntime(options);
853
995
  }
854
996
 
855
- export { ScopeRuntime, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberRoot };
997
+ export { ScopeRuntime, capture, classifyType, clearConsoleEntries, createRuntime, detectErrors, detectSuspenseBoundaries, extractHooks, extractName, getConsoleEntries, getHook, getProfilingData, getRenderers, installConsoleInterceptor, installHook, installProfiler, resetProfilingData, uninstallConsoleInterceptor, waitForReact, walkFiber, walkFiberLightweight, walkFiberRoot, walkFiberRootLightweight };
856
998
  //# sourceMappingURL=index.js.map
857
999
  //# sourceMappingURL=index.js.map