hoi4-agent-tools 3.0.7 → 3.0.9
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 +26 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +21 -1
- 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/configuration.js +14 -28
- package/dist/hoi4_agent_tools/core/engine.js +7 -1
- package/dist/hoi4_agent_tools/core/index.js +25 -0
- package/dist/hoi4_agent_tools/core/request-scheduler.js +79 -0
- package/dist/hoi4_agent_tools/core/scenario-state.js +97 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +125 -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 +9 -3
- package/dist/hoi4_agent_tools/mcp/server/create.js +3 -1
- package/dist/hoi4_agent_tools/mcp/server/progress.js +29 -8
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +14 -0
- package/dist/hoi4_agent_tools/mcp/transports/bounded-stdio.js +44 -14
- package/dist/hoi4_agent_tools/mcp/transports/http.js +75 -7
- package/dist/hoi4_agent_tools/probability/scenario-state.js +2 -95
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +1 -287
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/development.md +2 -0
- package/docs/gui.md +3 -1
- package/docs/http.md +5 -1
- package/package.json +3 -2
- package/schemas/configuration.schema.json +151 -133
- 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/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';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
export const PACKAGE_NAME = 'hoi4-agent-tools';
|
|
3
|
-
export const PACKAGE_VERSION = '3.0.
|
|
3
|
+
export const PACKAGE_VERSION = '3.0.9';
|
|
4
4
|
export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
|
|
5
5
|
export const CONFIG_VERSION = 1;
|
|
6
6
|
export const TRANSACTION_VERSION = 1;
|
package/docs/development.md
CHANGED
|
@@ -23,6 +23,8 @@ The event-chain acceptance fixture contains more than 300 project-owned event de
|
|
|
23
23
|
|
|
24
24
|
The technology acceptance fixture contains 1,040 project-owned technologies across 13 folders and exercises classic and current doctrines, prerequisites, exclusive branches, multiple placements, unlocks, bonuses, grants, assets, unresolved references, rendering, and comparison.
|
|
25
25
|
|
|
26
|
+
`npx vitest run tests/integration/mcp-concurrent-workloads.test.ts` tests simultaneous production calls across all six domains over six HTTP sessions and four independent stdio processes, including artifact retrieval and queued cancellation. Execution limits and connection behavior are described in [HTTP](http.md) and the [concurrency decision](https://github.com/klimPaskov/hoi4-agent-tools/blob/main/docs/adr/0026-concurrent-request-execution.md).
|
|
27
|
+
|
|
26
28
|
The probability fixture contains more than 150 weighted blocks, 250 scenarios, and a declared stateful pool. Run `npm run benchmark` after analyzer changes. On the July 22, 2026 reference run, cold indexing took 28 ms, a cached block evaluation 29 ms, the 250-row matrix 85 ms, one million draws 9 ms, 3/12/25-candidate sequence cases 20/2,079/356 ms, and retrieval of a 1.84 MB result 3 ms. Review budgets are 250 ms, 150 ms, 750 ms, 100 ms, 200/10,000/3,000 ms, and 100 ms respectively; accuracy, method metadata, and omitted probability must not be weakened to meet them.
|
|
27
29
|
|
|
28
30
|
Local opt-in tests can also read an installed game and an external mod without copying their sources:
|
package/docs/gui.md
CHANGED
|
@@ -41,7 +41,9 @@ Layout follows Clausewitz orientation and element-origin anchors, including `CEN
|
|
|
41
41
|
|
|
42
42
|
Scripted-GUI composition follows `parent_scripted_gui` chains even when the linked child window is a top-level sibling in the `.gui` file. Literal `always = yes` and `always = no` visibility and click-enabled blocks are applied automatically. A scenario's `values` object supplies concrete runtime values in one place: variable names resolve numeric text, scripted-localisation names resolve dynamic labels, a progress-bar element name sets its fill value, and element keys ending in `.image`, `.frame`, `.x`, `.y`, `.visible`, or `.enabled` update that visual property. Scripted-GUI property expressions such as `image = "[GetMeterSprite]"` or `x = meter_fill_width` consume matching scenario values automatically. Dynamic text is parsed after scenario and scripted-localisation substitution, so a runtime string containing `£command_power` or `£[GetResourceIcon]` renders the resolved inline HOI4 text icon instead of literal marker text. Dynamic lists instantiate their declared `entry_container` or `country_scope_entry_container`; each scenario row can select the country template with `countryScope`, choose an explicit `entryContainer`, provide `countryTag` and `ideology` for `GetFlag`, and supply row-scoped text, image, frame, position, visibility, and enabled values. Masked country sprites use the actual mask and overlay dimensions, so custom flag sizes are discovered from GFX assets rather than assumed by the renderer.
|
|
43
43
|
|
|
44
|
-
`hoi4.gui_inspect` and `hoi4.gui_render` generate a
|
|
44
|
+
`hoi4.gui_inspect` and `hoi4.gui_render` generate a seeded exploratory preview by default. The generator follows the complete `parent_scripted_gui` surface, discovers numeric and text tokens, fills meters within their declared ranges, and populates dynamic lists with coherent rows. Scripted-localisation text and sprite branches use the same structured condition evaluator as probability analysis, including compound conditions, inclusive comparisons, constants, declared scopes, and scripted triggers. An unknown earlier condition leaves the token unresolved instead of guessing a branch. The scenario's `conditionResults` records checked choices and missing inputs. Explicit values remain authoritative; generated inputs are not verified campaign state. Normal controls and animation frames remain stable unless variation is requested. Every explicit `relatedScenarios` entry is rendered beside the generated view.
|
|
45
|
+
|
|
46
|
+
Declare scoped facts with `scopes`, for example `"scopes": { "FROM": { "id": "target", "actor": "FRA", "state": { "support": 10 } } }`. Existing flat values such as `FROM.support` remain usable. Seeded pane and active/idle selection is exploratory layout sampling, not proof that every visibility branch is reachable.
|
|
45
47
|
|
|
46
48
|
Generation is reproducible. Set `generatedScenarios.seed` to any stable string for an exact rerun, use `count` for several plausible versions, or configure numeric bounds, integer or decimal values, list row bounds, text samples, visibility probability, and state variation. Generated numeric values favour ordinary mid-range states, obey literal limits such as `[?capacity]/10`, keep displayed current/limit pairs coherent, and remain inside progress-bar ranges. The generated view is the default. Set `enabled` to `false` when only the supplied placeholder scenario should render, or set `preservePlaceholder` to `true` to include the placeholder beside generated scenarios.
|
|
47
49
|
|
package/docs/http.md
CHANGED
|
@@ -4,7 +4,11 @@ Local MCP clients should use the default stdio registration. Use `hoi4-agent-too
|
|
|
4
4
|
|
|
5
5
|
The HTTP server exposes the MCP endpoint at `/mcp`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The server accepts up to 128 simultaneous HTTP requests by default and queues expensive tool work automatically. Cancellation, discovery, and protocol replies retain separate admission headroom. Active requests keep their sessions alive; credential expiry still applies.
|
|
8
|
+
|
|
9
|
+
JSON request bodies default to and are capped at 64 MiB, with a shared 128 MiB budget based on declared request sizes. Chunked bodies reserve their configured maximum. These byte reservations are independent of the number of accepted requests.
|
|
10
|
+
|
|
11
|
+
`maxConcurrentTools` controls expensive operations per server engine (default 2), and `maxSharedTools` controls operations across local processes sharing the same `serverStateRoot` (default 4). Calls waiting for capacity remain cancellable and emit progress when requested. Increase execution capacity only when the host has enough memory for the corresponding scans and renders. HTTP admission, connection, session, and rate limits remain separately configurable.
|
|
8
12
|
|
|
9
13
|
Resumable event history defaults to 2 MiB per session within the 16 MiB global store, enough to retain one framed 1 MiB artifact-resource chunk.
|
|
10
14
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hoi4-agent-tools",
|
|
3
3
|
"mcpName": "io.github.klimPaskov/hoi4-agent-tools",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.9",
|
|
5
5
|
"description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -130,7 +130,8 @@
|
|
|
130
130
|
"overrides": {
|
|
131
131
|
"@hono/node-server": "2.1.0",
|
|
132
132
|
"brace-expansion": "5.0.9",
|
|
133
|
-
"fast-uri": "3.1.
|
|
133
|
+
"fast-uri": "3.1.7",
|
|
134
|
+
"qs": "6.16.0",
|
|
134
135
|
"hono": "4.13.1",
|
|
135
136
|
"nanoid": "3.3.18",
|
|
136
137
|
"ip-address": "10.4.0",
|