hoi4-agent-tools 3.0.8 → 3.1.0
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/CHANGELOG.md +35 -1
- package/README.md +4 -0
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +399 -0
- package/dist/hoi4_agent_tools/core/condition-model.js +1 -0
- package/dist/hoi4_agent_tools/core/condition-schema.js +42 -0
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/scenario-state.js +97 -0
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +74 -25
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/scenario-conditions.js +119 -0
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +59 -73
- package/dist/hoi4_agent_tools/gui/scenario.js +3 -0
- package/dist/hoi4_agent_tools/gui/studio.js +5 -2
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/scenario-state.js +2 -95
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +1 -287
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +2 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/gui.md +3 -1
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +85 -0
- package/docs/technology.md +6 -2
- package/package.json +11 -5
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +189 -33
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { assignments, astPathFor, firstScalar, nodeLocation, } from './source/index.js';
|
|
2
|
+
import { resolveScopeContext, rootScopeContext, rootStateValue, scopeIdentity, scopeStateValue, } from './scenario-state.js';
|
|
3
|
+
function helperWithArguments(block, invocation) {
|
|
4
|
+
const parameters = new Map();
|
|
5
|
+
for (const entry of invocation.entries) {
|
|
6
|
+
if (entry.type !== 'assignment' || entry.value.type !== 'scalar')
|
|
7
|
+
return undefined;
|
|
8
|
+
parameters.set(entry.key.value, entry.value.value);
|
|
9
|
+
}
|
|
10
|
+
const missing = new Set();
|
|
11
|
+
const substitute = (value) => value.replace(/\$([A-Za-z0-9_]+)\$/gu, (match, key) => {
|
|
12
|
+
const replacement = parameters.get(key);
|
|
13
|
+
if (replacement === undefined) {
|
|
14
|
+
missing.add(key);
|
|
15
|
+
return match;
|
|
16
|
+
}
|
|
17
|
+
return replacement;
|
|
18
|
+
});
|
|
19
|
+
const visit = (entry) => {
|
|
20
|
+
if (entry.type === 'scalar')
|
|
21
|
+
return { ...entry, value: substitute(entry.value) };
|
|
22
|
+
if (entry.type === 'block')
|
|
23
|
+
return { ...entry, entries: entry.entries.map(visit) };
|
|
24
|
+
return {
|
|
25
|
+
...entry,
|
|
26
|
+
key: { ...entry.key, value: substitute(entry.key.value) },
|
|
27
|
+
value: entry.value.type === 'scalar'
|
|
28
|
+
? { ...entry.value, value: substitute(entry.value.value) }
|
|
29
|
+
: { ...entry.value, entries: entry.value.entries.map(visit) },
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const expanded = { ...block, entries: block.entries.map(visit) };
|
|
33
|
+
return missing.size === 0 ? expanded : undefined;
|
|
34
|
+
}
|
|
35
|
+
function combineAnd(values) {
|
|
36
|
+
if (values.includes('false'))
|
|
37
|
+
return 'false';
|
|
38
|
+
return values.includes('unresolved') ? 'unresolved' : 'true';
|
|
39
|
+
}
|
|
40
|
+
function combineOr(values) {
|
|
41
|
+
if (values.includes('true'))
|
|
42
|
+
return 'true';
|
|
43
|
+
return values.includes('unresolved') ? 'unresolved' : 'false';
|
|
44
|
+
}
|
|
45
|
+
function invert(value) {
|
|
46
|
+
return value === 'true' ? 'false' : value === 'false' ? 'true' : 'unresolved';
|
|
47
|
+
}
|
|
48
|
+
function bool(value) {
|
|
49
|
+
if (typeof value === 'boolean')
|
|
50
|
+
return value;
|
|
51
|
+
if (value === 'yes' || value === 'true')
|
|
52
|
+
return true;
|
|
53
|
+
if (value === 'no' || value === 'false')
|
|
54
|
+
return false;
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function numeric(value) {
|
|
58
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
59
|
+
return value;
|
|
60
|
+
if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))
|
|
61
|
+
return Number(value);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function compare(left, right, operator) {
|
|
65
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
66
|
+
if (operator === '>')
|
|
67
|
+
return left > right;
|
|
68
|
+
if (operator === '<')
|
|
69
|
+
return left < right;
|
|
70
|
+
}
|
|
71
|
+
return operator === '!=' ? left !== right : left === right;
|
|
72
|
+
}
|
|
73
|
+
function numberValue(expression, scenario, context, definitions, path, seen = new Set()) {
|
|
74
|
+
const literal = numeric(expression);
|
|
75
|
+
if (literal !== undefined)
|
|
76
|
+
return literal;
|
|
77
|
+
const identity = `${path ?? ''}:${expression}`;
|
|
78
|
+
if (seen.has(identity))
|
|
79
|
+
return undefined;
|
|
80
|
+
seen.add(identity);
|
|
81
|
+
const constant = expression.startsWith('constant:')
|
|
82
|
+
? definitions.scriptConstants.get(expression.slice(9))
|
|
83
|
+
: expression.startsWith('@') && path !== undefined
|
|
84
|
+
? definitions.localConstants.get(path)?.get(expression)
|
|
85
|
+
: undefined;
|
|
86
|
+
if (constant !== undefined)
|
|
87
|
+
return numberValue(constant.value, scenario, context, definitions, constant.file.displayPath, seen);
|
|
88
|
+
const direct = scopeStateValue(scenario, expression, context) ??
|
|
89
|
+
scopeStateValue(scenario, `variable.${expression}`, context);
|
|
90
|
+
if (direct !== undefined)
|
|
91
|
+
return numeric(direct);
|
|
92
|
+
for (let split = expression.lastIndexOf('.'); split > 0; split = expression.lastIndexOf('.', split - 1)) {
|
|
93
|
+
const scope = resolveScopeContext(scenario, expression.slice(0, split), context);
|
|
94
|
+
if (scope !== undefined)
|
|
95
|
+
return numberValue(expression.slice(split + 1), scenario, scope, definitions, path, seen);
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function unresolved(assignment, candidate, reason) {
|
|
100
|
+
return {
|
|
101
|
+
state: 'unresolved',
|
|
102
|
+
unresolved: [
|
|
103
|
+
{
|
|
104
|
+
code: 'TRIGGER_UNRESOLVED',
|
|
105
|
+
message: reason,
|
|
106
|
+
path: assignment.key.value,
|
|
107
|
+
candidateId: candidate.id,
|
|
108
|
+
...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function malformedBlock(block, candidate) {
|
|
114
|
+
if (block.entries.every(({ type }) => type === 'assignment'))
|
|
115
|
+
return undefined;
|
|
116
|
+
return {
|
|
117
|
+
state: 'unresolved',
|
|
118
|
+
unresolved: [
|
|
119
|
+
{
|
|
120
|
+
code: 'TRIGGER_UNRESOLVED',
|
|
121
|
+
message: 'Condition contains a bare value or block instead of a trigger assignment',
|
|
122
|
+
candidateId: candidate.id,
|
|
123
|
+
...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function evaluateCheckVariable(block, scenario, candidate, assignment, scopeContext, definitions) {
|
|
129
|
+
const short = assignments(block).find(({ key }) => !['var', 'value', 'compare', 'tooltip'].includes(key.value));
|
|
130
|
+
const variable = firstScalar(block, 'var')?.value ?? short?.key.value;
|
|
131
|
+
const target = firstScalar(block, 'value')?.value ??
|
|
132
|
+
(short?.value.type === 'scalar' ? short.value.value : undefined);
|
|
133
|
+
if (variable === undefined || target === undefined)
|
|
134
|
+
return unresolved(assignment, candidate, 'check_variable requires declared var and value');
|
|
135
|
+
const path = candidate.provenance[0]?.path;
|
|
136
|
+
const leftNumber = numberValue(variable, scenario, scopeContext, definitions, path);
|
|
137
|
+
const rightNumber = numberValue(target, scenario, scopeContext, definitions, path);
|
|
138
|
+
if (leftNumber === undefined || rightNumber === undefined)
|
|
139
|
+
return unresolved(assignment, candidate, `Scenario does not resolve numeric operands ${variable} and ${target}`);
|
|
140
|
+
const compareName = firstScalar(block, 'compare')?.value ??
|
|
141
|
+
(short?.operator.text === '>'
|
|
142
|
+
? 'greater_than'
|
|
143
|
+
: short?.operator.text === '<'
|
|
144
|
+
? 'less_than'
|
|
145
|
+
: 'equals');
|
|
146
|
+
if (![
|
|
147
|
+
'greater_than',
|
|
148
|
+
'greater_than_or_equals',
|
|
149
|
+
'less_than',
|
|
150
|
+
'less_than_or_equals',
|
|
151
|
+
'not_equals',
|
|
152
|
+
'equals',
|
|
153
|
+
].includes(compareName))
|
|
154
|
+
return unresolved(assignment, candidate, `Unsupported check_variable comparison ${compareName}`);
|
|
155
|
+
const result = compareName === 'greater_than'
|
|
156
|
+
? leftNumber > rightNumber
|
|
157
|
+
: compareName === 'greater_than_or_equals'
|
|
158
|
+
? leftNumber >= rightNumber
|
|
159
|
+
: compareName === 'less_than'
|
|
160
|
+
? leftNumber < rightNumber
|
|
161
|
+
: compareName === 'less_than_or_equals'
|
|
162
|
+
? leftNumber <= rightNumber
|
|
163
|
+
: compareName === 'not_equals'
|
|
164
|
+
? leftNumber !== rightNumber
|
|
165
|
+
: leftNumber === rightNumber;
|
|
166
|
+
return { state: result ? 'true' : 'false', unresolved: [] };
|
|
167
|
+
}
|
|
168
|
+
function evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
169
|
+
const key = assignment.key.value;
|
|
170
|
+
if (assignment.value.type === 'block') {
|
|
171
|
+
const malformed = malformedBlock(assignment.value, candidate);
|
|
172
|
+
if (malformed !== undefined)
|
|
173
|
+
return malformed;
|
|
174
|
+
if (key === 'AND')
|
|
175
|
+
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, scopeContext);
|
|
176
|
+
if (key === 'OR') {
|
|
177
|
+
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
178
|
+
return {
|
|
179
|
+
state: combineOr(children.map(({ state }) => state)),
|
|
180
|
+
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
181
|
+
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (key === 'NOT' || key === 'NOR' || key === 'NAND') {
|
|
185
|
+
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
186
|
+
const base = key === 'NAND'
|
|
187
|
+
? combineAnd(children.map(({ state }) => state))
|
|
188
|
+
: combineOr(children.map(({ state }) => state));
|
|
189
|
+
return {
|
|
190
|
+
state: invert(base),
|
|
191
|
+
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
192
|
+
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (key === 'check_variable')
|
|
196
|
+
return evaluateCheckVariable(assignment.value, scenario, candidate, assignment, scopeContext, definitions);
|
|
197
|
+
if (key === 'is_in_array' || key === 'is_variable_in_array') {
|
|
198
|
+
const arrayId = firstScalar(assignment.value, 'array')?.value;
|
|
199
|
+
const declaredValue = firstScalar(assignment.value, 'value')?.value;
|
|
200
|
+
if (arrayId === undefined || declaredValue === undefined)
|
|
201
|
+
return unresolved(assignment, candidate, `${key} requires array and value`);
|
|
202
|
+
const array = scopeStateValue(scenario, `array.${arrayId}`, scopeContext) ??
|
|
203
|
+
rootStateValue(scenario, `array.${arrayId}`);
|
|
204
|
+
if (!Array.isArray(array))
|
|
205
|
+
return unresolved(assignment, candidate, `Scenario does not declare array ${arrayId}`);
|
|
206
|
+
const compared = ['THIS', 'ROOT', 'PREV'].includes(declaredValue.toUpperCase()) ||
|
|
207
|
+
resolveScopeContext(scenario, declaredValue, scopeContext) !== undefined
|
|
208
|
+
? scopeIdentity(resolveScopeContext(scenario, declaredValue, scopeContext) ?? scopeContext)
|
|
209
|
+
: (scopeStateValue(scenario, declaredValue, scopeContext) ?? declaredValue);
|
|
210
|
+
return { state: array.includes(compared) ? 'true' : 'false', unresolved: [] };
|
|
211
|
+
}
|
|
212
|
+
const resolvedScope = resolveScopeContext(scenario, key, scopeContext);
|
|
213
|
+
if (resolvedScope !== undefined)
|
|
214
|
+
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, resolvedScope);
|
|
215
|
+
const helper = definitions.scriptedTriggers.get(key);
|
|
216
|
+
if (helper !== undefined) {
|
|
217
|
+
if (helperStack.includes(key))
|
|
218
|
+
return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
|
|
219
|
+
const expanded = helperWithArguments(helper.value, assignment.value);
|
|
220
|
+
if (expanded === undefined)
|
|
221
|
+
return unresolved(assignment, candidate, `Scripted trigger ${key} requires declared scalar arguments for every parameter`);
|
|
222
|
+
const helperProvenance = {
|
|
223
|
+
path: helper.file.displayPath,
|
|
224
|
+
rootKind: helper.file.rootKind,
|
|
225
|
+
loadOrder: helper.file.loadOrder,
|
|
226
|
+
sourceHash: helper.file.sha256,
|
|
227
|
+
location: nodeLocation(helper.document, helper.node, key),
|
|
228
|
+
...(astPathFor(helper.document, helper.node) === undefined
|
|
229
|
+
? {}
|
|
230
|
+
: { astPath: astPathFor(helper.document, helper.node) }),
|
|
231
|
+
symbol: key,
|
|
232
|
+
helperChain: [...helperStack, key],
|
|
233
|
+
};
|
|
234
|
+
const evaluated = evaluateTriggerBlock(expanded, scenario, {
|
|
235
|
+
...candidate,
|
|
236
|
+
document: helper.document,
|
|
237
|
+
provenance: [helperProvenance, ...candidate.provenance],
|
|
238
|
+
}, definitions, [...helperStack, key], scopeContext);
|
|
239
|
+
return {
|
|
240
|
+
...evaluated,
|
|
241
|
+
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const declaredScope = bool(rootStateValue(scenario, `scope.${key}`));
|
|
245
|
+
if (declaredScope !== undefined)
|
|
246
|
+
return { state: declaredScope ? 'true' : 'false', unresolved: [] };
|
|
247
|
+
return unresolved(assignment, candidate, `Scoped or compound trigger ${key} is not declared by the scenario`);
|
|
248
|
+
}
|
|
249
|
+
const right = assignment.value.value;
|
|
250
|
+
if (key === 'always') {
|
|
251
|
+
const value = bool(right);
|
|
252
|
+
return value === undefined
|
|
253
|
+
? unresolved(assignment, candidate, 'always trigger is not boolean')
|
|
254
|
+
: { state: value ? 'true' : 'false', unresolved: [] };
|
|
255
|
+
}
|
|
256
|
+
if (key === 'has_country_flag' || key === 'has_global_flag' || key === 'has_state_flag') {
|
|
257
|
+
const explicit = bool(scopeStateValue(scenario, `flag.${right}`, scopeContext));
|
|
258
|
+
if (explicit !== undefined)
|
|
259
|
+
return { state: explicit ? 'true' : 'false', unresolved: [] };
|
|
260
|
+
const flags = scopeContext.expression === 'ROOT' ? scenario.flags : scopeContext.binding.flags;
|
|
261
|
+
if (scenario.closedFlags === false && flags?.includes(right) !== true)
|
|
262
|
+
return unresolved(assignment, candidate, `Scenario does not declare flag ${right}`);
|
|
263
|
+
return { state: flags?.includes(right) === true ? 'true' : 'false', unresolved: [] };
|
|
264
|
+
}
|
|
265
|
+
if (key === 'has_event_target') {
|
|
266
|
+
const eventTargets = scopeContext.binding.eventTargets ?? scenario.eventTargets;
|
|
267
|
+
return {
|
|
268
|
+
state: Object.hasOwn(eventTargets ?? {}, right) ? 'true' : 'false',
|
|
269
|
+
unresolved: [],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (key === 'tag' || key === 'original_tag') {
|
|
273
|
+
const actor = scopeContext.expression === 'ROOT'
|
|
274
|
+
? scenario.actor
|
|
275
|
+
: (scopeContext.binding.actor ?? scopeContext.binding.id);
|
|
276
|
+
if (actor === undefined)
|
|
277
|
+
return unresolved(assignment, candidate, `Scenario does not declare actor for ${key}`);
|
|
278
|
+
return {
|
|
279
|
+
state: compare(actor, right, assignment.operator.text) ? 'true' : 'false',
|
|
280
|
+
unresolved: [],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (key === 'date') {
|
|
284
|
+
if (scenario.date === undefined)
|
|
285
|
+
return unresolved(assignment, candidate, 'Scenario does not declare date');
|
|
286
|
+
const dateNumber = (value) => {
|
|
287
|
+
const parts = /^(\d+)\.(\d{1,2})\.(\d{1,2})$/u.exec(value);
|
|
288
|
+
if (parts === null)
|
|
289
|
+
return undefined;
|
|
290
|
+
const month = Number(parts[2]);
|
|
291
|
+
const day = Number(parts[3]);
|
|
292
|
+
return month < 1 || month > 12 || day < 1 || day > 31
|
|
293
|
+
? undefined
|
|
294
|
+
: Number(parts[1]) * 10_000 + month * 100 + day;
|
|
295
|
+
};
|
|
296
|
+
const leftDate = dateNumber(scenario.date);
|
|
297
|
+
const rightDate = dateNumber(right);
|
|
298
|
+
if (leftDate === undefined || rightDate === undefined)
|
|
299
|
+
return unresolved(assignment, candidate, 'Date comparison is not numeric');
|
|
300
|
+
return {
|
|
301
|
+
state: compare(leftDate, rightDate, assignment.operator.text) ? 'true' : 'false',
|
|
302
|
+
unresolved: [],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const helper = definitions.scriptedTriggers.get(key);
|
|
306
|
+
if (helper !== undefined && bool(right) !== undefined) {
|
|
307
|
+
if (helperStack.includes(key))
|
|
308
|
+
return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
|
|
309
|
+
const helperProvenance = {
|
|
310
|
+
path: helper.file.displayPath,
|
|
311
|
+
rootKind: helper.file.rootKind,
|
|
312
|
+
loadOrder: helper.file.loadOrder,
|
|
313
|
+
sourceHash: helper.file.sha256,
|
|
314
|
+
location: nodeLocation(helper.document, helper.node, key),
|
|
315
|
+
...(astPathFor(helper.document, helper.node) === undefined
|
|
316
|
+
? {}
|
|
317
|
+
: { astPath: astPathFor(helper.document, helper.node) }),
|
|
318
|
+
symbol: key,
|
|
319
|
+
helperChain: [...helperStack, key],
|
|
320
|
+
};
|
|
321
|
+
const evaluated = evaluateTriggerBlock(helper.value, scenario, {
|
|
322
|
+
...candidate,
|
|
323
|
+
document: helper.document,
|
|
324
|
+
provenance: [helperProvenance, ...candidate.provenance],
|
|
325
|
+
}, definitions, [...helperStack, key], scopeContext);
|
|
326
|
+
return {
|
|
327
|
+
...evaluated,
|
|
328
|
+
state: bool(right) === false ? invert(evaluated.state) : evaluated.state,
|
|
329
|
+
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const declared = scopeStateValue(scenario, key, scopeContext) ??
|
|
333
|
+
numberValue(key, scenario, scopeContext, definitions, candidate.provenance[0]?.path);
|
|
334
|
+
if (declared === undefined)
|
|
335
|
+
return unresolved(assignment, candidate, `Scenario does not declare trigger ${key}`);
|
|
336
|
+
const expectedBoolean = bool(right);
|
|
337
|
+
const actualBoolean = bool(declared);
|
|
338
|
+
if (expectedBoolean !== undefined && actualBoolean !== undefined)
|
|
339
|
+
return { state: expectedBoolean === actualBoolean ? 'true' : 'false', unresolved: [] };
|
|
340
|
+
const leftNumber = numeric(declared);
|
|
341
|
+
const rightNumber = numberValue(right, scenario, scopeContext, definitions, candidate.provenance[0]?.path);
|
|
342
|
+
if (leftNumber !== undefined && rightNumber !== undefined)
|
|
343
|
+
return {
|
|
344
|
+
state: compare(leftNumber, rightNumber, assignment.operator.text) ? 'true' : 'false',
|
|
345
|
+
unresolved: [],
|
|
346
|
+
};
|
|
347
|
+
if (typeof declared === 'string')
|
|
348
|
+
return {
|
|
349
|
+
state: compare(declared, right, assignment.operator.text) ? 'true' : 'false',
|
|
350
|
+
unresolved: [],
|
|
351
|
+
};
|
|
352
|
+
return unresolved(assignment, candidate, `Trigger ${key} cannot compare the declared scenario value`);
|
|
353
|
+
}
|
|
354
|
+
function evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
355
|
+
const evaluated = evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext);
|
|
356
|
+
const source = candidate.provenance[0];
|
|
357
|
+
const provenance = candidate.document === undefined || source === undefined
|
|
358
|
+
? source
|
|
359
|
+
: {
|
|
360
|
+
...source,
|
|
361
|
+
location: nodeLocation(candidate.document, assignment, assignment.key.value),
|
|
362
|
+
...(astPathFor(candidate.document, assignment) === undefined
|
|
363
|
+
? {}
|
|
364
|
+
: { astPath: astPathFor(candidate.document, assignment) }),
|
|
365
|
+
symbol: assignment.key.value,
|
|
366
|
+
};
|
|
367
|
+
const expression = assignment.value.type === 'scalar'
|
|
368
|
+
? `${assignment.key.value} ${assignment.operator.text} ${assignment.value.value}`
|
|
369
|
+
: assignment.key.value;
|
|
370
|
+
return {
|
|
371
|
+
...evaluated,
|
|
372
|
+
trace: [
|
|
373
|
+
{
|
|
374
|
+
operation: 'eligibility',
|
|
375
|
+
expression,
|
|
376
|
+
applied: evaluated.state,
|
|
377
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
378
|
+
},
|
|
379
|
+
...(evaluated.trace ?? []),
|
|
380
|
+
],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
export function evaluateTriggerBlock(block, scenario, candidate, definitions, helperStack = [], scopeContext = rootScopeContext(scenario)) {
|
|
384
|
+
const override = scenario.candidateOverrides?.[candidate.id];
|
|
385
|
+
if (override !== undefined)
|
|
386
|
+
return { state: override ? 'true' : 'false', unresolved: [] };
|
|
387
|
+
if (block === undefined)
|
|
388
|
+
return { state: 'true', unresolved: [] };
|
|
389
|
+
const malformed = malformedBlock(block, candidate);
|
|
390
|
+
if (malformed !== undefined)
|
|
391
|
+
return malformed;
|
|
392
|
+
const children = assignments(block).map((assignment) => evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext));
|
|
393
|
+
return {
|
|
394
|
+
state: combineAnd(children.map(({ state }) => state)),
|
|
395
|
+
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
396
|
+
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
397
|
+
trace: children.flatMap(({ trace }) => trace ?? []),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const ScenarioScalarSchema = z.union([
|
|
3
|
+
z.string().max(16_384),
|
|
4
|
+
z.number(),
|
|
5
|
+
z.boolean(),
|
|
6
|
+
z.null(),
|
|
7
|
+
]);
|
|
8
|
+
export const ScenarioValueSchema = z.union([
|
|
9
|
+
ScenarioScalarSchema,
|
|
10
|
+
z.array(ScenarioScalarSchema).max(10_000),
|
|
11
|
+
]);
|
|
12
|
+
export const ConditionScopeBindingSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
id: z.string().min(1).max(256),
|
|
15
|
+
type: z
|
|
16
|
+
.enum([
|
|
17
|
+
'country',
|
|
18
|
+
'state',
|
|
19
|
+
'character',
|
|
20
|
+
'unit_leader',
|
|
21
|
+
'operative',
|
|
22
|
+
'strategic_region',
|
|
23
|
+
'province',
|
|
24
|
+
'unknown',
|
|
25
|
+
])
|
|
26
|
+
.optional(),
|
|
27
|
+
actor: z.string().max(256).optional(),
|
|
28
|
+
state: z.record(z.string(), ScenarioValueSchema).default({}),
|
|
29
|
+
flags: z.array(z.string().max(256)).max(10_000).optional(),
|
|
30
|
+
eventTargets: z.record(z.string(), z.string().max(256)).optional(),
|
|
31
|
+
weight: z.union([z.number(), z.string().max(256)]).optional(),
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
export const ConditionResultSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
token: z.string(),
|
|
37
|
+
sourcePath: z.string(),
|
|
38
|
+
state: z.enum(['true', 'false', 'unresolved']),
|
|
39
|
+
localisationKey: z.string().optional(),
|
|
40
|
+
unresolved: z.array(z.string()),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { hashCanonical } from './canonical.js';
|
|
3
|
+
import { ServiceError } from './result.js';
|
|
4
|
+
const ordinal = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
5
|
+
export const dependencyPageCursorSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
version: z.literal(1),
|
|
8
|
+
key: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
9
|
+
root: ordinal,
|
|
10
|
+
steps: ordinal,
|
|
11
|
+
emitted: ordinal,
|
|
12
|
+
cycles: ordinal,
|
|
13
|
+
depthStops: ordinal,
|
|
14
|
+
frames: z
|
|
15
|
+
.array(z.object({ edge: ordinal, next: ordinal, entered: z.boolean() }).strict())
|
|
16
|
+
.max(256),
|
|
17
|
+
visited: z.array(z.string().min(1)).max(1_000_000),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
/** Bounded DFS pages: retain only active ancestors and, for node policy, this root's visited nodes. */
|
|
21
|
+
export class DependencyPages {
|
|
22
|
+
input;
|
|
23
|
+
key;
|
|
24
|
+
state;
|
|
25
|
+
roots;
|
|
26
|
+
outgoing = new Map();
|
|
27
|
+
branches;
|
|
28
|
+
visited;
|
|
29
|
+
constructor(input, restored) {
|
|
30
|
+
this.input = input;
|
|
31
|
+
if (!Number.isSafeInteger(input.maximumDepth) ||
|
|
32
|
+
input.maximumDepth < 1 ||
|
|
33
|
+
input.maximumDepth > 256 ||
|
|
34
|
+
input.edges.length > 1_000_000)
|
|
35
|
+
throw new ServiceError('DEPENDENCY_PAGE_INPUT_INVALID', 'Dependency pagination requires bounded edge and depth inventories');
|
|
36
|
+
const byId = new Map(input.edges.map(({ id }, index) => [id, index]));
|
|
37
|
+
if (byId.size !== input.edges.length ||
|
|
38
|
+
input.roots.some((id) => !byId.has(id)) ||
|
|
39
|
+
new Set(input.roots).size !== input.roots.length)
|
|
40
|
+
throw new ServiceError('DEPENDENCY_PAGE_INPUT_INVALID', 'Dependency edge and root identities must be unique and all roots must exist');
|
|
41
|
+
this.key = hashCanonical({ algorithm: 'dependency-pages.v1', ...input });
|
|
42
|
+
this.roots = input.roots.map((id) => byId.get(id));
|
|
43
|
+
this.branches = new Set(input.branches);
|
|
44
|
+
for (const [index, { from }] of input.edges.entries()) {
|
|
45
|
+
const values = this.outgoing.get(from) ?? [];
|
|
46
|
+
values.push(index);
|
|
47
|
+
this.outgoing.set(from, values);
|
|
48
|
+
}
|
|
49
|
+
if (input.reverseEdges)
|
|
50
|
+
for (const values of this.outgoing.values())
|
|
51
|
+
values.reverse();
|
|
52
|
+
if (restored === undefined)
|
|
53
|
+
this.state = {
|
|
54
|
+
version: 1,
|
|
55
|
+
key: this.key,
|
|
56
|
+
root: 0,
|
|
57
|
+
steps: 0,
|
|
58
|
+
emitted: 0,
|
|
59
|
+
cycles: 0,
|
|
60
|
+
depthStops: 0,
|
|
61
|
+
frames: [],
|
|
62
|
+
visited: [],
|
|
63
|
+
};
|
|
64
|
+
else {
|
|
65
|
+
const parsed = dependencyPageCursorSchema.safeParse(restored);
|
|
66
|
+
if (!parsed.success)
|
|
67
|
+
throw new ServiceError('DEPENDENCY_CURSOR_INVALID', 'Dependency continuation has an invalid state schema');
|
|
68
|
+
this.state = parsed.data;
|
|
69
|
+
}
|
|
70
|
+
if (this.state.key !== this.key)
|
|
71
|
+
throw new ServiceError('DEPENDENCY_CURSOR_STALE', 'Dependency continuation does not match the exact source revision and traversal contract');
|
|
72
|
+
this.validate();
|
|
73
|
+
this.visited = new Set(this.state.visited);
|
|
74
|
+
}
|
|
75
|
+
validate() {
|
|
76
|
+
const invalid = () => {
|
|
77
|
+
throw new ServiceError('DEPENDENCY_CURSOR_INVALID', 'Dependency continuation has inconsistent counters or DFS ancestry');
|
|
78
|
+
};
|
|
79
|
+
const { root, frames, visited, steps, emitted, cycles, depthStops } = this.state;
|
|
80
|
+
if (root > this.roots.length ||
|
|
81
|
+
frames.length > this.input.maximumDepth ||
|
|
82
|
+
(root === this.roots.length && frames.length !== 0) ||
|
|
83
|
+
emitted > steps ||
|
|
84
|
+
cycles + depthStops > emitted ||
|
|
85
|
+
new Set(visited).size !== visited.length ||
|
|
86
|
+
(frames.length === 0 && visited.length !== 0) ||
|
|
87
|
+
(this.input.visitPolicy === 'edge_path' && visited.length !== 0))
|
|
88
|
+
invalid();
|
|
89
|
+
const nodes = new Set(this.input.edges.map(({ to }) => to));
|
|
90
|
+
if (visited.some((node) => !nodes.has(node)))
|
|
91
|
+
invalid();
|
|
92
|
+
const ancestors = new Set();
|
|
93
|
+
for (const [index, frame] of frames.entries()) {
|
|
94
|
+
const edge = this.input.edges[frame.edge];
|
|
95
|
+
if (edge === undefined || ancestors.has(edge.to))
|
|
96
|
+
invalid();
|
|
97
|
+
ancestors.add(edge.to);
|
|
98
|
+
if (frame.next > (this.outgoing.get(edge.to)?.length ?? 0) ||
|
|
99
|
+
(!frame.entered && (index !== frames.length - 1 || frame.next !== 0)))
|
|
100
|
+
invalid();
|
|
101
|
+
if (index === 0) {
|
|
102
|
+
if (frame.edge !== this.roots[root])
|
|
103
|
+
invalid();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const parent = frames[index - 1];
|
|
107
|
+
const previous = this.input.edges[parent.edge];
|
|
108
|
+
if (!parent.entered ||
|
|
109
|
+
parent.next === 0 ||
|
|
110
|
+
this.outgoing.get(previous.to)?.[parent.next - 1] !== frame.edge ||
|
|
111
|
+
previous.to !== edge.from ||
|
|
112
|
+
!this.branches.has(edge.to))
|
|
113
|
+
invalid();
|
|
114
|
+
}
|
|
115
|
+
if (this.input.visitPolicy === 'node' && !visited.includes(edge.to))
|
|
116
|
+
invalid();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
cursor() {
|
|
120
|
+
return {
|
|
121
|
+
...this.state,
|
|
122
|
+
frames: this.state.frames.map((frame) => ({ ...frame })),
|
|
123
|
+
visited: [...this.visited],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
page(maximumRecords, maximumWork, signal) {
|
|
127
|
+
if (!Number.isSafeInteger(maximumRecords) ||
|
|
128
|
+
maximumRecords < 1 ||
|
|
129
|
+
maximumRecords > 5_000 ||
|
|
130
|
+
!Number.isSafeInteger(maximumWork) ||
|
|
131
|
+
maximumWork < 1 ||
|
|
132
|
+
maximumWork > 100_000)
|
|
133
|
+
throw new ServiceError('DEPENDENCY_PAGE_BOUND_INVALID', 'Dependency pages require 1–5,000 records and 1–100,000 work transitions');
|
|
134
|
+
const records = [];
|
|
135
|
+
const startedAt = this.state.steps;
|
|
136
|
+
const emit = (kind, extra) => {
|
|
137
|
+
records.push({
|
|
138
|
+
kind,
|
|
139
|
+
root: this.state.root,
|
|
140
|
+
path: [
|
|
141
|
+
...this.state.frames.map(({ edge }) => edge),
|
|
142
|
+
...(extra === undefined ? [] : [extra]),
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
this.state.emitted++;
|
|
146
|
+
if (kind === 'cycle')
|
|
147
|
+
this.state.cycles++;
|
|
148
|
+
if (kind === 'depth')
|
|
149
|
+
this.state.depthStops++;
|
|
150
|
+
};
|
|
151
|
+
while (this.state.root < this.roots.length && this.state.steps - startedAt < maximumWork) {
|
|
152
|
+
signal?.throwIfAborted();
|
|
153
|
+
if (this.state.steps === Number.MAX_SAFE_INTEGER)
|
|
154
|
+
throw new ServiceError('DEPENDENCY_PAGE_COUNTER_LIMIT', 'Dependency pagination exhausted its exact integer work counter');
|
|
155
|
+
const frame = this.state.frames.at(-1);
|
|
156
|
+
if (frame === undefined) {
|
|
157
|
+
const edge = this.roots[this.state.root];
|
|
158
|
+
this.state.frames.push({ edge, next: 0, entered: false });
|
|
159
|
+
this.visited.clear();
|
|
160
|
+
if (this.input.visitPolicy === 'node')
|
|
161
|
+
this.visited.add(this.input.edges[edge].to);
|
|
162
|
+
}
|
|
163
|
+
else if (!frame.entered) {
|
|
164
|
+
if (records.length >= maximumRecords)
|
|
165
|
+
break;
|
|
166
|
+
emit('visit');
|
|
167
|
+
frame.entered = true;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const node = this.input.edges[frame.edge].to;
|
|
171
|
+
const candidate = this.outgoing.get(node)?.[frame.next];
|
|
172
|
+
if (candidate === undefined) {
|
|
173
|
+
this.state.frames.pop();
|
|
174
|
+
if (this.state.frames.length === 0) {
|
|
175
|
+
this.state.root++;
|
|
176
|
+
this.visited.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const edge = this.input.edges[candidate];
|
|
181
|
+
const cycle = this.state.frames.some((ancestor) => this.input.edges[ancestor.edge].to === edge.to);
|
|
182
|
+
const kind = !this.branches.has(edge.to)
|
|
183
|
+
? 'terminal'
|
|
184
|
+
: cycle
|
|
185
|
+
? 'cycle'
|
|
186
|
+
: this.state.frames.length >= this.input.maximumDepth
|
|
187
|
+
? 'depth'
|
|
188
|
+
: undefined;
|
|
189
|
+
if (kind !== undefined) {
|
|
190
|
+
if (records.length >= maximumRecords)
|
|
191
|
+
break;
|
|
192
|
+
emit(kind, candidate);
|
|
193
|
+
frame.next++;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
frame.next++;
|
|
197
|
+
if (this.input.visitPolicy === 'edge_path' || !this.visited.has(edge.to)) {
|
|
198
|
+
if (this.input.visitPolicy === 'node')
|
|
199
|
+
this.visited.add(edge.to);
|
|
200
|
+
this.state.frames.push({ edge: candidate, next: 0, entered: false });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.state.steps++;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
records,
|
|
209
|
+
finished: this.state.root === this.roots.length,
|
|
210
|
+
completedRoots: this.state.root,
|
|
211
|
+
totalRoots: this.roots.length,
|
|
212
|
+
work: this.state.steps - startedAt,
|
|
213
|
+
cursor: this.cursor(),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|