agentfootprint-lens 0.13.0 → 0.13.1

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
@@ -1217,12 +1217,31 @@ var RunTreeFlow = ({
1217
1217
  }
1218
1218
  } : void 0;
1219
1219
  const containerRef = (0, import_react6.useRef)(null);
1220
+ const graphFingerprint = (0, import_react6.useMemo)(() => {
1221
+ let h = 0;
1222
+ for (const n of nodes) {
1223
+ const s = `${n.id}|${n.position.x}|${n.position.y}`;
1224
+ for (let i = 0; i < s.length; i++) h = (h << 5) - h + s.charCodeAt(i) | 0;
1225
+ }
1226
+ return `${nodes.length}:${edges.length}:${h}`;
1227
+ }, [nodes, edges]);
1220
1228
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1221
1229
  "div",
1222
1230
  {
1223
1231
  ref: containerRef,
1224
1232
  "data-fp-lens": "run-tree-flow",
1225
- style: { width: "100%", height: "100%", minHeight: 320, background: "var(--lens-bg, transparent)" },
1233
+ style: {
1234
+ width: "100%",
1235
+ height: "100%",
1236
+ // minHeight keeps the flowchart usable when the parent is
1237
+ // shorter than the natural layout. minWidth was missing and
1238
+ // let parents squeeze us to ~280px, which made React Flow
1239
+ // auto-zoom to ~30% and render nodes invisibly small.
1240
+ // 480px keeps nodes legible in side-panel layouts.
1241
+ minHeight: 320,
1242
+ minWidth: 480,
1243
+ background: "var(--lens-bg, transparent)"
1244
+ },
1226
1245
  children: [
1227
1246
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: LENS_DEFAULT_CSS }),
1228
1247
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -1246,9 +1265,9 @@ var RunTreeFlow = ({
1246
1265
  preventScrolling: false,
1247
1266
  proOptions: { hideAttribution: true },
1248
1267
  children: [
1249
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FitViewOnResize, { depKey: nodes.length, containerRef }),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FitViewOnResize, { depKey: graphFingerprint, containerRef }),
1250
1269
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react7.Background, { gap: 20, size: 1, color: "var(--lens-background-dots, #e5e7eb)" }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react7.Controls, { showInteractive: false })
1270
+ nodes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react7.Controls, { showInteractive: false })
1252
1271
  ]
1253
1272
  }
1254
1273
  )
@@ -1261,11 +1280,13 @@ function FitViewOnResize({
1261
1280
  containerRef
1262
1281
  }) {
1263
1282
  const { fitView } = (0, import_react7.useReactFlow)();
1283
+ const nodesInitialized = (0, import_react7.useNodesInitialized)();
1264
1284
  (0, import_react6.useEffect)(() => {
1285
+ if (!nodesInitialized) return;
1265
1286
  const handler = () => {
1266
1287
  requestAnimationFrame(() => fitView({ padding: 0.3, duration: 200 }));
1267
1288
  };
1268
- const timer = setTimeout(handler, 50);
1289
+ handler();
1269
1290
  const el = containerRef.current;
1270
1291
  let observer;
1271
1292
  if (el && typeof ResizeObserver !== "undefined") {
@@ -1274,11 +1295,10 @@ function FitViewOnResize({
1274
1295
  }
1275
1296
  window.addEventListener("resize", handler);
1276
1297
  return () => {
1277
- clearTimeout(timer);
1278
1298
  observer?.disconnect();
1279
1299
  window.removeEventListener("resize", handler);
1280
1300
  };
1281
- }, [fitView, depKey, containerRef]);
1301
+ }, [fitView, depKey, containerRef, nodesInitialized]);
1282
1302
  return null;
1283
1303
  }
1284
1304
  function buildFlow(view, graph, selectedId) {
@@ -1309,7 +1329,12 @@ function buildFlow(view, graph, selectedId) {
1309
1329
  selected: agent.groupId === selectedId,
1310
1330
  ...agent.primitiveKind ? { primitiveKind: agent.primitiveKind } : {}
1311
1331
  },
1312
- style: { width: AGENT_GROUP_WIDTH, height: AGENT_GROUP_HEIGHT },
1332
+ // xyflow v12: parent sizing comes from top-level `width`/`height`.
1333
+ // Setting `style.width/height` AS WELL appears to confuse RF —
1334
+ // children get clipped to a phantom zero-size box. Single source
1335
+ // of truth: top-level only.
1336
+ width: AGENT_GROUP_WIDTH,
1337
+ height: AGENT_GROUP_HEIGHT,
1313
1338
  selectable: true,
1314
1339
  draggable: false
1315
1340
  });
@@ -1317,7 +1342,6 @@ function buildFlow(view, graph, selectedId) {
1317
1342
  nodes.push({
1318
1343
  id: `${agent.groupId}-ctx`,
1319
1344
  parentId: agent.groupId,
1320
- extent: "parent",
1321
1345
  position: CONTEXT_BIN_IN_GROUP,
1322
1346
  type: "contextBin",
1323
1347
  data: { chips: chipsForAgent },
@@ -1328,7 +1352,6 @@ function buildFlow(view, graph, selectedId) {
1328
1352
  nodes.push({
1329
1353
  id: agent.llmId,
1330
1354
  parentId: agent.groupId,
1331
- extent: "parent",
1332
1355
  position: LLM_IN_GROUP,
1333
1356
  type: "llm",
1334
1357
  data: { updatedSlot },
@@ -1871,32 +1894,66 @@ var ReActStepBody = ({ node }) => {
1871
1894
  if (node.llmModel) rows.push(["model", node.llmModel]);
1872
1895
  if (node.slotUpdated) rows.push(["what landed in", node.slotUpdated]);
1873
1896
  if (duration !== void 0 && duration > 0) rows.push(["duration", `${Math.round(duration)}ms`]);
1874
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: sectionStyle, children: [
1875
- rows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "grid", gap: 4, fontSize: 12, padding: 8 }, children: rows.map(([label, value]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Field, { label, children: value }, label)) }),
1876
- (() => {
1877
- const engineered = (node.injections ?? []).filter(
1878
- (inj) => !BASELINE_SOURCES2.has(inj.source)
1879
- );
1880
- if (engineered.length === 0) return null;
1881
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "0 8px 8px" }, children: [
1882
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: sectionLabelStyle, children: "Context engineering" }),
1883
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: { margin: "4px 0 0", paddingLeft: 14, fontSize: 11, color: T.textSecondary }, children: engineered.map((inj, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { style: { padding: "2px 0" }, children: [
1884
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("code", { style: { fontSize: 11 }, children: [
1885
- "[",
1886
- inj.slot,
1887
- "] ",
1888
- inj.source,
1889
- inj.sourceId ? `:${inj.sourceId}` : ""
1890
- ] }),
1891
- inj.contentSummary && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { opacity: 0.7, marginLeft: 6 }, children: [
1892
- "\xB7 ",
1893
- inj.contentSummary
1894
- ] })
1895
- ] }, i)) })
1896
- ] });
1897
- })()
1897
+ const ioSections = ioSectionsFor(node);
1898
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: sectionStyle, children: [
1900
+ rows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "grid", gap: 4, fontSize: 12, padding: 8 }, children: rows.map(([label, value]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Field, { label, children: value }, label)) }),
1901
+ (() => {
1902
+ const engineered = (node.injections ?? []).filter(
1903
+ (inj) => !BASELINE_SOURCES2.has(inj.source)
1904
+ );
1905
+ if (engineered.length === 0) return null;
1906
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "0 8px 8px" }, children: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: sectionLabelStyle, children: "Context engineering" }),
1908
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: { margin: "4px 0 0", paddingLeft: 14, fontSize: 11, color: T.textSecondary }, children: engineered.map((inj, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { style: { padding: "2px 0" }, children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("code", { style: { fontSize: 11 }, children: [
1910
+ "[",
1911
+ inj.slot,
1912
+ "] ",
1913
+ inj.source,
1914
+ inj.sourceId ? `:${inj.sourceId}` : ""
1915
+ ] }),
1916
+ inj.contentSummary && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { opacity: 0.7, marginLeft: 6 }, children: [
1917
+ "\xB7 ",
1918
+ inj.contentSummary
1919
+ ] })
1920
+ ] }, i)) })
1921
+ ] });
1922
+ })()
1923
+ ] }),
1924
+ ioSections.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PayloadSection, { label: s.label, payload: s.payload, emptyHint: "(none)" }, s.label))
1898
1925
  ] });
1899
1926
  };
1927
+ function ioSectionsFor(node) {
1928
+ const out = [];
1929
+ switch (node.kind) {
1930
+ case "llm->tool": {
1931
+ if (node.assistantText) {
1932
+ out.push({ label: "LLM's reasoning", payload: node.assistantText });
1933
+ }
1934
+ if (node.toolArgs !== void 0) {
1935
+ out.push({ label: "Tool input (args)", payload: node.toolArgs });
1936
+ }
1937
+ break;
1938
+ }
1939
+ case "tool->llm": {
1940
+ if (node.toolResult !== void 0) {
1941
+ out.push({ label: "Tool result sent to LLM", payload: node.toolResult });
1942
+ }
1943
+ break;
1944
+ }
1945
+ case "llm->user": {
1946
+ if (node.assistantText) {
1947
+ out.push({ label: "Final answer", payload: node.assistantText });
1948
+ }
1949
+ break;
1950
+ }
1951
+ case "user->llm":
1952
+ default:
1953
+ break;
1954
+ }
1955
+ return out;
1956
+ }
1900
1957
  var BASELINE_SOURCES2 = /* @__PURE__ */ new Set([
1901
1958
  "user",
1902
1959
  // current-turn user message
@@ -1914,15 +1971,20 @@ var Field = ({ label, children }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx
1914
1971
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: T.textPrimary }, children })
1915
1972
  ] });
1916
1973
  function prettyPrint(value) {
1917
- try {
1918
- const str = JSON.stringify(value, null, 2);
1919
- if (str.length > 4e3) {
1920
- return str.slice(0, 4e3) + "\n\n... (truncated; " + (str.length - 4e3) + " chars)";
1974
+ let str;
1975
+ if (typeof value === "string") {
1976
+ str = value;
1977
+ } else {
1978
+ try {
1979
+ str = JSON.stringify(value, null, 2);
1980
+ } catch {
1981
+ return "(unable to serialize)";
1921
1982
  }
1922
- return str;
1923
- } catch {
1924
- return "(unable to serialize)";
1925
1983
  }
1984
+ if (str.length > 4e3) {
1985
+ return str.slice(0, 4e3) + "\n\n... (truncated; " + (str.length - 4e3) + " chars)";
1986
+ }
1987
+ return str;
1926
1988
  }
1927
1989
  var panelStyle = {
1928
1990
  display: "flex",
@@ -2202,6 +2264,42 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2202
2264
  const focusedNode = stepGraph?.nodes[focusStep];
2203
2265
  const focusedRuntimeStageId = focusedNode?.runtimeStageId;
2204
2266
  const currentStepLabel = focusedNode?.label;
2267
+ const stepToEventSeq = (0, import_react11.useMemo)(() => {
2268
+ if (!stepGraph || log.length === 0) return [];
2269
+ const firstSeq = log[0].seq;
2270
+ const seqs = [];
2271
+ let lastResolvedSeq = firstSeq;
2272
+ const anchorSide = (kind) => kind === "llm->user" ? "last" : "first";
2273
+ for (const node of stepGraph.nodes) {
2274
+ const id = node.runtimeStageId;
2275
+ let resolved = -1;
2276
+ if (id !== void 0) {
2277
+ const side = anchorSide(node.kind);
2278
+ if (side === "first") {
2279
+ for (const e of log) {
2280
+ const stageId = e.event.meta?.runtimeStageId;
2281
+ if (stageId === id) {
2282
+ resolved = e.seq;
2283
+ break;
2284
+ }
2285
+ }
2286
+ } else {
2287
+ for (let i = log.length - 1; i >= 0; i--) {
2288
+ const stageId = log[i].event.meta?.runtimeStageId;
2289
+ if (stageId === id) {
2290
+ resolved = log[i].seq;
2291
+ break;
2292
+ }
2293
+ }
2294
+ }
2295
+ }
2296
+ if (resolved === -1) resolved = lastResolvedSeq;
2297
+ seqs.push(resolved);
2298
+ lastResolvedSeq = resolved;
2299
+ }
2300
+ return seqs;
2301
+ }, [stepGraph, log]);
2302
+ const focusedSeq = stepToEventSeq[focusStep] ?? -1;
2205
2303
  const handleNodeSelect = (nodeId) => {
2206
2304
  if (!stepGraph) return;
2207
2305
  const idx = stepGraph.nodes.findIndex((n) => n.id === nodeId);
@@ -2249,7 +2347,7 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2249
2347
  overflow: "hidden"
2250
2348
  },
2251
2349
  children: [
2252
- leftExpanded ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2350
+ agentNodes.length >= 2 && (leftExpanded ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2253
2351
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2254
2352
  "div",
2255
2353
  {
@@ -2291,7 +2389,7 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2291
2389
  side: "left",
2292
2390
  onClick: () => setLeftExpanded(true)
2293
2391
  }
2294
- ),
2392
+ )),
2295
2393
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2296
2394
  "div",
2297
2395
  {
@@ -2351,20 +2449,21 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2351
2449
  "div",
2352
2450
  {
2353
2451
  style: {
2354
- width: 320,
2355
- flexShrink: 0,
2452
+ // Flex-shrink 1 with min/max so the panel yields width to
2453
+ // the central flowchart when the container is narrow. The
2454
+ // hard 320px-fixed width was squeezing the flowchart to
2455
+ // ~280px in compact layouts (e.g., embedded in a 30%
2456
+ // sidebar), causing React Flow to auto-zoom to ~30% and
2457
+ // render nodes invisibly small.
2458
+ flex: "0 1 320px",
2459
+ minWidth: 220,
2460
+ maxWidth: 360,
2356
2461
  display: "flex",
2357
2462
  flexDirection: "column",
2358
2463
  overflow: "hidden",
2359
2464
  borderLeft: `1px solid ${T.border}`
2360
2465
  },
2361
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2362
- NodeDetailPanel,
2363
- {
2364
- ...focusedNode ? { node: focusedNode } : {},
2365
- onClose: () => onFocusChange(0)
2366
- }
2367
- )
2466
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeDetailPanel, { ...focusedNode ? { node: focusedNode } : {} })
2368
2467
  }
2369
2468
  )
2370
2469
  ]
@@ -2397,7 +2496,8 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2397
2496
  log,
2398
2497
  humanizer,
2399
2498
  liveStreamLine,
2400
- ...focusedRuntimeStageId ? { focusRuntimeStageId: focusedRuntimeStageId } : {}
2499
+ isLastStep: stepGraph != null && focusStep === stepGraph.nodes.length - 1,
2500
+ focusedSeq
2401
2501
  }
2402
2502
  )
2403
2503
  }
@@ -2406,16 +2506,16 @@ var EngineerView = ({ stepGraph, summary, log, humanizer, total, focusStep, onFo
2406
2506
  }
2407
2507
  );
2408
2508
  };
2409
- var Commentary = ({ log, humanizer, focusRuntimeStageId, liveStreamLine }) => {
2509
+ var Commentary = ({ log, humanizer, focusedSeq, liveStreamLine, isLastStep }) => {
2410
2510
  const containerRef = (0, import_react11.useRef)(null);
2411
2511
  const firstFocusRef = (0, import_react11.useRef)(null);
2412
2512
  (0, import_react11.useEffect)(() => {
2413
- if (!focusRuntimeStageId || !firstFocusRef.current) return;
2513
+ if (focusedSeq === void 0 || focusedSeq < 0 || !firstFocusRef.current) return;
2414
2514
  firstFocusRef.current.scrollIntoView({
2415
2515
  block: "center",
2416
2516
  behavior: "smooth"
2417
2517
  });
2418
- }, [focusRuntimeStageId]);
2518
+ }, [focusedSeq]);
2419
2519
  let firstFocusAssigned = false;
2420
2520
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2421
2521
  "div",
@@ -2434,24 +2534,14 @@ var Commentary = ({ log, humanizer, focusRuntimeStageId, liveStreamLine }) => {
2434
2534
  if (log.length === 0) {
2435
2535
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { color: T.textSecondary, fontStyle: "italic" }, children: "No moments yet \u2014 run a sample to see commentary." });
2436
2536
  }
2437
- const idOf = (e) => (
2438
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2439
- e.event.meta?.runtimeStageId
2440
- );
2441
- let lastFocusIdx = -1;
2442
- if (focusRuntimeStageId !== void 0) {
2443
- for (let i = 0; i < log.length; i++) {
2444
- if (idOf(log[i]) === focusRuntimeStageId) lastFocusIdx = i;
2445
- }
2446
- }
2447
- const cutoff = lastFocusIdx === -1 ? log.length - 1 : lastFocusIdx;
2537
+ const cutoff = isLastStep || focusedSeq === void 0 || focusedSeq < 0 ? log.length - 1 : Math.max(0, log.findIndex((e) => e.seq === focusedSeq));
2448
2538
  const visible = log.slice(0, cutoff + 1);
2449
2539
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2450
2540
  visible.map((entry, i) => {
2451
2541
  const line = humanizer(entry.event);
2452
2542
  if (line === null) return null;
2453
- const focused = focusRuntimeStageId !== void 0 && idOf(entry) === focusRuntimeStageId;
2454
- const isLastFocused = focused && i === lastFocusIdx;
2543
+ const focused = focusedSeq !== void 0 && entry.seq === focusedSeq;
2544
+ const isLastFocused = focused && i === cutoff;
2455
2545
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2456
2546
  "div",
2457
2547
  {