@you-agent-factory/factory-replay 0.0.0 → 0.0.2

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/src/index.js DELETED
@@ -1,477 +0,0 @@
1
- import { projectFactoryTopology } from "./topology.js";
2
- import { projectFactoryActivity } from "./activity.js";
3
- import { projectFactoryWorkProgress } from "./progress.js";
4
-
5
- export { projectFactoryTopology } from "./topology.js";
6
- export { projectFactoryActivity } from "./activity.js";
7
- export { projectFactoryWorkProgress } from "./progress.js";
8
-
9
- /**
10
- * Compare Factory events using the established dashboard replay order.
11
- *
12
- * The event timestamp and id remain deterministic tie-breakers for events
13
- * that share one logical tick and sequence.
14
- *
15
- * @param {import("@you-agent-factory/client").FactoryEvent} left
16
- * @param {import("@you-agent-factory/client").FactoryEvent} right
17
- * @returns {number}
18
- */
19
- export function compareFactoryEvents(left, right) {
20
- if (left.context.tick !== right.context.tick) {
21
- return left.context.tick - right.context.tick;
22
- }
23
- if (left.context.sequence !== right.context.sequence) {
24
- return left.context.sequence - right.context.sequence;
25
- }
26
- if (left.context.eventTime !== right.context.eventTime) {
27
- return left.context.eventTime.localeCompare(right.context.eventTime);
28
- }
29
- return left.id.localeCompare(right.id);
30
- }
31
-
32
- /**
33
- * Return the canonical accepted Factory event history without mutating input.
34
- * The first event in canonical order owns a duplicate id.
35
- *
36
- * @param {readonly import("@you-agent-factory/client").FactoryEvent[]} events
37
- * @returns {import("@you-agent-factory/client").FactoryEvent[]}
38
- */
39
- export function canonicalizeFactoryEvents(events) {
40
- const acceptedIDs = new Set();
41
- return [...events]
42
- .sort(compareFactoryEvents)
43
- .filter((event) => {
44
- if (acceptedIDs.has(event.id)) {
45
- return false;
46
- }
47
- acceptedIDs.add(event.id);
48
- return true;
49
- });
50
- }
51
-
52
- /**
53
- * Build one deterministic Factory-event replay result for current or fixed
54
- * logical-tick selection. The reducer owns Factory domain interpretation;
55
- * this kernel owns canonical ordering, id acceptance, and tick selection.
56
- *
57
- * @template State, World
58
- * @param {import("./index.d.ts").FactoryReplayInitialization<State, World>} input
59
- * @returns {import("./index.d.ts").FactoryReplayResult<State, World>}
60
- */
61
- export function initializeFactoryReplay(input) {
62
- const events = canonicalizeFactoryEvents(input.events);
63
- const latestTick = events.reduce(
64
- (latest, event) => Math.max(latest, event.context.tick),
65
- 0,
66
- );
67
- const selectedTick =
68
- input.selection.mode === "current" ? latestTick : input.selection.tick;
69
- const appliedEvents = events.filter(
70
- (event) => event.context.tick <= selectedTick,
71
- );
72
- let state = input.reducer.createState(selectedTick);
73
- for (const event of appliedEvents) {
74
- state = input.reducer.applyEvent(state, event);
75
- }
76
-
77
- return {
78
- appliedEvents,
79
- events,
80
- latestTick,
81
- selectedTick,
82
- selection: input.selection,
83
- state,
84
- world: input.reducer.projectWorld(state),
85
- };
86
- }
87
-
88
- /**
89
- * Create an independent checkpoint from an already projected replay result.
90
- * The caller provides cloning because the kernel cannot know the shape of a
91
- * domain-specific replay state.
92
- *
93
- * @template State, World
94
- * @param {import("./index.d.ts").FactoryReplayResult<State, World>} result
95
- * @param {import("./index.d.ts").FactoryReplayStateCloner<State>} cloneState
96
- * @returns {import("./index.d.ts").FactoryReplayCheckpoint<State>}
97
- */
98
- export function createFactoryReplayCheckpoint(result, cloneState) {
99
- return {
100
- acceptedEventIDs: result.appliedEvents.map((event) => event.id),
101
- selectedTick: result.selectedTick,
102
- state: cloneState(result.state),
103
- };
104
- }
105
-
106
- /**
107
- * Advance an immutable replay checkpoint with an accepted event tail.
108
- * Only previously unseen events at or before the target tick are applied.
109
- * Event IDs, rather than the checkpoint tick, define what has already been
110
- * applied so later canonical events at the checkpoint's tick are retained.
111
- * The returned checkpoint is independently cloned so callers can retain it
112
- * for another historical reconstruction.
113
- *
114
- * @template State, World
115
- * @param {import("./index.d.ts").FactoryReplayAdvanceInput<State, World>} input
116
- * @returns {import("./index.d.ts").FactoryReplayAdvanceResult<State, World>}
117
- */
118
- export function advanceFactoryReplay(input) {
119
- const acceptedEventIDs = new Set(input.checkpoint.acceptedEventIDs);
120
- const appliedEvents = canonicalizeFactoryEvents(input.events).filter(
121
- (event) => {
122
- if (
123
- acceptedEventIDs.has(event.id) ||
124
- event.context.tick > input.tick
125
- ) {
126
- return false;
127
- }
128
- acceptedEventIDs.add(event.id);
129
- return true;
130
- },
131
- );
132
- let state = input.setSelectedTick(
133
- input.cloneState(input.checkpoint.state),
134
- input.tick,
135
- );
136
- for (const event of appliedEvents) {
137
- state = input.reducer.applyEvent(state, event);
138
- }
139
-
140
- return {
141
- appliedEvents,
142
- checkpoint: {
143
- acceptedEventIDs: [...acceptedEventIDs],
144
- selectedTick: input.tick,
145
- state: input.cloneState(state),
146
- },
147
- latestTick: appliedEvents.reduce(
148
- (latest, event) => Math.max(latest, event.context.tick),
149
- input.checkpoint.selectedTick,
150
- ),
151
- selectedTick: input.tick,
152
- state,
153
- world: input.reducer.projectWorld(state),
154
- };
155
- }
156
-
157
- /**
158
- * Reconstruct the canonical Factory world at one explicit logical tick.
159
- *
160
- * @template State, World
161
- * @param {Omit<import("./index.d.ts").FactoryReplayInitialization<State, World>, "selection"> & { tick: number }} input
162
- * @returns {import("./index.d.ts").FactoryReplayResult<State, World>}
163
- */
164
- export function projectFactoryWorldAtTick(input) {
165
- return initializeFactoryReplay({
166
- ...input,
167
- selection: { mode: "fixed", tick: input.tick },
168
- });
169
- }
170
-
171
- /**
172
- * Reconstruct and project the public Factory topology at one logical tick.
173
- * Only canonical topology replacement events participate in this projection.
174
- *
175
- * @param {import("./index.d.ts").FactoryTopologyAtTickInput} input
176
- * @returns {import("./index.d.ts").FactoryTopologyProjection}
177
- */
178
- export function projectFactoryTopologyAtTick(input) {
179
- const events = canonicalizeFactoryEvents(input.events).filter(
180
- (event) => event.context.tick <= input.tick,
181
- );
182
- /** @type {import("@you-agent-factory/client").FactoryDefinition | undefined} */
183
- let factory;
184
- for (const event of events) {
185
- if (
186
- event.type !== "INITIAL_STRUCTURE_REQUEST" &&
187
- event.type !== "FACTORY_CHANGE"
188
- ) {
189
- continue;
190
- }
191
- const payload = event.payload;
192
- if (
193
- payload &&
194
- typeof payload === "object" &&
195
- "factory" in payload &&
196
- payload.factory &&
197
- typeof payload.factory === "object"
198
- ) {
199
- factory = payload.factory;
200
- }
201
- }
202
- return projectFactoryTopology({ factory, selectedTick: input.tick });
203
- }
204
-
205
- /**
206
- * Reconstruct active customer Dispatches and resource occupancy at one tick.
207
- * A response removes its matching request in canonical sequence order, even
208
- * when both events share a logical tick.
209
- *
210
- * @param {import("./index.d.ts").FactoryActivityAtTickInput} input
211
- * @returns {import("./index.d.ts").FactoryActivityProjection}
212
- */
213
- export function projectFactoryActivityAtTick(input) {
214
- const events = canonicalizeFactoryEvents(input.events).filter(
215
- (event) => event.context.tick <= input.tick,
216
- );
217
- let factory;
218
- const activeDispatches = new Map();
219
- for (const event of events) {
220
- if (
221
- event.type === "INITIAL_STRUCTURE_REQUEST" ||
222
- event.type === "FACTORY_CHANGE"
223
- ) {
224
- const payload = event.payload;
225
- if (
226
- payload &&
227
- typeof payload === "object" &&
228
- "factory" in payload &&
229
- payload.factory &&
230
- typeof payload.factory === "object"
231
- ) {
232
- factory = payload.factory;
233
- }
234
- continue;
235
- }
236
- if (event.type === "DISPATCH_RESPONSE") {
237
- if (event.context.dispatchId) {
238
- activeDispatches.delete(event.context.dispatchId);
239
- }
240
- continue;
241
- }
242
- if (event.type !== "DISPATCH_REQUEST" || !event.context.dispatchId) {
243
- continue;
244
- }
245
- const payload = event.payload;
246
- if (
247
- !payload ||
248
- typeof payload !== "object" ||
249
- !("transitionId" in payload) ||
250
- typeof payload.transitionId !== "string" ||
251
- payload.transitionId.startsWith("__system_time:")
252
- ) {
253
- continue;
254
- }
255
- const resources =
256
- "resources" in payload && Array.isArray(payload.resources)
257
- ? payload.resources
258
- : undefined;
259
- activeDispatches.set(event.context.dispatchId, {
260
- id: event.context.dispatchId,
261
- ...(resources
262
- ? {
263
- resourceNames: resources.flatMap((resource) =>
264
- resource &&
265
- typeof resource === "object" &&
266
- "name" in resource &&
267
- typeof resource.name === "string"
268
- ? [resource.name]
269
- : [],
270
- ),
271
- }
272
- : {}),
273
- startedTick: event.context.tick,
274
- transitionId: payload.transitionId,
275
- workIds: [...(event.context.workIds ?? [])],
276
- });
277
- }
278
- return projectFactoryActivity({
279
- activeDispatches: [...activeDispatches.values()],
280
- factory,
281
- selectedTick: input.tick,
282
- });
283
- }
284
-
285
- /**
286
- * Reconstruct and exclusively classify customer Work at one logical tick.
287
- * Canonical sequence order owns same-tick lifecycle and Dispatch changes.
288
- *
289
- * @param {import("./index.d.ts").FactoryWorkProgressAtTickInput} input
290
- * @returns {import("./index.d.ts").FactoryWorkProgressProjection}
291
- */
292
- export function projectFactoryWorkProgressAtTick(input) {
293
- const events = canonicalizeFactoryEvents(input.events).filter(
294
- (event) => event.context.tick <= input.tick,
295
- );
296
- /** @type {import("@you-agent-factory/client").FactoryDefinition | undefined} */
297
- let factory;
298
- const works = new Map();
299
- const initialWorkIds = new Set();
300
- const activeDispatches = new Map();
301
- for (const event of events) {
302
- if (
303
- event.type === "INITIAL_STRUCTURE_REQUEST" ||
304
- event.type === "FACTORY_CHANGE"
305
- ) {
306
- const payload = objectPayload(event.payload);
307
- if (payload?.factory && typeof payload.factory === "object") {
308
- factory = /** @type {import("@you-agent-factory/client").FactoryDefinition} */ (
309
- payload.factory
310
- );
311
- }
312
- continue;
313
- }
314
- if (event.type === "WORK_REQUEST") {
315
- const payload = objectPayload(event.payload);
316
- for (const work of Array.isArray(payload?.works) ? payload.works : []) {
317
- mergeEventWork(works, work);
318
- const record = objectPayload(work);
319
- if (
320
- typeof record?.workId === "string" &&
321
- !objectPayload(record.state)
322
- ) {
323
- initialWorkIds.add(record.workId);
324
- }
325
- }
326
- continue;
327
- }
328
- if (event.type === "WORK_STATE_CHANGE") {
329
- const payload = objectPayload(event.payload);
330
- if (typeof payload?.workId !== "string" || !payload.workId) continue;
331
- const previous = works.get(payload.workId) ?? { id: payload.workId };
332
- initialWorkIds.delete(payload.workId);
333
- const state =
334
- typeof payload.toState === "string"
335
- ? { name: payload.toState }
336
- : undefined;
337
- works.set(payload.workId, {
338
- ...previous,
339
- ...(typeof payload.workTypeName === "string"
340
- ? { workTypeId: payload.workTypeName }
341
- : {}),
342
- ...(state ? { state } : {}),
343
- });
344
- continue;
345
- }
346
- if (event.type === "DISPATCH_REQUEST" && event.context.dispatchId) {
347
- const payload = objectPayload(event.payload);
348
- if (
349
- typeof payload?.transitionId === "string" &&
350
- !payload.transitionId.startsWith("__system_time:")
351
- ) {
352
- const inputIds = Array.isArray(payload.inputs)
353
- ? payload.inputs.flatMap((input) => {
354
- const record = objectPayload(input);
355
- return typeof record?.workId === "string"
356
- ? [record.workId]
357
- : [];
358
- })
359
- : [];
360
- activeDispatches.set(event.context.dispatchId, [
361
- ...new Set([...(event.context.workIds ?? []), ...inputIds]),
362
- ]);
363
- }
364
- continue;
365
- }
366
- if (event.type !== "DISPATCH_RESPONSE" || !event.context.dispatchId) {
367
- continue;
368
- }
369
- const consumedIds = activeDispatches.get(event.context.dispatchId) ?? [];
370
- activeDispatches.delete(event.context.dispatchId);
371
- const payload = objectPayload(event.payload);
372
- const outputWorks = Array.isArray(payload?.outputWork)
373
- ? payload.outputWork
374
- : [];
375
- const outputIds = new Set(
376
- outputWorks.flatMap((work) => {
377
- const record = objectPayload(work);
378
- return typeof record?.workId === "string" ? [record.workId] : [];
379
- }),
380
- );
381
- for (const workId of consumedIds) {
382
- const previous = works.get(workId);
383
- if (!previous || outputIds.has(workId)) continue;
384
- initialWorkIds.delete(workId);
385
- const category = previous.state?.category;
386
- if (category !== "FAILED" && category !== "TERMINAL") {
387
- works.set(workId, {
388
- id: previous.id,
389
- ...(previous.workTypeId ? { workTypeId: previous.workTypeId } : {}),
390
- });
391
- }
392
- }
393
- for (const work of outputWorks) {
394
- mergeEventWork(works, work);
395
- const record = objectPayload(work);
396
- if (typeof record?.workId === "string") {
397
- initialWorkIds.delete(record.workId);
398
- }
399
- }
400
- }
401
-
402
- return projectFactoryWorkProgress({
403
- activeWorkIds: [...activeDispatches.values()].flat(),
404
- factory,
405
- selectedTick: input.tick,
406
- works: [...works.values()].map((work) => {
407
- const initialState = initialWorkIds.has(work.id)
408
- ? initialStateForWork(factory, work.workTypeId)
409
- : undefined;
410
- return initialState ? { ...work, state: initialState } : work;
411
- }),
412
- });
413
- }
414
-
415
- /**
416
- * @param {import("@you-agent-factory/client").FactoryDefinition | undefined} factory
417
- * @param {string | undefined} workTypeId
418
- * @returns {import("./index.d.ts").FactoryWorkProgressStateEvidence | undefined}
419
- */
420
- function initialStateForWork(factory, workTypeId) {
421
- if (!workTypeId) return undefined;
422
- const workType = (factory?.workTypes ?? []).find(
423
- (candidate) => candidate.name === workTypeId || candidate.id === workTypeId,
424
- );
425
- const state = workType?.states.find((candidate) => candidate.type === "INITIAL");
426
- return state
427
- ? {
428
- category: state.type,
429
- ...(state.id ? { id: state.id } : {}),
430
- name: state.name,
431
- }
432
- : undefined;
433
- }
434
-
435
- /** @param {unknown} payload @returns {Record<string, unknown> | undefined} */
436
- function objectPayload(payload) {
437
- return payload && typeof payload === "object"
438
- ? /** @type {Record<string, unknown>} */ (payload)
439
- : undefined;
440
- }
441
-
442
- /** @param {Map<string, import("./index.d.ts").FactoryWorkProgressEvidence>} works @param {unknown} value */
443
- function mergeEventWork(works, value) {
444
- const record = objectPayload(value);
445
- if (typeof record?.workId !== "string" || !record.workId) return;
446
- const previous = works.get(record.workId) ?? { id: record.workId };
447
- const stateRecord = objectPayload(record.state);
448
- const state =
449
- stateRecord
450
- ? {
451
- ...(typeof stateRecord.id === "string" ? { id: stateRecord.id } : {}),
452
- ...(typeof stateRecord.name === "string"
453
- ? { name: stateRecord.name }
454
- : {}),
455
- ...(isWorkStateCategory(stateRecord.type)
456
- ? { category: stateRecord.type }
457
- : {}),
458
- }
459
- : previous.state;
460
- works.set(record.workId, {
461
- ...previous,
462
- ...(typeof record.workTypeName === "string"
463
- ? { workTypeId: record.workTypeName }
464
- : {}),
465
- ...(state ? { state } : {}),
466
- });
467
- }
468
-
469
- /** @param {unknown} value @returns {value is import("./index.d.ts").FactoryWorkStateCategory} */
470
- function isWorkStateCategory(value) {
471
- return (
472
- value === "FAILED" ||
473
- value === "INITIAL" ||
474
- value === "PROCESSING" ||
475
- value === "TERMINAL"
476
- );
477
- }
package/src/progress.js DELETED
@@ -1,117 +0,0 @@
1
- const SYSTEM_WORK_TYPE_ID = "__system_time";
2
-
3
- /**
4
- * Partition customer Work into one mutually exclusive progress category.
5
- *
6
- * @param {import("./index.d.ts").FactoryWorkProgressProjectionInput} input
7
- * @returns {import("./index.d.ts").FactoryWorkProgressProjection}
8
- */
9
- export function projectFactoryWorkProgress(input) {
10
- const statesByWorkType = indexWorkStates(input.factory);
11
- const activeWorkIds = new Set(input.activeWorkIds);
12
- const worksById = new Map();
13
- for (const evidence of input.works) {
14
- if (!evidence.id || evidence.workTypeId === SYSTEM_WORK_TYPE_ID) {
15
- continue;
16
- }
17
- worksById.set(evidence.id, { ...evidence });
18
- }
19
-
20
- /** @type {import("./index.d.ts").FactoryWorkProgressProjection} */
21
- const projection = {
22
- active: [],
23
- completed: [],
24
- counts: { active: 0, completed: 0, failed: 0, queued: 0, unclassified: 0 },
25
- failed: [],
26
- queued: [],
27
- selectedTick: input.selectedTick,
28
- total: worksById.size,
29
- unclassified: [],
30
- };
31
- for (const evidence of [...worksById.values()].sort((left, right) =>
32
- left.id.localeCompare(right.id),
33
- )) {
34
- const state = resolveState(evidence, statesByWorkType);
35
- const work = {
36
- id: evidence.id,
37
- ...(evidence.workTypeId ? { workTypeId: evidence.workTypeId } : {}),
38
- ...(state?.id ? { stateId: state.id } : {}),
39
- ...(state?.name ? { stateName: state.name } : {}),
40
- };
41
- const category = classifyWork(state?.category, activeWorkIds.has(evidence.id));
42
- projection[category].push(work);
43
- projection.counts[category] += 1;
44
- }
45
- return projection;
46
- }
47
-
48
- /** @typedef {{id?: string, name: string, type: import("./index.d.ts").FactoryWorkStateCategory}} IndexedWorkState */
49
-
50
- /**
51
- * @param {import("@you-agent-factory/client").FactoryDefinition | undefined} factory
52
- * @returns {Map<string, Map<string, IndexedWorkState>>}
53
- */
54
- function indexWorkStates(factory) {
55
- const statesByWorkType = new Map();
56
- for (const workType of factory?.workTypes ?? []) {
57
- const states = new Map();
58
- for (const state of workType.states) {
59
- states.set(state.name, state);
60
- if (state.id?.trim()) {
61
- states.set(state.id, state);
62
- }
63
- }
64
- statesByWorkType.set(workType.name, states);
65
- if (workType.id?.trim()) {
66
- statesByWorkType.set(workType.id, states);
67
- }
68
- }
69
- return statesByWorkType;
70
- }
71
-
72
- /**
73
- * @param {import("./index.d.ts").FactoryWorkProgressEvidence} evidence
74
- * @param {Map<string, Map<string, IndexedWorkState>>} statesByWorkType
75
- */
76
- function resolveState(evidence, statesByWorkType) {
77
- if (evidence.state?.category) {
78
- return evidence.state;
79
- }
80
- if (!evidence.workTypeId) {
81
- return evidence.state;
82
- }
83
- const states = statesByWorkType.get(evidence.workTypeId);
84
- if (evidence.state?.name) {
85
- return progressState(states?.get(evidence.state.name)) ?? evidence.state;
86
- }
87
- if (evidence.state?.id) {
88
- return progressState(states?.get(evidence.state.id)) ?? evidence.state;
89
- }
90
- return evidence.state;
91
- }
92
-
93
- /** @param {IndexedWorkState | undefined} state @returns {import("./index.d.ts").FactoryWorkProgressStateEvidence | undefined} */
94
- function progressState(state) {
95
- return state
96
- ? {
97
- category: state.type,
98
- ...(state.id ? { id: state.id } : {}),
99
- name: state.name,
100
- }
101
- : undefined;
102
- }
103
-
104
- /**
105
- * @param {import("./index.d.ts").FactoryWorkStateCategory | undefined} stateCategory
106
- * @param {boolean} active
107
- * @returns {import("./index.d.ts").FactoryWorkProgressCategory}
108
- */
109
- function classifyWork(stateCategory, active) {
110
- if (stateCategory === "FAILED") return "failed";
111
- if (stateCategory === "TERMINAL") return "completed";
112
- if (active) return "active";
113
- if (stateCategory === "INITIAL" || stateCategory === "PROCESSING") {
114
- return "queued";
115
- }
116
- return "unclassified";
117
- }