agentfootprint-lens 0.53.5 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +32 -1
  2. package/dist/{chunk-47W3TQT6.js → chunk-37NHCNSS.js} +352 -1
  3. package/dist/chunk-37NHCNSS.js.map +1 -0
  4. package/dist/{chunk-S4KD5E4C.js → chunk-4AKGKXD6.js} +2 -2
  5. package/dist/{chunk-5CKIGMDD.js → chunk-73SE3HB7.js} +44 -22
  6. package/dist/chunk-73SE3HB7.js.map +1 -0
  7. package/dist/{chunk-RARQT7O5.js → chunk-AE7C4TPA.js} +2 -354
  8. package/dist/chunk-AE7C4TPA.js.map +1 -0
  9. package/dist/{chunk-A3SG5MAG.js → chunk-JQKBC34F.js} +41 -2
  10. package/dist/chunk-JQKBC34F.js.map +1 -0
  11. package/dist/chunk-MTPFNZ4L.js +1419 -0
  12. package/dist/chunk-MTPFNZ4L.js.map +1 -0
  13. package/dist/{chunk-KGQIRUBC.js → chunk-UYL7DUOI.js} +322 -1650
  14. package/dist/chunk-UYL7DUOI.js.map +1 -0
  15. package/dist/context.cjs +1766 -81
  16. package/dist/context.cjs.map +1 -1
  17. package/dist/context.d.cts +14 -3
  18. package/dist/context.d.ts +14 -3
  19. package/dist/context.js +4 -4
  20. package/dist/core.cjs +35 -0
  21. package/dist/core.cjs.map +1 -1
  22. package/dist/core.d.cts +59 -3
  23. package/dist/core.d.ts +59 -3
  24. package/dist/core.js +16 -10
  25. package/dist/index.cjs +252 -138
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +19 -6
  28. package/dist/index.d.ts +19 -6
  29. package/dist/index.js +119 -57
  30. package/dist/index.js.map +1 -1
  31. package/dist/why.js +4 -4
  32. package/package.json +1 -1
  33. package/dist/chunk-35DQLVDZ.js +0 -88
  34. package/dist/chunk-35DQLVDZ.js.map +0 -1
  35. package/dist/chunk-47W3TQT6.js.map +0 -1
  36. package/dist/chunk-5CKIGMDD.js.map +0 -1
  37. package/dist/chunk-A3SG5MAG.js.map +0 -1
  38. package/dist/chunk-KGQIRUBC.js.map +0 -1
  39. package/dist/chunk-RARQT7O5.js.map +0 -1
  40. /package/dist/{chunk-S4KD5E4C.js.map → chunk-4AKGKXD6.js.map} +0 -0
package/README.md CHANGED
@@ -78,7 +78,7 @@ changes.
78
78
  |---|---|
79
79
  | `agentfootprint-lens/why` | Replay a recording as the agent's own milestones. `<WhyLens recording={...} />` takes the recording straight (`recordRun()`'s `{ snapshot, events, structure }`, or the `persistRecording` envelope), validates it at mount, and mounts the shipped `<Lens>` shell on the milestone axis — plus the axis helpers (`scrubAxisFor`, `stepForCommitIdx`, `stepForRuntimeStageId`, `resolveNavigation`) for hosts holding one cursor across views. |
80
80
  | `agentfootprint-lens/skillgraph` | Debug how a run routed through its skills. `<SkillGraphDebugger recorder={...} />` plus its headless selectors (`selectSkillRoute`, `selectSkillBeats`, `selectSkillTopology`, `selectSkillFrameContext`, `stepForRuntimeStageId`, `resolveNavigation`). |
81
- | `agentfootprint-lens/context` | The context object at a stop, key by key — who wrote each, what moved, what was served. `<ContextView runner={...} />` walks the milestone axis on its own; hand it `cursor` and it follows the ONE cursor (0.53.1). |
81
+ | `agentfootprint-lens/context` | What the model HAD at a stop — the served document of that epoch on top (the Served tab itself: pieces, messages, tools, verdicts, delta since the previous call) — and what it was BUILT from beneath: the record key by key, who wrote each, what moved; a long value opens in place (0.54.0). `<ContextView runner={...} />` walks the milestone axis on its own; hand it `cursor` and it follows the ONE cursor (0.53.1). |
82
82
 
83
83
  The axis model in three sentences: one run leaves one causal trace, and each
84
84
  lens replays one AXIS of it — the Why reading scrubs the milestones, the Flow
@@ -812,6 +812,37 @@ const href = to.ok ? `/runs/${runId}?step=${to.step}` : undefined;
812
812
 
813
813
  ---
814
814
 
815
+ ### One cursor across several lenses: `useSharedCursor`
816
+
817
+ A debug tool that mounts Why, Flow, Context and the Skill Graph over one
818
+ recording keeps ONE cursor. The lenses draw different axes — Flow every
819
+ commit, Why and Context the agent's milestones — so what the host holds must
820
+ be an ADDRESS on the record, not a step on an axis. `useSharedCursor` holds
821
+ that address and hands each lens a `LensCursor` for the axis it draws:
822
+
823
+ ```tsx
824
+ import { useSharedCursor, Lens, ContextView } from 'agentfootprint-lens';
825
+
826
+ const shared = useSharedCursor(recorder);
827
+
828
+ // Reads the address on the milestone axis; a click here moves the address.
829
+ <ContextView runner={recording} cursor={shared.forAxis('group')} />
830
+
831
+ // The older step + report contract, bridged into the same address — so a
832
+ // `<Lens>` or a Skill Graph transport keeps working while you migrate one
833
+ // lens at a time.
834
+ <Lens recorder={recorder} granularity="step"
835
+ step={shared.forAxis('step').at.step} onStepChange={shared.onStepChange} />
836
+ ```
837
+
838
+ The law: a tab DERIVES its step from the address (the exact stage when its
839
+ axis has it, else the stop that contains the commit); only a MOVER changes
840
+ the address; a visit to a coarser axis and back lands on the same commit.
841
+ With no move yet the address is the run's end, from which each axis derives
842
+ its own last stop; a new recorder drops the held address. Headless pieces on
843
+ `/core`: `stepForAddress`, `cursorForAddress`, `addressOf`. Design and the
844
+ facts the fixtures taught: docs/design/2026-09-shared-cursor.md.
845
+
815
846
  ## Time travel through one port
816
847
 
817
848
  **Nothing to do — this is how the cursor already moves.** Read it if you are
@@ -486,6 +486,351 @@ function verify(view, receipt, runId, cause) {
486
486
  });
487
487
  }
488
488
 
489
+ // src/core/served/foldFactsAt.ts
490
+ import { stateAt } from "footprintjs/trace";
491
+ var FOLD_FACT_KEYS = Object.freeze([
492
+ "iteration",
493
+ "currentSkillId",
494
+ "stepPointer",
495
+ "mapEngagement",
496
+ "activeInjections",
497
+ "hiddenSkillIds"
498
+ ]);
499
+ function skippedIndicesOf(folded) {
500
+ const gaps = folded.skipped;
501
+ return gaps !== void 0 && gaps.length > 0 ? Object.freeze(gaps.map((g) => g.index)) : void 0;
502
+ }
503
+ function foldFactsAt(recording, cursor) {
504
+ const source = typeof recording === "object" && recording !== null ? recording : void 0;
505
+ let folded;
506
+ try {
507
+ folded = stateAt(source, cursor.commitIdx);
508
+ } catch (e) {
509
+ return Object.freeze({
510
+ redacted: false,
511
+ present: Object.freeze([]),
512
+ foldError: e instanceof Error ? e.message : String(e)
513
+ });
514
+ }
515
+ const state = folded.state;
516
+ const present = [];
517
+ const out = {};
518
+ for (const key of FOLD_FACT_KEYS) {
519
+ const value = state[key];
520
+ if (value === void 0) continue;
521
+ present.push(key);
522
+ out[key] = value;
523
+ }
524
+ const hidden = out.hiddenSkillIds;
525
+ const injections = out.activeInjections;
526
+ const skipped = skippedIndicesOf(folded);
527
+ return Object.freeze({
528
+ basis: folded.basis,
529
+ redacted: folded.redacted,
530
+ present: Object.freeze(present),
531
+ ...skipped !== void 0 ? { skipped } : {},
532
+ ...typeof out.iteration === "number" ? { iteration: out.iteration } : {},
533
+ ...typeof out.currentSkillId === "string" ? { currentSkillId: out.currentSkillId } : {},
534
+ ...out.stepPointer !== void 0 ? { stepPointer: out.stepPointer } : {},
535
+ ...out.mapEngagement !== void 0 ? { mapEngagement: out.mapEngagement } : {},
536
+ ...Array.isArray(injections) ? { activeInjections: Object.freeze([...injections]) } : {},
537
+ ...Array.isArray(hidden) && hidden.every((h) => typeof h === "string") ? { hiddenSkillIds: Object.freeze([...hidden]) } : {}
538
+ });
539
+ }
540
+
541
+ // src/core/served/servedGraphAt.ts
542
+ import {
543
+ SERVED_GAPS as SERVED_GAPS2
544
+ } from "agentfootprint";
545
+ var SLOT_ORDER = Object.freeze({
546
+ "system-prompt": 0,
547
+ messages: 1,
548
+ tools: 2
549
+ });
550
+ var SERVED_SLOTS = Object.freeze(
551
+ Object.keys(SLOT_ORDER).sort((a, b) => SLOT_ORDER[a] - SLOT_ORDER[b])
552
+ );
553
+ var HELD_HONESTY_KEYS = Object.freeze([
554
+ "basis",
555
+ "redacted",
556
+ "skipped",
557
+ "foldError"
558
+ ]);
559
+ var HELD_KEYS = Object.freeze([
560
+ ...FOLD_FACT_KEYS,
561
+ ...HELD_HONESTY_KEYS
562
+ ]);
563
+ var RECONSTRUCTED = Object.freeze({ status: "reconstructed" });
564
+ var NOT_ON_RECORD = Object.freeze({ status: "not-on-record" });
565
+ function slotsOfFields(fields) {
566
+ const out = [];
567
+ for (const field of fields) {
568
+ const slot = field.startsWith("system.") ? "system-prompt" : field.startsWith("messages.") ? "messages" : field.startsWith("tools.") ? "tools" : void 0;
569
+ if (slot !== void 0 && !out.includes(slot)) out.push(slot);
570
+ }
571
+ return out;
572
+ }
573
+ function heldNodes(fold) {
574
+ const node = (key, value, status) => Object.freeze(value === void 0 ? { key, status } : { key, value, status });
575
+ const out = [];
576
+ for (const key of FOLD_FACT_KEYS) {
577
+ const value = fold[key];
578
+ out.push(value === void 0 ? node(key, void 0, "not-on-record") : node(key, value, "reconstructed"));
579
+ }
580
+ out.push(
581
+ fold.basis === void 0 ? node("basis", void 0, "not-on-record") : node("basis", fold.basis, "reconstructed")
582
+ );
583
+ out.push(node("redacted", fold.redacted, "reconstructed"));
584
+ if (fold.skipped !== void 0) out.push(node("skipped", fold.skipped, "damaged"));
585
+ if (fold.foldError !== void 0) out.push(node("foldError", fold.foldError, "damaged"));
586
+ return Object.freeze(out);
587
+ }
588
+ function servedEdges(input) {
589
+ const { row, checks, since } = input;
590
+ const view = row.view;
591
+ const edge = (e) => Object.freeze(e);
592
+ const out = [];
593
+ const compared = since !== void 0;
594
+ const enteredPieces = new Set(since?.system.enteredIndexes ?? []);
595
+ const enteredMessages = new Set(since?.messages.enteredIndexes ?? []);
596
+ const stateOf = (entered) => compared ? entered ? "entered" : "unchanged" : void 0;
597
+ const withState = (state) => state !== void 0 ? { state } : {};
598
+ view.system.pieces.forEach((piece, i) => {
599
+ out.push(
600
+ edge({
601
+ id: `piece:${i}`,
602
+ kind: "piece",
603
+ slot: piece.slot,
604
+ source: piece.source,
605
+ origin: piece.source,
606
+ text: piece.text,
607
+ check: checks.pieces[i] ?? RECONSTRUCTED,
608
+ ...withState(stateOf(enteredPieces.has(i)))
609
+ })
610
+ );
611
+ });
612
+ (since?.system.leftPieces ?? []).forEach((piece, i) => {
613
+ out.push(
614
+ edge({
615
+ id: `piece-left:${i}`,
616
+ kind: "piece",
617
+ slot: piece.slot,
618
+ source: piece.source,
619
+ origin: piece.source,
620
+ text: piece.text,
621
+ // Not on this request at all: there is no row here for a hash to check.
622
+ check: NOT_ON_RECORD,
623
+ state: "left"
624
+ })
625
+ );
626
+ });
627
+ view.messages.asSent.forEach((message, i) => {
628
+ const label = message.toolName ?? message.toolCallId;
629
+ out.push(
630
+ edge({
631
+ id: `message:${i}`,
632
+ kind: "message",
633
+ slot: "messages",
634
+ role: message.role,
635
+ origin: message.role,
636
+ ...label !== void 0 ? { label } : {},
637
+ text: message.content,
638
+ check: checks.messages[i] ?? RECONSTRUCTED,
639
+ ...withState(stateOf(enteredMessages.has(i)))
640
+ })
641
+ );
642
+ });
643
+ (since?.messages.leftEntries ?? []).forEach((message, i) => {
644
+ out.push(
645
+ edge({
646
+ id: `message-left:${i}`,
647
+ kind: "message",
648
+ slot: "messages",
649
+ role: message.role,
650
+ origin: message.role,
651
+ text: message.content,
652
+ check: NOT_ON_RECORD,
653
+ state: "left"
654
+ })
655
+ );
656
+ });
657
+ view.messages.requestOnly.forEach((line, i) => {
658
+ out.push(
659
+ edge({
660
+ id: `request-only:${i}`,
661
+ kind: "request-only",
662
+ slot: "messages",
663
+ role: line.role,
664
+ origin: line.role,
665
+ label: line.reason,
666
+ text: line.text,
667
+ check: checks.requestOnly[i] ?? RECONSTRUCTED
668
+ })
669
+ );
670
+ });
671
+ view.tools.names.forEach((name) => {
672
+ const schema = view.tools.schemas.find((s) => s.name === name);
673
+ const state = compared ? since !== void 0 && since.tools.added.includes(name) ? "entered" : "unchanged" : void 0;
674
+ out.push(
675
+ edge({
676
+ id: `tool:${name}`,
677
+ kind: "tool",
678
+ slot: "tools",
679
+ label: name,
680
+ // No rebuilt schema means no hash on this side; a NAME is never
681
+ // verified — the receipt carries names as plain strings.
682
+ check: schema !== void 0 ? checks.toolSchemas[name] ?? RECONSTRUCTED : RECONSTRUCTED,
683
+ ...view.tools.forced === name ? { forced: true } : {},
684
+ ...checks.onReceiptOnly.schemas.includes(name) ? { schemaOnReceiptOnly: true } : {},
685
+ ...since !== void 0 && since.tools.schemaChanged.includes(name) ? { schemaChanged: true } : {},
686
+ ...withState(state)
687
+ })
688
+ );
689
+ });
690
+ (since?.tools.removed ?? []).forEach((name) => {
691
+ out.push(
692
+ edge({
693
+ id: `tool-left:${name}`,
694
+ kind: "tool",
695
+ slot: "tools",
696
+ label: name,
697
+ check: NOT_ON_RECORD,
698
+ state: "left"
699
+ })
700
+ );
701
+ });
702
+ return Object.freeze(out);
703
+ }
704
+ function slotNodes(input, edges) {
705
+ const { row, checks } = input;
706
+ const view = row.view;
707
+ const receipt = row.receipt;
708
+ const gapsFor = (slot) => Object.freeze(view.gaps.filter((g) => slotsOfFields(g.fields).includes(slot)));
709
+ const rebuiltIn = (slot) => edges.filter((e) => e.slot === slot && e.state !== "left").length;
710
+ const onReceipt = {
711
+ "system-prompt": receipt?.system.pieces.length,
712
+ messages: receipt !== void 0 ? receipt.messages.entries.length + receipt.messages.requestOnly.length : void 0,
713
+ tools: checks.namesOnReceipt?.length
714
+ };
715
+ const check2 = {
716
+ "system-prompt": checks.system,
717
+ messages: void 0,
718
+ tools: checks.toolNames
719
+ };
720
+ const onReceiptOnly = {
721
+ "system-prompt": checks.onReceiptOnly.pieces,
722
+ messages: checks.onReceiptOnly.messages + checks.onReceiptOnly.requestOnly,
723
+ tools: checks.onReceiptOnly.toolNames.length
724
+ };
725
+ const rebuiltOnly = {
726
+ "system-prompt": checks.rebuiltOnly.pieces,
727
+ messages: checks.rebuiltOnly.messages + checks.rebuiltOnly.requestOnly,
728
+ tools: checks.rebuiltOnly.toolNames.length
729
+ };
730
+ return Object.freeze(
731
+ SERVED_SLOTS.map((slot) => {
732
+ const aggregate = check2[slot];
733
+ const count = onReceipt[slot];
734
+ return Object.freeze({
735
+ slot,
736
+ ...aggregate !== void 0 ? { check: aggregate } : {},
737
+ rebuilt: rebuiltIn(slot),
738
+ ...count !== void 0 ? { onReceipt: count } : {},
739
+ onReceiptOnly: onReceiptOnly[slot],
740
+ rebuiltOnly: rebuiltOnly[slot],
741
+ gaps: gapsFor(slot)
742
+ });
743
+ })
744
+ );
745
+ }
746
+ function withheldNodes(input) {
747
+ const { row, fold } = input;
748
+ const view = row.view;
749
+ const out = [];
750
+ if (view.tools.withheld !== void 0) {
751
+ out.push(
752
+ Object.freeze({
753
+ kind: "tool-withheld",
754
+ name: view.tools.withheld,
755
+ from: "view",
756
+ slot: "tools"
757
+ })
758
+ );
759
+ }
760
+ for (const id of fold.hiddenSkillIds ?? []) {
761
+ out.push(Object.freeze({ kind: "hidden-skill", name: id, from: "fold" }));
762
+ }
763
+ for (const turn of row.evictedTurns ?? []) {
764
+ out.push(
765
+ Object.freeze({
766
+ kind: "attention-drop",
767
+ name: "omittedForAttention",
768
+ from: "receipt",
769
+ slot: "messages",
770
+ hash: turn.hash,
771
+ ...turn.lastServedOn !== void 0 ? { lastServedOn: turn.lastServedOn } : {}
772
+ })
773
+ );
774
+ }
775
+ if (input.checks.omittedForAttention === "not-on-record") {
776
+ out.push(
777
+ Object.freeze({
778
+ kind: "attention-drop",
779
+ name: "omittedForAttention",
780
+ from: "receipt",
781
+ status: "not-on-record"
782
+ })
783
+ );
784
+ }
785
+ if (fold.redacted) {
786
+ out.push(Object.freeze({ kind: "redacted", name: "redacted", from: "fold" }));
787
+ }
788
+ for (const gap of view.gaps) {
789
+ const slots = slotsOfFields(gap.fields);
790
+ out.push(
791
+ Object.freeze({
792
+ kind: "gap",
793
+ name: gap.gap,
794
+ from: "view",
795
+ // One slot named: draw it there. Several, or none: the band itself.
796
+ ...slots.length === 1 ? { slot: slots[0] } : {},
797
+ why: SERVED_GAPS2[gap.gap].why,
798
+ fields: gap.fields,
799
+ ...gap.cause !== void 0 ? { cause: gap.cause } : {}
800
+ })
801
+ );
802
+ }
803
+ return Object.freeze(out);
804
+ }
805
+ function callNode(input, edges) {
806
+ const { row, checks } = input;
807
+ const basis = row.view.basis;
808
+ return Object.freeze({
809
+ epoch: row.epoch,
810
+ callRuntimeStageId: row.callRuntimeStageId,
811
+ commitIdx: row.commitIdx,
812
+ betweenCalls: row.betweenCalls,
813
+ ...row.previousEpoch !== void 0 ? { previousEpoch: row.previousEpoch } : {},
814
+ ...basis !== void 0 ? { basis } : {},
815
+ basisStatus: basis !== void 0 ? "reconstructed" : "not-on-record",
816
+ // Present only where the receipt SAYS — a string or null. A receipt
817
+ // without the key (pre-9.93.0) leaves it absent: "cannot say".
818
+ ...row.receipt !== void 0 && carriesCacheStrategy(row.receipt) ? { cacheStrategy: row.receipt.cache.strategy } : {},
819
+ ...row.receiptCause !== void 0 ? { receiptCause: row.receiptCause } : {},
820
+ damaged: checks.damaged || edges.some((e) => e.check.status === "damaged")
821
+ });
822
+ }
823
+ function servedGraphAt(input) {
824
+ const edges = servedEdges(input);
825
+ return Object.freeze({
826
+ held: heldNodes(input.fold),
827
+ served: slotNodes(input, edges),
828
+ withheld: withheldNodes(input),
829
+ call: callNode(input, edges),
830
+ edges
831
+ });
832
+ }
833
+
489
834
  // src/core/utils/snapshotOfRunner.ts
490
835
  function snapshotOfRunner(runner) {
491
836
  const fn = runner?.getLastSnapshot;
@@ -590,6 +935,12 @@ export {
590
935
  sincePrevious,
591
936
  EXCUSING_GAPS,
592
937
  verify,
938
+ FOLD_FACT_KEYS,
939
+ foldFactsAt,
940
+ SERVED_SLOTS,
941
+ HELD_HONESTY_KEYS,
942
+ HELD_KEYS,
943
+ servedGraphAt,
593
944
  snapshotOfRunner,
594
945
  mountLogsOf,
595
946
  snapshotLogKey,
@@ -597,4 +948,4 @@ export {
597
948
  tagStopsFor,
598
949
  tagAxisPositions
599
950
  };
600
- //# sourceMappingURL=chunk-47W3TQT6.js.map
951
+ //# sourceMappingURL=chunk-37NHCNSS.js.map