@glyphlab/scenario-core 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +16 -2
- package/index.d.ts +310 -0
- package/index.js +535 -0
- package/index.js.map +1 -0
- package/package.json +40 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Subtubes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @glyphlab/scenario-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The scenario engine, without UI. Peer dependencies: `zod`, `@glyphlab/diagram-core`.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { defaultInput, readScenarioFile, runScenario } from '@glyphlab/scenario-core'
|
|
7
|
+
|
|
8
|
+
const file = readScenarioFile(json) // { ok, value: { scenarios } } or a typed error
|
|
9
|
+
const graph = { nodes: [{ id: 'a', title: 'Web' }], edges: [{ id: 'e', from: 'a', to: 'b' }] }
|
|
10
|
+
const scenario = file.ok ? file.value.scenarios[0] : undefined
|
|
11
|
+
const trace = scenario && runScenario(graph, scenario, defaultInput(scenario)) // steps, outcome, message
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- `runScenario(graph, scenario, input)` is pure: the same graph, scenario and input give the same trace.
|
|
15
|
+
- `validateScenario` / `checkNewScenario` report the steps that point at nodes or edges the graph does not have.
|
|
16
|
+
- `highlightsAt(trace, cursor)` gives what to light up on a diagram at a step (the types are `diagram-core`'s).
|
|
17
|
+
- `scenarioSchema` is the one definition used by the file format and by the createScenario tool.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as _glyphlab_diagram_core from '@glyphlab/diagram-core';
|
|
2
|
+
import { Highlights, DiagramFile } from '@glyphlab/diagram-core';
|
|
3
|
+
export { EdgeHighlight, Highlights, NodeHighlight } from '@glyphlab/diagram-core';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
type Primitive = string | number | boolean;
|
|
7
|
+
type Values = Record<string, Primitive>;
|
|
8
|
+
interface GraphModel {
|
|
9
|
+
nodes: {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}[];
|
|
13
|
+
edges: {
|
|
14
|
+
id: string;
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
label: string;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
interface InputField {
|
|
21
|
+
name: string;
|
|
22
|
+
label: string;
|
|
23
|
+
type: 'text' | 'number' | 'boolean' | 'select';
|
|
24
|
+
default: Primitive;
|
|
25
|
+
options?: string[];
|
|
26
|
+
help?: string;
|
|
27
|
+
fromChat?: boolean;
|
|
28
|
+
}
|
|
29
|
+
type CompareOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains' | 'truthy';
|
|
30
|
+
type Condition = {
|
|
31
|
+
all: Condition[];
|
|
32
|
+
} | {
|
|
33
|
+
any: Condition[];
|
|
34
|
+
} | {
|
|
35
|
+
not: Condition;
|
|
36
|
+
} | {
|
|
37
|
+
path: string;
|
|
38
|
+
op: CompareOp;
|
|
39
|
+
value?: Primitive | Primitive[];
|
|
40
|
+
};
|
|
41
|
+
type Outcome = 'success' | 'failure' | 'blocked' | 'stopped';
|
|
42
|
+
interface EndSpec {
|
|
43
|
+
outcome: Exclude<Outcome, 'stopped'>;
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
interface DeriveRule {
|
|
47
|
+
contains: string[];
|
|
48
|
+
value: Primitive;
|
|
49
|
+
}
|
|
50
|
+
interface Derive {
|
|
51
|
+
from: string;
|
|
52
|
+
rules: DeriveRule[];
|
|
53
|
+
default: Primitive;
|
|
54
|
+
}
|
|
55
|
+
type Effect = {
|
|
56
|
+
set: string;
|
|
57
|
+
value: Primitive;
|
|
58
|
+
} | {
|
|
59
|
+
set: string;
|
|
60
|
+
add: number;
|
|
61
|
+
} | {
|
|
62
|
+
set: string;
|
|
63
|
+
derive: Derive;
|
|
64
|
+
ai?: boolean;
|
|
65
|
+
};
|
|
66
|
+
interface Transition {
|
|
67
|
+
to?: string;
|
|
68
|
+
end?: EndSpec;
|
|
69
|
+
when?: Condition;
|
|
70
|
+
label?: string;
|
|
71
|
+
}
|
|
72
|
+
interface Call {
|
|
73
|
+
to: string;
|
|
74
|
+
title: string;
|
|
75
|
+
detail?: string;
|
|
76
|
+
when?: Condition;
|
|
77
|
+
}
|
|
78
|
+
interface StepRule {
|
|
79
|
+
node: string;
|
|
80
|
+
title: string;
|
|
81
|
+
detail?: string;
|
|
82
|
+
effects?: Effect[];
|
|
83
|
+
calls?: Call[];
|
|
84
|
+
next?: Transition[];
|
|
85
|
+
end?: EndSpec;
|
|
86
|
+
}
|
|
87
|
+
interface Preset {
|
|
88
|
+
name: string;
|
|
89
|
+
values: Values;
|
|
90
|
+
}
|
|
91
|
+
interface Scenario {
|
|
92
|
+
id: string;
|
|
93
|
+
title: string;
|
|
94
|
+
description: string;
|
|
95
|
+
triggers?: string[];
|
|
96
|
+
inputs: InputField[];
|
|
97
|
+
presets?: Preset[];
|
|
98
|
+
initialState?: Values;
|
|
99
|
+
start: string;
|
|
100
|
+
steps: Record<string, StepRule>;
|
|
101
|
+
}
|
|
102
|
+
interface ScenarioFile {
|
|
103
|
+
schemaVersion: 1;
|
|
104
|
+
scenarios: Scenario[];
|
|
105
|
+
}
|
|
106
|
+
type StepKind = 'enter' | 'call' | 'end';
|
|
107
|
+
interface StateChange {
|
|
108
|
+
key: string;
|
|
109
|
+
from?: Primitive;
|
|
110
|
+
to: Primitive;
|
|
111
|
+
ai?: boolean;
|
|
112
|
+
}
|
|
113
|
+
interface DecisionOption {
|
|
114
|
+
label: string;
|
|
115
|
+
taken: boolean;
|
|
116
|
+
}
|
|
117
|
+
interface TraceStep {
|
|
118
|
+
index: number;
|
|
119
|
+
kind: StepKind;
|
|
120
|
+
stepId: string;
|
|
121
|
+
nodeId: string;
|
|
122
|
+
nodeTitle: string;
|
|
123
|
+
edgeId?: string;
|
|
124
|
+
fromNodeId?: string;
|
|
125
|
+
title: string;
|
|
126
|
+
detail?: string;
|
|
127
|
+
changes: StateChange[];
|
|
128
|
+
state: Values;
|
|
129
|
+
decision?: {
|
|
130
|
+
options: DecisionOption[];
|
|
131
|
+
};
|
|
132
|
+
outcome?: Outcome;
|
|
133
|
+
warnings: string[];
|
|
134
|
+
}
|
|
135
|
+
interface Trace {
|
|
136
|
+
scenarioId: string;
|
|
137
|
+
scenarioTitle: string;
|
|
138
|
+
input: Values;
|
|
139
|
+
steps: TraceStep[];
|
|
140
|
+
outcome: Outcome;
|
|
141
|
+
message: string;
|
|
142
|
+
}
|
|
143
|
+
interface ScenarioIssue {
|
|
144
|
+
severity: 'error' | 'warning';
|
|
145
|
+
message: string;
|
|
146
|
+
}
|
|
147
|
+
interface ScenarioAttachment {
|
|
148
|
+
scenarioId: string;
|
|
149
|
+
scenarioVersion?: string;
|
|
150
|
+
engineVersion?: number;
|
|
151
|
+
input: Values;
|
|
152
|
+
result?: {
|
|
153
|
+
outcome: Outcome;
|
|
154
|
+
message: string;
|
|
155
|
+
steps: number;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
declare function edgeBetween(graph: GraphModel, from: string, to: string): {
|
|
160
|
+
id: string;
|
|
161
|
+
from: string;
|
|
162
|
+
to: string;
|
|
163
|
+
label: string;
|
|
164
|
+
} | undefined;
|
|
165
|
+
declare function runScenario(graph: GraphModel, scenario: Scenario, input: Values): Trace;
|
|
166
|
+
declare function defaultInput(scenario: Scenario): Values;
|
|
167
|
+
|
|
168
|
+
type NewScenarioCheck = {
|
|
169
|
+
ok: true;
|
|
170
|
+
issues: ScenarioIssue[];
|
|
171
|
+
preview: {
|
|
172
|
+
outcome: 'success' | 'failure' | 'blocked' | 'stopped';
|
|
173
|
+
message: string;
|
|
174
|
+
steps: number;
|
|
175
|
+
};
|
|
176
|
+
} | {
|
|
177
|
+
ok: false;
|
|
178
|
+
error: 'duplicate-id' | 'unknown-nodes' | 'invalid';
|
|
179
|
+
message: string;
|
|
180
|
+
issues?: ScenarioIssue[];
|
|
181
|
+
};
|
|
182
|
+
declare function checkNewScenario(scenario: Scenario, graph: GraphModel, existingIds: string[]): NewScenarioCheck;
|
|
183
|
+
|
|
184
|
+
declare function validateScenario(scenario: Scenario, graph: GraphModel): ScenarioIssue[];
|
|
185
|
+
|
|
186
|
+
declare function highlightsAt(trace: Trace, cursor: number): Highlights;
|
|
187
|
+
|
|
188
|
+
type ScenarioCondition = {
|
|
189
|
+
all: ScenarioCondition[];
|
|
190
|
+
} | {
|
|
191
|
+
any: ScenarioCondition[];
|
|
192
|
+
} | {
|
|
193
|
+
not: ScenarioCondition;
|
|
194
|
+
} | {
|
|
195
|
+
path: string;
|
|
196
|
+
op: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains' | 'truthy';
|
|
197
|
+
value?: string | number | boolean | (string | number | boolean)[];
|
|
198
|
+
};
|
|
199
|
+
declare const conditionSchema: z.ZodType<ScenarioCondition>;
|
|
200
|
+
declare const scenarioSchema: z.ZodObject<{
|
|
201
|
+
id: z.ZodString;
|
|
202
|
+
title: z.ZodString;
|
|
203
|
+
description: z.ZodString;
|
|
204
|
+
triggers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
205
|
+
inputs: z.ZodArray<z.ZodObject<{
|
|
206
|
+
name: z.ZodString;
|
|
207
|
+
label: z.ZodString;
|
|
208
|
+
type: z.ZodEnum<{
|
|
209
|
+
number: "number";
|
|
210
|
+
boolean: "boolean";
|
|
211
|
+
text: "text";
|
|
212
|
+
select: "select";
|
|
213
|
+
}>;
|
|
214
|
+
default: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
215
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
216
|
+
help: z.ZodOptional<z.ZodString>;
|
|
217
|
+
fromChat: z.ZodOptional<z.ZodBoolean>;
|
|
218
|
+
}, z.core.$strict>>;
|
|
219
|
+
presets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
220
|
+
name: z.ZodString;
|
|
221
|
+
values: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
222
|
+
}, z.core.$strict>>>;
|
|
223
|
+
initialState: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
224
|
+
start: z.ZodString;
|
|
225
|
+
steps: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
226
|
+
node: z.ZodString;
|
|
227
|
+
title: z.ZodString;
|
|
228
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
229
|
+
effects: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
230
|
+
set: z.ZodString;
|
|
231
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
232
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
233
|
+
set: z.ZodString;
|
|
234
|
+
add: z.ZodNumber;
|
|
235
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
236
|
+
set: z.ZodString;
|
|
237
|
+
derive: z.ZodObject<{
|
|
238
|
+
from: z.ZodString;
|
|
239
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
240
|
+
contains: z.ZodArray<z.ZodString>;
|
|
241
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
242
|
+
}, z.core.$strict>>;
|
|
243
|
+
default: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
244
|
+
}, z.core.$strict>;
|
|
245
|
+
ai: z.ZodOptional<z.ZodBoolean>;
|
|
246
|
+
}, z.core.$strict>]>>>;
|
|
247
|
+
calls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
248
|
+
to: z.ZodString;
|
|
249
|
+
title: z.ZodString;
|
|
250
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
251
|
+
when: z.ZodOptional<z.ZodType<ScenarioCondition, unknown, z.core.$ZodTypeInternals<ScenarioCondition, unknown>>>;
|
|
252
|
+
}, z.core.$strict>>>;
|
|
253
|
+
next: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
254
|
+
to: z.ZodOptional<z.ZodString>;
|
|
255
|
+
end: z.ZodOptional<z.ZodObject<{
|
|
256
|
+
outcome: z.ZodEnum<{
|
|
257
|
+
success: "success";
|
|
258
|
+
failure: "failure";
|
|
259
|
+
blocked: "blocked";
|
|
260
|
+
}>;
|
|
261
|
+
message: z.ZodString;
|
|
262
|
+
}, z.core.$strict>>;
|
|
263
|
+
when: z.ZodOptional<z.ZodType<ScenarioCondition, unknown, z.core.$ZodTypeInternals<ScenarioCondition, unknown>>>;
|
|
264
|
+
label: z.ZodOptional<z.ZodString>;
|
|
265
|
+
}, z.core.$strict>>>;
|
|
266
|
+
end: z.ZodOptional<z.ZodObject<{
|
|
267
|
+
outcome: z.ZodEnum<{
|
|
268
|
+
success: "success";
|
|
269
|
+
failure: "failure";
|
|
270
|
+
blocked: "blocked";
|
|
271
|
+
}>;
|
|
272
|
+
message: z.ZodString;
|
|
273
|
+
}, z.core.$strict>>;
|
|
274
|
+
}, z.core.$strict>>;
|
|
275
|
+
}, z.core.$strict>;
|
|
276
|
+
type ScenarioDraft = z.infer<typeof scenarioSchema>;
|
|
277
|
+
|
|
278
|
+
declare const SCENARIO_SCHEMA_VERSION = 1;
|
|
279
|
+
|
|
280
|
+
declare const scenarioFileV1Schema: z.ZodType<ScenarioFile>;
|
|
281
|
+
declare const scenarioFileSchema: z.ZodType<ScenarioFile, unknown, z.core.$ZodTypeInternals<ScenarioFile, unknown>>;
|
|
282
|
+
declare const scenarioFileFormat: _glyphlab_diagram_core.Format<ScenarioFile>;
|
|
283
|
+
declare function parseScenarioFile(input: unknown): ScenarioFile;
|
|
284
|
+
declare function readScenarioFile(input: unknown): _glyphlab_diagram_core.ReadResult<ScenarioFile>;
|
|
285
|
+
|
|
286
|
+
declare const ENGINE_VERSION = 1;
|
|
287
|
+
declare function scenarioVersion(scenario: Scenario): string;
|
|
288
|
+
|
|
289
|
+
declare const DESIGN_EXPORT_FORMAT = "glyph-design";
|
|
290
|
+
declare const DESIGN_EXPORT_VERSION = 1;
|
|
291
|
+
interface DesignExport {
|
|
292
|
+
format: typeof DESIGN_EXPORT_FORMAT;
|
|
293
|
+
schemaVersion: typeof DESIGN_EXPORT_VERSION;
|
|
294
|
+
diagram: DiagramFile;
|
|
295
|
+
scenarios: Scenario[];
|
|
296
|
+
}
|
|
297
|
+
declare function writeDesignExport(diagram: DiagramFile, scenarios: Scenario[]): string;
|
|
298
|
+
type ReadDesignExport = {
|
|
299
|
+
ok: true;
|
|
300
|
+
diagram: DiagramFile;
|
|
301
|
+
scenarios: Scenario[];
|
|
302
|
+
warnings: string[];
|
|
303
|
+
} | {
|
|
304
|
+
ok: false;
|
|
305
|
+
reason: string;
|
|
306
|
+
};
|
|
307
|
+
declare function readDesignExport(text: string): ReadDesignExport;
|
|
308
|
+
|
|
309
|
+
export { DESIGN_EXPORT_FORMAT, DESIGN_EXPORT_VERSION, ENGINE_VERSION, SCENARIO_SCHEMA_VERSION, checkNewScenario, conditionSchema, defaultInput, edgeBetween, highlightsAt, parseScenarioFile, readDesignExport, readScenarioFile, runScenario, scenarioFileFormat, scenarioFileSchema, scenarioFileV1Schema, scenarioSchema, scenarioVersion, validateScenario, writeDesignExport };
|
|
310
|
+
export type { Call, CompareOp, Condition, DecisionOption, Derive, DeriveRule, DesignExport, Effect, EndSpec, GraphModel, InputField, NewScenarioCheck, Outcome, Preset, Primitive, ReadDesignExport, Scenario, ScenarioAttachment, ScenarioCondition, ScenarioDraft, ScenarioFile, ScenarioIssue, StateChange, StepKind, StepRule, Trace, TraceStep, Transition, Values };
|
package/index.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
// packages/scenario-core/src/conditions.ts
|
|
2
|
+
function resolvePath(scope, path) {
|
|
3
|
+
const [root, ...rest] = path.split(".");
|
|
4
|
+
const key = rest.join(".");
|
|
5
|
+
if (root === "input") return scope.input[key];
|
|
6
|
+
if (root === "state") return scope.state[key];
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
function compare(op, left, right) {
|
|
10
|
+
switch (op) {
|
|
11
|
+
case "truthy":
|
|
12
|
+
return Boolean(left);
|
|
13
|
+
case "eq":
|
|
14
|
+
return left === right;
|
|
15
|
+
case "ne":
|
|
16
|
+
return left !== right;
|
|
17
|
+
case "gt":
|
|
18
|
+
return Number(left) > Number(right);
|
|
19
|
+
case "gte":
|
|
20
|
+
return Number(left) >= Number(right);
|
|
21
|
+
case "lt":
|
|
22
|
+
return Number(left) < Number(right);
|
|
23
|
+
case "lte":
|
|
24
|
+
return Number(left) <= Number(right);
|
|
25
|
+
case "in":
|
|
26
|
+
return Array.isArray(right) && left !== void 0 && right.includes(left);
|
|
27
|
+
case "contains":
|
|
28
|
+
return typeof right === "string" && String(left ?? "").toLowerCase().includes(right.toLowerCase());
|
|
29
|
+
default:
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function evaluate(condition, scope) {
|
|
34
|
+
if ("all" in condition) return condition.all.every((c) => evaluate(c, scope));
|
|
35
|
+
if ("any" in condition) return condition.any.some((c) => evaluate(c, scope));
|
|
36
|
+
if ("not" in condition) return !evaluate(condition.not, scope);
|
|
37
|
+
return compare(condition.op, resolvePath(scope, condition.path), condition.value);
|
|
38
|
+
}
|
|
39
|
+
function show(value) {
|
|
40
|
+
if (Array.isArray(value)) return `[${value.map(show).join(", ")}]`;
|
|
41
|
+
return typeof value === "string" ? `\u201C${value}\u201D` : String(value);
|
|
42
|
+
}
|
|
43
|
+
var OP_TEXT = {
|
|
44
|
+
eq: "is",
|
|
45
|
+
ne: "is not",
|
|
46
|
+
gt: ">",
|
|
47
|
+
gte: "\u2265",
|
|
48
|
+
lt: "<",
|
|
49
|
+
lte: "\u2264",
|
|
50
|
+
in: "is one of",
|
|
51
|
+
contains: "contains"
|
|
52
|
+
};
|
|
53
|
+
function describeCondition(condition) {
|
|
54
|
+
if ("all" in condition) return condition.all.map(describeCondition).join(" and ");
|
|
55
|
+
if ("any" in condition) return condition.any.map(describeCondition).join(" or ");
|
|
56
|
+
if ("not" in condition) return `not (${describeCondition(condition.not)})`;
|
|
57
|
+
const name = condition.path.split(".").slice(1).join(".");
|
|
58
|
+
if (condition.op === "truthy") return `${name} is on`;
|
|
59
|
+
return `${name} ${OP_TEXT[condition.op]} ${show(condition.value)}`;
|
|
60
|
+
}
|
|
61
|
+
function interpolate(text, scope) {
|
|
62
|
+
return text.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, path) => String(resolvePath(scope, path) ?? ""));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// packages/scenario-core/src/engine.ts
|
|
66
|
+
var MAX_STEPS = 60;
|
|
67
|
+
function edgeBetween(graph, from, to) {
|
|
68
|
+
return graph.edges.find((e) => e.from === from && e.to === to);
|
|
69
|
+
}
|
|
70
|
+
function applyEffects(effects, input, state) {
|
|
71
|
+
const changes = [];
|
|
72
|
+
for (const effect of effects) {
|
|
73
|
+
const scope = { input, state };
|
|
74
|
+
let value;
|
|
75
|
+
let ai = false;
|
|
76
|
+
if ("derive" in effect) {
|
|
77
|
+
const source = String(resolvePath(scope, effect.derive.from) ?? "").toLowerCase();
|
|
78
|
+
const rule = effect.derive.rules.find((r) => r.contains.some((word) => source.includes(word.toLowerCase())));
|
|
79
|
+
value = rule ? rule.value : effect.derive.default;
|
|
80
|
+
ai = effect.ai === true;
|
|
81
|
+
} else if ("add" in effect) {
|
|
82
|
+
value = Number(state[effect.set] ?? 0) + effect.add;
|
|
83
|
+
} else {
|
|
84
|
+
value = typeof effect.value === "string" ? interpolate(effect.value, scope) : effect.value;
|
|
85
|
+
}
|
|
86
|
+
if (state[effect.set] !== value) {
|
|
87
|
+
changes.push({ key: effect.set, from: state[effect.set], to: value, ai: ai || void 0 });
|
|
88
|
+
state[effect.set] = value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return changes;
|
|
92
|
+
}
|
|
93
|
+
function runScenario(graph, scenario, input) {
|
|
94
|
+
const titles = new Map(graph.nodes.map((n) => [n.id, n.title]));
|
|
95
|
+
const nodeTitle = (id) => titles.get(id) ?? id;
|
|
96
|
+
const state = { ...scenario.initialState };
|
|
97
|
+
const steps = [];
|
|
98
|
+
let outcome = "stopped";
|
|
99
|
+
let message = "";
|
|
100
|
+
const scope = () => ({ input, state });
|
|
101
|
+
const push = (step) => steps.push({ ...step, index: steps.length, nodeTitle: nodeTitle(step.nodeId), state: { ...state } });
|
|
102
|
+
const end = (stepId, nodeId, result) => {
|
|
103
|
+
outcome = result.outcome;
|
|
104
|
+
message = interpolate(result.message, scope());
|
|
105
|
+
push({ kind: "end", stepId, nodeId, title: message, outcome, changes: [], warnings: [] });
|
|
106
|
+
};
|
|
107
|
+
let current = scenario.start;
|
|
108
|
+
let incoming = {};
|
|
109
|
+
while (current !== void 0) {
|
|
110
|
+
const rule = scenario.steps[current];
|
|
111
|
+
if (!rule) {
|
|
112
|
+
outcome = "stopped";
|
|
113
|
+
message = `Step \u201C${current}\u201D is not defined.`;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (steps.length >= MAX_STEPS) {
|
|
117
|
+
end(current, rule.node, { outcome: "stopped", message: `Stopped after ${MAX_STEPS} steps; the scenario may be looping.` });
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const changes = applyEffects(rule.effects ?? [], input, state);
|
|
121
|
+
const warnings = [];
|
|
122
|
+
if (incoming.from && !incoming.edgeId) {
|
|
123
|
+
warnings.push(`The diagram has no edge from \u201C${nodeTitle(incoming.from)}\u201D to \u201C${nodeTitle(rule.node)}\u201D.`);
|
|
124
|
+
}
|
|
125
|
+
push({
|
|
126
|
+
kind: "enter",
|
|
127
|
+
stepId: current,
|
|
128
|
+
nodeId: rule.node,
|
|
129
|
+
edgeId: incoming.edgeId,
|
|
130
|
+
fromNodeId: incoming.from,
|
|
131
|
+
title: interpolate(rule.title, scope()),
|
|
132
|
+
detail: rule.detail ? interpolate(rule.detail, scope()) : void 0,
|
|
133
|
+
changes,
|
|
134
|
+
warnings
|
|
135
|
+
});
|
|
136
|
+
const enterIndex = steps.length - 1;
|
|
137
|
+
for (const call of rule.calls ?? []) {
|
|
138
|
+
if (call.when && !evaluate(call.when, scope())) continue;
|
|
139
|
+
const edge2 = edgeBetween(graph, rule.node, call.to);
|
|
140
|
+
push({
|
|
141
|
+
kind: "call",
|
|
142
|
+
stepId: current,
|
|
143
|
+
nodeId: call.to,
|
|
144
|
+
edgeId: edge2?.id,
|
|
145
|
+
fromNodeId: rule.node,
|
|
146
|
+
title: interpolate(call.title, scope()),
|
|
147
|
+
detail: call.detail ? interpolate(call.detail, scope()) : void 0,
|
|
148
|
+
changes: [],
|
|
149
|
+
warnings: edge2 ? [] : [`The diagram has no edge from \u201C${nodeTitle(rule.node)}\u201D to \u201C${nodeTitle(call.to)}\u201D.`]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (rule.end) {
|
|
153
|
+
end(current, rule.node, rule.end);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
const next = rule.next ?? [];
|
|
157
|
+
if (next.length === 0) {
|
|
158
|
+
end(current, rule.node, { outcome: "success", message: "The flow finished." });
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
const taken = next.findIndex((t) => !t.when || evaluate(t.when, scope()));
|
|
162
|
+
if (next.length > 1) {
|
|
163
|
+
steps[enterIndex].decision = {
|
|
164
|
+
options: next.map((t, i) => ({
|
|
165
|
+
label: t.label ?? (t.when ? describeCondition(t.when) : "otherwise"),
|
|
166
|
+
taken: i === taken
|
|
167
|
+
}))
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (taken < 0) {
|
|
171
|
+
end(current, rule.node, { outcome: "stopped", message: "No branch matched here, so the run stopped." });
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
const transition = next[taken];
|
|
175
|
+
if (transition.end) {
|
|
176
|
+
end(current, rule.node, transition.end);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
const target = transition.to !== void 0 ? scenario.steps[transition.to] : void 0;
|
|
180
|
+
const edge = target ? edgeBetween(graph, rule.node, target.node) : void 0;
|
|
181
|
+
incoming = { edgeId: edge?.id, from: rule.node };
|
|
182
|
+
current = transition.to;
|
|
183
|
+
}
|
|
184
|
+
return { scenarioId: scenario.id, scenarioTitle: scenario.title, input, steps, outcome, message };
|
|
185
|
+
}
|
|
186
|
+
function defaultInput(scenario) {
|
|
187
|
+
return Object.fromEntries(scenario.inputs.map((f) => [f.name, f.default]));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// packages/scenario-core/src/validate.ts
|
|
191
|
+
function validateScenario(scenario, graph) {
|
|
192
|
+
const issues = [];
|
|
193
|
+
const nodeIds = new Set(graph.nodes.map((n) => n.id));
|
|
194
|
+
for (const [stepId, step] of Object.entries(scenario.steps)) {
|
|
195
|
+
if (!nodeIds.has(step.node)) {
|
|
196
|
+
issues.push({ severity: "error", message: `Step \u201C${stepId}\u201D refers to a node that is not in the diagram: ${step.node}` });
|
|
197
|
+
}
|
|
198
|
+
for (const call of step.calls ?? []) {
|
|
199
|
+
if (nodeIds.has(step.node) && nodeIds.has(call.to) && !edgeBetween(graph, step.node, call.to)) {
|
|
200
|
+
issues.push({ severity: "warning", message: `No edge from ${step.node} to ${call.to} for the call in \u201C${stepId}\u201D.` });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const next of step.next ?? []) {
|
|
204
|
+
const target = next.to !== void 0 ? scenario.steps[next.to] : void 0;
|
|
205
|
+
if (target && nodeIds.has(step.node) && nodeIds.has(target.node) && !edgeBetween(graph, step.node, target.node)) {
|
|
206
|
+
issues.push({ severity: "warning", message: `No edge from ${step.node} to ${target.node} for the branch in \u201C${stepId}\u201D.` });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return issues;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// packages/scenario-core/src/check.ts
|
|
214
|
+
var MAX_LISTED_NODES = 40;
|
|
215
|
+
function checkNewScenario(scenario, graph, existingIds) {
|
|
216
|
+
if (existingIds.includes(scenario.id)) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: "duplicate-id",
|
|
220
|
+
message: `This design already has a scenario with the id "${scenario.id}". Use a different id. Existing ids: ${existingIds.join(", ")}.`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const inputProblems = checkInputs(scenario);
|
|
224
|
+
if (inputProblems.length > 0) {
|
|
225
|
+
return { ok: false, error: "invalid", message: `The scenario's inputs are not usable: ${inputProblems.join(" ")}` };
|
|
226
|
+
}
|
|
227
|
+
const unknown = unknownNodes(scenario, graph);
|
|
228
|
+
if (unknown.length > 0) {
|
|
229
|
+
const byTitle = new Map(graph.nodes.map((n) => [n.title.trim().toLowerCase(), n.id]));
|
|
230
|
+
const lines = unknown.map((ref) => {
|
|
231
|
+
const guess = byTitle.get(ref.trim().toLowerCase());
|
|
232
|
+
return guess ? `"${ref}" is a title, not an id; use "${guess}".` : `"${ref}" is not a node in the diagram.`;
|
|
233
|
+
});
|
|
234
|
+
const known = graph.nodes.slice(0, MAX_LISTED_NODES).map((n) => `${n.id} (${n.title})`).join(", ");
|
|
235
|
+
const more = graph.nodes.length > MAX_LISTED_NODES ? `, and ${graph.nodes.length - MAX_LISTED_NODES} more (call getDiagram)` : "";
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: "unknown-nodes",
|
|
239
|
+
message: `Steps must happen at nodes that exist. ${lines.join(" ")} Nodes in the diagram: ${known || "none"}${more}.`
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const trace = runScenario(graph, scenario, defaultInput(scenario));
|
|
243
|
+
if (trace.outcome === "stopped") {
|
|
244
|
+
return { ok: false, error: "invalid", message: `A trial run with the default inputs did not finish: ${trace.message} Make sure every step either ends or has a branch that always applies (a last one with no "when").` };
|
|
245
|
+
}
|
|
246
|
+
const issues = [...validateScenario(scenario, graph).filter((i) => i.severity === "warning"), ...lint(scenario)];
|
|
247
|
+
return { ok: true, issues, preview: { outcome: trace.outcome, message: trace.message, steps: trace.steps.length } };
|
|
248
|
+
}
|
|
249
|
+
function checkInputs(scenario) {
|
|
250
|
+
const problems = [];
|
|
251
|
+
const seen = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const field of scenario.inputs) {
|
|
253
|
+
if (seen.has(field.name)) problems.push(`Two inputs are called "${field.name}".`);
|
|
254
|
+
seen.add(field.name);
|
|
255
|
+
const kind = typeof field.default;
|
|
256
|
+
if (field.type === "number" && kind !== "number") problems.push(`Input "${field.name}" is a number, so its default must be a number.`);
|
|
257
|
+
if (field.type === "boolean" && kind !== "boolean") problems.push(`Input "${field.name}" is a boolean, so its default must be true or false.`);
|
|
258
|
+
if (field.type === "text" && kind !== "string") problems.push(`Input "${field.name}" is text, so its default must be a string.`);
|
|
259
|
+
if (field.type === "select") {
|
|
260
|
+
if (!field.options || field.options.length === 0) problems.push(`Input "${field.name}" is a select, so it needs "options".`);
|
|
261
|
+
else if (!field.options.includes(String(field.default))) problems.push(`The default of "${field.name}" must be one of its options.`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return problems;
|
|
265
|
+
}
|
|
266
|
+
function unknownNodes(scenario, graph) {
|
|
267
|
+
const ids = new Set(graph.nodes.map((n) => n.id));
|
|
268
|
+
const unknown = [];
|
|
269
|
+
const check = (ref) => {
|
|
270
|
+
if (!ids.has(ref) && !unknown.includes(ref)) unknown.push(ref);
|
|
271
|
+
};
|
|
272
|
+
for (const step of Object.values(scenario.steps)) {
|
|
273
|
+
check(step.node);
|
|
274
|
+
for (const call of step.calls ?? []) check(call.to);
|
|
275
|
+
}
|
|
276
|
+
return unknown;
|
|
277
|
+
}
|
|
278
|
+
function lint(scenario) {
|
|
279
|
+
const issues = [];
|
|
280
|
+
const warn = (message) => issues.push({ severity: "warning", message });
|
|
281
|
+
const inputNames = new Set(scenario.inputs.map((f) => f.name));
|
|
282
|
+
const stateNames = new Set(Object.keys(scenario.initialState ?? {}));
|
|
283
|
+
for (const step of Object.values(scenario.steps)) for (const effect of step.effects ?? []) stateNames.add(effect.set);
|
|
284
|
+
const checkRef = (ref, where) => {
|
|
285
|
+
const [root, ...rest] = ref.split(".");
|
|
286
|
+
const name = rest.join(".");
|
|
287
|
+
if (root === "input" && !inputNames.has(name)) warn(`${where} reads input.${name}, but the scenario has no input called "${name}".`);
|
|
288
|
+
if (root === "state" && !stateNames.has(name)) warn(`${where} reads state.${name}, but no step sets it and it is not in initialState.`);
|
|
289
|
+
};
|
|
290
|
+
const conditionPaths = (condition) => {
|
|
291
|
+
if (typeof condition !== "object" || condition === null) return [];
|
|
292
|
+
const c = condition;
|
|
293
|
+
if (Array.isArray(c.all)) return c.all.flatMap(conditionPaths);
|
|
294
|
+
if (Array.isArray(c.any)) return c.any.flatMap(conditionPaths);
|
|
295
|
+
if ("not" in c) return conditionPaths(c.not);
|
|
296
|
+
return typeof c.path === "string" ? [c.path] : [];
|
|
297
|
+
};
|
|
298
|
+
for (const [stepId, step] of Object.entries(scenario.steps)) {
|
|
299
|
+
const where = `Step \u201C${stepId}\u201D`;
|
|
300
|
+
for (const next of step.next ?? []) for (const path of conditionPaths(next.when)) checkRef(path, where);
|
|
301
|
+
for (const call of step.calls ?? []) for (const path of conditionPaths(call.when)) checkRef(path, where);
|
|
302
|
+
for (const effect of step.effects ?? []) if ("derive" in effect) checkRef(effect.derive.from, where);
|
|
303
|
+
}
|
|
304
|
+
for (const text of strings(scenario)) {
|
|
305
|
+
for (const match of text.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) checkRef(match[1], "The text");
|
|
306
|
+
}
|
|
307
|
+
for (const preset of scenario.presets ?? []) {
|
|
308
|
+
for (const key of Object.keys(preset.values)) {
|
|
309
|
+
if (!inputNames.has(key)) warn(`Preset \u201C${preset.name}\u201D sets "${key}", which is not an input.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const reached = /* @__PURE__ */ new Set();
|
|
313
|
+
const visit = (id) => {
|
|
314
|
+
if (reached.has(id) || !scenario.steps[id]) return;
|
|
315
|
+
reached.add(id);
|
|
316
|
+
for (const next of scenario.steps[id].next ?? []) if (next.to !== void 0) visit(next.to);
|
|
317
|
+
};
|
|
318
|
+
visit(scenario.start);
|
|
319
|
+
for (const id of Object.keys(scenario.steps)) if (!reached.has(id)) warn(`Step \u201C${id}\u201D can never be reached from the start.`);
|
|
320
|
+
return dedupe(issues);
|
|
321
|
+
}
|
|
322
|
+
function strings(value) {
|
|
323
|
+
if (typeof value === "string") return [value];
|
|
324
|
+
if (Array.isArray(value)) return value.flatMap(strings);
|
|
325
|
+
if (typeof value === "object" && value !== null) return Object.values(value).flatMap(strings);
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
function dedupe(issues) {
|
|
329
|
+
const seen = /* @__PURE__ */ new Set();
|
|
330
|
+
return issues.filter((i) => seen.has(i.message) ? false : (seen.add(i.message), true));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// packages/scenario-core/src/highlights.ts
|
|
334
|
+
var OUTCOME_HIGHLIGHT = { success: "success", failure: "failure", blocked: "blocked" };
|
|
335
|
+
function highlightsAt(trace, cursor) {
|
|
336
|
+
const nodes = {};
|
|
337
|
+
const edges = {};
|
|
338
|
+
for (let i = 0; i <= cursor && i < trace.steps.length; i++) {
|
|
339
|
+
const step = trace.steps[i];
|
|
340
|
+
nodes[step.nodeId] = "visited";
|
|
341
|
+
if (step.edgeId) edges[step.edgeId] = "visited";
|
|
342
|
+
}
|
|
343
|
+
const current = trace.steps[cursor];
|
|
344
|
+
if (current) {
|
|
345
|
+
const outcome = current.kind === "end" ? current.outcome : void 0;
|
|
346
|
+
nodes[current.nodeId] = outcome && outcome !== "stopped" ? OUTCOME_HIGHLIGHT[outcome] : "active";
|
|
347
|
+
if (current.edgeId) edges[current.edgeId] = "active";
|
|
348
|
+
}
|
|
349
|
+
return { nodes, edges };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// packages/scenario-core/src/scenarioSchema.ts
|
|
353
|
+
import { z } from "zod";
|
|
354
|
+
var idSchema = z.string().min(1);
|
|
355
|
+
var primitive = z.union([z.string(), z.number(), z.boolean()]);
|
|
356
|
+
var pathSchema = z.string().regex(/^(input|state)\.[\w]+$/, "Use input.<name> or state.<name>").describe('Where to read a value: "input.<name>" is something the user entered; "state.<name>" is something an earlier step set.');
|
|
357
|
+
var outcomeSchema = z.enum(["success", "failure", "blocked"]);
|
|
358
|
+
var conditionSchema = z.lazy(
|
|
359
|
+
() => z.union([
|
|
360
|
+
z.strictObject({ all: z.array(conditionSchema) }),
|
|
361
|
+
z.strictObject({ any: z.array(conditionSchema) }),
|
|
362
|
+
z.strictObject({ not: conditionSchema }),
|
|
363
|
+
z.strictObject({
|
|
364
|
+
path: pathSchema,
|
|
365
|
+
op: z.enum(["eq", "ne", "gt", "gte", "lt", "lte", "in", "contains", "truthy"]).describe('"eq"/"ne" compare for equality; "gt"/"gte"/"lt"/"lte" compare numbers; "in" needs a list as value; "contains" is a case-insensitive text match; "truthy" needs no value.'),
|
|
366
|
+
value: z.union([primitive, z.array(primitive)]).optional()
|
|
367
|
+
})
|
|
368
|
+
])
|
|
369
|
+
).describe("A test on the input or the run state. Combine tests with {all:[...]}, {any:[...]} or {not:...}.");
|
|
370
|
+
var endSchema = z.strictObject({
|
|
371
|
+
outcome: outcomeSchema.describe('How the run ended: "success", "failure" (something went wrong), or "blocked" (it was refused or had to stop).'),
|
|
372
|
+
message: z.string().min(1).describe("One sentence saying what happened. May use {{input.x}} and {{state.x}}.")
|
|
373
|
+
}).describe("Ends the run here.");
|
|
374
|
+
var effectSchema = z.union([
|
|
375
|
+
z.strictObject({ set: idSchema, value: primitive }),
|
|
376
|
+
z.strictObject({ set: idSchema, add: z.number() }),
|
|
377
|
+
z.strictObject({
|
|
378
|
+
set: idSchema,
|
|
379
|
+
derive: z.strictObject({
|
|
380
|
+
from: pathSchema,
|
|
381
|
+
rules: z.array(z.strictObject({ contains: z.array(z.string().min(1)).min(1), value: primitive })).describe("Tried in order; the first whose words appear in the text wins."),
|
|
382
|
+
default: primitive
|
|
383
|
+
}),
|
|
384
|
+
ai: z.boolean().optional().describe("Mark the result as something the assistant would work out, in the display.")
|
|
385
|
+
})
|
|
386
|
+
]).describe("Changes the run state when the step happens. {set, value} stores a value (text may use {{input.x}}/{{state.x}}); {set, add} adds to a number; {set, derive} reads free text and picks a value by keyword.");
|
|
387
|
+
var transitionSchema = z.strictObject({
|
|
388
|
+
to: idSchema.optional().describe('The id of the next step (a key of "steps").'),
|
|
389
|
+
end: endSchema.optional(),
|
|
390
|
+
when: conditionSchema.optional().describe('Take this branch only if the condition holds. Branches are tried in order, so put a branch with no "when" last as the fallback.'),
|
|
391
|
+
label: z.string().optional().describe("A short label for the branch, shown where the choice was made.")
|
|
392
|
+
}).refine((t) => t.to !== void 0 !== (t.end !== void 0), 'A branch needs exactly one of "to" or "end"').describe('A way forward from a step: either "to" another step, or "end" the run.');
|
|
393
|
+
var stepSchema = z.strictObject({
|
|
394
|
+
node: idSchema.describe("The id of the diagram node where this step happens. Use ids from getDiagram, never titles."),
|
|
395
|
+
title: z.string().min(1).describe("What happens here, as a short sentence. May use {{input.x}} and {{state.x}}."),
|
|
396
|
+
detail: z.string().optional(),
|
|
397
|
+
effects: z.array(effectSchema).optional(),
|
|
398
|
+
calls: z.array(
|
|
399
|
+
z.strictObject({
|
|
400
|
+
to: idSchema.describe("The id of a diagram node that this node calls and gets an answer from straight away (a lookup, a tool). Ideally an edge goes from this node to it."),
|
|
401
|
+
title: z.string().min(1),
|
|
402
|
+
detail: z.string().optional(),
|
|
403
|
+
when: conditionSchema.optional()
|
|
404
|
+
})
|
|
405
|
+
).optional().describe("Side trips to other nodes that return immediately; they show on the diagram but do not move the flow."),
|
|
406
|
+
next: z.array(transitionSchema).optional().describe('Where to go after this step. With none (and no "end"), the run ends as a success.'),
|
|
407
|
+
end: endSchema.optional().describe('Ends the run at this step, instead of "next".')
|
|
408
|
+
}).describe("One step of the scenario, at one diagram node.");
|
|
409
|
+
var inputSchema = z.strictObject({
|
|
410
|
+
name: z.string().regex(/^\w+$/).describe('The name used in "input.<name>" and {{input.<name>}}. Letters, digits and underscore.'),
|
|
411
|
+
label: z.string().min(1).describe("What the person sees next to the field."),
|
|
412
|
+
type: z.enum(["text", "number", "boolean", "select"]),
|
|
413
|
+
default: primitive.describe("The value used when the person does not change it. Must suit the type."),
|
|
414
|
+
options: z.array(z.string()).optional().describe('The choices, for type "select".'),
|
|
415
|
+
help: z.string().optional(),
|
|
416
|
+
fromChat: z.boolean().optional().describe("Fill this field from what the person wrote in the chat when the scenario is proposed.")
|
|
417
|
+
});
|
|
418
|
+
var scenarioSchema = z.strictObject({
|
|
419
|
+
id: idSchema.describe('A short unique id, lowercase with dashes, for example "refund-declined". Not already used by another scenario in the design.'),
|
|
420
|
+
title: z.string().min(1).describe("The name shown in the scenario list."),
|
|
421
|
+
description: z.string().describe("One or two sentences on what the scenario shows."),
|
|
422
|
+
triggers: z.array(z.string().min(1)).optional().describe("Words that make the chat suggest this scenario."),
|
|
423
|
+
inputs: z.array(inputSchema).describe("What the person can vary before a run. May be empty."),
|
|
424
|
+
presets: z.array(z.strictObject({ name: z.string().min(1), values: z.record(z.string(), primitive) })).optional().describe("Named sets of input values, for quick choices."),
|
|
425
|
+
initialState: z.record(z.string(), primitive).optional().describe('Starting values of the run state, read as "state.<name>".'),
|
|
426
|
+
start: idSchema.describe('The id of the first step: a key of "steps".'),
|
|
427
|
+
steps: z.record(z.string(), stepSchema).describe('The steps, by id. Branches and "start" refer to these ids.')
|
|
428
|
+
}).superRefine((scenario, ctx) => {
|
|
429
|
+
const ids = new Set(Object.keys(scenario.steps));
|
|
430
|
+
if (!ids.has(scenario.start)) {
|
|
431
|
+
ctx.addIssue({ code: "custom", path: ["start"], message: `Start step "${scenario.start}" is not defined` });
|
|
432
|
+
}
|
|
433
|
+
for (const [stepId, step] of Object.entries(scenario.steps)) {
|
|
434
|
+
step.next?.forEach((t, i) => {
|
|
435
|
+
if (t.to !== void 0 && !ids.has(t.to)) {
|
|
436
|
+
ctx.addIssue({ code: "custom", path: ["steps", stepId, "next", i, "to"], message: `Unknown step "${t.to}"` });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}).describe("A scripted walk through the design: steps at diagram nodes, joined by branches, ending with an outcome.");
|
|
441
|
+
|
|
442
|
+
// packages/scenario-core/src/schema.ts
|
|
443
|
+
import { z as z2 } from "zod";
|
|
444
|
+
import { defineFormat, readVersioned, readVersionedOrThrow } from "@glyphlab/diagram-core/versioning";
|
|
445
|
+
var SCENARIO_SCHEMA_VERSION = 1;
|
|
446
|
+
var scenarioFileV1Schema = z2.strictObject({
|
|
447
|
+
schemaVersion: z2.literal(SCENARIO_SCHEMA_VERSION),
|
|
448
|
+
scenarios: z2.array(scenarioSchema)
|
|
449
|
+
});
|
|
450
|
+
var scenarioFileSchema = scenarioFileV1Schema;
|
|
451
|
+
var scenarioFileFormat = defineFormat({
|
|
452
|
+
name: "scenario file",
|
|
453
|
+
history: [{ version: SCENARIO_SCHEMA_VERSION, schema: scenarioFileV1Schema }]
|
|
454
|
+
});
|
|
455
|
+
function parseScenarioFile(input) {
|
|
456
|
+
return readVersionedOrThrow(scenarioFileFormat, input);
|
|
457
|
+
}
|
|
458
|
+
function readScenarioFile(input) {
|
|
459
|
+
return readVersioned(scenarioFileFormat, input);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// packages/scenario-core/src/version.ts
|
|
463
|
+
var ENGINE_VERSION = 1;
|
|
464
|
+
function stable(value) {
|
|
465
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
466
|
+
if (typeof value === "object" && value !== null) {
|
|
467
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
468
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(",")}}`;
|
|
469
|
+
}
|
|
470
|
+
return JSON.stringify(value);
|
|
471
|
+
}
|
|
472
|
+
function scenarioVersion(scenario) {
|
|
473
|
+
const text = stable(scenario);
|
|
474
|
+
let hash = 2166136261;
|
|
475
|
+
for (let i = 0; i < text.length; i++) {
|
|
476
|
+
hash ^= text.charCodeAt(i);
|
|
477
|
+
hash = Math.imul(hash, 16777619);
|
|
478
|
+
}
|
|
479
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// packages/scenario-core/src/designExport.ts
|
|
483
|
+
import { readDiagram, serializeDiagram } from "@glyphlab/diagram-core";
|
|
484
|
+
var DESIGN_EXPORT_FORMAT = "glyph-design";
|
|
485
|
+
var DESIGN_EXPORT_VERSION = 1;
|
|
486
|
+
function writeDesignExport(diagram, scenarios) {
|
|
487
|
+
const file = { format: DESIGN_EXPORT_FORMAT, schemaVersion: DESIGN_EXPORT_VERSION, diagram: JSON.parse(serializeDiagram(diagram)), scenarios };
|
|
488
|
+
return `${JSON.stringify(file, null, 2)}
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
function readDesignExport(text) {
|
|
492
|
+
let data;
|
|
493
|
+
try {
|
|
494
|
+
data = JSON.parse(text);
|
|
495
|
+
} catch {
|
|
496
|
+
return { ok: false, reason: "The file is not valid JSON." };
|
|
497
|
+
}
|
|
498
|
+
if (typeof data !== "object" || data === null) return { ok: false, reason: "This is not a Glyph design file." };
|
|
499
|
+
const file = data;
|
|
500
|
+
if (file.format !== DESIGN_EXPORT_FORMAT) return { ok: false, reason: "This is not a Glyph design file." };
|
|
501
|
+
if (file.schemaVersion !== DESIGN_EXPORT_VERSION) return { ok: false, reason: `schemaVersion ${String(file.schemaVersion)} is not supported (use ${DESIGN_EXPORT_VERSION}).` };
|
|
502
|
+
const diagram = readDiagram(file.diagram);
|
|
503
|
+
if (!diagram.ok) return { ok: false, reason: "The diagram in this file could not be read." };
|
|
504
|
+
const warnings = [...diagram.warnings];
|
|
505
|
+
const list = Array.isArray(file.scenarios) ? file.scenarios : [];
|
|
506
|
+
const scenarios = list.flatMap((item) => {
|
|
507
|
+
const parsed = scenarioSchema.safeParse(item);
|
|
508
|
+
return parsed.success ? [parsed.data] : [];
|
|
509
|
+
});
|
|
510
|
+
if (scenarios.length < list.length) warnings.push("Some scenarios in this file could not be read and were left out.");
|
|
511
|
+
return { ok: true, diagram: diagram.value, scenarios, warnings };
|
|
512
|
+
}
|
|
513
|
+
export {
|
|
514
|
+
DESIGN_EXPORT_FORMAT,
|
|
515
|
+
DESIGN_EXPORT_VERSION,
|
|
516
|
+
ENGINE_VERSION,
|
|
517
|
+
SCENARIO_SCHEMA_VERSION,
|
|
518
|
+
checkNewScenario,
|
|
519
|
+
conditionSchema,
|
|
520
|
+
defaultInput,
|
|
521
|
+
edgeBetween,
|
|
522
|
+
highlightsAt,
|
|
523
|
+
parseScenarioFile,
|
|
524
|
+
readDesignExport,
|
|
525
|
+
readScenarioFile,
|
|
526
|
+
runScenario,
|
|
527
|
+
scenarioFileFormat,
|
|
528
|
+
scenarioFileSchema,
|
|
529
|
+
scenarioFileV1Schema,
|
|
530
|
+
scenarioSchema,
|
|
531
|
+
scenarioVersion,
|
|
532
|
+
validateScenario,
|
|
533
|
+
writeDesignExport
|
|
534
|
+
};
|
|
535
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/conditions.ts","../src/engine.ts","../src/validate.ts","../src/check.ts","../src/highlights.ts","../src/scenarioSchema.ts","../src/schema.ts","../src/version.ts","../src/designExport.ts"],"sourcesContent":["import type { Condition, Primitive, Values } from './types'\n\nexport interface Scope {\n input: Values\n state: Values\n}\n\nexport function resolvePath(scope: Scope, path: string): Primitive | undefined {\n const [root, ...rest] = path.split('.')\n const key = rest.join('.')\n if (root === 'input') return scope.input[key]\n if (root === 'state') return scope.state[key]\n return undefined\n}\n\nfunction compare(op: string, left: Primitive | undefined, right: Primitive | Primitive[] | undefined): boolean {\n switch (op) {\n case 'truthy':\n return Boolean(left)\n case 'eq':\n return left === right\n case 'ne':\n return left !== right\n case 'gt':\n return Number(left) > Number(right)\n case 'gte':\n return Number(left) >= Number(right)\n case 'lt':\n return Number(left) < Number(right)\n case 'lte':\n return Number(left) <= Number(right)\n case 'in':\n return Array.isArray(right) && left !== undefined && right.includes(left)\n case 'contains':\n return typeof right === 'string' && String(left ?? '').toLowerCase().includes(right.toLowerCase())\n default:\n return false\n }\n}\n\nexport function evaluate(condition: Condition, scope: Scope): boolean {\n if ('all' in condition) return condition.all.every((c) => evaluate(c, scope))\n if ('any' in condition) return condition.any.some((c) => evaluate(c, scope))\n if ('not' in condition) return !evaluate(condition.not, scope)\n return compare(condition.op, resolvePath(scope, condition.path), condition.value)\n}\n\nfunction show(value: Primitive | Primitive[] | undefined): string {\n if (Array.isArray(value)) return `[${value.map(show).join(', ')}]`\n return typeof value === 'string' ? `“${value}”` : String(value)\n}\n\nconst OP_TEXT: Record<string, string> = {\n eq: 'is',\n ne: 'is not',\n gt: '>',\n gte: '≥',\n lt: '<',\n lte: '≤',\n in: 'is one of',\n contains: 'contains',\n}\n\n// Human-readable form of a condition, used to label branches that have no label.\nexport function describeCondition(condition: Condition): string {\n if ('all' in condition) return condition.all.map(describeCondition).join(' and ')\n if ('any' in condition) return condition.any.map(describeCondition).join(' or ')\n if ('not' in condition) return `not (${describeCondition(condition.not)})`\n const name = condition.path.split('.').slice(1).join('.')\n if (condition.op === 'truthy') return `${name} is on`\n return `${name} ${OP_TEXT[condition.op]} ${show(condition.value)}`\n}\n\n// Replaces {{input.x}} and {{state.x}}; unknown paths become empty text.\nexport function interpolate(text: string, scope: Scope): string {\n return text.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, (_, path: string) => String(resolvePath(scope, path) ?? ''))\n}\n","import { describeCondition, evaluate, interpolate, resolvePath } from './conditions'\nimport type { Scope } from './conditions'\nimport type {\n Effect,\n EndSpec,\n GraphModel,\n Outcome,\n Scenario,\n StateChange,\n StepRule,\n Trace,\n TraceStep,\n Transition,\n Values,\n} from './types'\n\n// Guards against scenarios that loop forever.\nconst MAX_STEPS = 60\n\nexport function edgeBetween(graph: GraphModel, from: string, to: string) {\n return graph.edges.find((e) => e.from === from && e.to === to)\n}\n\nfunction applyEffects(effects: Effect[], input: Values, state: Values): StateChange[] {\n const changes: StateChange[] = []\n\n for (const effect of effects) {\n const scope: Scope = { input, state }\n let value: StateChange['to']\n let ai = false\n\n if ('derive' in effect) {\n const source = String(resolvePath(scope, effect.derive.from) ?? '').toLowerCase()\n const rule = effect.derive.rules.find((r) => r.contains.some((word) => source.includes(word.toLowerCase())))\n value = rule ? rule.value : effect.derive.default\n ai = effect.ai === true\n } else if ('add' in effect) {\n value = Number(state[effect.set] ?? 0) + effect.add\n } else {\n value = typeof effect.value === 'string' ? interpolate(effect.value, scope) : effect.value\n }\n\n if (state[effect.set] !== value) {\n changes.push({ key: effect.set, from: state[effect.set], to: value, ai: ai || undefined })\n state[effect.set] = value\n }\n }\n return changes\n}\n\n// Runs a scenario against a graph for one input and returns the whole trace up front.\n// Pure and deterministic: playing it back (pause, step, rewind) is just moving a cursor.\nexport function runScenario(graph: GraphModel, scenario: Scenario, input: Values): Trace {\n const titles = new Map(graph.nodes.map((n) => [n.id, n.title]))\n const nodeTitle = (id: string) => titles.get(id) ?? id\n const state: Values = { ...scenario.initialState }\n const steps: TraceStep[] = []\n let outcome: Outcome = 'stopped'\n let message = ''\n\n const scope = (): Scope => ({ input, state })\n const push = (step: Omit<TraceStep, 'index' | 'state' | 'nodeTitle'>) =>\n steps.push({ ...step, index: steps.length, nodeTitle: nodeTitle(step.nodeId), state: { ...state } })\n\n const end = (stepId: string, nodeId: string, result: EndSpec | { outcome: 'stopped'; message: string }) => {\n outcome = result.outcome\n message = interpolate(result.message, scope())\n push({ kind: 'end', stepId, nodeId, title: message, outcome, changes: [], warnings: [] })\n }\n\n let current: string | undefined = scenario.start\n let incoming: { edgeId?: string; from?: string } = {}\n\n while (current !== undefined) {\n const rule: StepRule | undefined = scenario.steps[current]\n if (!rule) {\n outcome = 'stopped'\n message = `Step “${current}” is not defined.`\n break\n }\n if (steps.length >= MAX_STEPS) {\n end(current, rule.node, { outcome: 'stopped', message: `Stopped after ${MAX_STEPS} steps; the scenario may be looping.` })\n break\n }\n\n const changes = applyEffects(rule.effects ?? [], input, state)\n const warnings: string[] = []\n if (incoming.from && !incoming.edgeId) {\n warnings.push(`The diagram has no edge from “${nodeTitle(incoming.from)}” to “${nodeTitle(rule.node)}”.`)\n }\n push({\n kind: 'enter',\n stepId: current,\n nodeId: rule.node,\n edgeId: incoming.edgeId,\n fromNodeId: incoming.from,\n title: interpolate(rule.title, scope()),\n detail: rule.detail ? interpolate(rule.detail, scope()) : undefined,\n changes,\n warnings,\n })\n const enterIndex = steps.length - 1\n\n for (const call of rule.calls ?? []) {\n if (call.when && !evaluate(call.when, scope())) continue\n const edge = edgeBetween(graph, rule.node, call.to)\n push({\n kind: 'call',\n stepId: current,\n nodeId: call.to,\n edgeId: edge?.id,\n fromNodeId: rule.node,\n title: interpolate(call.title, scope()),\n detail: call.detail ? interpolate(call.detail, scope()) : undefined,\n changes: [],\n warnings: edge ? [] : [`The diagram has no edge from “${nodeTitle(rule.node)}” to “${nodeTitle(call.to)}”.`],\n })\n }\n\n if (rule.end) {\n end(current, rule.node, rule.end)\n break\n }\n\n const next: Transition[] = rule.next ?? []\n if (next.length === 0) {\n end(current, rule.node, { outcome: 'success', message: 'The flow finished.' })\n break\n }\n\n const taken = next.findIndex((t) => !t.when || evaluate(t.when, scope()))\n if (next.length > 1) {\n steps[enterIndex].decision = {\n options: next.map((t, i) => ({\n label: t.label ?? (t.when ? describeCondition(t.when) : 'otherwise'),\n taken: i === taken,\n })),\n }\n }\n if (taken < 0) {\n end(current, rule.node, { outcome: 'stopped', message: 'No branch matched here, so the run stopped.' })\n break\n }\n\n const transition: Transition = next[taken]\n if (transition.end) {\n end(current, rule.node, transition.end)\n break\n }\n\n const target = transition.to !== undefined ? scenario.steps[transition.to] : undefined\n const edge = target ? edgeBetween(graph, rule.node, target.node) : undefined\n incoming = { edgeId: edge?.id, from: rule.node }\n current = transition.to\n }\n\n return { scenarioId: scenario.id, scenarioTitle: scenario.title, input, steps, outcome, message }\n}\n\nexport function defaultInput(scenario: Scenario): Values {\n return Object.fromEntries(scenario.inputs.map((f) => [f.name, f.default]))\n}\n","import { edgeBetween } from './engine'\nimport type { GraphModel, Scenario, ScenarioIssue } from './types'\n\n// Checks a scenario against the diagram it will run on. Errors mean part of it cannot\n// run; warnings mean it runs but the diagram does not show that hop.\nexport function validateScenario(scenario: Scenario, graph: GraphModel): ScenarioIssue[] {\n const issues: ScenarioIssue[] = []\n const nodeIds = new Set(graph.nodes.map((n) => n.id))\n\n for (const [stepId, step] of Object.entries(scenario.steps)) {\n if (!nodeIds.has(step.node)) {\n issues.push({ severity: 'error', message: `Step “${stepId}” refers to a node that is not in the diagram: ${step.node}` })\n }\n for (const call of step.calls ?? []) {\n if (nodeIds.has(step.node) && nodeIds.has(call.to) && !edgeBetween(graph, step.node, call.to)) {\n issues.push({ severity: 'warning', message: `No edge from ${step.node} to ${call.to} for the call in “${stepId}”.` })\n }\n }\n for (const next of step.next ?? []) {\n const target = next.to !== undefined ? scenario.steps[next.to] : undefined\n if (target && nodeIds.has(step.node) && nodeIds.has(target.node) && !edgeBetween(graph, step.node, target.node)) {\n issues.push({ severity: 'warning', message: `No edge from ${step.node} to ${target.node} for the branch in “${stepId}”.` })\n }\n }\n }\n return issues\n}\n","import { defaultInput, runScenario } from './engine'\nimport { validateScenario } from './validate'\nimport type { GraphModel, Scenario, ScenarioIssue } from './types'\n\nexport type NewScenarioCheck =\n | { ok: true; issues: ScenarioIssue[]; preview: { outcome: 'success' | 'failure' | 'blocked' | 'stopped'; message: string; steps: number } }\n | { ok: false; error: 'duplicate-id' | 'unknown-nodes' | 'invalid'; message: string; issues?: ScenarioIssue[] }\n\nconst MAX_LISTED_NODES = 40\n\n// Everything that has to hold before a scenario written by the assistant is saved into a design. The shape (zod)\n// has already been checked; this is what the shape cannot say: that it fits this diagram, that its inputs make\n// sense, and that it runs. Errors refuse it, with a message the assistant can act on. Warnings come back with it.\nexport function checkNewScenario(scenario: Scenario, graph: GraphModel, existingIds: string[]): NewScenarioCheck {\n if (existingIds.includes(scenario.id)) {\n return {\n ok: false,\n error: 'duplicate-id',\n message: `This design already has a scenario with the id \"${scenario.id}\". Use a different id. Existing ids: ${existingIds.join(', ')}.`,\n }\n }\n\n const inputProblems = checkInputs(scenario)\n if (inputProblems.length > 0) {\n return { ok: false, error: 'invalid', message: `The scenario's inputs are not usable: ${inputProblems.join(' ')}` }\n }\n\n const unknown = unknownNodes(scenario, graph)\n if (unknown.length > 0) {\n const byTitle = new Map(graph.nodes.map((n) => [n.title.trim().toLowerCase(), n.id]))\n const lines = unknown.map((ref) => {\n const guess = byTitle.get(ref.trim().toLowerCase())\n return guess ? `\"${ref}\" is a title, not an id; use \"${guess}\".` : `\"${ref}\" is not a node in the diagram.`\n })\n const known = graph.nodes.slice(0, MAX_LISTED_NODES).map((n) => `${n.id} (${n.title})`).join(', ')\n const more = graph.nodes.length > MAX_LISTED_NODES ? `, and ${graph.nodes.length - MAX_LISTED_NODES} more (call getDiagram)` : ''\n return {\n ok: false,\n error: 'unknown-nodes',\n message: `Steps must happen at nodes that exist. ${lines.join(' ')} Nodes in the diagram: ${known || 'none'}${more}.`,\n }\n }\n\n const trace = runScenario(graph, scenario, defaultInput(scenario))\n if (trace.outcome === 'stopped') {\n return { ok: false, error: 'invalid', message: `A trial run with the default inputs did not finish: ${trace.message} Make sure every step either ends or has a branch that always applies (a last one with no \"when\").` }\n }\n\n const issues = [...validateScenario(scenario, graph).filter((i) => i.severity === 'warning'), ...lint(scenario)]\n return { ok: true, issues, preview: { outcome: trace.outcome, message: trace.message, steps: trace.steps.length } }\n}\n\nfunction checkInputs(scenario: Scenario): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n for (const field of scenario.inputs) {\n if (seen.has(field.name)) problems.push(`Two inputs are called \"${field.name}\".`)\n seen.add(field.name)\n const kind = typeof field.default\n if (field.type === 'number' && kind !== 'number') problems.push(`Input \"${field.name}\" is a number, so its default must be a number.`)\n if (field.type === 'boolean' && kind !== 'boolean') problems.push(`Input \"${field.name}\" is a boolean, so its default must be true or false.`)\n if (field.type === 'text' && kind !== 'string') problems.push(`Input \"${field.name}\" is text, so its default must be a string.`)\n if (field.type === 'select') {\n if (!field.options || field.options.length === 0) problems.push(`Input \"${field.name}\" is a select, so it needs \"options\".`)\n else if (!field.options.includes(String(field.default))) problems.push(`The default of \"${field.name}\" must be one of its options.`)\n }\n }\n return problems\n}\n\n// Node references that are not in the diagram, in first-seen order.\nfunction unknownNodes(scenario: Scenario, graph: GraphModel): string[] {\n const ids = new Set(graph.nodes.map((n) => n.id))\n const unknown: string[] = []\n const check = (ref: string) => {\n if (!ids.has(ref) && !unknown.includes(ref)) unknown.push(ref)\n }\n for (const step of Object.values(scenario.steps)) {\n check(step.node)\n for (const call of step.calls ?? []) check(call.to)\n }\n return unknown\n}\n\n// Things that still run but are probably mistakes.\nfunction lint(scenario: Scenario): ScenarioIssue[] {\n const issues: ScenarioIssue[] = []\n const warn = (message: string) => issues.push({ severity: 'warning', message })\n\n const inputNames = new Set(scenario.inputs.map((f) => f.name))\n const stateNames = new Set(Object.keys(scenario.initialState ?? {}))\n for (const step of Object.values(scenario.steps)) for (const effect of step.effects ?? []) stateNames.add(effect.set)\n\n const checkRef = (ref: string, where: string) => {\n const [root, ...rest] = ref.split('.')\n const name = rest.join('.')\n if (root === 'input' && !inputNames.has(name)) warn(`${where} reads input.${name}, but the scenario has no input called \"${name}\".`)\n if (root === 'state' && !stateNames.has(name)) warn(`${where} reads state.${name}, but no step sets it and it is not in initialState.`)\n }\n\n const conditionPaths = (condition: unknown): string[] => {\n if (typeof condition !== 'object' || condition === null) return []\n const c = condition as Record<string, unknown>\n if (Array.isArray(c.all)) return c.all.flatMap(conditionPaths)\n if (Array.isArray(c.any)) return c.any.flatMap(conditionPaths)\n if ('not' in c) return conditionPaths(c.not)\n return typeof c.path === 'string' ? [c.path] : []\n }\n\n for (const [stepId, step] of Object.entries(scenario.steps)) {\n const where = `Step “${stepId}”`\n for (const next of step.next ?? []) for (const path of conditionPaths(next.when)) checkRef(path, where)\n for (const call of step.calls ?? []) for (const path of conditionPaths(call.when)) checkRef(path, where)\n for (const effect of step.effects ?? []) if ('derive' in effect) checkRef(effect.derive.from, where)\n }\n for (const text of strings(scenario)) {\n for (const match of text.matchAll(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g)) checkRef(match[1], 'The text')\n }\n\n for (const preset of scenario.presets ?? []) {\n for (const key of Object.keys(preset.values)) {\n if (!inputNames.has(key)) warn(`Preset “${preset.name}” sets \"${key}\", which is not an input.`)\n }\n }\n\n const reached = new Set<string>()\n const visit = (id: string) => {\n if (reached.has(id) || !scenario.steps[id]) return\n reached.add(id)\n for (const next of scenario.steps[id].next ?? []) if (next.to !== undefined) visit(next.to)\n }\n visit(scenario.start)\n for (const id of Object.keys(scenario.steps)) if (!reached.has(id)) warn(`Step “${id}” can never be reached from the start.`)\n\n return dedupe(issues)\n}\n\nfunction strings(value: unknown): string[] {\n if (typeof value === 'string') return [value]\n if (Array.isArray(value)) return value.flatMap(strings)\n if (typeof value === 'object' && value !== null) return Object.values(value).flatMap(strings)\n return []\n}\n\nfunction dedupe(issues: ScenarioIssue[]): ScenarioIssue[] {\n const seen = new Set<string>()\n return issues.filter((i) => (seen.has(i.message) ? false : (seen.add(i.message), true)))\n}\n","import type { EdgeHighlight, Highlights, NodeHighlight } from '@glyphlab/diagram-core'\nimport type { Trace } from './types'\n\n// The shape of what is lit up belongs to the diagram (@glyphlab/diagram-core), so the canvas and this agree on it.\nexport type { EdgeHighlight, Highlights, NodeHighlight }\n\nconst OUTCOME_HIGHLIGHT = { success: 'success', failure: 'failure', blocked: 'blocked' } as const\n\n// What the host should light up on its diagram for the steps played so far. Pure data,\n// so the diagram never needs to import the simulator.\nexport function highlightsAt(trace: Trace, cursor: number): Highlights {\n const nodes: Highlights['nodes'] = {}\n const edges: Highlights['edges'] = {}\n\n for (let i = 0; i <= cursor && i < trace.steps.length; i++) {\n const step = trace.steps[i]\n nodes[step.nodeId] = 'visited'\n if (step.edgeId) edges[step.edgeId] = 'visited'\n }\n\n const current = trace.steps[cursor]\n if (current) {\n const outcome = current.kind === 'end' ? current.outcome : undefined\n nodes[current.nodeId] = outcome && outcome !== 'stopped' ? OUTCOME_HIGHLIGHT[outcome] : 'active'\n if (current.edgeId) edges[current.edgeId] = 'active'\n }\n return { nodes, edges }\n}\n","import { z } from 'zod'\n\n// The shape of a scenario: a scripted walk through a design's diagram. It is the one definition used by the scenario\n// file format (src/simulation/schema.ts) and by the createScenario tool, so what the assistant is told to write and\n// what the app accepts cannot drift apart. The descriptions are what the model reads.\n\nconst idSchema = z.string().min(1)\nconst primitive = z.union([z.string(), z.number(), z.boolean()])\nconst pathSchema = z\n .string()\n .regex(/^(input|state)\\.[\\w]+$/, 'Use input.<name> or state.<name>')\n .describe('Where to read a value: \"input.<name>\" is something the user entered; \"state.<name>\" is something an earlier step set.')\nconst outcomeSchema = z.enum(['success', 'failure', 'blocked'])\n\nexport type ScenarioCondition =\n | { all: ScenarioCondition[] }\n | { any: ScenarioCondition[] }\n | { not: ScenarioCondition }\n | { path: string; op: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains' | 'truthy'; value?: string | number | boolean | (string | number | boolean)[] }\n\nexport const conditionSchema: z.ZodType<ScenarioCondition> = z\n .lazy(() =>\n z.union([\n z.strictObject({ all: z.array(conditionSchema) }),\n z.strictObject({ any: z.array(conditionSchema) }),\n z.strictObject({ not: conditionSchema }),\n z.strictObject({\n path: pathSchema,\n op: z\n .enum(['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'contains', 'truthy'])\n .describe('\"eq\"/\"ne\" compare for equality; \"gt\"/\"gte\"/\"lt\"/\"lte\" compare numbers; \"in\" needs a list as value; \"contains\" is a case-insensitive text match; \"truthy\" needs no value.'),\n value: z.union([primitive, z.array(primitive)]).optional(),\n }),\n ]),\n )\n .describe('A test on the input or the run state. Combine tests with {all:[...]}, {any:[...]} or {not:...}.')\n\nconst endSchema = z\n .strictObject({\n outcome: outcomeSchema.describe('How the run ended: \"success\", \"failure\" (something went wrong), or \"blocked\" (it was refused or had to stop).'),\n message: z.string().min(1).describe('One sentence saying what happened. May use {{input.x}} and {{state.x}}.'),\n })\n .describe('Ends the run here.')\n\nconst effectSchema = z\n .union([\n z.strictObject({ set: idSchema, value: primitive }),\n z.strictObject({ set: idSchema, add: z.number() }),\n z.strictObject({\n set: idSchema,\n derive: z.strictObject({\n from: pathSchema,\n rules: z.array(z.strictObject({ contains: z.array(z.string().min(1)).min(1), value: primitive })).describe('Tried in order; the first whose words appear in the text wins.'),\n default: primitive,\n }),\n ai: z.boolean().optional().describe('Mark the result as something the assistant would work out, in the display.'),\n }),\n ])\n .describe('Changes the run state when the step happens. {set, value} stores a value (text may use {{input.x}}/{{state.x}}); {set, add} adds to a number; {set, derive} reads free text and picks a value by keyword.')\n\nconst transitionSchema = z\n .strictObject({\n to: idSchema.optional().describe('The id of the next step (a key of \"steps\").'),\n end: endSchema.optional(),\n when: conditionSchema.optional().describe('Take this branch only if the condition holds. Branches are tried in order, so put a branch with no \"when\" last as the fallback.'),\n label: z.string().optional().describe('A short label for the branch, shown where the choice was made.'),\n })\n .refine((t) => (t.to !== undefined) !== (t.end !== undefined), 'A branch needs exactly one of \"to\" or \"end\"')\n .describe('A way forward from a step: either \"to\" another step, or \"end\" the run.')\n\nconst stepSchema = z\n .strictObject({\n node: idSchema.describe('The id of the diagram node where this step happens. Use ids from getDiagram, never titles.'),\n title: z.string().min(1).describe('What happens here, as a short sentence. May use {{input.x}} and {{state.x}}.'),\n detail: z.string().optional(),\n effects: z.array(effectSchema).optional(),\n calls: z\n .array(\n z.strictObject({\n to: idSchema.describe('The id of a diagram node that this node calls and gets an answer from straight away (a lookup, a tool). Ideally an edge goes from this node to it.'),\n title: z.string().min(1),\n detail: z.string().optional(),\n when: conditionSchema.optional(),\n }),\n )\n .optional()\n .describe('Side trips to other nodes that return immediately; they show on the diagram but do not move the flow.'),\n next: z.array(transitionSchema).optional().describe('Where to go after this step. With none (and no \"end\"), the run ends as a success.'),\n end: endSchema.optional().describe('Ends the run at this step, instead of \"next\".'),\n })\n .describe('One step of the scenario, at one diagram node.')\n\nconst inputSchema = z.strictObject({\n name: z.string().regex(/^\\w+$/).describe('The name used in \"input.<name>\" and {{input.<name>}}. Letters, digits and underscore.'),\n label: z.string().min(1).describe('What the person sees next to the field.'),\n type: z.enum(['text', 'number', 'boolean', 'select']),\n default: primitive.describe('The value used when the person does not change it. Must suit the type.'),\n options: z.array(z.string()).optional().describe('The choices, for type \"select\".'),\n help: z.string().optional(),\n fromChat: z.boolean().optional().describe('Fill this field from what the person wrote in the chat when the scenario is proposed.'),\n})\n\nexport const scenarioSchema = z\n .strictObject({\n id: idSchema.describe('A short unique id, lowercase with dashes, for example \"refund-declined\". Not already used by another scenario in the design.'),\n title: z.string().min(1).describe('The name shown in the scenario list.'),\n description: z.string().describe('One or two sentences on what the scenario shows.'),\n triggers: z.array(z.string().min(1)).optional().describe('Words that make the chat suggest this scenario.'),\n inputs: z.array(inputSchema).describe('What the person can vary before a run. May be empty.'),\n presets: z.array(z.strictObject({ name: z.string().min(1), values: z.record(z.string(), primitive) })).optional().describe('Named sets of input values, for quick choices.'),\n initialState: z.record(z.string(), primitive).optional().describe('Starting values of the run state, read as \"state.<name>\".'),\n start: idSchema.describe('The id of the first step: a key of \"steps\".'),\n steps: z.record(z.string(), stepSchema).describe('The steps, by id. Branches and \"start\" refer to these ids.'),\n })\n .superRefine((scenario, ctx) => {\n const ids = new Set(Object.keys(scenario.steps))\n if (!ids.has(scenario.start)) {\n ctx.addIssue({ code: 'custom', path: ['start'], message: `Start step \"${scenario.start}\" is not defined` })\n }\n for (const [stepId, step] of Object.entries(scenario.steps)) {\n step.next?.forEach((t, i) => {\n if (t.to !== undefined && !ids.has(t.to)) {\n ctx.addIssue({ code: 'custom', path: ['steps', stepId, 'next', i, 'to'], message: `Unknown step \"${t.to}\"` })\n }\n })\n }\n })\n .describe('A scripted walk through the design: steps at diagram nodes, joined by branches, ending with an outcome.')\n\nexport type ScenarioDraft = z.infer<typeof scenarioSchema>\n","import { z } from 'zod'\nimport { defineFormat, readVersioned, readVersionedOrThrow } from '@glyphlab/diagram-core/versioning'\nimport { scenarioSchema } from './scenarioSchema'\nimport type { ScenarioFile } from './types'\n\nexport const SCENARIO_SCHEMA_VERSION = 1\n\n// The scenario shape itself is shared with the createScenario tool, so the assistant writes what the app reads.\nexport { scenarioSchema } from './scenarioSchema'\n\n// Version 1 of the scenario file format. Frozen once released.\nexport const scenarioFileV1Schema: z.ZodType<ScenarioFile> = z.strictObject({\n schemaVersion: z.literal(SCENARIO_SCHEMA_VERSION),\n scenarios: z.array(scenarioSchema),\n})\n\nexport const scenarioFileSchema = scenarioFileV1Schema\n\n// Every version the app can read, oldest first, ending with the current one.\nexport const scenarioFileFormat = defineFormat<ScenarioFile>({\n name: 'scenario file',\n history: [{ version: SCENARIO_SCHEMA_VERSION, schema: scenarioFileV1Schema }],\n})\n\n// Throws a typed error; use for trusted input such as bundled samples.\nexport function parseScenarioFile(input: unknown): ScenarioFile {\n return readVersionedOrThrow(scenarioFileFormat, input)\n}\n\n// Returns a result instead of throwing; use for user-supplied files.\nexport function readScenarioFile(input: unknown) {\n return readVersioned(scenarioFileFormat, input)\n}\n","import type { Scenario } from './types'\n\n// Bump when the engine's behaviour changes in a way that can change a trace (new condition\n// semantics, a different way of choosing the next step). A recorded result carries the version\n// it was made with, so a replay under a newer engine can say why the result may differ.\nexport const ENGINE_VERSION = 1\n\nfunction stable(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stable).join(',')}]`\n if (typeof value === 'object' && value !== null) {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n// A short fingerprint of a scenario's content (FNV-1a over its canonical JSON). It tells a\n// recorded result whether the scenario it ran has been edited since; it is not a security hash.\nexport function scenarioVersion(scenario: Scenario): string {\n const text = stable(scenario)\n let hash = 0x811c9dc5\n for (let i = 0; i < text.length; i++) {\n hash ^= text.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n","import { readDiagram, serializeDiagram } from '@glyphlab/diagram-core'\nimport type { DiagramFile } from '@glyphlab/diagram-core'\nimport { scenarioSchema } from './scenarioSchema'\nimport type { Scenario } from './types'\n\n// One file with a design's diagram and its scenarios, to take out of the app (for a website that shows the diagram and plays\n// the scenarios). The diagram inside is the diagram file itself, and each scenario is the scenario shape, so each is read\n// by the same code that reads them anywhere else.\nexport const DESIGN_EXPORT_FORMAT = 'glyph-design'\nexport const DESIGN_EXPORT_VERSION = 1\n\nexport interface DesignExport {\n format: typeof DESIGN_EXPORT_FORMAT\n schemaVersion: typeof DESIGN_EXPORT_VERSION\n diagram: DiagramFile\n scenarios: Scenario[]\n}\n\nexport function writeDesignExport(diagram: DiagramFile, scenarios: Scenario[]): string {\n const file = { format: DESIGN_EXPORT_FORMAT, schemaVersion: DESIGN_EXPORT_VERSION, diagram: JSON.parse(serializeDiagram(diagram)) as unknown, scenarios }\n return `${JSON.stringify(file, null, 2)}\\n`\n}\n\nexport type ReadDesignExport = { ok: true; diagram: DiagramFile; scenarios: Scenario[]; warnings: string[] } | { ok: false; reason: string }\n\n// Never throws. A scenario that no longer parses is left out with a warning, so one bad scenario does not lose the diagram.\nexport function readDesignExport(text: string): ReadDesignExport {\n let data: unknown\n try {\n data = JSON.parse(text)\n } catch {\n return { ok: false, reason: 'The file is not valid JSON.' }\n }\n if (typeof data !== 'object' || data === null) return { ok: false, reason: 'This is not a Glyph design file.' }\n const file = data as Record<string, unknown>\n if (file.format !== DESIGN_EXPORT_FORMAT) return { ok: false, reason: 'This is not a Glyph design file.' }\n if (file.schemaVersion !== DESIGN_EXPORT_VERSION) return { ok: false, reason: `schemaVersion ${String(file.schemaVersion)} is not supported (use ${DESIGN_EXPORT_VERSION}).` }\n\n const diagram = readDiagram(file.diagram)\n if (!diagram.ok) return { ok: false, reason: 'The diagram in this file could not be read.' }\n\n const warnings = [...diagram.warnings]\n const list = Array.isArray(file.scenarios) ? file.scenarios : []\n const scenarios = list.flatMap((item) => {\n const parsed = scenarioSchema.safeParse(item)\n return parsed.success ? [parsed.data as Scenario] : []\n })\n if (scenarios.length < list.length) warnings.push('Some scenarios in this file could not be read and were left out.')\n return { ok: true, diagram: diagram.value, scenarios, warnings }\n}\n"],"mappings":"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","names":["edge","z","z"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glyphlab/scenario-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The scenario engine: the scenario file format, running a scripted walk against a graph of nodes and edges, checking that it fits, and the highlights a run lights up. No UI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.js",
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"*.js",
|
|
17
|
+
"*.js.map",
|
|
18
|
+
"*.d.ts",
|
|
19
|
+
"*.css",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@glyphlab/diagram-core": "^0.1.1",
|
|
26
|
+
"zod": "^4"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/subtubes-io/subtubes-editor.git",
|
|
31
|
+
"directory": "packages/scenario-core"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"diagram",
|
|
38
|
+
"react",
|
|
39
|
+
"system-design",
|
|
40
|
+
"glyph"
|
|
41
|
+
]
|
|
42
|
+
}
|