agentfootprint 9.87.1 → 9.89.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 (143) hide show
  1. package/CHANGELOG.md +946 -0
  2. package/dist/core/Agent.js +32 -0
  3. package/dist/core/Agent.js.map +1 -1
  4. package/dist/core/LLMCall.js +7 -10
  5. package/dist/core/LLMCall.js.map +1 -1
  6. package/dist/core/agent/buildAgentMessageApiChart.js +2 -4
  7. package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -1
  8. package/dist/core/agent/buildDynamicAgentChart.js +10 -0
  9. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  10. package/dist/core/agent/buildMessageApiChart.js +2 -4
  11. package/dist/core/agent/buildMessageApiChart.js.map +1 -1
  12. package/dist/core/agent/composeRequest.js +134 -0
  13. package/dist/core/agent/composeRequest.js.map +1 -0
  14. package/dist/core/agent/stages/callLLM.js +62 -25
  15. package/dist/core/agent/stages/callLLM.js.map +1 -1
  16. package/dist/core/agent/stages/seed.js +12 -0
  17. package/dist/core/agent/stages/seed.js.map +1 -1
  18. package/dist/core/agent/stages/toolCalls.js +2 -4
  19. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  20. package/dist/core/flowchartAsTool.js.map +1 -1
  21. package/dist/esm/core/Agent.d.ts +3 -0
  22. package/dist/esm/core/Agent.js +32 -0
  23. package/dist/esm/core/Agent.js.map +1 -1
  24. package/dist/esm/core/LLMCall.js +7 -10
  25. package/dist/esm/core/LLMCall.js.map +1 -1
  26. package/dist/esm/core/agent/buildAgentMessageApiChart.js +2 -4
  27. package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -1
  28. package/dist/esm/core/agent/buildDynamicAgentChart.js +10 -0
  29. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  30. package/dist/esm/core/agent/buildMessageApiChart.js +2 -4
  31. package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -1
  32. package/dist/esm/core/agent/composeRequest.d.ts +121 -0
  33. package/dist/esm/core/agent/composeRequest.js +127 -0
  34. package/dist/esm/core/agent/composeRequest.js.map +1 -0
  35. package/dist/esm/core/agent/stages/callLLM.d.ts +28 -0
  36. package/dist/esm/core/agent/stages/callLLM.js +61 -24
  37. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  38. package/dist/esm/core/agent/stages/seed.d.ts +26 -0
  39. package/dist/esm/core/agent/stages/seed.js +12 -0
  40. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  41. package/dist/esm/core/agent/stages/toolCalls.js +2 -4
  42. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  43. package/dist/esm/core/agent/types.d.ts +64 -0
  44. package/dist/esm/core/flowchartAsTool.d.ts +15 -7
  45. package/dist/esm/core/flowchartAsTool.js.map +1 -1
  46. package/dist/esm/index.d.ts +1 -0
  47. package/dist/esm/index.js +9 -0
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  50. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js +0 -0
  51. package/dist/esm/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  52. package/dist/esm/lib/context-bisect/trajectory.js +3 -7
  53. package/dist/esm/lib/context-bisect/trajectory.js.map +1 -1
  54. package/dist/esm/lib/context-ledger/contextLedger.js +5 -12
  55. package/dist/esm/lib/context-ledger/contextLedger.js.map +1 -1
  56. package/dist/esm/lib/time-travel/epochs.d.ts +210 -0
  57. package/dist/esm/lib/time-travel/epochs.js +274 -0
  58. package/dist/esm/lib/time-travel/epochs.js.map +1 -0
  59. package/dist/esm/lib/time-travel/index.d.ts +10 -4
  60. package/dist/esm/lib/time-travel/index.js +10 -4
  61. package/dist/esm/lib/time-travel/index.js.map +1 -1
  62. package/dist/esm/lib/time-travel/keyedFold.d.ts +112 -0
  63. package/dist/esm/lib/time-travel/keyedFold.js +286 -0
  64. package/dist/esm/lib/time-travel/keyedFold.js.map +1 -0
  65. package/dist/esm/lib/time-travel/milestoneStops.d.ts +57 -36
  66. package/dist/esm/lib/time-travel/milestoneStops.js +75 -82
  67. package/dist/esm/lib/time-travel/milestoneStops.js.map +1 -1
  68. package/dist/esm/lib/time-travel/receipt.d.ts +496 -0
  69. package/dist/esm/lib/time-travel/receipt.js +434 -0
  70. package/dist/esm/lib/time-travel/receipt.js.map +1 -0
  71. package/dist/esm/lib/time-travel/servedView.d.ts +525 -0
  72. package/dist/esm/lib/time-travel/servedView.js +820 -0
  73. package/dist/esm/lib/time-travel/servedView.js.map +1 -0
  74. package/dist/esm/lib/time-travel/sha256.d.ts +24 -0
  75. package/dist/esm/lib/time-travel/sha256.js +103 -0
  76. package/dist/esm/lib/time-travel/sha256.js.map +1 -0
  77. package/dist/esm/recorders/observability/BoundaryRecorder.d.ts +21 -0
  78. package/dist/esm/recorders/observability/BoundaryRecorder.js +6 -0
  79. package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
  80. package/dist/index.js +124 -103
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/context-bisect/llmEdgeWeigher.js +0 -0
  83. package/dist/lib/context-bisect/llmEdgeWeigher.js.map +1 -1
  84. package/dist/lib/context-bisect/trajectory.js +3 -7
  85. package/dist/lib/context-bisect/trajectory.js.map +1 -1
  86. package/dist/lib/context-ledger/contextLedger.js +5 -12
  87. package/dist/lib/context-ledger/contextLedger.js.map +1 -1
  88. package/dist/lib/time-travel/epochs.js +284 -0
  89. package/dist/lib/time-travel/epochs.js.map +1 -0
  90. package/dist/lib/time-travel/index.js +35 -5
  91. package/dist/lib/time-travel/index.js.map +1 -1
  92. package/dist/lib/time-travel/keyedFold.js +290 -0
  93. package/dist/lib/time-travel/keyedFold.js.map +1 -0
  94. package/dist/lib/time-travel/milestoneStops.js +74 -81
  95. package/dist/lib/time-travel/milestoneStops.js.map +1 -1
  96. package/dist/lib/time-travel/receipt.js +442 -0
  97. package/dist/lib/time-travel/receipt.js.map +1 -0
  98. package/dist/lib/time-travel/servedView.js +826 -0
  99. package/dist/lib/time-travel/servedView.js.map +1 -0
  100. package/dist/lib/time-travel/sha256.js +107 -0
  101. package/dist/lib/time-travel/sha256.js.map +1 -0
  102. package/dist/recorders/observability/BoundaryRecorder.js +6 -0
  103. package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
  104. package/dist/types/core/Agent.d.ts +3 -0
  105. package/dist/types/core/Agent.d.ts.map +1 -1
  106. package/dist/types/core/LLMCall.d.ts.map +1 -1
  107. package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -1
  108. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  109. package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -1
  110. package/dist/types/core/agent/composeRequest.d.ts +122 -0
  111. package/dist/types/core/agent/composeRequest.d.ts.map +1 -0
  112. package/dist/types/core/agent/stages/callLLM.d.ts +28 -0
  113. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  114. package/dist/types/core/agent/stages/seed.d.ts +26 -0
  115. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  116. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  117. package/dist/types/core/agent/types.d.ts +64 -0
  118. package/dist/types/core/agent/types.d.ts.map +1 -1
  119. package/dist/types/core/flowchartAsTool.d.ts +15 -7
  120. package/dist/types/core/flowchartAsTool.d.ts.map +1 -1
  121. package/dist/types/index.d.ts +1 -0
  122. package/dist/types/index.d.ts.map +1 -1
  123. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts +8 -0
  124. package/dist/types/lib/context-bisect/llmEdgeWeigher.d.ts.map +1 -1
  125. package/dist/types/lib/context-bisect/trajectory.d.ts.map +1 -1
  126. package/dist/types/lib/context-ledger/contextLedger.d.ts.map +1 -1
  127. package/dist/types/lib/time-travel/epochs.d.ts +211 -0
  128. package/dist/types/lib/time-travel/epochs.d.ts.map +1 -0
  129. package/dist/types/lib/time-travel/index.d.ts +10 -4
  130. package/dist/types/lib/time-travel/index.d.ts.map +1 -1
  131. package/dist/types/lib/time-travel/keyedFold.d.ts +113 -0
  132. package/dist/types/lib/time-travel/keyedFold.d.ts.map +1 -0
  133. package/dist/types/lib/time-travel/milestoneStops.d.ts +57 -36
  134. package/dist/types/lib/time-travel/milestoneStops.d.ts.map +1 -1
  135. package/dist/types/lib/time-travel/receipt.d.ts +497 -0
  136. package/dist/types/lib/time-travel/receipt.d.ts.map +1 -0
  137. package/dist/types/lib/time-travel/servedView.d.ts +526 -0
  138. package/dist/types/lib/time-travel/servedView.d.ts.map +1 -0
  139. package/dist/types/lib/time-travel/sha256.d.ts +25 -0
  140. package/dist/types/lib/time-travel/sha256.d.ts.map +1 -0
  141. package/dist/types/recorders/observability/BoundaryRecorder.d.ts +21 -0
  142. package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
  143. package/package.json +3 -3
@@ -0,0 +1,286 @@
1
+ /**
2
+ * keyedFold — the value of ONE state key at one commit, folded from the run's
3
+ * own fold base.
4
+ *
5
+ * Role: Fold. It reads a finished run's commit log plus the base that log
6
+ * was recorded against, and answers "what was `key` at commit `idx`?"
7
+ * It executes nothing, records nothing and stores nothing beside the
8
+ * log. Its answers are DETACHED — deep-frozen before they are cached,
9
+ * because a memo that hands out live references is a fold that lets a
10
+ * reader rewrite the run (`keyedFold.ts` · `freezeDeep`).
11
+ * Reads: a `FoldSource` — a snapshot (`commitLog` + `initialState`) or a
12
+ * subflow subtree (`history` + `initialState`), through footprintjs's
13
+ * own `stateAt` (for the base) and `applySmartMerge` (for the replay).
14
+ * Emits: N/A.
15
+ *
16
+ * ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
17
+ * A commit log stores DIFFS. `commitValueAt` folds them for one key and says
18
+ * so in its own docstring: a value that came from the run's INITIAL state and
19
+ * was never `set` again folds to ABSENT, because the log alone cannot see the
20
+ * pre-run base.
21
+ *
22
+ * That is not an edge case here. A RESUMED agent run is a fresh executor
23
+ * seeded from `checkpoint.sharedState`, so the entire pre-pause world —
24
+ * `systemPromptInjections`, `history`, `dynamicToolSchemas`, every build-time
25
+ * constant `seed` wrote before the pause — is base, not log. Folding the
26
+ * resumed run with `commitValueAt` returns an empty system prompt and a
27
+ * truncated conversation and declares no gap: a confident falsehood about the
28
+ * one question this family exists to answer. footprintjs 9.17 shipped
29
+ * `RuntimeSnapshot.initialState` and `stateAt` for exactly this, and every
30
+ * read in `epochs.ts` goes through here so it lands on that base.
31
+ *
32
+ * ── WHY NOT JUST CALL `stateAt` PER READ ───────────────────────────────────
33
+ * `stateAt` folds the WHOLE state, and it clones that state once per bundle
34
+ * (`applySmartMerge` opens with a `structuredClone` of its base). One fold is
35
+ * therefore O(commits × state size) — measured on a real 100-turn agent
36
+ * (1,719 commits, 101 epochs): 323 ms for a single fold, and 11.7 s to fold
37
+ * once per epoch, against 78 ms for the whole per-epoch scrub before this
38
+ * release. A per-epoch reader cannot pay that.
39
+ *
40
+ * So `stateAt` is used for the ONE thing only it can give — the fold base and
41
+ * the honest `basis` verdict, at `commitIdx: -1`, where it folds nothing and
42
+ * clones the base once — and the forward replay is done per key, through
43
+ * footprintjs's own `applySmartMerge`. There is deliberately no second
44
+ * implementation of the trace verbs here: every `set` / `append` / `merge` /
45
+ * `delete` is applied by the same function the live commit uses.
46
+ * `keyed-fold-equivalence.test.ts` pins the result against `stateAt` itself on
47
+ * real runs, including a resumed one and a merge with no `set` anchor — the
48
+ * one shape that cannot be folded at all without the base.
49
+ */
50
+ import { applySmartMerge } from 'footprintjs/advanced';
51
+ import { stateAt } from 'footprintjs/trace';
52
+ /**
53
+ * The delimiter footprintjs joins nested trace paths with — the engine's own
54
+ * `DELIM`. Mirrored rather than imported because it is not on any public
55
+ * barrel.
56
+ *
57
+ * Today's scope facade records a nested write (`scope.profile.city = …`) as a
58
+ * MERGE on the top-level key rather than as a delimited path, so this is
59
+ * DEFENCE and not a hot path. It is still load-bearing: a delimited path must
60
+ * be attributed to its ROOT key, or a write to `history<DELIM>0<DELIM>content`
61
+ * would be filed under a key nobody asks for and dropped from every fold.
62
+ */
63
+ const PATH_DELIM = '\u001F';
64
+ /** The top-level state key a trace path belongs to. */
65
+ function rootKeyOf(path) {
66
+ const cut = path.indexOf(PATH_DELIM);
67
+ return cut < 0 ? path : path.slice(0, cut);
68
+ }
69
+ /**
70
+ * Deep-freeze in place, skipping subtrees that are frozen already.
71
+ *
72
+ * ── A FOLD RESULT IS DETACHED, OR IT IS NOT A FOLD ─────────────────────────
73
+ * This module memoizes: `valueAt` hands the SAME object back on every call for
74
+ * the same question, and the forward cursor seeds the replay of every LATER
75
+ * index from that very object. Handing out a live reference therefore does not
76
+ * merely risk a surprise — a consumer that edits what it was handed silently
77
+ * rewrites what every later epoch reports was served, which is the one thing
78
+ * this family exists to be trusted about.
79
+ *
80
+ * It was not a hazard in the reader this module replaced. footprintjs's
81
+ * `commitValueAt` clones per call, so the same edit was harmless there; the
82
+ * memo is what introduced it, so the memo is where it is closed.
83
+ *
84
+ * FREEZING RATHER THAN CLONING PER READ, for two reasons. Cloning per read is
85
+ * exactly the cost the forward cursor exists to avoid — it puts an O(value)
86
+ * copy back on every question, on the hot path of a scrub. And freezing is the
87
+ * answer footprintjs itself already gives: `stateAt` returns
88
+ * `freezeDeep(structuredClone(out))`, so a caller that moves between the two
89
+ * folds meets one contract instead of two.
90
+ *
91
+ * The forward replay is unaffected: `applySmartMerge` opens with a
92
+ * `structuredClone` of its base, and a clone of a frozen value is not frozen.
93
+ *
94
+ * MEASURED, so the choice is not a guess, and reported because it is not free.
95
+ * On a 601-epoch run (10,219 commits) the whole per-epoch scrub — every
96
+ * epoch's system pieces, conversation and tool list, folded and rebuilt —
97
+ * costs 1,141 ms with the freeze and 930 ms without it: +211 ms, +23%, on a
98
+ * scrub that cost 20.8 s two fixes ago. The batch form moves 919 ms to
99
+ * 1,116 ms. The alternative is DEARER, not cheaper: on the same structure
100
+ * (1,200 messages) `structuredClone` costs 3.6x what this walk does — 0.94 ms
101
+ * against 0.26 ms — and a copy would have to run once per READ where the
102
+ * freeze runs once per memoized answer. A mutable answer is not worth 23%, and
103
+ * buying safety by copying would cost more than 23%.
104
+ */
105
+ function freezeDeep(value) {
106
+ if (value === null || typeof value !== 'object')
107
+ return value;
108
+ Object.freeze(value);
109
+ for (const key of Object.getOwnPropertyNames(value)) {
110
+ const inner = value[key];
111
+ if (inner !== null && typeof inner === 'object' && !Object.isFrozen(inner))
112
+ freezeDeep(inner);
113
+ }
114
+ return value;
115
+ }
116
+ /** The bundles, from whichever field this source spells them in. */
117
+ function commitsOf(source) {
118
+ if (!source)
119
+ return [];
120
+ if (Array.isArray(source.commitLog))
121
+ return source.commitLog;
122
+ if (Array.isArray(source.history))
123
+ return source.history;
124
+ return [];
125
+ }
126
+ /** Build the per-key touch index in ONE pass over the log. */
127
+ function indexTouches(log) {
128
+ const index = new Map();
129
+ for (let at = 0; at < log.length; at++) {
130
+ const bundle = log[at];
131
+ if (!bundle)
132
+ continue;
133
+ for (const entry of bundle.trace ?? []) {
134
+ const path = entry.path;
135
+ if (typeof path !== 'string')
136
+ continue;
137
+ const key = rootKeyOf(path);
138
+ const verb = entry.verb;
139
+ const touch = {
140
+ at,
141
+ bundle,
142
+ entry,
143
+ anchors: path === key && (verb === 'set' || verb === 'delete'),
144
+ };
145
+ const list = index.get(key);
146
+ if (list === undefined)
147
+ index.set(key, [touch]);
148
+ else
149
+ list.push(touch);
150
+ }
151
+ }
152
+ return index;
153
+ }
154
+ /** The last position in `touches` whose bundle index is `<= idx`, or `-1`. */
155
+ function lastTouchAt(touches, idx) {
156
+ let lo = 0;
157
+ let hi = touches.length - 1;
158
+ let found = -1;
159
+ while (lo <= hi) {
160
+ const mid = (lo + hi) >> 1;
161
+ if (touches[mid].at <= idx) {
162
+ found = mid;
163
+ lo = mid + 1;
164
+ }
165
+ else {
166
+ hi = mid - 1;
167
+ }
168
+ }
169
+ return found;
170
+ }
171
+ /** Memoized folds, one per source object. */
172
+ const FOLDS = new WeakMap();
173
+ /**
174
+ * The keyed fold for one source, memoized on the source object.
175
+ *
176
+ * Memoized because a per-epoch reader asks the same source for the same keys
177
+ * at many different commits, and the index is the expensive half: building it
178
+ * is one pass over the log, and answering a read afterwards is a binary search
179
+ * plus a replay from the nearest full-value write (normally exactly one).
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * import { keyedFold } from 'agentfootprint';
184
+ *
185
+ * const fold = keyedFold(agent.getSnapshot());
186
+ * fold.basis; // 'initial+log' — the base travelled
187
+ * fold.valueAt('history', 12); // the conversation after the 13th commit
188
+ * fold.valueAt('history', -1); // …and what the run STARTED from
189
+ * ```
190
+ */
191
+ export function keyedFold(source) {
192
+ if (source !== null && typeof source === 'object') {
193
+ const memo = FOLDS.get(source);
194
+ if (memo !== undefined)
195
+ return memo;
196
+ }
197
+ const log = commitsOf(source);
198
+ // `stateAt` at -1 folds NOTHING: it clones the base once and reports whether
199
+ // there was one. That is the whole reason to call the engine's own fold here
200
+ // rather than read `initialState` directly — the `basis` verdict is its
201
+ // answer to give, and a recording that travelled without its base must be
202
+ // able to say so.
203
+ const seed = stateAt(source, -1);
204
+ const index = indexTouches(log);
205
+ const answers = new Map();
206
+ /** Per key, the furthest replay position reached and the value there — see
207
+ * the forward-cursor note in `compute`. */
208
+ const cursor = new Map();
209
+ /** One touch applied, through the ENGINE's verb switch, over a state
210
+ * projected to this key alone. `applySmartMerge` navigates from the root,
211
+ * so a delimited path lands exactly where it did in the real fold. */
212
+ const step = (key, value, touch) => applySmartMerge({ [key]: value }, touch.bundle.updates, touch.bundle.overwrite, [
213
+ touch.entry,
214
+ ])[key];
215
+ const compute = (key, idx) => {
216
+ const touches = index.get(key);
217
+ const last = touches === undefined ? -1 : lastTouchAt(touches, idx);
218
+ // The base's own value. `stateAt` already deep-freezes what it returns, so
219
+ // this is a no-op today — asserted rather than assumed, because the law
220
+ // this module states about its answers must not rest on another module
221
+ // continuing to keep it.
222
+ if (touches === undefined || last < 0)
223
+ return freezeDeep(seed.state[key]);
224
+ // Anchor at the latest write that fully determines the key on its own;
225
+ // everything before it — the base included — cannot be read back through
226
+ // it. With no anchor the base IS the starting value, which is the whole
227
+ // fix: `commitValueAt` starts from `undefined` here and folds a fiction.
228
+ let start = 0;
229
+ let anchored = false;
230
+ for (let i = last; i >= 0; i--) {
231
+ if (touches[i].anchors) {
232
+ start = i;
233
+ anchored = true;
234
+ break;
235
+ }
236
+ }
237
+ let from = start;
238
+ let value = anchored ? undefined : seed.state[key];
239
+ // THE FORWARD CURSOR, and why this module would be slower than the reader
240
+ // it replaces without it. An agent's `history` is `set` ONCE and appended
241
+ // to every turn, so a query at turn k has to replay k appends from the
242
+ // anchor — and a per-epoch scrub asks E times, which is O(E²) replays of a
243
+ // growing array. Resuming from the furthest position already reached makes
244
+ // the whole scrub O(E) replays instead, because a scrub walks forward.
245
+ // (Measured on a 96-turn run: 201 ms per scrub without this, 40 ms with.)
246
+ // A backward or out-of-order query simply falls back to the anchor, which
247
+ // is correct and no worse than never having cached anything.
248
+ const held = cursor.get(key);
249
+ if (held !== undefined && held.position >= start && held.position <= last) {
250
+ from = held.position + 1;
251
+ value = held.value;
252
+ }
253
+ for (let i = from; i <= last; i++)
254
+ value = step(key, value, touches[i]);
255
+ // Frozen HERE, before anything else can hold it: the cursor keeps this very
256
+ // object as the seed for every later index, and `valueAt` memoizes it. One
257
+ // freeze covers both — `keyedFold.ts` · `freezeDeep`.
258
+ freezeDeep(value);
259
+ cursor.set(key, { position: last, value });
260
+ return value;
261
+ };
262
+ const fold = {
263
+ basis: seed.basis,
264
+ length: log.length,
265
+ valueAt(key, idx) {
266
+ const at = Math.min(Math.floor(idx), log.length - 1);
267
+ let perKey = answers.get(key);
268
+ if (perKey === undefined) {
269
+ perKey = new Map();
270
+ answers.set(key, perKey);
271
+ }
272
+ if (perKey.has(at))
273
+ return perKey.get(at);
274
+ const value = compute(key, at);
275
+ perKey.set(at, value);
276
+ return value;
277
+ },
278
+ };
279
+ // The fold OBJECT is memoized too — the same one every caller of `keyedFold`
280
+ // gets for this source — so it is frozen for the same reason its answers are.
281
+ Object.freeze(fold);
282
+ if (source !== null && typeof source === 'object')
283
+ FOLDS.set(source, fold);
284
+ return fold;
285
+ }
286
+ //# sourceMappingURL=keyedFold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyedFold.js","sourceRoot":"","sources":["../../../../src/lib/time-travel/keyedFold.ts"],"names":[],"mappings":"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"}
@@ -8,16 +8,26 @@
8
8
  * it), and `conventions.ts` · `milestoneFor` for the domain vocabulary.
9
9
  * Emits: N/A.
10
10
  *
11
- * WHY THIS FILE EXISTS. footprintjs 9.17 ships the reader's cursor
12
- * (`timeTravel`) and one stop grammar — `commitStops`, one stop per executed
13
- * stage — because that is the only grammar the substrate itself knows. It does
14
- * NOT know what an LLM turn is. agentfootprint does: `milestoneFor` has
15
- * classified a local stage id into an iteration / slot / llm-turn / tool-call /
16
- * decision since 9.x, and until now every consumer that wanted a milestone
17
- * slider mapped that classifier onto commits by hand. The Why Lens did it one
18
- * way; anyone else with a recording had to write it again. This is that
19
- * mapping, once, on the strategy seam the port opened — so the agent SUPPLIES
20
- * the stops for its own runs and every reader gets the same axis.
11
+ * WHY THIS FILE EXISTS. footprintjs ships the reader's cursor (`timeTravel`)
12
+ * and one stop grammar — `commitStops`, one stop per executed stage — because
13
+ * that is the only grammar the substrate itself knows. It does NOT know what an
14
+ * LLM turn is. agentfootprint does: `milestoneFor` has classified a local stage
15
+ * id into an iteration / slot / llm-turn / tool-call / decision since 9.x, and
16
+ * until 9.87 every consumer that wanted a milestone slider mapped that
17
+ * classifier onto commits by hand. This is that mapping, once, on the strategy
18
+ * seam the port opened — so the agent SUPPLIES the stops for its own runs and
19
+ * every reader gets the same axis.
20
+ *
21
+ * WHAT IT IS (9.89.0). A FILTER over the port's own axis. footprintjs 9.18
22
+ * shipped `filterStops(stops, keep)` — the bookend guard, the re-partition and
23
+ * a `meta` slot on the stop — because two consumers had each re-derived all
24
+ * three by hand against 9.17, this file among them. The hand-rolled copies are
25
+ * gone: the one owner of the `[start, …stages, end]` contract is the library
26
+ * that returns it, and this strategy is one expression over it. The axis is
27
+ * byte-for-byte the 9.88.0 axis (`test/lib/time-travel/milestone-stops-equivalence.test.ts` pins
28
+ * that against a verbatim copy of the 9.88.0 implementation); what is new is that the milestone now RIDES on the
29
+ * stop as `meta`, and that a start which absorbed pre-milestone stages says so
30
+ * with `prologue: true`.
21
31
  *
22
32
  * WHAT IT IS NOT. Not a second cursor. A strategy only says where the one
23
33
  * cursor may rest; `timeTravel` still owns the position, the folds and the
@@ -32,15 +42,14 @@ import { type Milestone } from '../../conventions.js';
32
42
  * The milestone a stop stands for, or `null` when it stands for none (the
33
43
  * `'start'` / `'end'` bookends).
34
44
  *
35
- * WHY A FUNCTION AND NOT A FIELD. footprintjs's `Stop` is a closed shape —
36
- * `step`, `runtimeStageId`, `commitIdx`, `lastCommitIdx`, `stageId`,
37
- * `subflowPath`, `label`, `kind` — with no extension slot a strategy may write
38
- * its own vocabulary into, and `kind` is the port's own `StopKind`
39
- * (`'commit' | 'mount' | 'start' | 'end'`), not ours to overload. So the
40
- * milestone kind travels the only way it honestly can: re-derived from the
41
- * stop's `runtimeStageId`, by the same classifier that put the stop on the
42
- * axis. Same input, same function, same answer — there is no second source of
43
- * truth here, only a second reading of the one there is.
45
+ * A stop that {@link milestoneStops} made carries its milestone as
46
+ * `stop.meta`, and that is what is read — the classification the strategy
47
+ * made when it put the stop on the axis, not a second run of the classifier.
48
+ * A stop from ANOTHER strategy — the port's own `commitStops`, or a consumer's
49
+ * filter that kept the stop without a milestone meta — has none, so the answer
50
+ * falls back to where it always came from: `milestoneFor` over the stop's
51
+ * `runtimeStageId`. Same classifier, same answer; a `meta` of some other
52
+ * vocabulary is not mistaken for ours.
44
53
  *
45
54
  * @example
46
55
  * ```ts
@@ -48,7 +57,7 @@ import { type Milestone } from '../../conventions.js';
48
57
  * milestoneOf(stop)?.kind; // 'llm-turn'
49
58
  * ```
50
59
  */
51
- export declare function milestoneOf(stop: Stop): Milestone | null;
60
+ export declare function milestoneOf(stop: Stop<unknown>): Milestone | null;
52
61
  /**
53
62
  * Derive one stop per committed MILESTONE from a recorded commit log.
54
63
  *
@@ -61,9 +70,11 @@ export declare function milestoneOf(stop: Stop): Milestone | null;
61
70
  * parallel fork child commits twice and siblings interleave — every repeat is
62
71
  * an empty bundle), the authoritative mount set read off the execution tree,
63
72
  * the `'start'` / `'end'` bookends, and the id-less leading commit that carries
64
- * a subflow's `inputMapper` seed. This function calls `commitStops` and filters
65
- * its result. A second implementation of that collapsing would be a second
66
- * chance to disagree with the library about what a stage is.
73
+ * a subflow's `inputMapper` seed. And since 9.89.0 the COMPOSITION itself is
74
+ * the port's too: `filterStops` checks the `[start, …stages, end]` shape,
75
+ * keeps what `keep` keeps, and re-partitions the log. A second implementation
76
+ * of any of that would be a second chance to disagree with the library about
77
+ * what a stage is.
67
78
  *
68
79
  * **The survivors re-partition the log.** A stage that classifies `null` is not
69
80
  * a stop, but its commits still happened, so they are folded into the stop that
@@ -74,14 +85,21 @@ export declare function milestoneOf(stop: Stop): Milestone | null;
74
85
  * so on a drilled cursor `'start'` reads as "what this subflow began with,
75
86
  * after its plumbing ran".
76
87
  *
77
- * **What that costs `'start'`.** On footprintjs's own axis `'start'` is the fold
78
- * base: the state before ANY stage ran. Here it absorbs every stage that ran
79
- * before the first milestone, so `stateAt(start)` is the state the first
80
- * milestone READ, not the run's raw base — a real agent seeds a couple of dozen
81
- * keys in `seed` before anything a reader would scrub to. That is the right
82
- * answer for this axis (the stops must still partition the log) and the wrong
83
- * one to assume from `kind: 'start'` alone, so it is said out loud here, in the
84
- * folder README, on the docs page and in a test.
88
+ * **What that costs `'start'`, and the flag that says so.** On footprintjs's
89
+ * own axis `'start'` is the fold base: the state before ANY stage ran. Here it
90
+ * absorbs every stage that ran before the first milestone, so `stateAt(start)`
91
+ * is the state the first milestone READ, not the run's raw base — a real agent
92
+ * seeds a couple of dozen keys in `seed` before anything a reader would scrub
93
+ * to. That is the right answer for this axis (the stops must still partition
94
+ * the log), and since 9.18 the port marks it: the returned start carries
95
+ * `prologue: true` whenever it absorbed a stage, so a renderer that means
96
+ * "before anything ran" checks `kind === 'start' && !prologue` instead of
97
+ * assuming it from the kind.
98
+ *
99
+ * **The milestone rides on the stop.** Every kept stop carries its
100
+ * classification as `meta` — `Stop<Milestone>` — so a reader asks
101
+ * `stop.meta?.kind` (or {@link milestoneOf}, which reads the same slot) rather
102
+ * than re-running the classifier over the id. The bookends carry none.
85
103
  *
86
104
  * **A log with no milestones in it at all.** A non-empty log the classifier
87
105
  * recognises nothing in — a non-agent chart handed this strategy — yields the
@@ -109,20 +127,23 @@ export declare function milestoneOf(stop: Stop): Milestone | null;
109
127
  * @example
110
128
  * ```ts
111
129
  * import { timeTravel } from 'footprintjs/trace';
112
- * import { milestoneStopsStrategy, milestoneOf } from 'agentfootprint';
130
+ * import { milestoneStopsStrategy } from 'agentfootprint';
113
131
  *
114
132
  * const cursor = timeTravel(agent.getSnapshot()!, { strategy: milestoneStopsStrategy });
115
- * cursor.stops.map((s) => [s.label, milestoneOf(s)?.kind]);
133
+ * cursor.stops.map((s) => [s.label, s.meta?.kind]);
116
134
  * // measured on a two-turn `dynamic` run — 42 commits, 15 stops:
117
135
  * // [['Run start', undefined], ['Iteration', 'iteration'],
118
136
  * // ['System prompt', 'slot'], ['Messages', 'slot'], ['Tools', 'slot'],
119
137
  * // ['LLM turn', 'llm-turn'], ['Route', 'decision'], ['Tool call', 'tool-call'],
120
138
  * // … the second turn …, ['Run end', undefined]]
139
+ * cursor.stops[0].prologue; // true — `seed` and the plumbing ran before the first Iteration
121
140
  * ```
122
141
  */
123
- export declare function milestoneStops(commitLog: readonly CommitBundle[], executionTree?: StageSnapshot): Stop[];
142
+ export declare function milestoneStops(commitLog: readonly CommitBundle[], executionTree?: StageSnapshot): Stop<Milestone>[];
124
143
  /**
125
144
  * `milestoneStops` as a footprintjs `TimeTravelStrategy` — what you hand
126
- * `timeTravel(snapshot, { strategy })`.
145
+ * `timeTravel(snapshot, { strategy })`. Typed over {@link Milestone}, so the
146
+ * cursor's stops are `Stop<Milestone>` and `cursor.at()?.meta?.kind` is typed;
147
+ * it is still assignable wherever a bare `TimeTravelStrategy` is expected.
127
148
  */
128
- export declare const milestoneStopsStrategy: TimeTravelStrategy;
149
+ export declare const milestoneStopsStrategy: TimeTravelStrategy<Milestone>;