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,290 @@
1
+ "use strict";
2
+ /**
3
+ * keyedFold — the value of ONE state key at one commit, folded from the run's
4
+ * own fold base.
5
+ *
6
+ * Role: Fold. It reads a finished run's commit log plus the base that log
7
+ * was recorded against, and answers "what was `key` at commit `idx`?"
8
+ * It executes nothing, records nothing and stores nothing beside the
9
+ * log. Its answers are DETACHED — deep-frozen before they are cached,
10
+ * because a memo that hands out live references is a fold that lets a
11
+ * reader rewrite the run (`keyedFold.ts` · `freezeDeep`).
12
+ * Reads: a `FoldSource` — a snapshot (`commitLog` + `initialState`) or a
13
+ * subflow subtree (`history` + `initialState`), through footprintjs's
14
+ * own `stateAt` (for the base) and `applySmartMerge` (for the replay).
15
+ * Emits: N/A.
16
+ *
17
+ * ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
18
+ * A commit log stores DIFFS. `commitValueAt` folds them for one key and says
19
+ * so in its own docstring: a value that came from the run's INITIAL state and
20
+ * was never `set` again folds to ABSENT, because the log alone cannot see the
21
+ * pre-run base.
22
+ *
23
+ * That is not an edge case here. A RESUMED agent run is a fresh executor
24
+ * seeded from `checkpoint.sharedState`, so the entire pre-pause world —
25
+ * `systemPromptInjections`, `history`, `dynamicToolSchemas`, every build-time
26
+ * constant `seed` wrote before the pause — is base, not log. Folding the
27
+ * resumed run with `commitValueAt` returns an empty system prompt and a
28
+ * truncated conversation and declares no gap: a confident falsehood about the
29
+ * one question this family exists to answer. footprintjs 9.17 shipped
30
+ * `RuntimeSnapshot.initialState` and `stateAt` for exactly this, and every
31
+ * read in `epochs.ts` goes through here so it lands on that base.
32
+ *
33
+ * ── WHY NOT JUST CALL `stateAt` PER READ ───────────────────────────────────
34
+ * `stateAt` folds the WHOLE state, and it clones that state once per bundle
35
+ * (`applySmartMerge` opens with a `structuredClone` of its base). One fold is
36
+ * therefore O(commits × state size) — measured on a real 100-turn agent
37
+ * (1,719 commits, 101 epochs): 323 ms for a single fold, and 11.7 s to fold
38
+ * once per epoch, against 78 ms for the whole per-epoch scrub before this
39
+ * release. A per-epoch reader cannot pay that.
40
+ *
41
+ * So `stateAt` is used for the ONE thing only it can give — the fold base and
42
+ * the honest `basis` verdict, at `commitIdx: -1`, where it folds nothing and
43
+ * clones the base once — and the forward replay is done per key, through
44
+ * footprintjs's own `applySmartMerge`. There is deliberately no second
45
+ * implementation of the trace verbs here: every `set` / `append` / `merge` /
46
+ * `delete` is applied by the same function the live commit uses.
47
+ * `keyed-fold-equivalence.test.ts` pins the result against `stateAt` itself on
48
+ * real runs, including a resumed one and a merge with no `set` anchor — the
49
+ * one shape that cannot be folded at all without the base.
50
+ */
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.keyedFold = void 0;
53
+ const advanced_1 = require("footprintjs/advanced");
54
+ const trace_1 = require("footprintjs/trace");
55
+ /**
56
+ * The delimiter footprintjs joins nested trace paths with — the engine's own
57
+ * `DELIM`. Mirrored rather than imported because it is not on any public
58
+ * barrel.
59
+ *
60
+ * Today's scope facade records a nested write (`scope.profile.city = …`) as a
61
+ * MERGE on the top-level key rather than as a delimited path, so this is
62
+ * DEFENCE and not a hot path. It is still load-bearing: a delimited path must
63
+ * be attributed to its ROOT key, or a write to `history<DELIM>0<DELIM>content`
64
+ * would be filed under a key nobody asks for and dropped from every fold.
65
+ */
66
+ const PATH_DELIM = '\u001F';
67
+ /** The top-level state key a trace path belongs to. */
68
+ function rootKeyOf(path) {
69
+ const cut = path.indexOf(PATH_DELIM);
70
+ return cut < 0 ? path : path.slice(0, cut);
71
+ }
72
+ /**
73
+ * Deep-freeze in place, skipping subtrees that are frozen already.
74
+ *
75
+ * ── A FOLD RESULT IS DETACHED, OR IT IS NOT A FOLD ─────────────────────────
76
+ * This module memoizes: `valueAt` hands the SAME object back on every call for
77
+ * the same question, and the forward cursor seeds the replay of every LATER
78
+ * index from that very object. Handing out a live reference therefore does not
79
+ * merely risk a surprise — a consumer that edits what it was handed silently
80
+ * rewrites what every later epoch reports was served, which is the one thing
81
+ * this family exists to be trusted about.
82
+ *
83
+ * It was not a hazard in the reader this module replaced. footprintjs's
84
+ * `commitValueAt` clones per call, so the same edit was harmless there; the
85
+ * memo is what introduced it, so the memo is where it is closed.
86
+ *
87
+ * FREEZING RATHER THAN CLONING PER READ, for two reasons. Cloning per read is
88
+ * exactly the cost the forward cursor exists to avoid — it puts an O(value)
89
+ * copy back on every question, on the hot path of a scrub. And freezing is the
90
+ * answer footprintjs itself already gives: `stateAt` returns
91
+ * `freezeDeep(structuredClone(out))`, so a caller that moves between the two
92
+ * folds meets one contract instead of two.
93
+ *
94
+ * The forward replay is unaffected: `applySmartMerge` opens with a
95
+ * `structuredClone` of its base, and a clone of a frozen value is not frozen.
96
+ *
97
+ * MEASURED, so the choice is not a guess, and reported because it is not free.
98
+ * On a 601-epoch run (10,219 commits) the whole per-epoch scrub — every
99
+ * epoch's system pieces, conversation and tool list, folded and rebuilt —
100
+ * costs 1,141 ms with the freeze and 930 ms without it: +211 ms, +23%, on a
101
+ * scrub that cost 20.8 s two fixes ago. The batch form moves 919 ms to
102
+ * 1,116 ms. The alternative is DEARER, not cheaper: on the same structure
103
+ * (1,200 messages) `structuredClone` costs 3.6x what this walk does — 0.94 ms
104
+ * against 0.26 ms — and a copy would have to run once per READ where the
105
+ * freeze runs once per memoized answer. A mutable answer is not worth 23%, and
106
+ * buying safety by copying would cost more than 23%.
107
+ */
108
+ function freezeDeep(value) {
109
+ if (value === null || typeof value !== 'object')
110
+ return value;
111
+ Object.freeze(value);
112
+ for (const key of Object.getOwnPropertyNames(value)) {
113
+ const inner = value[key];
114
+ if (inner !== null && typeof inner === 'object' && !Object.isFrozen(inner))
115
+ freezeDeep(inner);
116
+ }
117
+ return value;
118
+ }
119
+ /** The bundles, from whichever field this source spells them in. */
120
+ function commitsOf(source) {
121
+ if (!source)
122
+ return [];
123
+ if (Array.isArray(source.commitLog))
124
+ return source.commitLog;
125
+ if (Array.isArray(source.history))
126
+ return source.history;
127
+ return [];
128
+ }
129
+ /** Build the per-key touch index in ONE pass over the log. */
130
+ function indexTouches(log) {
131
+ const index = new Map();
132
+ for (let at = 0; at < log.length; at++) {
133
+ const bundle = log[at];
134
+ if (!bundle)
135
+ continue;
136
+ for (const entry of bundle.trace ?? []) {
137
+ const path = entry.path;
138
+ if (typeof path !== 'string')
139
+ continue;
140
+ const key = rootKeyOf(path);
141
+ const verb = entry.verb;
142
+ const touch = {
143
+ at,
144
+ bundle,
145
+ entry,
146
+ anchors: path === key && (verb === 'set' || verb === 'delete'),
147
+ };
148
+ const list = index.get(key);
149
+ if (list === undefined)
150
+ index.set(key, [touch]);
151
+ else
152
+ list.push(touch);
153
+ }
154
+ }
155
+ return index;
156
+ }
157
+ /** The last position in `touches` whose bundle index is `<= idx`, or `-1`. */
158
+ function lastTouchAt(touches, idx) {
159
+ let lo = 0;
160
+ let hi = touches.length - 1;
161
+ let found = -1;
162
+ while (lo <= hi) {
163
+ const mid = (lo + hi) >> 1;
164
+ if (touches[mid].at <= idx) {
165
+ found = mid;
166
+ lo = mid + 1;
167
+ }
168
+ else {
169
+ hi = mid - 1;
170
+ }
171
+ }
172
+ return found;
173
+ }
174
+ /** Memoized folds, one per source object. */
175
+ const FOLDS = new WeakMap();
176
+ /**
177
+ * The keyed fold for one source, memoized on the source object.
178
+ *
179
+ * Memoized because a per-epoch reader asks the same source for the same keys
180
+ * at many different commits, and the index is the expensive half: building it
181
+ * is one pass over the log, and answering a read afterwards is a binary search
182
+ * plus a replay from the nearest full-value write (normally exactly one).
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * import { keyedFold } from 'agentfootprint';
187
+ *
188
+ * const fold = keyedFold(agent.getSnapshot());
189
+ * fold.basis; // 'initial+log' — the base travelled
190
+ * fold.valueAt('history', 12); // the conversation after the 13th commit
191
+ * fold.valueAt('history', -1); // …and what the run STARTED from
192
+ * ```
193
+ */
194
+ function keyedFold(source) {
195
+ if (source !== null && typeof source === 'object') {
196
+ const memo = FOLDS.get(source);
197
+ if (memo !== undefined)
198
+ return memo;
199
+ }
200
+ const log = commitsOf(source);
201
+ // `stateAt` at -1 folds NOTHING: it clones the base once and reports whether
202
+ // there was one. That is the whole reason to call the engine's own fold here
203
+ // rather than read `initialState` directly — the `basis` verdict is its
204
+ // answer to give, and a recording that travelled without its base must be
205
+ // able to say so.
206
+ const seed = (0, trace_1.stateAt)(source, -1);
207
+ const index = indexTouches(log);
208
+ const answers = new Map();
209
+ /** Per key, the furthest replay position reached and the value there — see
210
+ * the forward-cursor note in `compute`. */
211
+ const cursor = new Map();
212
+ /** One touch applied, through the ENGINE's verb switch, over a state
213
+ * projected to this key alone. `applySmartMerge` navigates from the root,
214
+ * so a delimited path lands exactly where it did in the real fold. */
215
+ const step = (key, value, touch) => (0, advanced_1.applySmartMerge)({ [key]: value }, touch.bundle.updates, touch.bundle.overwrite, [
216
+ touch.entry,
217
+ ])[key];
218
+ const compute = (key, idx) => {
219
+ const touches = index.get(key);
220
+ const last = touches === undefined ? -1 : lastTouchAt(touches, idx);
221
+ // The base's own value. `stateAt` already deep-freezes what it returns, so
222
+ // this is a no-op today — asserted rather than assumed, because the law
223
+ // this module states about its answers must not rest on another module
224
+ // continuing to keep it.
225
+ if (touches === undefined || last < 0)
226
+ return freezeDeep(seed.state[key]);
227
+ // Anchor at the latest write that fully determines the key on its own;
228
+ // everything before it — the base included — cannot be read back through
229
+ // it. With no anchor the base IS the starting value, which is the whole
230
+ // fix: `commitValueAt` starts from `undefined` here and folds a fiction.
231
+ let start = 0;
232
+ let anchored = false;
233
+ for (let i = last; i >= 0; i--) {
234
+ if (touches[i].anchors) {
235
+ start = i;
236
+ anchored = true;
237
+ break;
238
+ }
239
+ }
240
+ let from = start;
241
+ let value = anchored ? undefined : seed.state[key];
242
+ // THE FORWARD CURSOR, and why this module would be slower than the reader
243
+ // it replaces without it. An agent's `history` is `set` ONCE and appended
244
+ // to every turn, so a query at turn k has to replay k appends from the
245
+ // anchor — and a per-epoch scrub asks E times, which is O(E²) replays of a
246
+ // growing array. Resuming from the furthest position already reached makes
247
+ // the whole scrub O(E) replays instead, because a scrub walks forward.
248
+ // (Measured on a 96-turn run: 201 ms per scrub without this, 40 ms with.)
249
+ // A backward or out-of-order query simply falls back to the anchor, which
250
+ // is correct and no worse than never having cached anything.
251
+ const held = cursor.get(key);
252
+ if (held !== undefined && held.position >= start && held.position <= last) {
253
+ from = held.position + 1;
254
+ value = held.value;
255
+ }
256
+ for (let i = from; i <= last; i++)
257
+ value = step(key, value, touches[i]);
258
+ // Frozen HERE, before anything else can hold it: the cursor keeps this very
259
+ // object as the seed for every later index, and `valueAt` memoizes it. One
260
+ // freeze covers both — `keyedFold.ts` · `freezeDeep`.
261
+ freezeDeep(value);
262
+ cursor.set(key, { position: last, value });
263
+ return value;
264
+ };
265
+ const fold = {
266
+ basis: seed.basis,
267
+ length: log.length,
268
+ valueAt(key, idx) {
269
+ const at = Math.min(Math.floor(idx), log.length - 1);
270
+ let perKey = answers.get(key);
271
+ if (perKey === undefined) {
272
+ perKey = new Map();
273
+ answers.set(key, perKey);
274
+ }
275
+ if (perKey.has(at))
276
+ return perKey.get(at);
277
+ const value = compute(key, at);
278
+ perKey.set(at, value);
279
+ return value;
280
+ },
281
+ };
282
+ // The fold OBJECT is memoized too — the same one every caller of `keyedFold`
283
+ // gets for this source — so it is frozen for the same reason its answers are.
284
+ Object.freeze(fold);
285
+ if (source !== null && typeof source === 'object')
286
+ FOLDS.set(source, fold);
287
+ return fold;
288
+ }
289
+ exports.keyedFold = keyedFold;
290
+ //# sourceMappingURL=keyedFold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyedFold.js","sourceRoot":"","sources":["../../../src/lib/time-travel/keyedFold.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;;;AAEH,mDAAuD;AACvD,6CAA4C;AA0B5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B,uDAAuD;AACvD,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAuCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,SAAS,UAAU,CAAI,KAAQ;IAC7B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAC;QACtD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,oEAAoE;AACpE,SAAS,SAAS,CAAC,MAAkC;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC;IAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,OAAkC,CAAC;IACpF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,8DAA8D;AAC9D,SAAS,YAAY,CAAC,GAA4B;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB,CAAC;IACzC,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACxB,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACvC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACxB,MAAM,KAAK,GAAU;gBACnB,EAAE;gBACF,MAAM;gBACN,KAAK;gBACL,OAAO,EAAE,IAAI,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,QAAQ,CAAC;aAC/D,CAAC;YACF,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;;gBAC3C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,OAAyB,EAAE,GAAW;IACzD,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;IACf,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,OAAO,CAAC,GAAG,CAAE,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC;YAC5B,KAAK,GAAG,GAAG,CAAC;YACZ,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;QACf,CAAC;aAAM,CAAC;YACN,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6CAA6C;AAC7C,MAAM,KAAK,GAAG,IAAI,OAAO,EAAqB,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,SAAS,CAAC,MAAkC;IAC1D,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,6EAA6E;IAC7E,6EAA6E;IAC7E,wEAAwE;IACxE,0EAA0E;IAC1E,kBAAkB;IAClB,MAAM,IAAI,GAAG,IAAA,eAAO,EAAC,MAAe,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgC,CAAC;IACxD;gDAC4C;IAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAgD,CAAC;IAEvE;;2EAEuE;IACvE,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,KAAY,EAAW,EAAE,CAEhE,IAAA,0BAAe,EAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE;QAC9E,KAAK,CAAC,KAAK;KACZ,CACF,CAAC,GAAG,CAAC,CAAC;IAET,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,GAAW,EAAW,EAAE;QACpD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpE,2EAA2E;QAC3E,wEAAwE;QACxE,uEAAuE;QACvE,yBAAyB;QACzB,IAAI,OAAO,KAAK,SAAS,IAAI,IAAI,GAAG,CAAC;YAAE,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAE1E,uEAAuE;QACvE,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,CAAC;gBACxB,KAAK,GAAG,CAAC,CAAC;gBACV,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,KAAK,GAAY,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE5D,0EAA0E;QAC1E,0EAA0E;QAC1E,uEAAuE;QACvE,2EAA2E;QAC3E,2EAA2E;QAC3E,uEAAuE;QACvE,0EAA0E;QAC1E,0EAA0E;QAC1E,6DAA6D;QAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;YAC1E,IAAI,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACzB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;YAAE,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;QACzE,4EAA4E;QAC5E,2EAA2E;QAC3E,sDAAsD;QACtD,UAAU,CAAC,KAAK,CAAC,CAAC;QAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,IAAI,GAAc;QACtB,KAAK,EAAE,IAAI,CAAC,KAAkB;QAC9B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,CAAC,GAAW,EAAE,GAAW;YAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;gBACpC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC3B,CAAC;YACD,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACtB,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;IAEF,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC;AAnGD,8BAmGC"}
@@ -9,16 +9,26 @@
9
9
  * it), and `conventions.ts` · `milestoneFor` for the domain vocabulary.
10
10
  * Emits: N/A.
11
11
  *
12
- * WHY THIS FILE EXISTS. footprintjs 9.17 ships the reader's cursor
13
- * (`timeTravel`) and one stop grammar — `commitStops`, one stop per executed
14
- * stage — because that is the only grammar the substrate itself knows. It does
15
- * NOT know what an LLM turn is. agentfootprint does: `milestoneFor` has
16
- * classified a local stage id into an iteration / slot / llm-turn / tool-call /
17
- * decision since 9.x, and until now every consumer that wanted a milestone
18
- * slider mapped that classifier onto commits by hand. The Why Lens did it one
19
- * way; anyone else with a recording had to write it again. This is that
20
- * mapping, once, on the strategy seam the port opened — so the agent SUPPLIES
21
- * the stops for its own runs and every reader gets the same axis.
12
+ * WHY THIS FILE EXISTS. footprintjs ships the reader's cursor (`timeTravel`)
13
+ * and one stop grammar — `commitStops`, one stop per executed stage — because
14
+ * that is the only grammar the substrate itself knows. It does NOT know what an
15
+ * LLM turn is. agentfootprint does: `milestoneFor` has classified a local stage
16
+ * id into an iteration / slot / llm-turn / tool-call / decision since 9.x, and
17
+ * until 9.87 every consumer that wanted a milestone slider mapped that
18
+ * classifier onto commits by hand. This is that mapping, once, on the strategy
19
+ * seam the port opened — so the agent SUPPLIES the stops for its own runs and
20
+ * every reader gets the same axis.
21
+ *
22
+ * WHAT IT IS (9.89.0). A FILTER over the port's own axis. footprintjs 9.18
23
+ * shipped `filterStops(stops, keep)` — the bookend guard, the re-partition and
24
+ * a `meta` slot on the stop — because two consumers had each re-derived all
25
+ * three by hand against 9.17, this file among them. The hand-rolled copies are
26
+ * gone: the one owner of the `[start, …stages, end]` contract is the library
27
+ * that returns it, and this strategy is one expression over it. The axis is
28
+ * byte-for-byte the 9.88.0 axis (`test/lib/time-travel/milestone-stops-equivalence.test.ts` pins
29
+ * that against a verbatim copy of the 9.88.0 implementation); what is new is that the milestone now RIDES on the
30
+ * stop as `meta`, and that a start which absorbed pre-milestone stages says so
31
+ * with `prologue: true`.
22
32
  *
23
33
  * WHAT IT IS NOT. Not a second cursor. A strategy only says where the one
24
34
  * cursor may rest; `timeTravel` still owns the position, the folds and the
@@ -30,19 +40,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
30
40
  exports.milestoneStopsStrategy = exports.milestoneStops = exports.milestoneOf = void 0;
31
41
  const trace_1 = require("footprintjs/trace");
32
42
  const conventions_js_1 = require("../../conventions.js");
43
+ const MILESTONE_KINDS = [
44
+ 'iteration',
45
+ 'slot',
46
+ 'llm-turn',
47
+ 'tool-call',
48
+ 'decision',
49
+ ];
50
+ /** Is this `meta` a {@link Milestone} — ours, not another strategy's? */
51
+ function isMilestone(meta) {
52
+ if (meta === null || typeof meta !== 'object')
53
+ return false;
54
+ const { kind, label } = meta;
55
+ return typeof label === 'string' && MILESTONE_KINDS.includes(kind);
56
+ }
33
57
  /**
34
58
  * The milestone a stop stands for, or `null` when it stands for none (the
35
59
  * `'start'` / `'end'` bookends).
36
60
  *
37
- * WHY A FUNCTION AND NOT A FIELD. footprintjs's `Stop` is a closed shape —
38
- * `step`, `runtimeStageId`, `commitIdx`, `lastCommitIdx`, `stageId`,
39
- * `subflowPath`, `label`, `kind` — with no extension slot a strategy may write
40
- * its own vocabulary into, and `kind` is the port's own `StopKind`
41
- * (`'commit' | 'mount' | 'start' | 'end'`), not ours to overload. So the
42
- * milestone kind travels the only way it honestly can: re-derived from the
43
- * stop's `runtimeStageId`, by the same classifier that put the stop on the
44
- * axis. Same input, same function, same answer — there is no second source of
45
- * truth here, only a second reading of the one there is.
61
+ * A stop that {@link milestoneStops} made carries its milestone as
62
+ * `stop.meta`, and that is what is read — the classification the strategy
63
+ * made when it put the stop on the axis, not a second run of the classifier.
64
+ * A stop from ANOTHER strategy — the port's own `commitStops`, or a consumer's
65
+ * filter that kept the stop without a milestone meta — has none, so the answer
66
+ * falls back to where it always came from: `milestoneFor` over the stop's
67
+ * `runtimeStageId`. Same classifier, same answer; a `meta` of some other
68
+ * vocabulary is not mistaken for ours.
46
69
  *
47
70
  * @example
48
71
  * ```ts
@@ -51,6 +74,8 @@ const conventions_js_1 = require("../../conventions.js");
51
74
  * ```
52
75
  */
53
76
  function milestoneOf(stop) {
77
+ if (isMilestone(stop.meta))
78
+ return stop.meta;
54
79
  return stop.runtimeStageId ? (0, conventions_js_1.milestoneFor)(stop.runtimeStageId) : null;
55
80
  }
56
81
  exports.milestoneOf = milestoneOf;
@@ -66,9 +91,11 @@ exports.milestoneOf = milestoneOf;
66
91
  * parallel fork child commits twice and siblings interleave — every repeat is
67
92
  * an empty bundle), the authoritative mount set read off the execution tree,
68
93
  * the `'start'` / `'end'` bookends, and the id-less leading commit that carries
69
- * a subflow's `inputMapper` seed. This function calls `commitStops` and filters
70
- * its result. A second implementation of that collapsing would be a second
71
- * chance to disagree with the library about what a stage is.
94
+ * a subflow's `inputMapper` seed. And since 9.89.0 the COMPOSITION itself is
95
+ * the port's too: `filterStops` checks the `[start, …stages, end]` shape,
96
+ * keeps what `keep` keeps, and re-partitions the log. A second implementation
97
+ * of any of that would be a second chance to disagree with the library about
98
+ * what a stage is.
72
99
  *
73
100
  * **The survivors re-partition the log.** A stage that classifies `null` is not
74
101
  * a stop, but its commits still happened, so they are folded into the stop that
@@ -79,14 +106,21 @@ exports.milestoneOf = milestoneOf;
79
106
  * so on a drilled cursor `'start'` reads as "what this subflow began with,
80
107
  * after its plumbing ran".
81
108
  *
82
- * **What that costs `'start'`.** On footprintjs's own axis `'start'` is the fold
83
- * base: the state before ANY stage ran. Here it absorbs every stage that ran
84
- * before the first milestone, so `stateAt(start)` is the state the first
85
- * milestone READ, not the run's raw base — a real agent seeds a couple of dozen
86
- * keys in `seed` before anything a reader would scrub to. That is the right
87
- * answer for this axis (the stops must still partition the log) and the wrong
88
- * one to assume from `kind: 'start'` alone, so it is said out loud here, in the
89
- * folder README, on the docs page and in a test.
109
+ * **What that costs `'start'`, and the flag that says so.** On footprintjs's
110
+ * own axis `'start'` is the fold base: the state before ANY stage ran. Here it
111
+ * absorbs every stage that ran before the first milestone, so `stateAt(start)`
112
+ * is the state the first milestone READ, not the run's raw base — a real agent
113
+ * seeds a couple of dozen keys in `seed` before anything a reader would scrub
114
+ * to. That is the right answer for this axis (the stops must still partition
115
+ * the log), and since 9.18 the port marks it: the returned start carries
116
+ * `prologue: true` whenever it absorbed a stage, so a renderer that means
117
+ * "before anything ran" checks `kind === 'start' && !prologue` instead of
118
+ * assuming it from the kind.
119
+ *
120
+ * **The milestone rides on the stop.** Every kept stop carries its
121
+ * classification as `meta` — `Stop<Milestone>` — so a reader asks
122
+ * `stop.meta?.kind` (or {@link milestoneOf}, which reads the same slot) rather
123
+ * than re-running the classifier over the id. The bookends carry none.
90
124
  *
91
125
  * **A log with no milestones in it at all.** A non-empty log the classifier
92
126
  * recognises nothing in — a non-agent chart handed this strategy — yields the
@@ -114,71 +148,30 @@ exports.milestoneOf = milestoneOf;
114
148
  * @example
115
149
  * ```ts
116
150
  * import { timeTravel } from 'footprintjs/trace';
117
- * import { milestoneStopsStrategy, milestoneOf } from 'agentfootprint';
151
+ * import { milestoneStopsStrategy } from 'agentfootprint';
118
152
  *
119
153
  * const cursor = timeTravel(agent.getSnapshot()!, { strategy: milestoneStopsStrategy });
120
- * cursor.stops.map((s) => [s.label, milestoneOf(s)?.kind]);
154
+ * cursor.stops.map((s) => [s.label, s.meta?.kind]);
121
155
  * // measured on a two-turn `dynamic` run — 42 commits, 15 stops:
122
156
  * // [['Run start', undefined], ['Iteration', 'iteration'],
123
157
  * // ['System prompt', 'slot'], ['Messages', 'slot'], ['Tools', 'slot'],
124
158
  * // ['LLM turn', 'llm-turn'], ['Route', 'decision'], ['Tool call', 'tool-call'],
125
159
  * // … the second turn …, ['Run end', undefined]]
160
+ * cursor.stops[0].prologue; // true — `seed` and the plumbing ran before the first Iteration
126
161
  * ```
127
162
  */
128
163
  function milestoneStops(commitLog, executionTree) {
129
- const perStage = (0, trace_1.commitStops)(commitLog, executionTree);
130
- if (perStage.length === 0)
131
- return [];
132
- // `commitStops` guarantees the shape [start, …stages, end] for a non-empty
133
- // log. The bookends are kept verbatim in KIND and re-partitioned below; only
134
- // the stages in between are filtered.
135
- //
136
- // CHECK THE PROPERTY, NOT MERE PRESENCE. `Stop[]` does not pin that shape in
137
- // the type, and everything below rests on it: `'start'` is the only stop that
138
- // may open at `-1` (the fold base no commit index reaches) and `'end'` is the
139
- // only one that folds the whole log. If a future `commitStops` returned some
140
- // other shape, a presence check would take the first and last STAGE stops for
141
- // bookends — the first would silently inherit start's `-1` arithmetic and keep
142
- // its raw stage label, and the last milestone would lose its milestone label.
143
- // Refusing loudly is the honest answer: an empty axis would be
144
- // indistinguishable from "this run committed nothing", which is a different
145
- // fact about a different run.
146
- const [start, ...rest] = perStage;
147
- const end = rest.pop();
148
- if (!start || start.kind !== 'start' || !end || end.kind !== 'end') {
149
- throw new Error('milestoneStops: commitStops returned an unexpected shape — expected ' +
150
- `[start, …stages, end], got kinds [${perStage.map((s) => s.kind).join(', ')}]. ` +
151
- 'This is a footprintjs contract change, not something a run can cause.');
152
- }
153
- const kept = rest
154
- .map((stop) => ({ stop, milestone: (0, conventions_js_1.milestoneFor)(stop.runtimeStageId) }))
155
- .filter((row) => row.milestone !== null);
156
- const first = kept[0];
157
- const stops = [
158
- {
159
- ...start,
160
- step: 0,
161
- // Everything before the first milestone folds into `'start'`.
162
- lastCommitIdx: (first ? first.stop.commitIdx : commitLog.length) - 1,
163
- },
164
- ];
165
- for (const [i, { stop, milestone }] of kept.entries()) {
166
- const next = kept[i + 1];
167
- stops.push({
168
- ...stop,
169
- step: stops.length,
170
- // Absorb the non-milestone stages that ran after this one.
171
- lastCommitIdx: next ? next.stop.commitIdx - 1 : commitLog.length - 1,
172
- label: milestone.label,
173
- });
174
- }
175
- stops.push({ ...end, step: stops.length });
176
- return stops;
164
+ return (0, trace_1.filterStops)((0, trace_1.commitStops)(commitLog, executionTree), (stop) => {
165
+ const milestone = (0, conventions_js_1.milestoneFor)(stop.runtimeStageId);
166
+ return milestone ? { label: milestone.label, meta: milestone } : null;
167
+ });
177
168
  }
178
169
  exports.milestoneStops = milestoneStops;
179
170
  /**
180
171
  * `milestoneStops` as a footprintjs `TimeTravelStrategy` — what you hand
181
- * `timeTravel(snapshot, { strategy })`.
172
+ * `timeTravel(snapshot, { strategy })`. Typed over {@link Milestone}, so the
173
+ * cursor's stops are `Stop<Milestone>` and `cursor.at()?.meta?.kind` is typed;
174
+ * it is still assignable wherever a bare `TimeTravelStrategy` is expected.
182
175
  */
183
176
  exports.milestoneStopsStrategy = { stopsFor: milestoneStops };
184
177
  //# sourceMappingURL=milestoneStops.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"milestoneStops.js","sourceRoot":"","sources":["../../../src/lib/time-travel/milestoneStops.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"milestoneStops.js","sourceRoot":"","sources":["../../../src/lib/time-travel/milestoneStops.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;;;AAGH,6CAA6D;AAE7D,yDAAwF;AAExF,MAAM,eAAe,GAA6B;IAChD,WAAW;IACX,MAAM;IACN,UAAU;IACV,WAAW;IACX,UAAU;CACX,CAAC;AAEF,yEAAyE;AACzE,SAAS,WAAW,CAAC,IAAa;IAChC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAA2C,CAAC;IACpE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,eAAsC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,WAAW,CAAC,IAAmB;IAC7C,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IAC7C,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAA,6BAAY,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC;AAHD,kCAGC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,SAAgB,cAAc,CAC5B,SAAkC,EAClC,aAA6B;IAE7B,OAAO,IAAA,mBAAW,EAAY,IAAA,mBAAW,EAAC,SAAS,EAAE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE;QAC5E,MAAM,SAAS,GAAG,IAAA,6BAAY,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,OAAO,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC;AARD,wCAQC;AAED;;;;;GAKG;AACU,QAAA,sBAAsB,GAAkC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC"}