@you-agent-factory/factory-emulator 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.
Files changed (69) hide show
  1. package/LICENSE.md +19 -1
  2. package/README.md +305 -286
  3. package/dist/compatibility.d.ts +19 -0
  4. package/dist/compatibility.js +156 -0
  5. package/dist/event-sink.d.ts +29 -0
  6. package/dist/event-sink.js +58 -0
  7. package/dist/generated/scenario-schema.d.ts +268 -0
  8. package/dist/generated/scenario-schema.js +319 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +8 -0
  11. package/dist/logical-move.d.ts +10 -0
  12. package/dist/logical-move.js +18 -0
  13. package/dist/recording-sink.d.ts +22 -0
  14. package/dist/recording-sink.js +122 -0
  15. package/dist/runtime-reference-conformance.d.ts +20 -0
  16. package/dist/runtime-reference-conformance.js +138 -0
  17. package/dist/runtime-reference-evidence.d.ts +9 -0
  18. package/dist/runtime-reference-evidence.js +193 -0
  19. package/dist/runtime-reference-fixtures.d.ts +7 -0
  20. package/dist/runtime-reference-fixtures.js +308 -0
  21. package/dist/runtime-reference.d.ts +48 -0
  22. package/dist/runtime-reference.js +143 -0
  23. package/dist/scenario-contracts.d.ts +81 -0
  24. package/dist/scenario-contracts.js +11 -0
  25. package/dist/scenario.d.ts +271 -0
  26. package/dist/scenario.js +333 -0
  27. package/dist/scheduler.d.ts +24 -0
  28. package/dist/scheduler.js +104 -0
  29. package/dist/session-contracts.d.ts +262 -0
  30. package/dist/session-contracts.js +74 -0
  31. package/dist/session.d.ts +4 -0
  32. package/dist/session.js +1490 -0
  33. package/dist/submission-replay.d.ts +14 -0
  34. package/dist/submission-replay.js +96 -0
  35. package/dist/submission-validation.d.ts +3 -0
  36. package/dist/submission-validation.js +113 -0
  37. package/examples/customer-support.scenario.v1.json +45 -0
  38. package/package.json +44 -22
  39. package/schema/scenario.schema.json +171 -0
  40. package/generated/factory-emulator-scenario.schema.json +0 -191
  41. package/generated/factory.schema.json +0 -2606
  42. package/src/contracts.ts +0 -41
  43. package/src/data-error.js +0 -21
  44. package/src/data-only.js +0 -208
  45. package/src/emulator.js +0 -195
  46. package/src/emulator.ts +0 -86
  47. package/src/examples.js +0 -86
  48. package/src/examples.ts +0 -11
  49. package/src/generated/factory-schema.d.ts +0 -3
  50. package/src/generated/factory-schema.js +0 -5
  51. package/src/generated/scenario-schema.d.ts +0 -4
  52. package/src/generated/scenario-schema.js +0 -6
  53. package/src/generated/scenario.ts +0 -103
  54. package/src/identity.js +0 -55
  55. package/src/index.js +0 -34
  56. package/src/index.ts +0 -102
  57. package/src/limits.js +0 -125
  58. package/src/parser.js +0 -113
  59. package/src/parser.ts +0 -56
  60. package/src/scheduler.js +0 -374
  61. package/src/semantics.js +0 -354
  62. package/src/semantics.ts +0 -38
  63. package/src/session.js +0 -1201
  64. package/src/session.ts +0 -324
  65. package/src/sinks.js +0 -118
  66. package/src/sinks.ts +0 -56
  67. package/src/support.js +0 -334
  68. package/src/support.ts +0 -15
  69. package/src/virtual-time.js +0 -36
@@ -0,0 +1,19 @@
1
+ import type { FactoryDefinition, FactoryEvent } from "@you-agent-factory/client";
2
+ import type { FactoryEventSink } from "./event-sink.js";
3
+ export type FactoryEmulatorCompatibilityIssueCode = "invalid_logical_move" | "invalid_route_reference" | "invalid_worker_reference" | "provider_global_resource" | "unresolved_resource_requirement" | "unsupported_classifier" | "unsupported_execution" | "unsupported_guard" | "unsupported_orchestrator" | "unsupported_relationship_behavior" | "unsupported_workstation_behavior";
4
+ export interface FactoryEmulatorCompatibilityIssue {
5
+ readonly code: FactoryEmulatorCompatibilityIssueCode;
6
+ readonly path: readonly (string | number)[];
7
+ readonly message: string;
8
+ }
9
+ export type FactoryEmulatorCompatibilityResult = {
10
+ readonly supported: true;
11
+ readonly diagnostics: readonly [];
12
+ } | {
13
+ readonly supported: false;
14
+ readonly diagnostics: readonly FactoryEmulatorCompatibilityIssue[];
15
+ };
16
+ /** Inspect the executable subset without mutating or retaining the Factory. */
17
+ export declare function inspectFactoryEmulatorCompatibility(factory: FactoryDefinition): FactoryEmulatorCompatibilityResult;
18
+ /** Write only after the complete compatibility preflight succeeds. */
19
+ export declare function writeFactoryEventsIfCompatible(factory: FactoryDefinition, events: readonly FactoryEvent[], sink: FactoryEventSink): Promise<FactoryEmulatorCompatibilityResult>;
@@ -0,0 +1,156 @@
1
+ function addIssue(diagnostics, code, path, message) {
2
+ diagnostics.push({ code, path, message });
3
+ }
4
+ function inspectRoutes(factory, workstationIndex, diagnostics) {
5
+ const workstation = factory.workstations?.[workstationIndex];
6
+ if (workstation === undefined)
7
+ return;
8
+ const workStates = new Map((factory.workTypes ?? []).map((workType) => [
9
+ workType.name,
10
+ new Set(workType.states.map((state) => state.name)),
11
+ ]));
12
+ const routeGroups = [
13
+ ["inputs", workstation.inputs],
14
+ ["outputs", workstation.outputs ?? []],
15
+ ["onContinue", workstation.onContinue ?? []],
16
+ ["onRejection", workstation.onRejection ?? []],
17
+ ["onFailure", workstation.onFailure ?? []],
18
+ ];
19
+ for (const [routeName, routes] of routeGroups) {
20
+ for (const [routeIndex, route] of routes.entries()) {
21
+ const path = [
22
+ "workstations",
23
+ workstationIndex,
24
+ routeName,
25
+ routeIndex,
26
+ ];
27
+ const states = workStates.get(route.workType);
28
+ if (states === undefined) {
29
+ addIssue(diagnostics, "invalid_route_reference", [...path, "workType"], `Work type ${route.workType} is not declared by the Factory.`);
30
+ }
31
+ else if (!states.has(route.state)) {
32
+ addIssue(diagnostics, "invalid_route_reference", [...path, "state"], `State ${route.state} is not declared by Work type ${route.workType}.`);
33
+ }
34
+ for (const [guardIndex, guard] of (route.guards ?? []).entries()) {
35
+ const guardPath = [...path, "guards", guardIndex];
36
+ const relationshipAware = guard.parentInput !== undefined ||
37
+ guard.matchInput !== undefined ||
38
+ guard.spawnedBy !== undefined ||
39
+ guard.type !== "VISIT_COUNT";
40
+ addIssue(diagnostics, relationshipAware
41
+ ? "unsupported_relationship_behavior"
42
+ : "unsupported_guard", guardPath, relationshipAware
43
+ ? "Parent-aware, peer-aware, and runtime-spawn-aware input behavior is not supported by emulator v1."
44
+ : "Input guards are not supported by emulator v1.");
45
+ }
46
+ }
47
+ }
48
+ }
49
+ function inspectWorkstation(factory, workstationIndex, diagnostics) {
50
+ const workstation = factory.workstations?.[workstationIndex];
51
+ if (workstation === undefined)
52
+ return;
53
+ const path = ["workstations", workstationIndex];
54
+ const behavior = workstation.behavior ?? "STANDARD";
55
+ const type = workstation.type;
56
+ if (behavior !== "STANDARD" && behavior !== "REPEATER") {
57
+ addIssue(diagnostics, "unsupported_workstation_behavior", [...path, "behavior"], `Workstation behavior ${behavior} is not supported by emulator v1.`);
58
+ }
59
+ if (type === "CLASSIFIER_WORKSTATION" ||
60
+ workstation.classificationRoutes !== undefined ||
61
+ workstation.outcomeFormat !== undefined) {
62
+ addIssue(diagnostics, "unsupported_classifier", [
63
+ ...path,
64
+ type === "CLASSIFIER_WORKSTATION"
65
+ ? "type"
66
+ : workstation.classificationRoutes !== undefined
67
+ ? "classificationRoutes"
68
+ : "outcomeFormat",
69
+ ], "Classifier routing and classifier output contracts are not supported by emulator v1.");
70
+ }
71
+ else if (type !== "AGENT_RUN" && type !== "LOGICAL_MOVE") {
72
+ addIssue(diagnostics, "unsupported_execution", [...path, "type"], `Workstation execution ${type ?? "omitted"} is not supported by emulator v1.`);
73
+ }
74
+ const workerNames = new Set((factory.workers ?? []).map((worker) => worker.name));
75
+ if (type === "LOGICAL_MOVE") {
76
+ if (workstation.worker !== "") {
77
+ addIssue(diagnostics, "invalid_logical_move", [...path, "worker"], "A LOGICAL_MOVE must be workerless.");
78
+ }
79
+ const guards = workstation.guards ?? [];
80
+ if (guards.length === 0) {
81
+ addIssue(diagnostics, "invalid_logical_move", [...path, "guards"], "A LOGICAL_MOVE requires at least one VISIT_COUNT guard.");
82
+ }
83
+ for (const [guardIndex, guard] of guards.entries()) {
84
+ const guardPath = [...path, "guards", guardIndex];
85
+ if (guard.type !== "VISIT_COUNT" ||
86
+ guard.parentInput !== undefined ||
87
+ guard.matchInput !== undefined ||
88
+ guard.spawnedBy !== undefined) {
89
+ addIssue(diagnostics, "unsupported_guard", guardPath, "LOGICAL_MOVE supports only VISIT_COUNT guards in emulator v1.");
90
+ }
91
+ else if (guard.workstation === undefined ||
92
+ !new Set(factory.workstations?.map((item) => item.name)).has(guard.workstation) ||
93
+ guard.maxVisits === undefined ||
94
+ !Number.isInteger(guard.maxVisits) ||
95
+ guard.maxVisits < 1) {
96
+ addIssue(diagnostics, "invalid_logical_move", guardPath, "VISIT_COUNT requires a declared workstation and a positive integer maxVisits.");
97
+ }
98
+ }
99
+ }
100
+ else if (type === "AGENT_RUN") {
101
+ if (workstation.worker === "" || !workerNames.has(workstation.worker)) {
102
+ addIssue(diagnostics, "invalid_worker_reference", [...path, "worker"], `Worker ${workstation.worker || "<empty>"} is not declared by the Factory.`);
103
+ }
104
+ else {
105
+ const worker = factory.workers?.find((candidate) => candidate.name === workstation.worker);
106
+ if (worker?.type !== undefined && worker.type !== "AGENT_WORKER") {
107
+ addIssue(diagnostics, "unsupported_execution", ["workers", factory.workers?.indexOf(worker) ?? -1, "type"], `Worker execution ${worker.type} is not supported for AGENT_RUN emulator workstations.`);
108
+ }
109
+ }
110
+ for (const [guardIndex] of (workstation.guards ?? []).entries()) {
111
+ addIssue(diagnostics, "unsupported_guard", [...path, "guards", guardIndex], "AGENT_RUN workstation guards are not supported by emulator v1.");
112
+ }
113
+ }
114
+ else {
115
+ for (const [guardIndex] of (workstation.guards ?? []).entries()) {
116
+ addIssue(diagnostics, "unsupported_guard", [...path, "guards", guardIndex], "Workstation guards on unsupported execution types are not supported by emulator v1.");
117
+ }
118
+ }
119
+ const declaredResources = new Set((factory.resources ?? []).map((resource) => resource.name));
120
+ for (const [resourceIndex, requirement] of (workstation.resources ?? []).entries()) {
121
+ if (!declaredResources.has(requirement.name)) {
122
+ addIssue(diagnostics, "unresolved_resource_requirement", [...path, "resources", resourceIndex, "name"], `Resource ${requirement.name} is not declared at Factory scope.`);
123
+ }
124
+ }
125
+ inspectRoutes(factory, workstationIndex, diagnostics);
126
+ }
127
+ /** Inspect the executable subset without mutating or retaining the Factory. */
128
+ export function inspectFactoryEmulatorCompatibility(factory) {
129
+ const diagnostics = [];
130
+ if (factory.orchestrator !== undefined &&
131
+ factory.orchestrator.kind !== "PETRI") {
132
+ addIssue(diagnostics, "unsupported_orchestrator", ["orchestrator", "kind"], `Orchestrator ${factory.orchestrator.kind} is not supported by emulator v1.`);
133
+ }
134
+ for (const [guardIndex] of (factory.guards ?? []).entries()) {
135
+ addIssue(diagnostics, "unsupported_guard", ["guards", guardIndex], "Factory-level guards are not supported by emulator v1.");
136
+ }
137
+ for (const [resourceIndex, resource] of (factory.resources ?? []).entries()) {
138
+ if (resource.type === "PROVIDER_QUOTA" || resource.provider !== undefined) {
139
+ addIssue(diagnostics, "provider_global_resource", ["resources", resourceIndex], `Resource ${resource.name} depends on provider-global capacity.`);
140
+ }
141
+ }
142
+ for (const workstationIndex of (factory.workstations ?? []).keys()) {
143
+ inspectWorkstation(factory, workstationIndex, diagnostics);
144
+ }
145
+ return diagnostics.length === 0
146
+ ? { supported: true, diagnostics: [] }
147
+ : { supported: false, diagnostics };
148
+ }
149
+ /** Write only after the complete compatibility preflight succeeds. */
150
+ export async function writeFactoryEventsIfCompatible(factory, events, sink) {
151
+ const result = inspectFactoryEmulatorCompatibility(factory);
152
+ if (!result.supported)
153
+ return result;
154
+ await sink.write(events);
155
+ return result;
156
+ }
@@ -0,0 +1,29 @@
1
+ import type { FactoryEvent } from "@you-agent-factory/client";
2
+ export type FactoryEventSinkErrorCode = "capacity_exceeded" | "closed" | "duplicate_event_id" | "empty_batch" | "invalid_capacity" | "invalid_event_order" | "invalid_recording" | "mixed_factory_identity" | "mixed_session_identity";
3
+ export declare class FactoryEventSinkError extends Error {
4
+ readonly code: FactoryEventSinkErrorCode;
5
+ constructor(code: FactoryEventSinkErrorCode, message: string);
6
+ }
7
+ /** Caller-owned destination for one ordered, atomic Factory Event batch. */
8
+ export interface FactoryEventSink {
9
+ write(events: readonly FactoryEvent[]): Promise<void>;
10
+ /** Optional lifecycle boundary used when an emulator session closes. */
11
+ close?(): Promise<void>;
12
+ }
13
+ export interface MemoryFactoryEventSinkOptions {
14
+ /** Maximum number of events retained. A batch that crosses this bound fails whole. */
15
+ readonly maxEvents: number;
16
+ /** Optional asynchronous boundary for backpressure or caller-controlled failure injection. */
17
+ readonly beforeWrite?: (events: readonly FactoryEvent[]) => Promise<void>;
18
+ }
19
+ /**
20
+ * Bounded, caller-owned event history. Concurrent writes are processed in call
21
+ * order, and one rejected write does not prevent a later write or retry.
22
+ */
23
+ export declare class MemoryFactoryEventSink implements FactoryEventSink {
24
+ #private;
25
+ constructor(options: MemoryFactoryEventSinkOptions);
26
+ write(events: readonly FactoryEvent[]): Promise<void>;
27
+ snapshot(): readonly FactoryEvent[];
28
+ close(): Promise<void>;
29
+ }
@@ -0,0 +1,58 @@
1
+ export class FactoryEventSinkError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.name = "FactoryEventSinkError";
6
+ this.code = code;
7
+ }
8
+ }
9
+ function cloneEvents(events) {
10
+ return JSON.parse(JSON.stringify(events));
11
+ }
12
+ /**
13
+ * Bounded, caller-owned event history. Concurrent writes are processed in call
14
+ * order, and one rejected write does not prevent a later write or retry.
15
+ */
16
+ export class MemoryFactoryEventSink {
17
+ #maxEvents;
18
+ #beforeWrite;
19
+ #events = [];
20
+ #acceptingWrites = true;
21
+ #tail = Promise.resolve();
22
+ #closePromise;
23
+ constructor(options) {
24
+ if (!Number.isSafeInteger(options.maxEvents) || options.maxEvents < 1) {
25
+ throw new FactoryEventSinkError("invalid_capacity", "Memory sink maxEvents must be a positive safe integer.");
26
+ }
27
+ this.#maxEvents = options.maxEvents;
28
+ this.#beforeWrite = options.beforeWrite;
29
+ }
30
+ write(events) {
31
+ if (!this.#acceptingWrites) {
32
+ return Promise.reject(new FactoryEventSinkError("closed", "The event sink is closed."));
33
+ }
34
+ if (events.length === 0) {
35
+ return Promise.reject(new FactoryEventSinkError("empty_batch", "Factory Event batches must be non-empty."));
36
+ }
37
+ const proposedBatch = cloneEvents(events);
38
+ const write = this.#tail.then(async () => {
39
+ await this.#beforeWrite?.(cloneEvents(proposedBatch));
40
+ if (this.#events.length + proposedBatch.length > this.#maxEvents) {
41
+ throw new FactoryEventSinkError("capacity_exceeded", `The batch would exceed the configured ${this.#maxEvents}-event capacity.`);
42
+ }
43
+ this.#events = [...this.#events, ...proposedBatch];
44
+ });
45
+ this.#tail = write.catch(() => undefined);
46
+ return write;
47
+ }
48
+ snapshot() {
49
+ return cloneEvents(this.#events);
50
+ }
51
+ close() {
52
+ if (this.#closePromise !== undefined)
53
+ return this.#closePromise;
54
+ this.#acceptingWrites = false;
55
+ this.#closePromise = this.#tail;
56
+ return this.#closePromise;
57
+ }
58
+ }
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Generated by scripts/generate-scenario-schema.mjs from
3
+ * schema/scenario.schema.json. Do not edit this file directly.
4
+ */
5
+ export declare const generatedScenarioSchema: {
6
+ $schema: string;
7
+ $id: string;
8
+ title: string;
9
+ type: string;
10
+ additionalProperties: boolean;
11
+ required: string[];
12
+ properties: {
13
+ schemaVersion: {
14
+ const: string;
15
+ };
16
+ id: {
17
+ $ref: string;
18
+ };
19
+ factory: {
20
+ type: string;
21
+ additionalProperties: boolean;
22
+ required: string[];
23
+ properties: {
24
+ name: {
25
+ $ref: string;
26
+ };
27
+ };
28
+ };
29
+ seed: {
30
+ type: string;
31
+ minLength: number;
32
+ maxLength: number;
33
+ };
34
+ startAt: {
35
+ type: string;
36
+ format: string;
37
+ pattern: string;
38
+ };
39
+ rules: {
40
+ type: string;
41
+ items: {
42
+ $ref: string;
43
+ };
44
+ };
45
+ unmatched: {
46
+ $ref: string;
47
+ };
48
+ initialSubmissions: {
49
+ oneOf: ({
50
+ type: string;
51
+ items: {
52
+ $ref: string;
53
+ };
54
+ $ref?: undefined;
55
+ } | {
56
+ $ref: string;
57
+ type?: undefined;
58
+ items?: undefined;
59
+ })[];
60
+ };
61
+ };
62
+ $defs: {
63
+ stableIdentity: {
64
+ type: string;
65
+ minLength: number;
66
+ maxLength: number;
67
+ pattern: string;
68
+ };
69
+ nonEmptyText128: {
70
+ type: string;
71
+ minLength: number;
72
+ maxLength: number;
73
+ };
74
+ plainText: {
75
+ type: string;
76
+ maxLength: number;
77
+ };
78
+ feedbackText: {
79
+ type: string;
80
+ maxLength: number;
81
+ };
82
+ activityLabel: {
83
+ type: string;
84
+ minLength: number;
85
+ maxLength: number;
86
+ };
87
+ inputSelector: {
88
+ type: string;
89
+ additionalProperties: boolean;
90
+ properties: {
91
+ workType: {
92
+ $ref: string;
93
+ };
94
+ state: {
95
+ $ref: string;
96
+ };
97
+ name: {
98
+ $ref: string;
99
+ };
100
+ };
101
+ };
102
+ selector: {
103
+ type: string;
104
+ additionalProperties: boolean;
105
+ properties: {
106
+ workstation: {
107
+ $ref: string;
108
+ };
109
+ worker: {
110
+ $ref: string;
111
+ };
112
+ input: {
113
+ $ref: string;
114
+ };
115
+ };
116
+ };
117
+ cursor: {
118
+ type: string;
119
+ additionalProperties: boolean;
120
+ required: string[];
121
+ properties: {
122
+ scope: {
123
+ const: string;
124
+ };
125
+ input: {
126
+ const: string;
127
+ };
128
+ };
129
+ };
130
+ outcome: {
131
+ type: string;
132
+ additionalProperties: boolean;
133
+ required: string[];
134
+ properties: {
135
+ result: {
136
+ enum: string[];
137
+ };
138
+ durationMs: {
139
+ type: string;
140
+ minimum: number;
141
+ };
142
+ output: {
143
+ $ref: string;
144
+ };
145
+ feedback: {
146
+ $ref: string;
147
+ };
148
+ activityLabel: {
149
+ $ref: string;
150
+ };
151
+ error: {
152
+ $ref: string;
153
+ };
154
+ };
155
+ };
156
+ rule: {
157
+ type: string;
158
+ additionalProperties: boolean;
159
+ required: string[];
160
+ properties: {
161
+ id: {
162
+ $ref: string;
163
+ };
164
+ selector: {
165
+ $ref: string;
166
+ };
167
+ cursor: {
168
+ $ref: string;
169
+ };
170
+ outcomes: {
171
+ type: string;
172
+ minItems: number;
173
+ items: {
174
+ $ref: string;
175
+ };
176
+ };
177
+ exhaustion: {
178
+ enum: string[];
179
+ };
180
+ };
181
+ };
182
+ unmatched: {
183
+ oneOf: ({
184
+ type: string;
185
+ additionalProperties: boolean;
186
+ required: string[];
187
+ properties: {
188
+ behavior: {
189
+ const: string;
190
+ };
191
+ outcome?: undefined;
192
+ };
193
+ } | {
194
+ type: string;
195
+ additionalProperties: boolean;
196
+ required: string[];
197
+ properties: {
198
+ behavior: {
199
+ const: string;
200
+ };
201
+ outcome: {
202
+ $ref: string;
203
+ };
204
+ };
205
+ })[];
206
+ };
207
+ initialSubmission: {
208
+ type: string;
209
+ additionalProperties: boolean;
210
+ required: string[];
211
+ properties: {
212
+ name: {
213
+ $ref: string;
214
+ };
215
+ workType: {
216
+ $ref: string;
217
+ };
218
+ state: {
219
+ $ref: string;
220
+ };
221
+ input: {
222
+ $ref: string;
223
+ };
224
+ parent: {
225
+ $ref: string;
226
+ };
227
+ };
228
+ };
229
+ submissionRelation: {
230
+ type: string;
231
+ additionalProperties: boolean;
232
+ required: string[];
233
+ properties: {
234
+ type: {
235
+ enum: string[];
236
+ };
237
+ sourceWorkName: {
238
+ $ref: string;
239
+ };
240
+ targetWorkName: {
241
+ $ref: string;
242
+ };
243
+ requiredState: {
244
+ $ref: string;
245
+ };
246
+ };
247
+ };
248
+ submissionBatch: {
249
+ type: string;
250
+ additionalProperties: boolean;
251
+ required: string[];
252
+ properties: {
253
+ works: {
254
+ type: string;
255
+ items: {
256
+ $ref: string;
257
+ };
258
+ };
259
+ relations: {
260
+ type: string;
261
+ items: {
262
+ $ref: string;
263
+ };
264
+ };
265
+ };
266
+ };
267
+ };
268
+ };