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
|
@@ -1,287 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { resolveScopeContext, rootScopeContext, rootStateValue, scopeIdentity, scopeStateValue, } from './scenario-state.js';
|
|
3
|
-
function combineAnd(values) {
|
|
4
|
-
if (values.includes('false'))
|
|
5
|
-
return 'false';
|
|
6
|
-
return values.includes('unresolved') ? 'unresolved' : 'true';
|
|
7
|
-
}
|
|
8
|
-
function combineOr(values) {
|
|
9
|
-
if (values.includes('true'))
|
|
10
|
-
return 'true';
|
|
11
|
-
return values.includes('unresolved') ? 'unresolved' : 'false';
|
|
12
|
-
}
|
|
13
|
-
function invert(value) {
|
|
14
|
-
return value === 'true' ? 'false' : value === 'false' ? 'true' : 'unresolved';
|
|
15
|
-
}
|
|
16
|
-
function bool(value) {
|
|
17
|
-
if (typeof value === 'boolean')
|
|
18
|
-
return value;
|
|
19
|
-
if (value === 'yes' || value === 'true')
|
|
20
|
-
return true;
|
|
21
|
-
if (value === 'no' || value === 'false')
|
|
22
|
-
return false;
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
function numeric(value) {
|
|
26
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
27
|
-
return value;
|
|
28
|
-
if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))
|
|
29
|
-
return Number(value);
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
function compare(left, right, operator) {
|
|
33
|
-
if (typeof left === 'number' && typeof right === 'number') {
|
|
34
|
-
if (operator === '>')
|
|
35
|
-
return left > right;
|
|
36
|
-
if (operator === '<')
|
|
37
|
-
return left < right;
|
|
38
|
-
}
|
|
39
|
-
return operator === '!=' ? left !== right : left === right;
|
|
40
|
-
}
|
|
41
|
-
function unresolved(assignment, candidate, reason) {
|
|
42
|
-
return {
|
|
43
|
-
state: 'unresolved',
|
|
44
|
-
unresolved: [
|
|
45
|
-
{
|
|
46
|
-
code: 'TRIGGER_UNRESOLVED',
|
|
47
|
-
message: reason,
|
|
48
|
-
path: assignment.key.value,
|
|
49
|
-
candidateId: candidate.id,
|
|
50
|
-
...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
function evaluateCheckVariable(block, scenario, candidate, assignment, scopeContext) {
|
|
56
|
-
const variable = firstScalar(block, 'var')?.value;
|
|
57
|
-
const target = firstScalar(block, 'value')?.value;
|
|
58
|
-
if (variable === undefined || target === undefined)
|
|
59
|
-
return unresolved(assignment, candidate, 'check_variable requires declared var and value');
|
|
60
|
-
const left = scopeStateValue(scenario, `variable.${variable}`, scopeContext) ??
|
|
61
|
-
scopeStateValue(scenario, variable, scopeContext);
|
|
62
|
-
const leftNumber = numeric(left);
|
|
63
|
-
const rightNumber = numeric(target);
|
|
64
|
-
if (leftNumber === undefined || rightNumber === undefined)
|
|
65
|
-
return unresolved(assignment, candidate, `Scenario does not declare numeric variable ${variable}`);
|
|
66
|
-
const compareName = firstScalar(block, 'compare')?.value ?? 'equals';
|
|
67
|
-
const result = compareName === 'greater_than'
|
|
68
|
-
? leftNumber > rightNumber
|
|
69
|
-
: compareName === 'greater_than_or_equals'
|
|
70
|
-
? leftNumber >= rightNumber
|
|
71
|
-
: compareName === 'less_than'
|
|
72
|
-
? leftNumber < rightNumber
|
|
73
|
-
: compareName === 'less_than_or_equals'
|
|
74
|
-
? leftNumber <= rightNumber
|
|
75
|
-
: compareName === 'not_equals'
|
|
76
|
-
? leftNumber !== rightNumber
|
|
77
|
-
: leftNumber === rightNumber;
|
|
78
|
-
return { state: result ? 'true' : 'false', unresolved: [] };
|
|
79
|
-
}
|
|
80
|
-
function evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
81
|
-
const key = assignment.key.value;
|
|
82
|
-
if (assignment.value.type === 'block') {
|
|
83
|
-
if (key === 'AND')
|
|
84
|
-
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, scopeContext);
|
|
85
|
-
if (key === 'OR') {
|
|
86
|
-
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
87
|
-
return {
|
|
88
|
-
state: combineOr(children.map(({ state }) => state)),
|
|
89
|
-
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
90
|
-
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
if (key === 'NOT' || key === 'NOR' || key === 'NAND') {
|
|
94
|
-
const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
|
|
95
|
-
const base = key === 'NAND'
|
|
96
|
-
? combineAnd(children.map(({ state }) => state))
|
|
97
|
-
: combineOr(children.map(({ state }) => state));
|
|
98
|
-
return {
|
|
99
|
-
state: invert(base),
|
|
100
|
-
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
101
|
-
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
if (key === 'check_variable')
|
|
105
|
-
return evaluateCheckVariable(assignment.value, scenario, candidate, assignment, scopeContext);
|
|
106
|
-
if (key === 'is_in_array' || key === 'is_variable_in_array') {
|
|
107
|
-
const arrayId = firstScalar(assignment.value, 'array')?.value;
|
|
108
|
-
const declaredValue = firstScalar(assignment.value, 'value')?.value;
|
|
109
|
-
if (arrayId === undefined || declaredValue === undefined)
|
|
110
|
-
return unresolved(assignment, candidate, `${key} requires array and value`);
|
|
111
|
-
const array = scopeStateValue(scenario, `array.${arrayId}`, scopeContext) ??
|
|
112
|
-
rootStateValue(scenario, `array.${arrayId}`);
|
|
113
|
-
if (!Array.isArray(array))
|
|
114
|
-
return unresolved(assignment, candidate, `Scenario does not declare array ${arrayId}`);
|
|
115
|
-
const compared = ['THIS', 'ROOT', 'PREV'].includes(declaredValue.toUpperCase()) ||
|
|
116
|
-
resolveScopeContext(scenario, declaredValue, scopeContext) !== undefined
|
|
117
|
-
? scopeIdentity(resolveScopeContext(scenario, declaredValue, scopeContext) ?? scopeContext)
|
|
118
|
-
: (scopeStateValue(scenario, declaredValue, scopeContext) ?? declaredValue);
|
|
119
|
-
return { state: array.includes(compared) ? 'true' : 'false', unresolved: [] };
|
|
120
|
-
}
|
|
121
|
-
const resolvedScope = resolveScopeContext(scenario, key, scopeContext);
|
|
122
|
-
if (resolvedScope !== undefined)
|
|
123
|
-
return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, resolvedScope);
|
|
124
|
-
const helper = definitions.scriptedTriggers.get(key);
|
|
125
|
-
if (helper !== undefined) {
|
|
126
|
-
if (helperStack.includes(key))
|
|
127
|
-
return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
|
|
128
|
-
if (assignments(assignment.value).length > 0)
|
|
129
|
-
return unresolved(assignment, candidate, `Parameterized scripted trigger ${key} is not expanded without declared arguments`);
|
|
130
|
-
const helperProvenance = {
|
|
131
|
-
path: helper.file.displayPath,
|
|
132
|
-
rootKind: helper.file.rootKind,
|
|
133
|
-
loadOrder: helper.file.loadOrder,
|
|
134
|
-
sourceHash: helper.file.sha256,
|
|
135
|
-
location: nodeLocation(helper.document, helper.node, key),
|
|
136
|
-
...(astPathFor(helper.document, helper.node) === undefined
|
|
137
|
-
? {}
|
|
138
|
-
: { astPath: astPathFor(helper.document, helper.node) }),
|
|
139
|
-
symbol: key,
|
|
140
|
-
helperChain: [...helperStack, key],
|
|
141
|
-
};
|
|
142
|
-
const evaluated = evaluateTriggerBlock(helper.value, scenario, {
|
|
143
|
-
...candidate,
|
|
144
|
-
document: helper.document,
|
|
145
|
-
provenance: [helperProvenance, ...candidate.provenance],
|
|
146
|
-
}, definitions, [...helperStack, key], scopeContext);
|
|
147
|
-
return {
|
|
148
|
-
...evaluated,
|
|
149
|
-
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const declaredScope = bool(rootStateValue(scenario, `scope.${key}`));
|
|
153
|
-
if (declaredScope !== undefined)
|
|
154
|
-
return { state: declaredScope ? 'true' : 'false', unresolved: [] };
|
|
155
|
-
return unresolved(assignment, candidate, `Scoped or compound trigger ${key} is not declared by the scenario`);
|
|
156
|
-
}
|
|
157
|
-
const right = assignment.value.value;
|
|
158
|
-
if (key === 'always') {
|
|
159
|
-
const value = bool(right);
|
|
160
|
-
return value === undefined
|
|
161
|
-
? unresolved(assignment, candidate, 'always trigger is not boolean')
|
|
162
|
-
: { state: value ? 'true' : 'false', unresolved: [] };
|
|
163
|
-
}
|
|
164
|
-
if (key === 'has_country_flag' || key === 'has_global_flag' || key === 'has_state_flag') {
|
|
165
|
-
const flags = scopeContext.expression === 'ROOT' ? scenario.flags : scopeContext.binding.flags;
|
|
166
|
-
return { state: flags?.includes(right) === true ? 'true' : 'false', unresolved: [] };
|
|
167
|
-
}
|
|
168
|
-
if (key === 'has_event_target') {
|
|
169
|
-
const eventTargets = scopeContext.binding.eventTargets ?? scenario.eventTargets;
|
|
170
|
-
return {
|
|
171
|
-
state: Object.hasOwn(eventTargets ?? {}, right) ? 'true' : 'false',
|
|
172
|
-
unresolved: [],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (key === 'tag' || key === 'original_tag') {
|
|
176
|
-
const actor = scopeContext.expression === 'ROOT'
|
|
177
|
-
? scenario.actor
|
|
178
|
-
: (scopeContext.binding.actor ?? scopeContext.binding.id);
|
|
179
|
-
if (actor === undefined)
|
|
180
|
-
return unresolved(assignment, candidate, `Scenario does not declare actor for ${key}`);
|
|
181
|
-
return {
|
|
182
|
-
state: compare(actor, right, assignment.operator.text) ? 'true' : 'false',
|
|
183
|
-
unresolved: [],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
if (key === 'date') {
|
|
187
|
-
if (scenario.date === undefined)
|
|
188
|
-
return unresolved(assignment, candidate, 'Scenario does not declare date');
|
|
189
|
-
const leftDate = Number(scenario.date.replaceAll('.', ''));
|
|
190
|
-
const rightDate = Number(right.replaceAll('.', ''));
|
|
191
|
-
if (!Number.isFinite(leftDate) || !Number.isFinite(rightDate))
|
|
192
|
-
return unresolved(assignment, candidate, 'Date comparison is not numeric');
|
|
193
|
-
return {
|
|
194
|
-
state: compare(leftDate, rightDate, assignment.operator.text) ? 'true' : 'false',
|
|
195
|
-
unresolved: [],
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
const helper = definitions.scriptedTriggers.get(key);
|
|
199
|
-
if (helper !== undefined && bool(right) === true) {
|
|
200
|
-
if (helperStack.includes(key))
|
|
201
|
-
return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
|
|
202
|
-
const helperProvenance = {
|
|
203
|
-
path: helper.file.displayPath,
|
|
204
|
-
rootKind: helper.file.rootKind,
|
|
205
|
-
loadOrder: helper.file.loadOrder,
|
|
206
|
-
sourceHash: helper.file.sha256,
|
|
207
|
-
location: nodeLocation(helper.document, helper.node, key),
|
|
208
|
-
...(astPathFor(helper.document, helper.node) === undefined
|
|
209
|
-
? {}
|
|
210
|
-
: { astPath: astPathFor(helper.document, helper.node) }),
|
|
211
|
-
symbol: key,
|
|
212
|
-
helperChain: [...helperStack, key],
|
|
213
|
-
};
|
|
214
|
-
const evaluated = evaluateTriggerBlock(helper.value, scenario, {
|
|
215
|
-
...candidate,
|
|
216
|
-
document: helper.document,
|
|
217
|
-
provenance: [helperProvenance, ...candidate.provenance],
|
|
218
|
-
}, definitions, [...helperStack, key], scopeContext);
|
|
219
|
-
return {
|
|
220
|
-
...evaluated,
|
|
221
|
-
helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
const declared = scopeStateValue(scenario, key, scopeContext);
|
|
225
|
-
if (declared === undefined)
|
|
226
|
-
return unresolved(assignment, candidate, `Scenario does not declare trigger ${key}`);
|
|
227
|
-
const expectedBoolean = bool(right);
|
|
228
|
-
const actualBoolean = bool(declared);
|
|
229
|
-
if (expectedBoolean !== undefined && actualBoolean !== undefined)
|
|
230
|
-
return { state: expectedBoolean === actualBoolean ? 'true' : 'false', unresolved: [] };
|
|
231
|
-
const leftNumber = numeric(declared);
|
|
232
|
-
const rightNumber = numeric(right);
|
|
233
|
-
if (leftNumber !== undefined && rightNumber !== undefined)
|
|
234
|
-
return {
|
|
235
|
-
state: compare(leftNumber, rightNumber, assignment.operator.text) ? 'true' : 'false',
|
|
236
|
-
unresolved: [],
|
|
237
|
-
};
|
|
238
|
-
if (typeof declared === 'string')
|
|
239
|
-
return {
|
|
240
|
-
state: compare(declared, right, assignment.operator.text) ? 'true' : 'false',
|
|
241
|
-
unresolved: [],
|
|
242
|
-
};
|
|
243
|
-
return unresolved(assignment, candidate, `Trigger ${key} cannot compare the declared scenario value`);
|
|
244
|
-
}
|
|
245
|
-
function evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
|
|
246
|
-
const evaluated = evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext);
|
|
247
|
-
const source = candidate.provenance[0];
|
|
248
|
-
const provenance = candidate.document === undefined || source === undefined
|
|
249
|
-
? source
|
|
250
|
-
: {
|
|
251
|
-
...source,
|
|
252
|
-
location: nodeLocation(candidate.document, assignment, assignment.key.value),
|
|
253
|
-
...(astPathFor(candidate.document, assignment) === undefined
|
|
254
|
-
? {}
|
|
255
|
-
: { astPath: astPathFor(candidate.document, assignment) }),
|
|
256
|
-
symbol: assignment.key.value,
|
|
257
|
-
};
|
|
258
|
-
const expression = assignment.value.type === 'scalar'
|
|
259
|
-
? `${assignment.key.value} ${assignment.operator.text} ${assignment.value.value}`
|
|
260
|
-
: assignment.key.value;
|
|
261
|
-
return {
|
|
262
|
-
...evaluated,
|
|
263
|
-
trace: [
|
|
264
|
-
{
|
|
265
|
-
operation: 'eligibility',
|
|
266
|
-
expression,
|
|
267
|
-
applied: evaluated.state,
|
|
268
|
-
...(provenance === undefined ? {} : { provenance }),
|
|
269
|
-
},
|
|
270
|
-
...(evaluated.trace ?? []),
|
|
271
|
-
],
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
export function evaluateTriggerBlock(block, scenario, candidate, definitions, helperStack = [], scopeContext = rootScopeContext(scenario)) {
|
|
275
|
-
const override = scenario.candidateOverrides?.[candidate.id];
|
|
276
|
-
if (override !== undefined)
|
|
277
|
-
return { state: override ? 'true' : 'false', unresolved: [] };
|
|
278
|
-
if (block === undefined)
|
|
279
|
-
return { state: 'true', unresolved: [] };
|
|
280
|
-
const children = assignments(block).map((assignment) => evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext));
|
|
281
|
-
return {
|
|
282
|
-
state: combineAnd(children.map(({ state }) => state)),
|
|
283
|
-
unresolved: children.flatMap(({ unresolved: items }) => items),
|
|
284
|
-
helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
|
|
285
|
-
trace: children.flatMap(({ trace }) => trace ?? []),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
1
|
+
export { evaluateTriggerBlock } from '../core/condition-evaluator.js';
|
|
@@ -4,6 +4,8 @@ export const workspaceIdSchema = z
|
|
|
4
4
|
.string()
|
|
5
5
|
.regex(/^[a-z][a-z0-9_-]{0,63}$/u)
|
|
6
6
|
.default('current');
|
|
7
|
+
/** Caller-stable deduplication key for resumable background mutations. */
|
|
8
|
+
export const backgroundRequestKeySchema = z.string().min(1).max(256);
|
|
7
9
|
const diagnosticCodeSchema = z.string().min(1).max(256);
|
|
8
10
|
const diagnosticMessageSchema = z.string().max(4096);
|
|
9
11
|
const sourceDisplayPathSchema = z.string().min(1).max(4096);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
3
|
-
import { workspaceRelativePathSchema } from './common.js';
|
|
3
|
+
import { workspaceIdSchema, workspaceRelativePathSchema } from './common.js';
|
|
4
|
+
import { helperExpansionRequestSchema, validateHelperExpansionMode } from './helper-expansion.js';
|
|
4
5
|
const eventIdSchema = z.string().min(1).max(256);
|
|
5
6
|
const eventSourcePathSchema = z.string().min(1).max(1024);
|
|
6
7
|
const eventNodeIdSchema = z.string().min(1).max(1024);
|
|
@@ -13,6 +14,7 @@ export const eventInspectModeSchema = z.enum([
|
|
|
13
14
|
'state_flow',
|
|
14
15
|
'lint',
|
|
15
16
|
'impact',
|
|
17
|
+
'helper_expansion',
|
|
16
18
|
]);
|
|
17
19
|
export const eventDirectionSchema = z.enum(['upstream', 'downstream', 'both']);
|
|
18
20
|
export const eventRenderViewSchema = z.enum([
|
|
@@ -93,3 +95,72 @@ export const eventProposedSourceSchema = z
|
|
|
93
95
|
expectedSourceHash: eventSha256Schema.optional(),
|
|
94
96
|
})
|
|
95
97
|
.strict();
|
|
98
|
+
export function validateEventInspectRequest(value, context) {
|
|
99
|
+
validateHelperExpansionMode(value, context);
|
|
100
|
+
if (value.mode === 'trace' && value.selector === undefined)
|
|
101
|
+
context.addIssue({ code: 'custom', path: ['selector'], message: 'Trace requires selector' });
|
|
102
|
+
if (value.mode === 'explain_path' && (value.from === undefined || value.to === undefined))
|
|
103
|
+
context.addIssue({
|
|
104
|
+
code: 'custom',
|
|
105
|
+
path: value.from === undefined ? ['from'] : ['to'],
|
|
106
|
+
message: 'Path explanation requires from and to',
|
|
107
|
+
});
|
|
108
|
+
if (value.mode === 'impact' && value.impactSubject === undefined)
|
|
109
|
+
context.addIssue({
|
|
110
|
+
code: 'custom',
|
|
111
|
+
path: ['impactSubject'],
|
|
112
|
+
message: 'Impact analysis requires impactSubject',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export const eventInspectRequestSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
workspaceId: workspaceIdSchema,
|
|
118
|
+
mode: eventInspectModeSchema,
|
|
119
|
+
selector: eventSelectorSchema.optional(),
|
|
120
|
+
from: eventSelectorSchema.optional(),
|
|
121
|
+
to: eventSelectorSchema.optional(),
|
|
122
|
+
direction: eventDirectionSchema.optional(),
|
|
123
|
+
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
124
|
+
maxNodes: z.number().int().min(1).max(5_000).optional(),
|
|
125
|
+
maxEdges: z.number().int().min(1).max(20_000).optional(),
|
|
126
|
+
expandHelpers: z.boolean().optional(),
|
|
127
|
+
stateSubject: eventStateSubjectSchema.optional(),
|
|
128
|
+
impactSubject: eventImpactSubjectSchema.optional(),
|
|
129
|
+
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
130
|
+
refresh: z.boolean().optional(),
|
|
131
|
+
})
|
|
132
|
+
.strict()
|
|
133
|
+
.superRefine(validateEventInspectRequest);
|
|
134
|
+
export const eventRenderRequestSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
workspaceId: workspaceIdSchema,
|
|
137
|
+
view: eventRenderViewSchema,
|
|
138
|
+
selector: eventSelectorSchema.optional(),
|
|
139
|
+
direction: eventDirectionSchema.optional(),
|
|
140
|
+
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
141
|
+
maxNodes: z.number().int().min(1).max(240).optional(),
|
|
142
|
+
expandHelpers: z.boolean().optional(),
|
|
143
|
+
includeHtml: z.boolean().optional(),
|
|
144
|
+
refresh: z.boolean().optional(),
|
|
145
|
+
})
|
|
146
|
+
.strict();
|
|
147
|
+
export function validateEventCompareRequest(value, context) {
|
|
148
|
+
if (value.after !== undefined && value.proposedSources !== undefined)
|
|
149
|
+
context.addIssue({
|
|
150
|
+
code: 'custom',
|
|
151
|
+
path: ['after'],
|
|
152
|
+
message: 'after and proposedSources are mutually exclusive',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export const eventCompareRequestSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
workspaceId: workspaceIdSchema,
|
|
158
|
+
before: eventGraphReferenceSchema.optional(),
|
|
159
|
+
after: eventGraphReferenceSchema.optional(),
|
|
160
|
+
proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
|
|
161
|
+
render: z.boolean().optional(),
|
|
162
|
+
maxRenderNodes: z.number().int().min(1).max(240).optional(),
|
|
163
|
+
refresh: z.boolean().optional(),
|
|
164
|
+
})
|
|
165
|
+
.strict()
|
|
166
|
+
.superRefine(validateEventCompareRequest);
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { FOCUS_RENDER_MAX_OUTPUT_SCALE, FOCUS_RENDER_MIN_OUTPUT_SCALE } from '../focus/index.js';
|
|
3
|
+
import { backgroundRequestKeySchema, workspaceIdSchema, workspaceRelativePathSchema, } from './common.js';
|
|
4
|
+
import { continuousFocusPaletteSchema, focusLayoutSchema, focusTreePlanSchema } from './focus.js';
|
|
5
|
+
function compact(schema, description) {
|
|
6
|
+
return z.unknown().describe(description).pipe(schema);
|
|
7
|
+
}
|
|
8
|
+
const compactFocusLayoutSchema = compact(focusLayoutSchema, 'Prior deterministic focus layout used to stabilize unchanged positions.');
|
|
9
|
+
const compactFocusPlanSchema = compact(z.union([focusTreePlanSchema, continuousFocusPaletteSchema]), 'Complete national or continuous focus plan.');
|
|
10
|
+
export const focusInspectRequestSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
mode: z.enum(['national', 'continuous']).optional(),
|
|
13
|
+
workspaceId: workspaceIdSchema,
|
|
14
|
+
relativePath: workspaceRelativePathSchema.optional(),
|
|
15
|
+
treeId: z.string().min(1).max(256).optional(),
|
|
16
|
+
paletteId: z.string().min(1).max(256).optional(),
|
|
17
|
+
previous: compactFocusLayoutSchema.optional(),
|
|
18
|
+
laneSpacing: z.number().int().min(1).max(100).optional(),
|
|
19
|
+
nodeSpacing: z.number().int().min(1).max(100).optional(),
|
|
20
|
+
})
|
|
21
|
+
.strict()
|
|
22
|
+
.superRefine((value, context) => {
|
|
23
|
+
const mode = value.mode ?? 'national';
|
|
24
|
+
if (mode === 'national' && value.paletteId !== undefined)
|
|
25
|
+
context.addIssue({
|
|
26
|
+
code: 'custom',
|
|
27
|
+
path: ['paletteId'],
|
|
28
|
+
message: 'paletteId is only valid in continuous mode',
|
|
29
|
+
});
|
|
30
|
+
if (mode === 'continuous' && value.treeId !== undefined)
|
|
31
|
+
context.addIssue({
|
|
32
|
+
code: 'custom',
|
|
33
|
+
path: ['treeId'],
|
|
34
|
+
message: 'treeId is only valid in national mode',
|
|
35
|
+
});
|
|
36
|
+
if (mode === 'continuous') {
|
|
37
|
+
for (const field of ['previous', 'laneSpacing', 'nodeSpacing']) {
|
|
38
|
+
if (value[field] !== undefined)
|
|
39
|
+
context.addIssue({
|
|
40
|
+
code: 'custom',
|
|
41
|
+
path: [field],
|
|
42
|
+
message: `${field} is only valid in national mode`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (value.previous !== undefined && value.treeId === undefined)
|
|
47
|
+
context.addIssue({
|
|
48
|
+
code: 'custom',
|
|
49
|
+
path: ['previous'],
|
|
50
|
+
message: 'previous requires treeId so it applies to exactly one tree',
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
export const focusRenderRequestSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
mode: z.enum(['national', 'continuous']).optional(),
|
|
56
|
+
workspaceId: workspaceIdSchema,
|
|
57
|
+
relativePath: workspaceRelativePathSchema,
|
|
58
|
+
treeId: z.string().min(1).max(256).optional(),
|
|
59
|
+
paletteId: z.string().min(1).max(256).optional(),
|
|
60
|
+
horizontalSpacing: z.number().int().min(80).max(1000).optional(),
|
|
61
|
+
verticalSpacing: z.number().int().min(60).max(1000).optional(),
|
|
62
|
+
reviewScale: z
|
|
63
|
+
.number()
|
|
64
|
+
.min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
|
|
65
|
+
.max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
|
|
66
|
+
.optional(),
|
|
67
|
+
columns: z.number().int().min(1).max(12).optional(),
|
|
68
|
+
padding: z.number().int().min(0).max(1000).optional(),
|
|
69
|
+
})
|
|
70
|
+
.strict()
|
|
71
|
+
.superRefine((value, context) => {
|
|
72
|
+
const mode = value.mode ?? 'national';
|
|
73
|
+
const invalid = mode === 'national'
|
|
74
|
+
? [
|
|
75
|
+
['paletteId', value.paletteId],
|
|
76
|
+
['columns', value.columns],
|
|
77
|
+
]
|
|
78
|
+
: [
|
|
79
|
+
['treeId', value.treeId],
|
|
80
|
+
['horizontalSpacing', value.horizontalSpacing],
|
|
81
|
+
['verticalSpacing', value.verticalSpacing],
|
|
82
|
+
['reviewScale', value.reviewScale],
|
|
83
|
+
];
|
|
84
|
+
for (const [field, fieldValue] of invalid) {
|
|
85
|
+
if (fieldValue !== undefined)
|
|
86
|
+
context.addIssue({
|
|
87
|
+
code: 'custom',
|
|
88
|
+
path: [field],
|
|
89
|
+
message: `${field} is not valid in ${mode} mode`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (mode === 'continuous' && value.padding !== undefined && value.padding < 24)
|
|
93
|
+
context.addIssue({
|
|
94
|
+
code: 'custom',
|
|
95
|
+
path: ['padding'],
|
|
96
|
+
message: 'continuous focus padding must be at least 24 pixels',
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
export const focusRewriteRequestSchema = z
|
|
100
|
+
.object({
|
|
101
|
+
mode: z.enum(['national', 'continuous']).optional(),
|
|
102
|
+
workspaceId: workspaceIdSchema,
|
|
103
|
+
relativePath: workspaceRelativePathSchema,
|
|
104
|
+
treeId: z.string().min(1).max(256).optional(),
|
|
105
|
+
layoutMode: z.enum(['authored', 'compact']).default('authored'),
|
|
106
|
+
plan: compactFocusPlanSchema.optional(),
|
|
107
|
+
createIfMissing: z.boolean().default(false),
|
|
108
|
+
horizontalSpacing: z.number().int().min(80).max(1000).optional(),
|
|
109
|
+
verticalSpacing: z.number().int().min(60).max(1000).optional(),
|
|
110
|
+
padding: z.number().int().min(0).max(1000).optional(),
|
|
111
|
+
reviewScale: z
|
|
112
|
+
.number()
|
|
113
|
+
.min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
|
|
114
|
+
.max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
|
|
115
|
+
.optional(),
|
|
116
|
+
requestKey: backgroundRequestKeySchema.optional(),
|
|
117
|
+
})
|
|
118
|
+
.strict()
|
|
119
|
+
.superRefine((value, context) => {
|
|
120
|
+
const mode = value.mode ?? 'national';
|
|
121
|
+
const schema = mode === 'continuous' ? continuousFocusPaletteSchema : focusTreePlanSchema;
|
|
122
|
+
if (value.plan !== undefined && !schema.safeParse(value.plan).success)
|
|
123
|
+
context.addIssue({
|
|
124
|
+
code: 'custom',
|
|
125
|
+
path: ['plan'],
|
|
126
|
+
message: `plan must be a ${mode} focus plan`,
|
|
127
|
+
});
|
|
128
|
+
if (mode === 'continuous') {
|
|
129
|
+
if (value.plan === undefined)
|
|
130
|
+
context.addIssue({
|
|
131
|
+
code: 'custom',
|
|
132
|
+
path: ['plan'],
|
|
133
|
+
message: 'plan is required in continuous mode',
|
|
134
|
+
});
|
|
135
|
+
if (value.treeId !== undefined)
|
|
136
|
+
context.addIssue({
|
|
137
|
+
code: 'custom',
|
|
138
|
+
path: ['treeId'],
|
|
139
|
+
message: 'treeId is only valid in national mode',
|
|
140
|
+
});
|
|
141
|
+
if (value.layoutMode !== 'authored')
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: 'custom',
|
|
144
|
+
path: ['layoutMode'],
|
|
145
|
+
message: 'compact layout mode is only valid in national mode',
|
|
146
|
+
});
|
|
147
|
+
for (const [field, fieldValue] of [
|
|
148
|
+
['horizontalSpacing', value.horizontalSpacing],
|
|
149
|
+
['verticalSpacing', value.verticalSpacing],
|
|
150
|
+
['padding', value.padding],
|
|
151
|
+
['reviewScale', value.reviewScale],
|
|
152
|
+
])
|
|
153
|
+
if (fieldValue !== undefined)
|
|
154
|
+
context.addIssue({
|
|
155
|
+
code: 'custom',
|
|
156
|
+
path: [field],
|
|
157
|
+
message: `${field} is only valid for national focus review renders`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (value.plan === undefined && value.layoutMode !== 'compact')
|
|
162
|
+
context.addIssue({
|
|
163
|
+
code: 'custom',
|
|
164
|
+
path: ['plan'],
|
|
165
|
+
message: 'plan is required unless layoutMode is compact',
|
|
166
|
+
});
|
|
167
|
+
if (value.plan === undefined && value.treeId === undefined)
|
|
168
|
+
context.addIssue({
|
|
169
|
+
code: 'custom',
|
|
170
|
+
path: ['treeId'],
|
|
171
|
+
message: 'treeId is required for plan-free compact reflow',
|
|
172
|
+
});
|
|
173
|
+
if (value.plan === undefined && value.createIfMissing)
|
|
174
|
+
context.addIssue({
|
|
175
|
+
code: 'custom',
|
|
176
|
+
path: ['createIfMissing'],
|
|
177
|
+
message: 'plan-free compact reflow requires an existing focus source',
|
|
178
|
+
});
|
|
179
|
+
if (value.plan !== undefined &&
|
|
180
|
+
value.treeId !== undefined &&
|
|
181
|
+
'id' in value.plan &&
|
|
182
|
+
value.plan.id !== value.treeId)
|
|
183
|
+
context.addIssue({
|
|
184
|
+
code: 'custom',
|
|
185
|
+
path: ['treeId'],
|
|
186
|
+
message: 'treeId must match the supplied national focus plan',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|