agentfootprint-lens 0.48.0 → 0.49.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 CHANGED
@@ -1059,6 +1059,50 @@ on every epoch, no receipt was minted, and the tab says so with the library's
1059
1059
  `no-receipt-on-chart` gap and `cause: 'no-receipt-committed'` — every row
1060
1060
  **Reconstructed**, never a fabricated **Verified**.
1061
1061
 
1062
+ ### The Served graph (0.49.0)
1063
+
1064
+ A list is right for checking one row and wrong for seeing that the system string
1065
+ was assembled from a base plus one skill body plus two injections while three
1066
+ tools were withheld. That is one picture, and since 0.49.0 the Served tab has a
1067
+ **List ⇄ Graph** toggle (the list stays the default) that draws it: **HELD** —
1068
+ what the record holds at this stop, from the fold — then **SERVED** — what
1069
+ crossed into the call, one edge per system piece, message, request-only line and
1070
+ tool, each carrying its badge and, against the previous epoch, whether it
1071
+ *entered*, *left* or is *unchanged* — then **WITHHELD**: held and not sent. The
1072
+ withheld band is the reason to build it. The model never learns what it was
1073
+ denied; the operator should — the withheld tool list, the attention drops, the
1074
+ skills a role hid (read from the fold, since a receipt carries no authority
1075
+ names), a redacted fold, and every gap the view declares, each with the
1076
+ library's own sentence. Same cursor, same row, same badges: a Damaged row draws
1077
+ Damaged here too, and a field a gap covers draws the gap rather than an empty
1078
+ node.
1079
+
1080
+ | graph | what it draws | the question it answers |
1081
+ |---|---|---|
1082
+ | **skill graph** (`<SkillGraph>`) | the skills, their steps and their edges | where a run MAY go, and where it went |
1083
+ | **data graph** (*Where from*) | a value's writers, back through the commit log | where THIS value came from |
1084
+ | **Served graph** (Served tab · Graph) | one call: held · served · withheld | what this ONE CALL was made of, and what we held back |
1085
+
1086
+ ```ts
1087
+ import { foldFactsAt, servedGraphAt, servedRowAt, verify } from 'agentfootprint-lens/core';
1088
+
1089
+ const cursor = { runtimeStageId: 'call-llm#18', commitIdx: 15 };
1090
+ const row = servedRowAt(snapshot, cursor)!;
1091
+ const graph = servedGraphAt({
1092
+ row,
1093
+ fold: foldFactsAt(snapshot, cursor),
1094
+ checks: verify(row.view, row.receipt, row.receipt?.basis.runId ?? '', row.receiptCause),
1095
+ });
1096
+ graph.served.map((s) => `${s.slot} ${s.rebuilt}`); // ['system-prompt 3', …]
1097
+ graph.edges.filter((e) => e.state === 'entered').map((e) => e.origin); // ['instructions']
1098
+ graph.withheld.filter((w) => w.from === 'fold').map((w) => w.name); // ['payroll']
1099
+ ```
1100
+
1101
+ `<ServedGraph graph>` is exported for shells that build the input themselves.
1102
+ **Deliberately absent:** an edge from a served piece back to the STAGE that
1103
+ wrote it. It needs a commit-log walk keyed by piece text or slot — the part most
1104
+ likely to rot — and the question above is answered without it.
1105
+
1062
1106
  ---
1063
1107
 
1064
1108
  ## Bookmarks, and scrubbing by declared tag
@@ -3296,16 +3296,16 @@ function diffPromptsBounded(a, b, maxCells = DIFF_CELL_CAP) {
3296
3296
  }
3297
3297
 
3298
3298
  // src/core/served/sincePrevious.ts
3299
- function unmatched(a, b) {
3299
+ function unmatchedIndexes(a, b) {
3300
3300
  const pool = /* @__PURE__ */ new Map();
3301
3301
  for (const item of b) pool.set(item, (pool.get(item) ?? 0) + 1);
3302
- let count = 0;
3303
- for (const item of a) {
3302
+ const out = [];
3303
+ for (const [i, item] of a.entries()) {
3304
3304
  const n = pool.get(item) ?? 0;
3305
3305
  if (n > 0) pool.set(item, n - 1);
3306
- else count += 1;
3306
+ else out.push(i);
3307
3307
  }
3308
- return count;
3308
+ return out;
3309
3309
  }
3310
3310
  function sincePrevious(current, previous) {
3311
3311
  const now = current.view;
@@ -3328,6 +3328,10 @@ function sincePrevious(current, previous) {
3328
3328
  if (a !== void 0 && b !== void 0 && a !== b) schemaChanged.push(name);
3329
3329
  }
3330
3330
  }
3331
+ const piecesEntered = unmatchedIndexes(nowPieces, beforePieces);
3332
+ const piecesLeft = unmatchedIndexes(beforePieces, nowPieces);
3333
+ const messagesEntered = unmatchedIndexes(nowMessages, beforeMessages);
3334
+ const messagesLeft = unmatchedIndexes(beforeMessages, nowMessages);
3331
3335
  const changed = now.system.text !== before.system.text;
3332
3336
  const diff = changed ? diffPromptsBounded(before.system.text, now.system.text) : Object.freeze([]);
3333
3337
  return Object.freeze({
@@ -3336,12 +3340,16 @@ function sincePrevious(current, previous) {
3336
3340
  system: Object.freeze({
3337
3341
  changed,
3338
3342
  ...diff !== void 0 ? { diff: Object.freeze([...diff]) } : {},
3339
- piecesEntered: unmatched(nowPieces, beforePieces),
3340
- piecesLeft: unmatched(beforePieces, nowPieces)
3343
+ piecesEntered: piecesEntered.length,
3344
+ piecesLeft: piecesLeft.length,
3345
+ enteredIndexes: Object.freeze(piecesEntered),
3346
+ leftPieces: Object.freeze(piecesLeft.map((i) => before.system.pieces[i]))
3341
3347
  }),
3342
3348
  messages: Object.freeze({
3343
- entered: unmatched(nowMessages, beforeMessages),
3344
- left: unmatched(beforeMessages, nowMessages)
3349
+ entered: messagesEntered.length,
3350
+ left: messagesLeft.length,
3351
+ enteredIndexes: Object.freeze(messagesEntered),
3352
+ leftEntries: Object.freeze(messagesLeft.map((i) => before.messages.asSent[i]))
3345
3353
  }),
3346
3354
  tools: Object.freeze({
3347
3355
  added: Object.freeze(added),
@@ -3404,6 +3412,295 @@ function foldFactsAt(recording, cursor) {
3404
3412
  });
3405
3413
  }
3406
3414
 
3415
+ // src/core/served/servedGraphAt.ts
3416
+ import {
3417
+ SERVED_GAPS as SERVED_GAPS2,
3418
+ UNGAPPED_FIELDS
3419
+ } from "agentfootprint";
3420
+ var SLOT_ORDER = Object.freeze({
3421
+ "system-prompt": 0,
3422
+ messages: 1,
3423
+ tools: 2
3424
+ });
3425
+ var SERVED_SLOTS = Object.freeze(
3426
+ Object.keys(SLOT_ORDER).sort((a, b) => SLOT_ORDER[a] - SLOT_ORDER[b])
3427
+ );
3428
+ var HELD_HONESTY_KEYS = Object.freeze([
3429
+ "basis",
3430
+ "redacted",
3431
+ "skipped",
3432
+ "foldError"
3433
+ ]);
3434
+ var HELD_KEYS = Object.freeze([
3435
+ ...FOLD_FACT_KEYS,
3436
+ ...HELD_HONESTY_KEYS
3437
+ ]);
3438
+ var RECONSTRUCTED = Object.freeze({ status: "reconstructed" });
3439
+ var NOT_ON_RECORD = Object.freeze({ status: "not-on-record" });
3440
+ function slotsOfFields(fields) {
3441
+ const out = [];
3442
+ for (const field of fields) {
3443
+ const slot = field.startsWith("system.") ? "system-prompt" : field.startsWith("messages.") ? "messages" : field.startsWith("tools.") ? "tools" : void 0;
3444
+ if (slot !== void 0 && !out.includes(slot)) out.push(slot);
3445
+ }
3446
+ return out;
3447
+ }
3448
+ function heldNodes(fold) {
3449
+ const node = (key, value, status) => Object.freeze(value === void 0 ? { key, status } : { key, value, status });
3450
+ const out = [];
3451
+ for (const key of FOLD_FACT_KEYS) {
3452
+ const value = fold[key];
3453
+ out.push(value === void 0 ? node(key, void 0, "not-on-record") : node(key, value, "reconstructed"));
3454
+ }
3455
+ out.push(
3456
+ fold.basis === void 0 ? node("basis", void 0, "not-on-record") : node("basis", fold.basis, "reconstructed")
3457
+ );
3458
+ out.push(node("redacted", fold.redacted, "reconstructed"));
3459
+ if (fold.skipped !== void 0) out.push(node("skipped", fold.skipped, "damaged"));
3460
+ if (fold.foldError !== void 0) out.push(node("foldError", fold.foldError, "damaged"));
3461
+ return Object.freeze(out);
3462
+ }
3463
+ function servedEdges(input) {
3464
+ const { row, checks, since } = input;
3465
+ const view = row.view;
3466
+ const edge = (e) => Object.freeze(e);
3467
+ const out = [];
3468
+ const compared = since !== void 0;
3469
+ const enteredPieces = new Set(since?.system.enteredIndexes ?? []);
3470
+ const enteredMessages = new Set(since?.messages.enteredIndexes ?? []);
3471
+ const stateOf = (entered) => compared ? entered ? "entered" : "unchanged" : void 0;
3472
+ const withState = (state) => state !== void 0 ? { state } : {};
3473
+ view.system.pieces.forEach((piece, i) => {
3474
+ out.push(
3475
+ edge({
3476
+ id: `piece:${i}`,
3477
+ kind: "piece",
3478
+ slot: piece.slot,
3479
+ source: piece.source,
3480
+ origin: piece.source,
3481
+ text: piece.text,
3482
+ check: checks.pieces[i] ?? RECONSTRUCTED,
3483
+ ...withState(stateOf(enteredPieces.has(i)))
3484
+ })
3485
+ );
3486
+ });
3487
+ (since?.system.leftPieces ?? []).forEach((piece, i) => {
3488
+ out.push(
3489
+ edge({
3490
+ id: `piece-left:${i}`,
3491
+ kind: "piece",
3492
+ slot: piece.slot,
3493
+ source: piece.source,
3494
+ origin: piece.source,
3495
+ text: piece.text,
3496
+ // Not on this request at all: there is no row here for a hash to check.
3497
+ check: NOT_ON_RECORD,
3498
+ state: "left"
3499
+ })
3500
+ );
3501
+ });
3502
+ view.messages.asSent.forEach((message, i) => {
3503
+ const label = message.toolName ?? message.toolCallId;
3504
+ out.push(
3505
+ edge({
3506
+ id: `message:${i}`,
3507
+ kind: "message",
3508
+ slot: "messages",
3509
+ role: message.role,
3510
+ origin: message.role,
3511
+ ...label !== void 0 ? { label } : {},
3512
+ text: message.content,
3513
+ check: checks.messages[i] ?? RECONSTRUCTED,
3514
+ ...withState(stateOf(enteredMessages.has(i)))
3515
+ })
3516
+ );
3517
+ });
3518
+ (since?.messages.leftEntries ?? []).forEach((message, i) => {
3519
+ out.push(
3520
+ edge({
3521
+ id: `message-left:${i}`,
3522
+ kind: "message",
3523
+ slot: "messages",
3524
+ role: message.role,
3525
+ origin: message.role,
3526
+ text: message.content,
3527
+ check: NOT_ON_RECORD,
3528
+ state: "left"
3529
+ })
3530
+ );
3531
+ });
3532
+ view.messages.requestOnly.forEach((line, i) => {
3533
+ out.push(
3534
+ edge({
3535
+ id: `request-only:${i}`,
3536
+ kind: "request-only",
3537
+ slot: "messages",
3538
+ role: line.role,
3539
+ origin: line.role,
3540
+ label: line.reason,
3541
+ text: line.text,
3542
+ check: checks.requestOnly[i] ?? RECONSTRUCTED
3543
+ })
3544
+ );
3545
+ });
3546
+ view.tools.names.forEach((name) => {
3547
+ const schema = view.tools.schemas.find((s) => s.name === name);
3548
+ const state = compared ? since !== void 0 && since.tools.added.includes(name) ? "entered" : "unchanged" : void 0;
3549
+ out.push(
3550
+ edge({
3551
+ id: `tool:${name}`,
3552
+ kind: "tool",
3553
+ slot: "tools",
3554
+ label: name,
3555
+ // No rebuilt schema means no hash on this side; a NAME is never
3556
+ // verified — the receipt carries names as plain strings.
3557
+ check: schema !== void 0 ? checks.toolSchemas[name] ?? RECONSTRUCTED : RECONSTRUCTED,
3558
+ ...view.tools.forced === name ? { forced: true } : {},
3559
+ ...checks.onReceiptOnly.schemas.includes(name) ? { schemaOnReceiptOnly: true } : {},
3560
+ ...since !== void 0 && since.tools.schemaChanged.includes(name) ? { schemaChanged: true } : {},
3561
+ ...withState(state)
3562
+ })
3563
+ );
3564
+ });
3565
+ (since?.tools.removed ?? []).forEach((name) => {
3566
+ out.push(
3567
+ edge({
3568
+ id: `tool-left:${name}`,
3569
+ kind: "tool",
3570
+ slot: "tools",
3571
+ label: name,
3572
+ check: NOT_ON_RECORD,
3573
+ state: "left"
3574
+ })
3575
+ );
3576
+ });
3577
+ return Object.freeze(out);
3578
+ }
3579
+ function slotNodes(input, edges) {
3580
+ const { row, checks } = input;
3581
+ const view = row.view;
3582
+ const receipt = row.receipt;
3583
+ const gapsFor = (slot) => Object.freeze(view.gaps.filter((g) => slotsOfFields(g.fields).includes(slot)));
3584
+ const rebuiltIn = (slot) => edges.filter((e) => e.slot === slot && e.state !== "left").length;
3585
+ const onReceipt = {
3586
+ "system-prompt": receipt?.system.pieces.length,
3587
+ messages: receipt !== void 0 ? receipt.messages.entries.length + receipt.messages.requestOnly.length : void 0,
3588
+ tools: checks.namesOnReceipt?.length
3589
+ };
3590
+ const check2 = {
3591
+ "system-prompt": checks.system,
3592
+ messages: void 0,
3593
+ tools: checks.toolNames
3594
+ };
3595
+ const onReceiptOnly = {
3596
+ "system-prompt": checks.onReceiptOnly.pieces,
3597
+ messages: checks.onReceiptOnly.messages + checks.onReceiptOnly.requestOnly,
3598
+ tools: checks.onReceiptOnly.toolNames.length
3599
+ };
3600
+ const rebuiltOnly = {
3601
+ "system-prompt": checks.rebuiltOnly.pieces,
3602
+ messages: checks.rebuiltOnly.messages + checks.rebuiltOnly.requestOnly,
3603
+ tools: checks.rebuiltOnly.toolNames.length
3604
+ };
3605
+ return Object.freeze(
3606
+ SERVED_SLOTS.map((slot) => {
3607
+ const aggregate = check2[slot];
3608
+ const count = onReceipt[slot];
3609
+ return Object.freeze({
3610
+ slot,
3611
+ ...aggregate !== void 0 ? { check: aggregate } : {},
3612
+ rebuilt: rebuiltIn(slot),
3613
+ ...count !== void 0 ? { onReceipt: count } : {},
3614
+ onReceiptOnly: onReceiptOnly[slot],
3615
+ rebuiltOnly: rebuiltOnly[slot],
3616
+ gaps: gapsFor(slot)
3617
+ });
3618
+ })
3619
+ );
3620
+ }
3621
+ function withheldNodes(input) {
3622
+ const { row, fold } = input;
3623
+ const view = row.view;
3624
+ const out = [];
3625
+ if (view.tools.withheld !== void 0) {
3626
+ out.push(
3627
+ Object.freeze({
3628
+ kind: "tool-withheld",
3629
+ name: view.tools.withheld,
3630
+ from: "view",
3631
+ slot: "tools"
3632
+ })
3633
+ );
3634
+ }
3635
+ for (const id of fold.hiddenSkillIds ?? []) {
3636
+ out.push(Object.freeze({ kind: "hidden-skill", name: id, from: "fold" }));
3637
+ }
3638
+ const drops = row.receipt?.omittedForAttention;
3639
+ out.push(
3640
+ Object.freeze(
3641
+ drops !== void 0 ? {
3642
+ kind: "attention-drop",
3643
+ name: "omittedForAttention",
3644
+ from: "receipt",
3645
+ count: drops.count,
3646
+ hashes: Object.freeze([...drops.hashes])
3647
+ } : {
3648
+ // Absent is not none: the library's own sentence for a field no gap
3649
+ // explains, printed beside the Not-on-record badge.
3650
+ kind: "attention-drop",
3651
+ name: "omittedForAttention",
3652
+ from: "receipt",
3653
+ status: "not-on-record",
3654
+ why: UNGAPPED_FIELDS.omittedForAttention
3655
+ }
3656
+ )
3657
+ );
3658
+ if (fold.redacted) {
3659
+ out.push(Object.freeze({ kind: "redacted", name: "redacted", from: "fold" }));
3660
+ }
3661
+ for (const gap of view.gaps) {
3662
+ const slots = slotsOfFields(gap.fields);
3663
+ out.push(
3664
+ Object.freeze({
3665
+ kind: "gap",
3666
+ name: gap.gap,
3667
+ from: "view",
3668
+ // One slot named: draw it there. Several, or none: the band itself.
3669
+ ...slots.length === 1 ? { slot: slots[0] } : {},
3670
+ why: SERVED_GAPS2[gap.gap].why,
3671
+ fields: gap.fields,
3672
+ ...gap.cause !== void 0 ? { cause: gap.cause } : {}
3673
+ })
3674
+ );
3675
+ }
3676
+ return Object.freeze(out);
3677
+ }
3678
+ function callNode(input, edges) {
3679
+ const { row, checks } = input;
3680
+ const basis = row.view.basis;
3681
+ return Object.freeze({
3682
+ epoch: row.epoch,
3683
+ callRuntimeStageId: row.callRuntimeStageId,
3684
+ commitIdx: row.commitIdx,
3685
+ betweenCalls: row.betweenCalls,
3686
+ ...row.previousEpoch !== void 0 ? { previousEpoch: row.previousEpoch } : {},
3687
+ ...basis !== void 0 ? { basis } : {},
3688
+ basisStatus: basis !== void 0 ? "reconstructed" : "not-on-record",
3689
+ ...row.receiptCause !== void 0 ? { receiptCause: row.receiptCause } : {},
3690
+ damaged: checks.damaged || edges.some((e) => e.check.status === "damaged")
3691
+ });
3692
+ }
3693
+ function servedGraphAt(input) {
3694
+ const edges = servedEdges(input);
3695
+ return Object.freeze({
3696
+ held: heldNodes(input.fold),
3697
+ served: slotNodes(input, edges),
3698
+ withheld: withheldNodes(input),
3699
+ call: callNode(input, edges),
3700
+ edges
3701
+ });
3702
+ }
3703
+
3407
3704
  // src/core/bookmarks/bookmarkKey.ts
3408
3705
  function fnv1a(s) {
3409
3706
  let h = 2166136261;
@@ -3783,6 +4080,10 @@ export {
3783
4080
  sincePrevious,
3784
4081
  FOLD_FACT_KEYS,
3785
4082
  foldFactsAt,
4083
+ SERVED_SLOTS,
4084
+ HELD_HONESTY_KEYS,
4085
+ HELD_KEYS,
4086
+ servedGraphAt,
3786
4087
  bookmarkKey,
3787
4088
  snapshotOfRunner,
3788
4089
  snapshotLogKey,
@@ -3799,4 +4100,4 @@ export {
3799
4100
  tagStopsFor,
3800
4101
  tagAxisPositions
3801
4102
  };
3802
- //# sourceMappingURL=chunk-FZHRQNRS.js.map
4103
+ //# sourceMappingURL=chunk-AIZIILHJ.js.map