@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/topology.js DELETED
@@ -1,398 +0,0 @@
1
- /** @type {Record<import("./index.d.ts").FactoryTopologyNodeKind, import("./index.d.ts").FactoryTopologyHandle[]>} */
2
- const HANDLES_BY_KIND = {
3
- resource: [
4
- { id: "worker-resource-source", role: "source" },
5
- { id: "workstation-resource-source", role: "source" },
6
- ],
7
- worker: [
8
- { id: "worker-input-target", role: "target" },
9
- { id: "worker-assignment-source", role: "source" },
10
- ],
11
- "work-state": [
12
- { id: "workstation-input-source", role: "source" },
13
- { id: "work-state-input-target", role: "target" },
14
- { id: "work-type-state-target", role: "target" },
15
- ],
16
- "work-type": [{ id: "work-type-state-source", role: "source" }],
17
- workstation: [
18
- { id: "workstation-input-target", role: "target" },
19
- { id: "worker-assignment-target", role: "target" },
20
- { id: "workstation-resource-target", role: "target" },
21
- { id: "workstation-output-source", role: "source" },
22
- { id: "workstation-on-continue-source", role: "source" },
23
- { id: "workstation-on-failure-source", role: "source" },
24
- { id: "workstation-on-rejection-source", role: "source" },
25
- ],
26
- };
27
-
28
- const CONNECTION_HANDLES = {
29
- "worker-assignment": [
30
- "worker-assignment-source",
31
- "worker-assignment-target",
32
- ],
33
- "worker-resource": ["worker-resource-source", "worker-input-target"],
34
- "workstation-input": [
35
- "workstation-input-source",
36
- "workstation-input-target",
37
- ],
38
- "workstation-on-continue": [
39
- "workstation-on-continue-source",
40
- "work-state-input-target",
41
- ],
42
- "workstation-on-failure": [
43
- "workstation-on-failure-source",
44
- "work-state-input-target",
45
- ],
46
- "workstation-on-rejection": [
47
- "workstation-on-rejection-source",
48
- "work-state-input-target",
49
- ],
50
- "workstation-output": [
51
- "workstation-output-source",
52
- "work-state-input-target",
53
- ],
54
- "workstation-resource": [
55
- "workstation-resource-source",
56
- "workstation-resource-target",
57
- ],
58
- "work-type-state": ["work-type-state-source", "work-type-state-target"],
59
- };
60
-
61
- const RESOURCE_AVAILABILITY_STATE = "available";
62
-
63
- /** @param {string | undefined} explicitID @param {string} name */
64
- function entityID(explicitID, name) {
65
- return explicitID?.trim() || name;
66
- }
67
-
68
- /** @param {import("./index.d.ts").FactoryTopologyNodeKind} kind @param {string} id */
69
- function nodeID(kind, id) {
70
- return `${kind}:${id}`;
71
- }
72
-
73
- /**
74
- * @param {import("./index.d.ts").FactoryTopologyNodeKind} kind
75
- * @returns {import("./index.d.ts").FactoryTopologyHandle[]}
76
- */
77
- function handlesFor(kind) {
78
- return HANDLES_BY_KIND[kind].map((handle) => ({ ...handle }));
79
- }
80
-
81
- /**
82
- * @typedef {NonNullable<import("@you-agent-factory/client").FactoryDefinition["workstations"]>[number]["inputs"][number]} WorkstationIO
83
- * @typedef {import("@you-agent-factory/client").FactoryDefinition} FactoryDefinition
84
- * @typedef {{
85
- * connections: Map<string, import("./index.d.ts").FactoryTopologyConnection>,
86
- * issues: Map<string, import("./index.d.ts").FactoryTopologyProjectionIssue>,
87
- * nodes: Map<string, import("./index.d.ts").FactoryTopologyNode>,
88
- * resourcesByName: Map<string, string>,
89
- * workersByName: Map<string, string>,
90
- * workStatesByName: Map<string, Map<string, string>>,
91
- * workTypesByName: Map<string, string>,
92
- * }} TopologyContext
93
- */
94
-
95
- /** @param {unknown} value @returns {WorkstationIO[]} */
96
- function asRouteList(value) {
97
- if (!value) return [];
98
- return /** @type {WorkstationIO[]} */ (Array.isArray(value) ? value : [value]);
99
- }
100
-
101
- /** @returns {TopologyContext} */
102
- function createTopologyContext() {
103
- return {
104
- connections: new Map(),
105
- issues: new Map(),
106
- nodes: new Map(),
107
- resourcesByName: new Map(),
108
- workersByName: new Map(),
109
- workStatesByName: new Map(),
110
- workTypesByName: new Map(),
111
- };
112
- }
113
-
114
- /** @param {TopologyContext} context @param {import("./index.d.ts").FactoryTopologyNode} node */
115
- function addNode(context, node) {
116
- if (context.nodes.has(node.id)) {
117
- const id = `duplicate-entity:${node.id}`;
118
- context.issues.set(id, {
119
- code: "DUPLICATE_ENTITY_ID",
120
- id,
121
- message: `Factory topology contains duplicate node id ${node.id}.`,
122
- nodeId: node.id,
123
- });
124
- return;
125
- }
126
- context.nodes.set(node.id, node);
127
- }
128
-
129
- /**
130
- * @param {TopologyContext} context
131
- * @param {import("./index.d.ts").FactoryTopologyConnectionKind} kind
132
- * @param {string | undefined} sourceId
133
- * @param {string | undefined} targetId
134
- * @param {string} sourceReference
135
- * @param {string} targetReference
136
- */
137
- function addConnection(
138
- context,
139
- kind,
140
- sourceId,
141
- targetId,
142
- sourceReference,
143
- targetReference,
144
- ) {
145
- if (
146
- !sourceId ||
147
- !targetId ||
148
- !context.nodes.has(sourceId) ||
149
- !context.nodes.has(targetId)
150
- ) {
151
- const id = `unresolved-connection:${kind}:${sourceReference}->${targetReference}`;
152
- context.issues.set(id, {
153
- code: "UNRESOLVED_CONNECTION",
154
- connectionKind: kind,
155
- id,
156
- message: `Cannot resolve ${kind} connection from ${sourceReference} to ${targetReference}.`,
157
- sourceReference,
158
- targetReference,
159
- });
160
- return;
161
- }
162
- const [sourceHandle, targetHandle] = CONNECTION_HANDLES[kind];
163
- const id = `${kind}:${sourceId}->${targetId}`;
164
- context.connections.set(id, {
165
- id,
166
- kind,
167
- source: { handleId: sourceHandle, nodeId: sourceId },
168
- target: { handleId: targetHandle, nodeId: targetId },
169
- });
170
- }
171
-
172
- /** @param {FactoryDefinition} factory @param {TopologyContext} context */
173
- function appendResourceAndWorkerNodes(factory, context) {
174
- for (const resource of factory.resources ?? []) {
175
- const id = entityID(resource.id, resource.name);
176
- context.resourcesByName.set(resource.name, id);
177
- addNode(context, {
178
- capacity: resource.capacity,
179
- entityId: id,
180
- handles: handlesFor("resource"),
181
- id: nodeID("resource", id),
182
- kind: "resource",
183
- label: resource.name,
184
- });
185
- }
186
- for (const worker of factory.workers ?? []) {
187
- const id = entityID(worker.id, worker.name);
188
- context.workersByName.set(worker.name, id);
189
- addNode(context, {
190
- entityId: id,
191
- handles: handlesFor("worker"),
192
- id: nodeID("worker", id),
193
- kind: "worker",
194
- label: worker.name,
195
- });
196
- }
197
- }
198
-
199
- /** @param {FactoryDefinition} factory @param {TopologyContext} context */
200
- function appendWorkTypeNodes(factory, context) {
201
- for (const workType of factory.workTypes ?? []) {
202
- const id = entityID(workType.id, workType.name);
203
- context.workTypesByName.set(workType.name, id);
204
- addNode(context, {
205
- entityId: id,
206
- handles: handlesFor("work-type"),
207
- id: nodeID("work-type", id),
208
- kind: "work-type",
209
- label: workType.name,
210
- });
211
- const states = new Map();
212
- for (const state of workType.states ?? []) {
213
- const idForState = `${id}:${entityID(state.id, state.name)}`;
214
- states.set(state.name, idForState);
215
- addNode(context, {
216
- category: state.type,
217
- entityId: idForState,
218
- handles: handlesFor("work-state"),
219
- id: nodeID("work-state", idForState),
220
- kind: "work-state",
221
- label: state.name,
222
- workTypeId: id,
223
- });
224
- }
225
- context.workStatesByName.set(workType.name, states);
226
- }
227
- }
228
-
229
- /** @param {FactoryDefinition} factory @param {TopologyContext} context */
230
- function appendWorkstationNodes(factory, context) {
231
- for (const workstation of factory.workstations ?? []) {
232
- const id = entityID(workstation.id, workstation.name);
233
- addNode(context, {
234
- entityId: id,
235
- handles: handlesFor("workstation"),
236
- id: nodeID("workstation", id),
237
- kind: "workstation",
238
- label: workstation.name,
239
- });
240
- }
241
- }
242
-
243
- /** @param {FactoryDefinition} factory @param {TopologyContext} context */
244
- function appendWorkerAndWorkTypeConnections(factory, context) {
245
- for (const worker of factory.workers ?? []) {
246
- const workerId = context.workersByName.get(worker.name);
247
- for (const resource of worker.resources ?? []) {
248
- const resourceId = context.resourcesByName.get(resource.name);
249
- addConnection(
250
- context,
251
- "worker-resource",
252
- resourceId && nodeID("resource", resourceId),
253
- workerId && nodeID("worker", workerId),
254
- resource.name,
255
- worker.name,
256
- );
257
- }
258
- }
259
- for (const workType of factory.workTypes ?? []) {
260
- const workTypeId = context.workTypesByName.get(workType.name);
261
- for (const state of workType.states ?? []) {
262
- const stateId = context.workStatesByName
263
- .get(workType.name)
264
- ?.get(state.name);
265
- addConnection(
266
- context,
267
- "work-type-state",
268
- workTypeId && nodeID("work-type", workTypeId),
269
- stateId && nodeID("work-state", stateId),
270
- workType.name,
271
- `${workType.name}:${state.name}`,
272
- );
273
- }
274
- }
275
- }
276
-
277
- /**
278
- * @param {TopologyContext} context
279
- * @param {NonNullable<FactoryDefinition["workstations"]>[number]} workstation
280
- * @param {string} workstationId
281
- */
282
- function appendWorkstationRoutes(context, workstation, workstationId) {
283
- /** @type {Array<[import("./index.d.ts").FactoryTopologyConnectionKind, WorkstationIO[]]>} */
284
- const routes = [
285
- ["workstation-input", asRouteList(workstation.inputs)],
286
- ["workstation-output", asRouteList(workstation.outputs)],
287
- ["workstation-on-continue", asRouteList(workstation.onContinue)],
288
- ["workstation-on-failure", asRouteList(workstation.onFailure)],
289
- ["workstation-on-rejection", asRouteList(workstation.onRejection)],
290
- [
291
- "workstation-output",
292
- (workstation.classificationRoutes ?? []).flatMap(
293
- (route) => route.outputs ?? [],
294
- ),
295
- ],
296
- ];
297
- for (const [kind, ios] of routes) {
298
- for (const io of ios) {
299
- const stateId = context.workStatesByName.get(io.workType)?.get(io.state);
300
- // Canonical backend snapshots include internal resource acquisition and
301
- // release arcs in workstation IO. Resource relationships are projected
302
- // separately and must not become public Work-State routes or issues.
303
- if (
304
- !stateId &&
305
- io.state === RESOURCE_AVAILABILITY_STATE &&
306
- context.resourcesByName.has(io.workType)
307
- ) {
308
- continue;
309
- }
310
- const workStateId = stateId && nodeID("work-state", stateId);
311
- const input = kind === "workstation-input";
312
- addConnection(
313
- context,
314
- kind,
315
- input ? workStateId : workstationId,
316
- input ? workstationId : workStateId,
317
- input ? `${io.workType}:${io.state}` : workstation.name,
318
- input ? workstation.name : `${io.workType}:${io.state}`,
319
- );
320
- }
321
- }
322
- }
323
-
324
- /** @param {FactoryDefinition} factory @param {TopologyContext} context */
325
- function appendWorkstationConnections(factory, context) {
326
- for (const workstation of factory.workstations ?? []) {
327
- const workstationId = nodeID(
328
- "workstation",
329
- entityID(workstation.id, workstation.name),
330
- );
331
- const workerId = context.workersByName.get(workstation.worker);
332
- if (workstation.worker?.trim()) {
333
- addConnection(
334
- context,
335
- "worker-assignment",
336
- workerId && nodeID("worker", workerId),
337
- workstationId,
338
- workstation.worker,
339
- workstation.name,
340
- );
341
- }
342
- for (const resource of workstation.resources ?? []) {
343
- const resourceId = context.resourcesByName.get(resource.name);
344
- addConnection(
345
- context,
346
- "workstation-resource",
347
- resourceId && nodeID("resource", resourceId),
348
- workstationId,
349
- resource.name,
350
- workstation.name,
351
- );
352
- }
353
- appendWorkstationRoutes(context, workstation, workstationId);
354
- }
355
- }
356
-
357
- /**
358
- * Project one public Factory definition without mutating caller-owned data.
359
- *
360
- * @param {import("./index.d.ts").FactoryTopologyProjectionInput} input
361
- * @returns {import("./index.d.ts").FactoryTopologyProjection}
362
- */
363
- export function projectFactoryTopology(input) {
364
- const factory = input.factory;
365
- if (!factory) {
366
- return {
367
- connections: [],
368
- issues: [
369
- {
370
- code: "MISSING_FACTORY",
371
- id: "missing-factory",
372
- message: "No Factory topology is available at the selected tick.",
373
- },
374
- ],
375
- nodes: [],
376
- selectedTick: input.selectedTick,
377
- };
378
- }
379
- const context = createTopologyContext();
380
- appendResourceAndWorkerNodes(factory, context);
381
- appendWorkTypeNodes(factory, context);
382
- appendWorkstationNodes(factory, context);
383
- appendWorkerAndWorkTypeConnections(factory, context);
384
- appendWorkstationConnections(factory, context);
385
-
386
- return {
387
- connections: [...context.connections.values()].sort((left, right) =>
388
- left.id.localeCompare(right.id),
389
- ),
390
- issues: [...context.issues.values()].sort((left, right) =>
391
- left.id.localeCompare(right.id),
392
- ),
393
- nodes: [...context.nodes.values()].sort((left, right) =>
394
- left.id.localeCompare(right.id),
395
- ),
396
- selectedTick: input.selectedTick,
397
- };
398
- }