@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/LICENSE.md +1 -1
- package/README.md +88 -56
- package/dist/activity-contract.d.ts +62 -0
- package/dist/activity-contract.js +1 -0
- package/dist/activity-replay.d.ts +3 -0
- package/dist/activity-replay.js +165 -0
- package/dist/activity.d.ts +7 -0
- package/dist/activity.js +306 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/load-contract.d.ts +57 -0
- package/dist/load-contract.js +1 -0
- package/dist/load-replay.d.ts +3 -0
- package/dist/load-replay.js +156 -0
- package/dist/load.d.ts +3 -0
- package/dist/load.js +260 -0
- package/dist/progress.d.ts +44 -0
- package/dist/progress.js +278 -0
- package/dist/replay.d.ts +98 -0
- package/dist/replay.js +144 -0
- package/dist/topology-connection.d.ts +3 -0
- package/dist/topology-connection.js +80 -0
- package/dist/topology-contract.d.ts +187 -0
- package/dist/topology-contract.js +67 -0
- package/dist/topology-identity.d.ts +5 -0
- package/dist/topology-identity.js +8 -0
- package/dist/topology.d.ts +9 -0
- package/dist/topology.js +255 -0
- package/package.json +36 -11
- package/src/activity.js +0 -208
- package/src/index.d.ts +0 -324
- package/src/index.js +0 -477
- package/src/progress.js +0 -117
- package/src/topology.js +0 -398
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -1,58 +1,90 @@
|
|
|
1
1
|
# `@you-agent-factory/factory-replay`
|
|
2
2
|
|
|
3
|
-
Framework-independent
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
3
|
+
Framework-independent replay utilities for UI-local Factory consumers. The
|
|
4
|
+
package deterministically orders and accepts Factory events, reconstructs
|
|
5
|
+
reducer-owned state at a logical tick, advances immutable checkpoints, and
|
|
6
|
+
projects customer Work into exclusive progress categories.
|
|
7
|
+
|
|
8
|
+
See the [public package family guide](https://github.com/portpowered/you-agent-factory/blob/main/ui/packages/README.md)
|
|
9
|
+
for the complete layer ordering, packed consumer paths, and execution support
|
|
10
|
+
boundary.
|
|
11
|
+
|
|
12
|
+
The package consumes Factory contracts from `@you-agent-factory/client` as
|
|
13
|
+
types only. Its runtime has no React, React Flow, Zustand, dashboard, network,
|
|
14
|
+
event-stream, or browser-storage dependency.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Import the public entry point and provide a reducer for the state your consumer
|
|
19
|
+
owns:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import {
|
|
23
|
+
projectFactoryStateAtTick,
|
|
24
|
+
type FactoryReplayReducer,
|
|
25
|
+
} from "@you-agent-factory/factory-replay";
|
|
26
|
+
|
|
27
|
+
const result = projectFactoryStateAtTick({ events, reducer, tick: 12 });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Hosts that retain only a selected-tick domain checkpoint can use
|
|
31
|
+
`projectFactoryWorldAtTick` and `advanceFactoryReplay`. Those APIs keep domain
|
|
32
|
+
state cloning, tick assignment, and world projection explicit while retaining
|
|
33
|
+
only accepted event IDs in the checkpoint rather than replay history.
|
|
34
|
+
|
|
35
|
+
## High-volume replay evidence
|
|
36
|
+
|
|
37
|
+
`ui/src/features/timeline/state/timeline/performance/replay-retained-memory.test.ts`
|
|
38
|
+
replays a deterministic 10,000-event recording through `advanceFactoryReplay`.
|
|
39
|
+
It budgets the UTF-8 serialized retained checkpoint at 2,000,000 bytes and
|
|
40
|
+
verifies the final tick, Work totals, and topology state after an incremental
|
|
41
|
+
same-tick tail containing duplicate input. Replaying the full recording from
|
|
42
|
+
that checkpoint must retain exactly the same bytes and return the same final
|
|
43
|
+
world without reapplying events; the earlier historical world must remain
|
|
44
|
+
unchanged. The caller-owned recording fixture is deliberately excluded from
|
|
45
|
+
the retained measurement: `advanceFactoryReplay` retains only its compact
|
|
46
|
+
checkpoint. The harness uses no wall-clock or process-heap assertions, which
|
|
47
|
+
are not deterministic across CI runtimes, and reports the measured and maximum
|
|
48
|
+
byte counts when the retained bound is exceeded.
|
|
49
|
+
|
|
50
|
+
Use `projectFactoryWorkProgressAtTick` when canonical event history is the
|
|
51
|
+
source for Work progress, or `projectFactoryWorkProgress` when the consumer
|
|
52
|
+
already has explicit selected-tick evidence.
|
|
53
|
+
Progress classification follows failed, completed, active, queued, then
|
|
54
|
+
unclassified precedence. Active Dispatch evidence is ended by responses,
|
|
55
|
+
interruptions, and terminal reconciliation events, using the same lifecycle
|
|
56
|
+
rules as Dispatch overlays.
|
|
57
|
+
|
|
58
|
+
Use `projectFactoryTopologyAtTick` to reconstruct the last canonical Factory
|
|
59
|
+
topology effective at a selected tick. Use `projectFactoryTopology` when the
|
|
60
|
+
consumer already has the selected-tick Factory definition. Both return stable,
|
|
61
|
+
sorted public node and connection identities without mutating caller data.
|
|
62
|
+
`FACTORY_TOPOLOGY_RELATIONSHIPS` is the shared semantic node-kind and handle
|
|
63
|
+
vocabulary for renderers. Invalid relationship endpoints return a fail-closed
|
|
64
|
+
result with structured issues and no partial graph.
|
|
65
|
+
|
|
66
|
+
Use `projectFactoryLoadAtTick` to reconstruct distinct customer Work counts and
|
|
67
|
+
resource occupancy from canonical events, or `projectFactoryLoad` with explicit
|
|
68
|
+
selected-tick evidence. Count and occupancy entries reference the same stable
|
|
69
|
+
Work State and resource node IDs as the topology projection. Missing evidence
|
|
70
|
+
is reported as unavailable rather than fabricated as zero; dangling,
|
|
71
|
+
contradictory, invalid-capacity, and over-capacity evidence remains visible
|
|
72
|
+
through deterministic structured issues.
|
|
73
|
+
|
|
74
|
+
Use `projectFactoryActivityAtTick` to reconstruct active Dispatch overlays
|
|
75
|
+
after all canonically ordered events at a selected tick, or
|
|
76
|
+
`projectFactoryActivity` with explicit selected-tick evidence. Overlays use
|
|
77
|
+
stable Dispatch and Work projection IDs, reference known worker, workstation,
|
|
78
|
+
resource, and topology-connection IDs, and expose unavailable Work, resource,
|
|
79
|
+
or route evidence without inventing endpoints. Completion, interruption, and
|
|
80
|
+
terminal reconciliation remove activity and release occupancy.
|
|
81
|
+
|
|
82
|
+
## Distribution verification
|
|
83
|
+
|
|
84
|
+
Run `bun run verify` from this directory to typecheck and test the package,
|
|
85
|
+
produce clean ESM runtime and declaration output, validate the runtime boundary,
|
|
86
|
+
inspect the exact registry tarball inventory, and install both the packed client
|
|
87
|
+
and replay packages in a clean temporary consumer. The installed consumer
|
|
88
|
+
compiles against the public topology, semantic handle, issue, overlay, load,
|
|
89
|
+
and progress contracts, then verifies that repeated projections remain pure
|
|
90
|
+
and reproducible across caller-owned presentation changes.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { FactoryDefinition, FactoryEvent } from "@you-agent-factory/client";
|
|
2
|
+
import type { FactoryResourceOccupancyProjection } from "./load-contract.js";
|
|
3
|
+
export interface FactoryDispatchRouteEvidence {
|
|
4
|
+
stateId?: string;
|
|
5
|
+
stateName?: string;
|
|
6
|
+
workTypeId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface FactoryActiveDispatchEvidence {
|
|
9
|
+
id: string;
|
|
10
|
+
inputRoutes?: readonly FactoryDispatchRouteEvidence[];
|
|
11
|
+
resourceNames?: readonly string[];
|
|
12
|
+
startedTick: number;
|
|
13
|
+
transitionId?: string;
|
|
14
|
+
workIds?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface FactoryDispatchOverlayEvidenceStatus {
|
|
17
|
+
resources: "known" | "unavailable";
|
|
18
|
+
route: "known" | "unavailable";
|
|
19
|
+
work: "known" | "unavailable";
|
|
20
|
+
worker: "known" | "unavailable";
|
|
21
|
+
workstation: "known" | "unavailable";
|
|
22
|
+
}
|
|
23
|
+
export interface FactoryDispatchOverlayProjection {
|
|
24
|
+
connectionIds: string[];
|
|
25
|
+
dispatchId: string;
|
|
26
|
+
evidence: FactoryDispatchOverlayEvidenceStatus;
|
|
27
|
+
id: string;
|
|
28
|
+
resourceIds?: string[];
|
|
29
|
+
resourceNodeIds?: string[];
|
|
30
|
+
startedTick: number;
|
|
31
|
+
transitionId?: string;
|
|
32
|
+
workerId?: string;
|
|
33
|
+
workerNodeId?: string;
|
|
34
|
+
workIds?: string[];
|
|
35
|
+
workProjectionIds?: string[];
|
|
36
|
+
workstationId?: string;
|
|
37
|
+
workstationNodeId?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface FactoryActivityProjectionIssue {
|
|
40
|
+
code: "CONTRADICTORY_DISPATCH_EVIDENCE" | "INVALID_RESOURCE_CAPACITY" | "MISSING_FACTORY" | "RESOURCE_CAPACITY_EXCEEDED" | "UNAVAILABLE_TOPOLOGY_PATH" | "UNRESOLVED_RESOURCE" | "UNRESOLVED_ROUTE" | "UNRESOLVED_WORKER" | "UNRESOLVED_WORKSTATION";
|
|
41
|
+
dispatchId?: string;
|
|
42
|
+
id: string;
|
|
43
|
+
message: string;
|
|
44
|
+
reference?: string;
|
|
45
|
+
resourceId?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface FactoryActivityProjection {
|
|
48
|
+
activeDispatchOverlays: FactoryDispatchOverlayProjection[];
|
|
49
|
+
activeWorkstationNodeIds: string[];
|
|
50
|
+
issues: FactoryActivityProjectionIssue[];
|
|
51
|
+
resourceOccupancy: FactoryResourceOccupancyProjection[];
|
|
52
|
+
selectedTick: number;
|
|
53
|
+
}
|
|
54
|
+
export interface FactoryActivityProjectionInput {
|
|
55
|
+
activeDispatches: readonly FactoryActiveDispatchEvidence[];
|
|
56
|
+
factory?: FactoryDefinition;
|
|
57
|
+
selectedTick: number;
|
|
58
|
+
}
|
|
59
|
+
export interface FactoryActivityAtTickInput {
|
|
60
|
+
events: readonly FactoryEvent[];
|
|
61
|
+
tick: number;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FactoryActivityAtTickInput, FactoryActivityProjection } from "./activity-contract.js";
|
|
2
|
+
/** Reconstruct active Dispatch overlays after all canonical events at one tick. */
|
|
3
|
+
export declare function projectFactoryActivityAtTick(input: FactoryActivityAtTickInput): FactoryActivityProjection;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { projectFactoryActivity } from "./activity.js";
|
|
2
|
+
import { canonicalizeFactoryEvents } from "./replay.js";
|
|
3
|
+
/** Reconstruct active Dispatch overlays after all canonical events at one tick. */
|
|
4
|
+
export function projectFactoryActivityAtTick(input) {
|
|
5
|
+
const state = {
|
|
6
|
+
activeDispatches: new Map(),
|
|
7
|
+
works: new Map(),
|
|
8
|
+
};
|
|
9
|
+
for (const event of canonicalizeFactoryEvents(input.events)) {
|
|
10
|
+
if (event.context.tick > input.tick)
|
|
11
|
+
break;
|
|
12
|
+
applyActivityEvent(state, event);
|
|
13
|
+
}
|
|
14
|
+
return projectFactoryActivity({
|
|
15
|
+
activeDispatches: [...state.activeDispatches.values()],
|
|
16
|
+
factory: state.factory,
|
|
17
|
+
selectedTick: input.tick,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function applyActivityEvent(state, event) {
|
|
21
|
+
const payload = objectRecord(event.payload);
|
|
22
|
+
if (event.type === "INITIAL_STRUCTURE_REQUEST" ||
|
|
23
|
+
event.type === "FACTORY_CHANGE") {
|
|
24
|
+
const factory = objectRecord(payload?.factory);
|
|
25
|
+
if (factory)
|
|
26
|
+
state.factory = structuredClone(factory);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (event.type === "WORK_REQUEST") {
|
|
30
|
+
for (const value of arrayValue(payload?.works)) {
|
|
31
|
+
const work = workRouteEvidence(value, state.factory);
|
|
32
|
+
if (work)
|
|
33
|
+
state.works.set(work.id, work);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (event.type === "WORK_STATE_CHANGE") {
|
|
38
|
+
applyWorkStateChange(state, payload);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (event.type === "DISPATCH_REQUEST") {
|
|
42
|
+
applyDispatchRequest(state, event, payload);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (event.type === "DISPATCH_RESPONSE" ||
|
|
46
|
+
event.type === "DISPATCH_INTERRUPTED") {
|
|
47
|
+
if (event.context.dispatchId) {
|
|
48
|
+
state.activeDispatches.delete(event.context.dispatchId);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (event.type === "DISPATCH_RECONCILED") {
|
|
53
|
+
const status = payload?.reconciledStatus;
|
|
54
|
+
if (event.context.dispatchId &&
|
|
55
|
+
(status === "COMPLETED" ||
|
|
56
|
+
status === "FAILED" ||
|
|
57
|
+
status === "INTERRUPTED")) {
|
|
58
|
+
state.activeDispatches.delete(event.context.dispatchId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function applyWorkStateChange(state, payload) {
|
|
63
|
+
if (typeof payload?.workId !== "string" || !payload.workId)
|
|
64
|
+
return;
|
|
65
|
+
const previous = state.works.get(payload.workId);
|
|
66
|
+
state.works.set(payload.workId, {
|
|
67
|
+
id: payload.workId,
|
|
68
|
+
...(typeof payload.toState === "string"
|
|
69
|
+
? { stateName: payload.toState }
|
|
70
|
+
: {}),
|
|
71
|
+
...(typeof payload.workTypeName === "string"
|
|
72
|
+
? { workTypeId: payload.workTypeName }
|
|
73
|
+
: previous?.workTypeId
|
|
74
|
+
? { workTypeId: previous.workTypeId }
|
|
75
|
+
: {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function applyDispatchRequest(state, event, payload) {
|
|
79
|
+
const transitionId = typeof payload?.transitionId === "string"
|
|
80
|
+
? payload.transitionId
|
|
81
|
+
: undefined;
|
|
82
|
+
if (transitionId?.startsWith("__system_time:"))
|
|
83
|
+
return;
|
|
84
|
+
const inputValues = Array.isArray(payload?.inputs)
|
|
85
|
+
? payload.inputs
|
|
86
|
+
: undefined;
|
|
87
|
+
const inputIds = (inputValues ?? []).flatMap((value) => {
|
|
88
|
+
const input = objectRecord(value);
|
|
89
|
+
return typeof input?.workId === "string" && input.workId
|
|
90
|
+
? [input.workId]
|
|
91
|
+
: [];
|
|
92
|
+
});
|
|
93
|
+
const hasCompleteInputEvidence = inputValues !== undefined && inputIds.length === inputValues.length;
|
|
94
|
+
const hasWorkEvidence = event.context.workIds !== undefined || hasCompleteInputEvidence;
|
|
95
|
+
const workIds = hasWorkEvidence
|
|
96
|
+
? [...new Set([...(event.context.workIds ?? []), ...inputIds])]
|
|
97
|
+
: undefined;
|
|
98
|
+
const resourceValues = Array.isArray(payload?.resources)
|
|
99
|
+
? payload.resources
|
|
100
|
+
: undefined;
|
|
101
|
+
const resourceNames = resourceValues?.flatMap((value) => {
|
|
102
|
+
const resource = objectRecord(value);
|
|
103
|
+
return typeof resource?.name === "string" && resource.name
|
|
104
|
+
? [resource.name]
|
|
105
|
+
: [];
|
|
106
|
+
});
|
|
107
|
+
const completeResources = resourceNames !== undefined &&
|
|
108
|
+
resourceNames.length === resourceValues?.length;
|
|
109
|
+
const inputRoutes = workIds?.flatMap((workId) => {
|
|
110
|
+
const route = state.works.get(workId);
|
|
111
|
+
return route ? [withoutId(route)] : [];
|
|
112
|
+
});
|
|
113
|
+
const completeRoutes = inputRoutes !== undefined && inputRoutes.length === workIds?.length;
|
|
114
|
+
const dispatchId = event.context.dispatchId ?? `incomplete:${event.id}`;
|
|
115
|
+
state.activeDispatches.set(dispatchId, {
|
|
116
|
+
id: dispatchId,
|
|
117
|
+
...(completeRoutes ? { inputRoutes } : {}),
|
|
118
|
+
...(completeResources ? { resourceNames } : {}),
|
|
119
|
+
startedTick: event.context.tick,
|
|
120
|
+
...(transitionId ? { transitionId } : {}),
|
|
121
|
+
...(workIds ? { workIds } : {}),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function workRouteEvidence(value, factory) {
|
|
125
|
+
const work = objectRecord(value);
|
|
126
|
+
if (typeof work?.workId !== "string" || !work.workId)
|
|
127
|
+
return undefined;
|
|
128
|
+
const state = objectRecord(work.state);
|
|
129
|
+
const evidence = {
|
|
130
|
+
id: work.workId,
|
|
131
|
+
...(typeof state?.id === "string" ? { stateId: state.id } : {}),
|
|
132
|
+
...(typeof state?.name === "string" ? { stateName: state.name } : {}),
|
|
133
|
+
...(typeof work.workTypeName === "string"
|
|
134
|
+
? { workTypeId: work.workTypeName }
|
|
135
|
+
: {}),
|
|
136
|
+
};
|
|
137
|
+
if (evidence.stateId || evidence.stateName || !evidence.workTypeId) {
|
|
138
|
+
return evidence;
|
|
139
|
+
}
|
|
140
|
+
const workType = factory?.workTypes?.find((candidate) => candidate.name === evidence.workTypeId ||
|
|
141
|
+
candidate.id === evidence.workTypeId);
|
|
142
|
+
const initial = workType?.states.find((candidate) => candidate.type === "INITIAL");
|
|
143
|
+
return initial
|
|
144
|
+
? {
|
|
145
|
+
...evidence,
|
|
146
|
+
...(initial.id?.trim() ? { stateId: initial.id } : {}),
|
|
147
|
+
stateName: initial.name,
|
|
148
|
+
}
|
|
149
|
+
: evidence;
|
|
150
|
+
}
|
|
151
|
+
function withoutId(work) {
|
|
152
|
+
return {
|
|
153
|
+
...(work.stateId ? { stateId: work.stateId } : {}),
|
|
154
|
+
...(work.stateName ? { stateName: work.stateName } : {}),
|
|
155
|
+
...(work.workTypeId ? { workTypeId: work.workTypeId } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function objectRecord(value) {
|
|
159
|
+
return value !== null && typeof value === "object"
|
|
160
|
+
? value
|
|
161
|
+
: undefined;
|
|
162
|
+
}
|
|
163
|
+
function arrayValue(value) {
|
|
164
|
+
return Array.isArray(value) ? value : [];
|
|
165
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FactoryActivityProjection, FactoryActivityProjectionInput } from "./activity-contract.js";
|
|
2
|
+
/** Build the stable public identity of one active Dispatch overlay. */
|
|
3
|
+
export declare function factoryDispatchOverlayId(dispatchId: string): string;
|
|
4
|
+
/** Build the stable public identity used when an overlay references Work. */
|
|
5
|
+
export declare function factoryWorkProjectionId(workId: string): string;
|
|
6
|
+
/** Project selected-tick active Dispatch overlays and their topology activity. */
|
|
7
|
+
export declare function projectFactoryActivity(input: FactoryActivityProjectionInput): FactoryActivityProjection;
|