agentfootprint-lens 0.40.0 → 0.42.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/README.md +64 -2
- package/dist/{useCursorPositions-CF9RDUw0.d.cts → Lens-B039-UGw.d.cts} +98 -48
- package/dist/{useCursorPositions-B1HJiarC.d.ts → Lens-ey1EgN9i.d.ts} +98 -48
- package/dist/{chunk-S7FLEXJ3.js → chunk-F473UD5M.js} +61 -12
- package/dist/chunk-F473UD5M.js.map +1 -0
- package/dist/{chunk-EBQCTQQU.js → chunk-FFQ5EARP.js} +2 -2
- package/dist/{chunk-CHFRN2WV.js → chunk-JE37MDVV.js} +95 -77
- package/dist/chunk-JE37MDVV.js.map +1 -0
- package/dist/{chunk-EYCTYBSV.js → chunk-VBRT372Q.js} +143 -18
- package/dist/chunk-VBRT372Q.js.map +1 -0
- package/dist/{chunk-P4F3RGXR.js → chunk-VKBQYD6I.js} +2 -2
- package/dist/{chunk-L2Z6HTJI.js → chunk-YQXVXNXK.js} +219 -203
- package/dist/chunk-YQXVXNXK.js.map +1 -0
- package/dist/core.cjs +239 -92
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +5 -5
- package/dist/core.d.ts +5 -5
- package/dist/core.js +7 -3
- package/dist/index.cjs +523 -310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -20
- package/dist/index.d.ts +30 -20
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/{stepForRuntimeStageId-Bl6cG9wB.d.cts → resolveNavigation-o4E4cM_1.d.cts} +128 -1
- package/dist/{stepForRuntimeStageId-Bl6cG9wB.d.ts → resolveNavigation-o4E4cM_1.d.ts} +128 -1
- package/dist/{stepForCommitIdx-feXP5i-2.d.cts → scrubAxisFor-CNceBlz-.d.cts} +48 -2
- package/dist/{stepForCommitIdx-PnR_Kael.d.ts → scrubAxisFor-Cz214OHj.d.ts} +48 -2
- package/dist/{selectSkillFrameContext-DdObTJz3.d.cts → selectSkillFrameContext-CT_EKu4u.d.cts} +98 -22
- package/dist/{selectSkillFrameContext-Hjjf75t9.d.ts → selectSkillFrameContext-D0xQV_px.d.ts} +98 -22
- package/dist/skillgraph.cjs +201 -26
- package/dist/skillgraph.cjs.map +1 -1
- package/dist/skillgraph.d.cts +15 -11
- package/dist/skillgraph.d.ts +15 -11
- package/dist/skillgraph.js +5 -3
- package/dist/why.cjs +220 -128
- package/dist/why.cjs.map +1 -1
- package/dist/why.d.cts +5 -5
- package/dist/why.d.ts +5 -5
- package/dist/why.js +8 -6
- package/dist/why.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-CHFRN2WV.js.map +0 -1
- package/dist/chunk-EYCTYBSV.js.map +0 -1
- package/dist/chunk-L2Z6HTJI.js.map +0 -1
- package/dist/chunk-S7FLEXJ3.js.map +0 -1
- /package/dist/{chunk-EBQCTQQU.js.map → chunk-FFQ5EARP.js.map} +0 -0
- /package/dist/{chunk-P4F3RGXR.js.map → chunk-VKBQYD6I.js.map} +0 -0
package/README.md
CHANGED
|
@@ -76,8 +76,8 @@ changes.
|
|
|
76
76
|
|
|
77
77
|
| import | the job |
|
|
78
78
|
|---|---|
|
|
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`) for hosts holding one cursor across views. |
|
|
80
|
-
| `agentfootprint-lens/skillgraph` | Debug how a run routed through its skills. `<SkillGraphDebugger recorder={...} />` plus its headless selectors (`selectSkillRoute`, `selectSkillBeats`, `selectSkillTopology`, `selectSkillFrameContext`, `stepForRuntimeStageId`). |
|
|
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
|
+
| `agentfootprint-lens/skillgraph` | Debug how a run routed through its skills. `<SkillGraphDebugger recorder={...} />` plus its headless selectors (`selectSkillRoute`, `selectSkillBeats`, `selectSkillTopology`, `selectSkillFrameContext`, `stepForRuntimeStageId`, `resolveNavigation`). |
|
|
81
81
|
|
|
82
82
|
The axis model in three sentences: one run leaves one causal trace, and each
|
|
83
83
|
lens replays one AXIS of it — the Why reading scrubs the milestones, the Flow
|
|
@@ -654,6 +654,67 @@ click, a WHAT HAPPENED moment, a provenance jump, and the auto-advance that
|
|
|
654
654
|
follows a live run. One cursor, one funnel — a mover that moved without telling
|
|
655
655
|
you would be a second cursor wearing the first one's clothes.
|
|
656
656
|
|
|
657
|
+
### Pointing at a step: `navigatorRef`
|
|
658
|
+
|
|
659
|
+
`step` lets you *own* the cursor. `navigatorRef` lets you *say where* — when the
|
|
660
|
+
only thing you know is a stage's address. That is what a chat answer has: it can
|
|
661
|
+
tell you the tool call went wrong, and now it can point at the exact step it
|
|
662
|
+
means.
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
import { Lens, type LensNavigator } from 'agentfootprint-lens';
|
|
666
|
+
|
|
667
|
+
const nav = useRef<LensNavigator>(null);
|
|
668
|
+
|
|
669
|
+
<Lens recorder={recorder} navigatorRef={nav} />
|
|
670
|
+
|
|
671
|
+
// Anywhere — a chat message, a dashboard row, a deep link:
|
|
672
|
+
const to = nav.current?.navigateTo('call-llm#18');
|
|
673
|
+
// → { ok: true, step: 7, runtimeStageId: 'call-llm#18', match: 'exact', label: 'call-llm 1' }
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
It resolves against **the ruler you are actually scrubbing**, so the same
|
|
677
|
+
address is step 7 under `granularity="step"` and step 3 under
|
|
678
|
+
`granularity="group"` — and then it goes through the *same funnel* a click on
|
|
679
|
+
that stop uses. Uncontrolled, the lens moves and reports. Controlled, it reports
|
|
680
|
+
and your `step` lands it. Nothing new holds a position.
|
|
681
|
+
|
|
682
|
+
**A miss is answered, never guessed.** Three rungs, in order:
|
|
683
|
+
|
|
684
|
+
| | what happened | you get |
|
|
685
|
+
|---|---|---|
|
|
686
|
+
| `match: 'exact'` | a stop **is** that address | `{ ok: true, step, label }` |
|
|
687
|
+
| `match: 'enclosing'` | no stop is that address, but one **contains** it (a stage inside a subflow, on a ruler that stops at whole subflows) | `{ ok: true, step, runtimeStageId }` — the stop's own address, so you always know where it went |
|
|
688
|
+
| — | nothing holds it | `{ ok: false, reason, message, nearest? }` — **the cursor did not move** |
|
|
689
|
+
|
|
690
|
+
`nearest` is the stop just *before* the address, handed back as data. It is an
|
|
691
|
+
**offer, not a jump**: a cursor that silently lands somewhere the person did not
|
|
692
|
+
ask for is worse than one that stays put. Take it when you want to — one more
|
|
693
|
+
call, and it is an exact hit by construction:
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
const to = nav.current?.navigateTo(stageId);
|
|
697
|
+
if (to?.ok) {
|
|
698
|
+
// landed
|
|
699
|
+
} else if (to?.nearest) {
|
|
700
|
+
// "That step isn't on this ruler. Go to Iteration 1 instead?"
|
|
701
|
+
onConfirm(() => nav.current?.navigateTo(to.nearest.runtimeStageId));
|
|
702
|
+
} else {
|
|
703
|
+
show(to?.message); // says exactly why, in a sentence you can print
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**No component? Same answer.** The resolver is pure and ships headlessly, so a
|
|
708
|
+
server-rendered link or a CLI can compute the step with nothing mounted:
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
import { scrubAxisFor, resolveNavigation } from 'agentfootprint-lens/core';
|
|
712
|
+
|
|
713
|
+
const positions = scrubAxisFor(recorder, 'step'); // the ruler, as data
|
|
714
|
+
const to = resolveNavigation(positions, 'call-llm#18');
|
|
715
|
+
const href = to.ok ? `/runs/${runId}?step=${to.step}` : undefined;
|
|
716
|
+
```
|
|
717
|
+
|
|
657
718
|
---
|
|
658
719
|
|
|
659
720
|
## Rendering your own detail pane
|
|
@@ -812,6 +873,7 @@ graph in one call. Returns an unsubscribe. Call it once per run.
|
|
|
812
873
|
| `granularity` | `'step' \| 'group'?` | Which ruler is scrubbing the chart. `'group'` paints the cursor's group as a named place. Default `'step'`. See [Scrubbing by group](#scrubbing-by-group--the-active-group-is-a-named-place). |
|
|
813
874
|
| `step` | `number?` | Controlled cursor. **Omit it and the lens is self-driving, exactly as before.** Pass it and you own the position; out-of-range values are clamped and reported. See [Driving the cursor](#driving-the-cursor-from-your-app). |
|
|
814
875
|
| `onStepChange` | `(step, at) => void?` | Fires on every cursor move — required for movement in controlled mode, an observation hook otherwise. `at` carries `runtimeStageId`, `commitIdx`, `label`, `kind` and `clamped`. |
|
|
876
|
+
| `navigatorRef` | `Ref<LensNavigator>?` | Move the cursor to a stage **by its `runtimeStageId`**. `ref.current.navigateTo(id)` returns `{ ok: true, step, match, label }` or `{ ok: false, reason, message, nearest? }` — a miss never moves. See [Pointing at a step](#pointing-at-a-step-navigatorref). |
|
|
815
877
|
| `slots` | `LensSlots?` | Slot overrides. `slots.detail` renders your content in the shipped right column. Omit for the built-in timeline. See [Rendering your own detail pane](#rendering-your-own-detail-pane). |
|
|
816
878
|
|
|
817
879
|
### `<LensFlow>` — the chart canvas on its own
|
|
@@ -2,9 +2,9 @@ import * as agentfootprint_observe from 'agentfootprint/observe';
|
|
|
2
2
|
import { ToolChoiceCall, ToolChoiceSummary } from 'agentfootprint/observe';
|
|
3
3
|
import * as agentfootprint from 'agentfootprint';
|
|
4
4
|
import { CommentaryTemplates } from 'agentfootprint';
|
|
5
|
-
import React__default from 'react';
|
|
6
|
-
import {
|
|
7
|
-
import { C as ChartGroupHighlight, H as Humanizer } from './
|
|
5
|
+
import React__default, { Ref } from 'react';
|
|
6
|
+
import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-o4E4cM_1.cjs';
|
|
7
|
+
import { C as ChartGroupHighlight, H as Humanizer } from './scrubAxisFor-CNceBlz-.cjs';
|
|
8
8
|
import { NodeTypes } from '@xyflow/react';
|
|
9
9
|
import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
|
|
10
10
|
|
|
@@ -217,6 +217,73 @@ interface UseLensCursorResult {
|
|
|
217
217
|
declare function clampStep(n: number, maxStep: number): number;
|
|
218
218
|
declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, }: UseLensCursorArgs): UseLensCursorResult;
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* useLensNavigator — move the ONE cursor to a stage by its address.
|
|
222
|
+
*
|
|
223
|
+
* `<Lens step onStepChange>` lets a host OWN the cursor. This is the other
|
|
224
|
+
* half a pointing host needs: a way to SAY WHERE, when the only thing it knows
|
|
225
|
+
* is a `runtimeStageId` — a chat answer citing the step it means, a dashboard
|
|
226
|
+
* row, a deep link.
|
|
227
|
+
*
|
|
228
|
+
* THE ONE-CURSOR LAW, kept: this adds NO state and NO second channel. It is a
|
|
229
|
+
* RESOLUTION (`resolveNavigation`, the shipped ladder) followed by the SAME
|
|
230
|
+
* `moveTo` funnel every internal mover already goes through — the step strip,
|
|
231
|
+
* the ◀ ▶ buttons, the arrow keys, a chart click, the live auto-advance. So it
|
|
232
|
+
* behaves exactly like a person clicking that stop:
|
|
233
|
+
*
|
|
234
|
+
* - **Uncontrolled** — the cursor moves, and `onStepChange` reports it.
|
|
235
|
+
* - **Controlled** — nothing moves on its own; `onStepChange` fires with the
|
|
236
|
+
* step and the host's own `step` prop lands it. Two owners never fight,
|
|
237
|
+
* because there is still only one owner.
|
|
238
|
+
* - **Already there** — no move, no event (a non-move is not a change), and
|
|
239
|
+
* still `{ ok: true }` with the step the cursor is standing on.
|
|
240
|
+
*
|
|
241
|
+
* HONEST MISSES: an address the axis cannot hold comes back `{ ok: false }`
|
|
242
|
+
* with the nearest-previous stop OFFERED as data, never taken. See
|
|
243
|
+
* `resolveNavigation` for the ladder.
|
|
244
|
+
*
|
|
245
|
+
* @example The handle, on the component.
|
|
246
|
+
* ```tsx
|
|
247
|
+
* const nav = useRef<LensNavigator>(null);
|
|
248
|
+
*
|
|
249
|
+
* <Lens recorder={recorder} navigatorRef={nav} />
|
|
250
|
+
*
|
|
251
|
+
* // Later — a chat answer points at its evidence:
|
|
252
|
+
* const to = nav.current?.navigateTo('llm#7');
|
|
253
|
+
* if (to?.ok) show(`Jumped to ${to.label}`);
|
|
254
|
+
* else if (to?.nearest) offer(`Not on this ruler. Go to ${to.nearest.label}?`);
|
|
255
|
+
* // Taking the offer is one more call — `nearest.runtimeStageId` is an exact
|
|
256
|
+
* // hit by construction:
|
|
257
|
+
* // nav.current?.navigateTo(to.nearest.runtimeStageId);
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The imperative handle `<Lens navigatorRef>` fills in — the lens's cursor,
|
|
263
|
+
* addressable by stage.
|
|
264
|
+
*/
|
|
265
|
+
interface LensNavigator {
|
|
266
|
+
/**
|
|
267
|
+
* Move the ONE cursor to the stop that holds `runtimeStageId`, on whatever
|
|
268
|
+
* axis the lens is currently scrubbing (`granularity="step"` → the commit
|
|
269
|
+
* axis, `granularity="group"` → the milestone axis).
|
|
270
|
+
*
|
|
271
|
+
* Returns where it went — or, on a miss, why it did not and what was nearby.
|
|
272
|
+
* Never throws, and never moves on a miss.
|
|
273
|
+
*/
|
|
274
|
+
navigateTo(runtimeStageId: string): NavigationResult;
|
|
275
|
+
}
|
|
276
|
+
interface UseLensNavigatorArgs {
|
|
277
|
+
/** The ACTIVE scrub axis — the same list the lens renders its ruler from. */
|
|
278
|
+
readonly positions: readonly CursorPosition[];
|
|
279
|
+
/** The one cursor funnel (`useLensCursor`'s `moveTo`). */
|
|
280
|
+
readonly moveTo: (step: number) => void;
|
|
281
|
+
/** The host's ref, filled with the handle. Omit and the hook still returns
|
|
282
|
+
* the navigator for in-tree use. */
|
|
283
|
+
readonly navigatorRef?: Ref<LensNavigator> | undefined;
|
|
284
|
+
}
|
|
285
|
+
declare function useLensNavigator({ positions, moveTo, navigatorRef, }: UseLensNavigatorArgs): LensNavigator;
|
|
286
|
+
|
|
220
287
|
/**
|
|
221
288
|
* useToolChoice — async reader for the agentfootprint/observe
|
|
222
289
|
* `toolChoiceRecorder` handle (RFC-002 block C7).
|
|
@@ -498,6 +565,33 @@ interface LensProps {
|
|
|
498
565
|
* Memoize via `useCallback` to avoid downstream re-renders.
|
|
499
566
|
*/
|
|
500
567
|
readonly onStepChange?: (step: number, at: LensCursorAt) => void;
|
|
568
|
+
/**
|
|
569
|
+
* Move the cursor to a STAGE, by its address — the pointing half of the
|
|
570
|
+
* cursor API. `step` / `onStepChange` let a host OWN the cursor; this lets
|
|
571
|
+
* one SAY WHERE when all it knows is a `runtimeStageId` (a chat answer
|
|
572
|
+
* citing the step it means, a dashboard row, a deep link).
|
|
573
|
+
*
|
|
574
|
+
* ```tsx
|
|
575
|
+
* const nav = useRef<LensNavigator>(null);
|
|
576
|
+
* <Lens recorder={recorder} navigatorRef={nav} />
|
|
577
|
+
*
|
|
578
|
+
* const to = nav.current?.navigateTo('llm#7');
|
|
579
|
+
* // { ok: true, step: 7, runtimeStageId: 'llm#7', match: 'exact', label: … }
|
|
580
|
+
* ```
|
|
581
|
+
*
|
|
582
|
+
* It is a RESOLUTION plus the cursor channel you already have — not a second
|
|
583
|
+
* channel. The address is resolved against the ACTIVE axis (`granularity`),
|
|
584
|
+
* then handed to the same funnel a click on that stop uses: uncontrolled,
|
|
585
|
+
* the lens moves and reports; controlled, it reports and your `step` lands
|
|
586
|
+
* it. Nothing here holds a position.
|
|
587
|
+
*
|
|
588
|
+
* An address the axis cannot hold comes back `{ ok: false, reason, message }`
|
|
589
|
+
* with the nearest earlier stop OFFERED as `nearest` — never taken. The
|
|
590
|
+
* caller decides. See `resolveNavigation` for the ladder (exact → enclosing
|
|
591
|
+
* → nearest-previous-as-an-offer), which is also exported headlessly for
|
|
592
|
+
* hosts with nothing mounted.
|
|
593
|
+
*/
|
|
594
|
+
readonly navigatorRef?: React__default.Ref<LensNavigator>;
|
|
501
595
|
/**
|
|
502
596
|
* Optional slot overrides. Should be stable across renders (define at module
|
|
503
597
|
* scope or `useMemo`). Omit and the shipped panes render unchanged.
|
|
@@ -548,48 +642,4 @@ interface LensDetailSlotProps {
|
|
|
548
642
|
}
|
|
549
643
|
declare const Lens: React__default.FC<LensProps>;
|
|
550
644
|
|
|
551
|
-
|
|
552
|
-
* useCursorPositions — React hook exposing the slider's valid positions
|
|
553
|
-
* at the current drill level, on the requested AXIS.
|
|
554
|
-
*
|
|
555
|
-
* Layer 2 / Tier B / Lens v0.1.
|
|
556
|
-
*
|
|
557
|
-
* TWO AXES, ONE CURSOR (the 0.39.0 correction):
|
|
558
|
-
*
|
|
559
|
-
* `'commit'` — one stop per executed stage, straight off the commit log.
|
|
560
|
-
* The Flow Lens's axis (`granularity="step"`): every stage
|
|
561
|
-
* is a stop, nothing skippable, and the ruler's count is the
|
|
562
|
-
* commit log's count by construction.
|
|
563
|
-
* `'milestone'` — one stop per agent-meaningful moment (iteration → context
|
|
564
|
-
* → LLM turn → route → tool call), classified by the
|
|
565
|
-
* domain's `milestoneFor`. The Why Lens's axis
|
|
566
|
-
* (`granularity="group"`), which `stepBands` bands by
|
|
567
|
-
* iteration.
|
|
568
|
-
*
|
|
569
|
-
* The cursor type is still `runtimeStageId` either way. Only the SET of valid
|
|
570
|
-
* positions differs — the same compound-time-axis rule that already scaled the
|
|
571
|
-
* set by drill depth now also scales it by reading.
|
|
572
|
-
*
|
|
573
|
-
* Lifecycle
|
|
574
|
-
* ─────────
|
|
575
|
-
* Reactive to BOTH the recorder (events advance the commit log /
|
|
576
|
-
* open new groups) and to `drillPath` changes (user drill-in /
|
|
577
|
-
* drill-out). Memoized so a stable drillPath + recorder = stable
|
|
578
|
-
* array identity.
|
|
579
|
-
*/
|
|
580
|
-
|
|
581
|
-
/** Which projection of the run the scrub axis stops at. */
|
|
582
|
-
type ScrubAxis = 'commit' | 'milestone';
|
|
583
|
-
/**
|
|
584
|
-
* The scrub axis for a recording, as a PURE function — the same positions the
|
|
585
|
-
* mounted `<Lens>` scrubs at that granularity, computable outside React.
|
|
586
|
-
*
|
|
587
|
-
* This is the export a HOST holding the cursor across two granularities needs:
|
|
588
|
-
* to carry a position from one axis to the other, build the target axis here
|
|
589
|
-
* and resolve the commit with `stepForCommitIdx` (or an address with
|
|
590
|
-
* `stepForRuntimeStageId`). Returns `[]` when the recording has no commits yet
|
|
591
|
-
* (or on any read error — same posture as the hook).
|
|
592
|
-
*/
|
|
593
|
-
declare function scrubAxisFor(recorder: LensRecorder, granularity: 'step' | 'group', drillPath?: readonly string[]): readonly CursorPosition[];
|
|
594
|
-
|
|
595
|
-
export { type LensProps as L, type ScrubAxis as S, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type UseLensCursorResult as j, type UseToolChoiceResult as k, clampStep as l, useToolChoice as m, scrubAxisFor as s, useLensCursor as u };
|
|
645
|
+
export { type LensProps as L, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type UseLensCursorResult as k, type UseLensNavigatorArgs as l, type UseToolChoiceResult as m, clampStep as n, useLensNavigator as o, useToolChoice as p, useLensCursor as u };
|
|
@@ -2,9 +2,9 @@ import * as agentfootprint_observe from 'agentfootprint/observe';
|
|
|
2
2
|
import { ToolChoiceCall, ToolChoiceSummary } from 'agentfootprint/observe';
|
|
3
3
|
import * as agentfootprint from 'agentfootprint';
|
|
4
4
|
import { CommentaryTemplates } from 'agentfootprint';
|
|
5
|
-
import React__default from 'react';
|
|
6
|
-
import {
|
|
7
|
-
import { C as ChartGroupHighlight, H as Humanizer } from './
|
|
5
|
+
import React__default, { Ref } from 'react';
|
|
6
|
+
import { d as NavigationResult, C as CursorPosition, L as LensRecorder } from './resolveNavigation-o4E4cM_1.js';
|
|
7
|
+
import { C as ChartGroupHighlight, H as Humanizer } from './scrubAxisFor-Cz214OHj.js';
|
|
8
8
|
import { NodeTypes } from '@xyflow/react';
|
|
9
9
|
import { TraceGraph, TraceFlowLayout, RuntimeOverlay } from 'footprint-explainable-ui/flowchart';
|
|
10
10
|
|
|
@@ -217,6 +217,73 @@ interface UseLensCursorResult {
|
|
|
217
217
|
declare function clampStep(n: number, maxStep: number): number;
|
|
218
218
|
declare function useLensCursor({ controlledStep, onStepChange, maxStep, describe, }: UseLensCursorArgs): UseLensCursorResult;
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* useLensNavigator — move the ONE cursor to a stage by its address.
|
|
222
|
+
*
|
|
223
|
+
* `<Lens step onStepChange>` lets a host OWN the cursor. This is the other
|
|
224
|
+
* half a pointing host needs: a way to SAY WHERE, when the only thing it knows
|
|
225
|
+
* is a `runtimeStageId` — a chat answer citing the step it means, a dashboard
|
|
226
|
+
* row, a deep link.
|
|
227
|
+
*
|
|
228
|
+
* THE ONE-CURSOR LAW, kept: this adds NO state and NO second channel. It is a
|
|
229
|
+
* RESOLUTION (`resolveNavigation`, the shipped ladder) followed by the SAME
|
|
230
|
+
* `moveTo` funnel every internal mover already goes through — the step strip,
|
|
231
|
+
* the ◀ ▶ buttons, the arrow keys, a chart click, the live auto-advance. So it
|
|
232
|
+
* behaves exactly like a person clicking that stop:
|
|
233
|
+
*
|
|
234
|
+
* - **Uncontrolled** — the cursor moves, and `onStepChange` reports it.
|
|
235
|
+
* - **Controlled** — nothing moves on its own; `onStepChange` fires with the
|
|
236
|
+
* step and the host's own `step` prop lands it. Two owners never fight,
|
|
237
|
+
* because there is still only one owner.
|
|
238
|
+
* - **Already there** — no move, no event (a non-move is not a change), and
|
|
239
|
+
* still `{ ok: true }` with the step the cursor is standing on.
|
|
240
|
+
*
|
|
241
|
+
* HONEST MISSES: an address the axis cannot hold comes back `{ ok: false }`
|
|
242
|
+
* with the nearest-previous stop OFFERED as data, never taken. See
|
|
243
|
+
* `resolveNavigation` for the ladder.
|
|
244
|
+
*
|
|
245
|
+
* @example The handle, on the component.
|
|
246
|
+
* ```tsx
|
|
247
|
+
* const nav = useRef<LensNavigator>(null);
|
|
248
|
+
*
|
|
249
|
+
* <Lens recorder={recorder} navigatorRef={nav} />
|
|
250
|
+
*
|
|
251
|
+
* // Later — a chat answer points at its evidence:
|
|
252
|
+
* const to = nav.current?.navigateTo('llm#7');
|
|
253
|
+
* if (to?.ok) show(`Jumped to ${to.label}`);
|
|
254
|
+
* else if (to?.nearest) offer(`Not on this ruler. Go to ${to.nearest.label}?`);
|
|
255
|
+
* // Taking the offer is one more call — `nearest.runtimeStageId` is an exact
|
|
256
|
+
* // hit by construction:
|
|
257
|
+
* // nav.current?.navigateTo(to.nearest.runtimeStageId);
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The imperative handle `<Lens navigatorRef>` fills in — the lens's cursor,
|
|
263
|
+
* addressable by stage.
|
|
264
|
+
*/
|
|
265
|
+
interface LensNavigator {
|
|
266
|
+
/**
|
|
267
|
+
* Move the ONE cursor to the stop that holds `runtimeStageId`, on whatever
|
|
268
|
+
* axis the lens is currently scrubbing (`granularity="step"` → the commit
|
|
269
|
+
* axis, `granularity="group"` → the milestone axis).
|
|
270
|
+
*
|
|
271
|
+
* Returns where it went — or, on a miss, why it did not and what was nearby.
|
|
272
|
+
* Never throws, and never moves on a miss.
|
|
273
|
+
*/
|
|
274
|
+
navigateTo(runtimeStageId: string): NavigationResult;
|
|
275
|
+
}
|
|
276
|
+
interface UseLensNavigatorArgs {
|
|
277
|
+
/** The ACTIVE scrub axis — the same list the lens renders its ruler from. */
|
|
278
|
+
readonly positions: readonly CursorPosition[];
|
|
279
|
+
/** The one cursor funnel (`useLensCursor`'s `moveTo`). */
|
|
280
|
+
readonly moveTo: (step: number) => void;
|
|
281
|
+
/** The host's ref, filled with the handle. Omit and the hook still returns
|
|
282
|
+
* the navigator for in-tree use. */
|
|
283
|
+
readonly navigatorRef?: Ref<LensNavigator> | undefined;
|
|
284
|
+
}
|
|
285
|
+
declare function useLensNavigator({ positions, moveTo, navigatorRef, }: UseLensNavigatorArgs): LensNavigator;
|
|
286
|
+
|
|
220
287
|
/**
|
|
221
288
|
* useToolChoice — async reader for the agentfootprint/observe
|
|
222
289
|
* `toolChoiceRecorder` handle (RFC-002 block C7).
|
|
@@ -498,6 +565,33 @@ interface LensProps {
|
|
|
498
565
|
* Memoize via `useCallback` to avoid downstream re-renders.
|
|
499
566
|
*/
|
|
500
567
|
readonly onStepChange?: (step: number, at: LensCursorAt) => void;
|
|
568
|
+
/**
|
|
569
|
+
* Move the cursor to a STAGE, by its address — the pointing half of the
|
|
570
|
+
* cursor API. `step` / `onStepChange` let a host OWN the cursor; this lets
|
|
571
|
+
* one SAY WHERE when all it knows is a `runtimeStageId` (a chat answer
|
|
572
|
+
* citing the step it means, a dashboard row, a deep link).
|
|
573
|
+
*
|
|
574
|
+
* ```tsx
|
|
575
|
+
* const nav = useRef<LensNavigator>(null);
|
|
576
|
+
* <Lens recorder={recorder} navigatorRef={nav} />
|
|
577
|
+
*
|
|
578
|
+
* const to = nav.current?.navigateTo('llm#7');
|
|
579
|
+
* // { ok: true, step: 7, runtimeStageId: 'llm#7', match: 'exact', label: … }
|
|
580
|
+
* ```
|
|
581
|
+
*
|
|
582
|
+
* It is a RESOLUTION plus the cursor channel you already have — not a second
|
|
583
|
+
* channel. The address is resolved against the ACTIVE axis (`granularity`),
|
|
584
|
+
* then handed to the same funnel a click on that stop uses: uncontrolled,
|
|
585
|
+
* the lens moves and reports; controlled, it reports and your `step` lands
|
|
586
|
+
* it. Nothing here holds a position.
|
|
587
|
+
*
|
|
588
|
+
* An address the axis cannot hold comes back `{ ok: false, reason, message }`
|
|
589
|
+
* with the nearest earlier stop OFFERED as `nearest` — never taken. The
|
|
590
|
+
* caller decides. See `resolveNavigation` for the ladder (exact → enclosing
|
|
591
|
+
* → nearest-previous-as-an-offer), which is also exported headlessly for
|
|
592
|
+
* hosts with nothing mounted.
|
|
593
|
+
*/
|
|
594
|
+
readonly navigatorRef?: React__default.Ref<LensNavigator>;
|
|
501
595
|
/**
|
|
502
596
|
* Optional slot overrides. Should be stable across renders (define at module
|
|
503
597
|
* scope or `useMemo`). Omit and the shipped panes render unchanged.
|
|
@@ -548,48 +642,4 @@ interface LensDetailSlotProps {
|
|
|
548
642
|
}
|
|
549
643
|
declare const Lens: React__default.FC<LensProps>;
|
|
550
644
|
|
|
551
|
-
|
|
552
|
-
* useCursorPositions — React hook exposing the slider's valid positions
|
|
553
|
-
* at the current drill level, on the requested AXIS.
|
|
554
|
-
*
|
|
555
|
-
* Layer 2 / Tier B / Lens v0.1.
|
|
556
|
-
*
|
|
557
|
-
* TWO AXES, ONE CURSOR (the 0.39.0 correction):
|
|
558
|
-
*
|
|
559
|
-
* `'commit'` — one stop per executed stage, straight off the commit log.
|
|
560
|
-
* The Flow Lens's axis (`granularity="step"`): every stage
|
|
561
|
-
* is a stop, nothing skippable, and the ruler's count is the
|
|
562
|
-
* commit log's count by construction.
|
|
563
|
-
* `'milestone'` — one stop per agent-meaningful moment (iteration → context
|
|
564
|
-
* → LLM turn → route → tool call), classified by the
|
|
565
|
-
* domain's `milestoneFor`. The Why Lens's axis
|
|
566
|
-
* (`granularity="group"`), which `stepBands` bands by
|
|
567
|
-
* iteration.
|
|
568
|
-
*
|
|
569
|
-
* The cursor type is still `runtimeStageId` either way. Only the SET of valid
|
|
570
|
-
* positions differs — the same compound-time-axis rule that already scaled the
|
|
571
|
-
* set by drill depth now also scales it by reading.
|
|
572
|
-
*
|
|
573
|
-
* Lifecycle
|
|
574
|
-
* ─────────
|
|
575
|
-
* Reactive to BOTH the recorder (events advance the commit log /
|
|
576
|
-
* open new groups) and to `drillPath` changes (user drill-in /
|
|
577
|
-
* drill-out). Memoized so a stable drillPath + recorder = stable
|
|
578
|
-
* array identity.
|
|
579
|
-
*/
|
|
580
|
-
|
|
581
|
-
/** Which projection of the run the scrub axis stops at. */
|
|
582
|
-
type ScrubAxis = 'commit' | 'milestone';
|
|
583
|
-
/**
|
|
584
|
-
* The scrub axis for a recording, as a PURE function — the same positions the
|
|
585
|
-
* mounted `<Lens>` scrubs at that granularity, computable outside React.
|
|
586
|
-
*
|
|
587
|
-
* This is the export a HOST holding the cursor across two granularities needs:
|
|
588
|
-
* to carry a position from one axis to the other, build the target axis here
|
|
589
|
-
* and resolve the commit with `stepForCommitIdx` (or an address with
|
|
590
|
-
* `stepForRuntimeStageId`). Returns `[]` when the recording has no commits yet
|
|
591
|
-
* (or on any read error — same posture as the hook).
|
|
592
|
-
*/
|
|
593
|
-
declare function scrubAxisFor(recorder: LensRecorder, granularity: 'step' | 'group', drillPath?: readonly string[]): readonly CursorPosition[];
|
|
594
|
-
|
|
595
|
-
export { type LensProps as L, type ScrubAxis as S, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type UseLensCursorResult as j, type UseToolChoiceResult as k, clampStep as l, useToolChoice as m, scrubAxisFor as s, useLensCursor as u };
|
|
645
|
+
export { type LensProps as L, type ToolChoiceSource as T, type UseLensCursorArgs as U, Lens as a, type LensCursorAt as b, type LensDetailSlotProps as c, type LensSlots as d, type LensTheme as e, type LensView as f, type LensCursorPlace as g, LensFlow as h, type LensFlowProps as i, type LensNavigator as j, type UseLensCursorResult as k, type UseLensNavigatorArgs as l, type UseToolChoiceResult as m, clampStep as n, useLensNavigator as o, useToolChoice as p, useLensCursor as u };
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
describeReceived,
|
|
7
7
|
refusalDestinationFor,
|
|
8
8
|
useNarrowRow
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FFQ5EARP.js";
|
|
10
10
|
import {
|
|
11
11
|
selectSkillBeatAt,
|
|
12
12
|
selectSkillBeats,
|
|
13
13
|
selectSkillFrameContext,
|
|
14
14
|
selectSkillRoute,
|
|
15
15
|
selectSkillTopology
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VBRT372Q.js";
|
|
17
17
|
|
|
18
18
|
// src/react/skillGraphFlowLayout.ts
|
|
19
19
|
import dagre from "dagre";
|
|
@@ -237,6 +237,8 @@ function FrameFactsPanel({
|
|
|
237
237
|
return /* @__PURE__ */ jsx2("div", { style: panelStyle, "data-testid": "frame-facts", children: /* @__PURE__ */ jsx2("p", { style: { margin: 0, fontSize: 12, color: T.textMuted }, children: "Nothing has been sent to the model yet at this cursor position." }) });
|
|
238
238
|
}
|
|
239
239
|
const hop = beat.hop;
|
|
240
|
+
const promptOnRecord = hop.systemPromptText !== void 0;
|
|
241
|
+
const reachableAsData = beat.reachable?.source === "cursor-move";
|
|
240
242
|
return /* @__PURE__ */ jsxs2("div", { style: panelStyle, "data-testid": "frame-facts", children: [
|
|
241
243
|
/* @__PURE__ */ jsx2(
|
|
242
244
|
Header,
|
|
@@ -245,6 +247,14 @@ function FrameFactsPanel({
|
|
|
245
247
|
subtitle: `${beat.label} \xB7 the call this stop prepared`
|
|
246
248
|
}
|
|
247
249
|
),
|
|
250
|
+
hop.systemPromptText !== void 0 && /* @__PURE__ */ jsxs2(Section, { title: "system prompt, as sent", count: 1, children: [
|
|
251
|
+
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 10, color: T.textMuted, marginBottom: 4 }, children: [
|
|
252
|
+
"the assembled prompt, byte-for-byte as the provider received it (the producer opted in with ",
|
|
253
|
+
/* @__PURE__ */ jsx2("code", { children: "recordSystemPrompt: true" }),
|
|
254
|
+
")"
|
|
255
|
+
] }),
|
|
256
|
+
/* @__PURE__ */ jsx2(Mono, { testId: "system-prompt-text", children: hop.systemPromptText })
|
|
257
|
+
] }),
|
|
248
258
|
/* @__PURE__ */ jsx2(Section, { title: "read_skill, as sent", count: hop.readSkillDescription !== void 0 ? 1 : 0, children: hop.readSkillDescription !== void 0 ? /* @__PURE__ */ jsx2(Mono, { testId: "read-skill-description", children: hop.readSkillDescription }) : /* @__PURE__ */ jsxs2(Absent, { children: [
|
|
249
259
|
"This iteration's recording carries no ",
|
|
250
260
|
/* @__PURE__ */ jsx2("code", { children: "read_skill" }),
|
|
@@ -321,7 +331,7 @@ function FrameFactsPanel({
|
|
|
321
331
|
u.shape !== void 0 ? ` (${u.shape})` : ""
|
|
322
332
|
] }, i))
|
|
323
333
|
] }),
|
|
324
|
-
/* @__PURE__ */ jsxs2(
|
|
334
|
+
(!promptOnRecord || !reachableAsData) && /* @__PURE__ */ jsxs2(
|
|
325
335
|
"div",
|
|
326
336
|
{
|
|
327
337
|
"data-testid": "frame-absence",
|
|
@@ -337,9 +347,20 @@ function FrameFactsPanel({
|
|
|
337
347
|
},
|
|
338
348
|
children: [
|
|
339
349
|
/* @__PURE__ */ jsx2("strong", { style: { color: T.textSecondary }, children: "Not in this recording." }),
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
350
|
+
!promptOnRecord && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
351
|
+
" ",
|
|
352
|
+
"The system prompt as one assembled string is not recorded \u2014 the injections above are the pieces it was composed from. Recording the assembled prompt is an explicit opt-in (",
|
|
353
|
+
/* @__PURE__ */ jsx2("code", { children: "recordSystemPrompt: true" }),
|
|
354
|
+
"); the default keeps it out of every recording, because it carries everything injected into it."
|
|
355
|
+
] }),
|
|
356
|
+
!reachableAsData && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
357
|
+
" ",
|
|
358
|
+
"The reachable set is not recorded as data in this era's recording: it appears as prose inside ",
|
|
359
|
+
/* @__PURE__ */ jsx2("code", { children: "read_skill" }),
|
|
360
|
+
"'s description, and as a typed list only when the gate refused a pick."
|
|
361
|
+
] }),
|
|
362
|
+
" ",
|
|
363
|
+
"Nothing absent is reconstructed here."
|
|
343
364
|
]
|
|
344
365
|
}
|
|
345
366
|
)
|
|
@@ -647,7 +668,7 @@ function RouteDecisionCard({ beat, lens }) {
|
|
|
647
668
|
"\u201D"
|
|
648
669
|
] })
|
|
649
670
|
] }),
|
|
650
|
-
beat.reachable !== void 0 && /* @__PURE__ */ jsx4(Evidence, { label: `reachable (${beat.reachable.source})`, children: beat.reachable.ids.join(", ") }),
|
|
671
|
+
beat.reachable !== void 0 && /* @__PURE__ */ jsx4(Evidence, { label: `reachable (${beat.reachable.source})`, children: beat.reachable.ids.length > 0 ? beat.reachable.ids.join(", ") : "none \u2014 a dead end: no skill was admissible from this cursor" }),
|
|
651
672
|
beat.hop.offered !== void 0 && /* @__PURE__ */ jsx4(Evidence, { label: "menu offered", children: beat.hop.offered.join(", ") }),
|
|
652
673
|
beat.hop.refusals.map((r, i) => /* @__PURE__ */ jsxs4(
|
|
653
674
|
"div",
|
|
@@ -800,7 +821,10 @@ var SkillStateNode = ({ data }) => {
|
|
|
800
821
|
{
|
|
801
822
|
"data-testid": `skill-node-${d.label}`,
|
|
802
823
|
"data-state": d.state,
|
|
803
|
-
title:
|
|
824
|
+
title: [
|
|
825
|
+
d.description,
|
|
826
|
+
d.jumpable ? `Jump the cursor to "${d.label}"` : `The cursor never stood in "${d.label}"`
|
|
827
|
+
].filter((line) => line !== void 0).join("\n\n"),
|
|
804
828
|
style: {
|
|
805
829
|
width: 192,
|
|
806
830
|
minHeight: 56,
|
|
@@ -859,6 +883,21 @@ var SkillStateNode = ({ data }) => {
|
|
|
859
883
|
},
|
|
860
884
|
children: [
|
|
861
885
|
/* @__PURE__ */ jsx5("span", { children: d.stateLabel }),
|
|
886
|
+
d.isEntry && /* @__PURE__ */ jsx5(
|
|
887
|
+
"span",
|
|
888
|
+
{
|
|
889
|
+
style: {
|
|
890
|
+
padding: "1px 5px",
|
|
891
|
+
borderRadius: 5,
|
|
892
|
+
background: `${T.bgPrimary}`,
|
|
893
|
+
border: `1px solid ${T.textMuted}`,
|
|
894
|
+
color: T.textMuted,
|
|
895
|
+
textTransform: "none",
|
|
896
|
+
letterSpacing: 0
|
|
897
|
+
},
|
|
898
|
+
children: "entry"
|
|
899
|
+
}
|
|
900
|
+
),
|
|
862
901
|
d.pickedByModel && /* @__PURE__ */ jsx5(
|
|
863
902
|
"span",
|
|
864
903
|
{
|
|
@@ -910,11 +949,13 @@ function SkillTopologyCanvas({
|
|
|
910
949
|
draggable: false,
|
|
911
950
|
selectable: true,
|
|
912
951
|
data: {
|
|
913
|
-
label: n.id,
|
|
952
|
+
label: n.label ?? n.id,
|
|
914
953
|
state: n.state,
|
|
915
954
|
stateLabel: lens === "product" ? STATE_STYLE[n.state].product : STATE_STYLE[n.state].developer,
|
|
916
955
|
pickedByModel: n.pickedByModel,
|
|
917
|
-
jumpable: jumpable.has(n.id)
|
|
956
|
+
jumpable: jumpable.has(n.id),
|
|
957
|
+
...n.description !== void 0 ? { description: n.description } : {},
|
|
958
|
+
isEntry: n.isEntry
|
|
918
959
|
}
|
|
919
960
|
};
|
|
920
961
|
}),
|
|
@@ -1019,7 +1060,15 @@ function Legend({
|
|
|
1019
1060
|
/* @__PURE__ */ jsx5("span", { children: "\u2014 solid: an edge the author declared" }),
|
|
1020
1061
|
/* @__PURE__ */ jsx5("span", { children: "\u2504 dashed: a hop the run took, never declared on the record" })
|
|
1021
1062
|
] }),
|
|
1022
|
-
topology.declaredSource
|
|
1063
|
+
topology.declaredSource === "recording-declared" && /* @__PURE__ */ jsx5(
|
|
1064
|
+
"div",
|
|
1065
|
+
{
|
|
1066
|
+
"data-testid": "declared-complete-note",
|
|
1067
|
+
style: { flex: "1 1 100%", color: T.textMuted, lineHeight: 1.35 },
|
|
1068
|
+
children: "The author's complete declared map \u2014 this recording carries it (skill.graph_declared)."
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
!topology.declaredComplete && /* @__PURE__ */ jsx5("div", { style: { flex: "1 1 100%", color: T.warning, lineHeight: 1.35 }, children: topology.declaredSource === "recording" ? "Declared edges shown are only the ones this recording named \u2014 a recording names an edge once it fires, so the author may have drawn more." : "This recording named no declared edges; every line here is a hop that was observed." })
|
|
1023
1072
|
]
|
|
1024
1073
|
}
|
|
1025
1074
|
);
|
|
@@ -1326,4 +1375,4 @@ export {
|
|
|
1326
1375
|
SKILL_GRAPH_READS,
|
|
1327
1376
|
SkillGraphDebugger
|
|
1328
1377
|
};
|
|
1329
|
-
//# sourceMappingURL=chunk-
|
|
1378
|
+
//# sourceMappingURL=chunk-F473UD5M.js.map
|