hoi4-agent-tools 3.4.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/hoi4_agent_tools/core/condition-evaluator.js +7 -0
  3. package/dist/hoi4_agent_tools/core/index.js +1 -0
  4. package/dist/hoi4_agent_tools/core/job-worker-host.js +19 -1
  5. package/dist/hoi4_agent_tools/core/operation-result.js +4 -1
  6. package/dist/hoi4_agent_tools/event/operations.js +16 -10
  7. package/dist/hoi4_agent_tools/gui/validators.js +3 -0
  8. package/dist/hoi4_agent_tools/mechanic/interpreter.js +91 -30
  9. package/dist/hoi4_agent_tools/mechanic/source.js +19 -4
  10. package/dist/hoi4_agent_tools/technology/operations.js +29 -1
  11. package/dist/hoi4_agent_tools/technology/render.js +2 -1
  12. package/dist/hoi4_agent_tools/technology/service.js +1 -0
  13. package/dist/hoi4_agent_tools/version.js +1 -1
  14. package/docs/events.md +1 -1
  15. package/docs/gui.md +1 -0
  16. package/docs/jobs.md +1 -0
  17. package/docs/mechanics.md +2 -1
  18. package/docs/technology.md +5 -1
  19. package/package.json +1 -1
  20. package/schemas/configuration.schema.json +1 -1
  21. package/schemas/continuous-focus-palette.schema.json +1 -1
  22. package/schemas/custom-weighted-pool.schema.json +1 -1
  23. package/schemas/decision-inspect-input.schema.json +1 -1
  24. package/schemas/event-feature-manifest.schema.json +1 -1
  25. package/schemas/focus-plan.schema.json +1 -1
  26. package/schemas/focus-planning-sidecar.schema.json +1 -1
  27. package/schemas/gui-animation-source.schema.json +1 -1
  28. package/schemas/gui-helper.schema.json +1 -1
  29. package/schemas/gui-scenario.schema.json +1 -1
  30. package/schemas/helper-expansion-request.schema.json +1 -1
  31. package/schemas/helper-expansion-summary.schema.json +1 -1
  32. package/schemas/impact-inspect-input.schema.json +1 -1
  33. package/schemas/map-operation.schema.json +1 -1
  34. package/schemas/mechanic-test-input.schema.json +1 -1
  35. package/schemas/operation-result.schema.json +1 -1
  36. package/schemas/package-check-input.schema.json +1 -1
  37. package/schemas/package-manifest.schema.json +1 -1
  38. package/schemas/probability-analysis-result.schema.json +1 -1
  39. package/schemas/probability-compare-input.schema.json +1 -1
  40. package/schemas/probability-evaluate-input.schema.json +1 -1
  41. package/schemas/probability-inspect-input.schema.json +1 -1
  42. package/schemas/probability-render-input.schema.json +1 -1
  43. package/schemas/probability-scenario-set.schema.json +1 -1
  44. package/schemas/probability-sequence-input.schema.json +1 -1
  45. package/schemas/probability-simulate-input.schema.json +1 -1
  46. package/schemas/probability-sweep-input.schema.json +1 -1
  47. package/schemas/scenario-suite.schema.json +1 -1
  48. package/schemas/scenario-test-input.schema.json +1 -1
  49. package/schemas/shared-scenario.schema.json +1 -1
  50. package/server.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.4.1 - 2026-09-25
6
+
7
+ - Validate technology renders against the requested view's source placement, node coverage, and sprite resolution without inheriting unrelated graph-wide diagnostics, while reporting deferred whole-workspace helper projection as a separate analysis boundary; resolve subtechnology art from the source GUI slot before using a technology-id fallback and include every referenced slot texture in the asset scan.
8
+ - Accept focused event inspection and render evidence when every event source was scanned and no blocking diagnostics remain, while retaining deferred workspace-wide helper and lifecycle projections as an explicit boundary.
9
+ - Keep repeated window-local GUI control names additive in the shared index, exclude engine-owned compound-control roles from scripted click-effect requirements, and preserve compact operation summaries when linked evidence exceeds the MCP wire budget.
10
+ - Parse each active scripted-effect source document once when building mechanic helper inventories so bounded real-workspace scenarios do not multiply parser state by the number of helpers in a file.
11
+ - Interpret declared temporary-variable assignment, arithmetic, rounding, clamping, and scope-valued inputs with chain-wide unscoped semantics in bounded mechanic scenarios.
12
+ - Evaluate `has_variable` against declared scenario state so guarded defaults and subsequent temporary-variable arithmetic remain deterministic.
13
+ - Follow a live replacement worker through its terminal publication instead of returning a nonterminal job record during concurrent checkpoint recovery.
14
+
5
15
  ## 3.4.0 - 2026-09-20
6
16
 
7
17
  - Add branch-local focus cleanup with pinned anchors and symmetry groups, connector measurements, source-linked crops, and scenario-backed structural route diagnostics.
@@ -278,6 +278,13 @@ function evaluateAssignmentCore(assignment, scenario, candidate, definitions, he
278
278
  unresolved: [],
279
279
  };
280
280
  }
281
+ if (key === 'has_variable') {
282
+ const state = scopeContext.expression === 'ROOT' ? scenario.state : scopeContext.binding.state;
283
+ return {
284
+ state: Object.hasOwn(state, right) || Object.hasOwn(state, `variable.${right}`) ? 'true' : 'false',
285
+ unresolved: [],
286
+ };
287
+ }
281
288
  if (key === 'tag' || key === 'original_tag') {
282
289
  const actor = scopeContext.expression === 'ROOT'
283
290
  ? scenario.actor
@@ -1369,6 +1369,7 @@ export class SymbolIndex {
1369
1369
  this.#active.set(key, active);
1370
1370
  const additiveCategory = active.kind === 'decision_category' ||
1371
1371
  active.kind === 'technology_category' ||
1372
+ active.kind === 'gui_element' ||
1372
1373
  active.kind === 'texture';
1373
1374
  for (const candidate of group) {
1374
1375
  candidate.overridden = additiveCategory ? candidate.sourceShadowed : candidate !== active;
@@ -73,7 +73,12 @@ export class JobWorkerHost {
73
73
  for (;;) {
74
74
  try {
75
75
  await this.scheduler.run(this.owner, 1024, signal, () => this.capacity.run(signal, (lease) => this.launch(workspaceId, id, lease, principal)));
76
- return await this.jobs.get(workspaceId, id, principal);
76
+ const current = await this.jobs.get(workspaceId, id, principal);
77
+ if (['completed', 'cancelled', 'failed'].includes(current.status))
78
+ return current;
79
+ if (current.owner !== undefined && jobOwnerLiveness(current.owner) === 'alive')
80
+ return await this.followOwner(workspaceId, id, principal);
81
+ throw new ServiceError('JOB_WORKER_EXIT', 'The worker stopped without publishing a terminal job outcome');
77
82
  }
78
83
  catch (error) {
79
84
  const current = await this.jobs.get(workspaceId, id, principal);
@@ -108,6 +113,19 @@ export class JobWorkerHost {
108
113
  }
109
114
  }
110
115
  }
116
+ async followOwner(workspaceId, id, principal) {
117
+ for (;;) {
118
+ const record = await this.jobs.get(workspaceId, id, principal);
119
+ if (['completed', 'failed', 'cancelled'].includes(record.status))
120
+ return record;
121
+ const liveness = record.owner === undefined ? 'dead' : jobOwnerLiveness(record.owner);
122
+ if (liveness !== 'alive')
123
+ throw new ServiceError(liveness === 'unknown' ? 'JOB_OWNER_UNRESOLVED' : 'JOB_WORKER_EXIT', liveness === 'unknown'
124
+ ? 'The replacement execution owner cannot be verified on this host'
125
+ : 'The replacement worker stopped without publishing a terminal job outcome');
126
+ await delay(100);
127
+ }
128
+ }
111
129
  async launch(workspaceId, id, lease, principal) {
112
130
  // Resolve the same configured workspace and grants again immediately before dispatch.
113
131
  await this.jobs.get(workspaceId, id, principal);
@@ -114,6 +114,9 @@ function boundedResult(result, actualBytes) {
114
114
  : 'Tool output exceeded the wire budget; nonessential inline details were omitted',
115
115
  details: { actualBytes, maxBytes: MAX_TOOL_RESULT_BYTES },
116
116
  };
117
+ const compactData = result.data !== null && typeof result.data === 'object' && !Array.isArray(result.data)
118
+ ? result.data
119
+ : {};
117
120
  return {
118
121
  status: result.status,
119
122
  code: result.code.slice(0, 256),
@@ -141,7 +144,7 @@ function boundedResult(result, actualBytes) {
141
144
  },
142
145
  },
143
146
  ],
144
- data: {},
147
+ data: jsonBytesWithin(compactData, 2_048) ? compactData : {},
145
148
  };
146
149
  }
147
150
  function jsonStringBytes(value) {
@@ -37,21 +37,27 @@ export function eventGraphCounts(graph, artifacts) {
37
37
  }
38
38
  export function eventGraphValidation(graph) {
39
39
  const counts = eventGraphCounts(graph, 0);
40
+ const analysisBoundaryPassed = graph.complete || (graph.analysisMode === 'focused' && graph.skippedSourceCount === 0);
41
+ const passed = analysisBoundaryPassed && counts.blockingDiagnostics === 0;
40
42
  return {
41
- passed: graph.complete && counts.blockingDiagnostics === 0,
43
+ passed,
42
44
  checks: [
43
45
  {
44
46
  id: 'event-analysis',
45
- passed: graph.complete && counts.blockingDiagnostics === 0,
46
- message: graph.analysisMode === 'focused'
47
- ? 'Large workspace analysis deferred workspace-wide helper projections and lifecycle passes; direct evidence is linked'
47
+ passed,
48
+ message: counts.blockingDiagnostics > 0
49
+ ? `${counts.blockingDiagnostics} blocking event-chain diagnostics; full evidence is linked`
48
50
  : graph.complete
49
- ? `${counts.blockingDiagnostics} blocking event-chain diagnostics; full evidence is linked`
50
- : graph.issues.some(({ code }) => code === 'EVENT_HELPER_DEPTH_LIMIT' ||
51
- code === 'EVENT_HELPER_PROJECTION_LIMIT' ||
52
- code === 'EVENT_HELPER_STATE_PROJECTION_LIMIT')
53
- ? 'Helper expansion reached a depth or materialization boundary; helper_expansion mode provides bounded source-linked continuation'
54
- : `${graph.skippedSourceCount} event-analysis source(s) were skipped; full evidence is linked`,
51
+ ? 'The event-chain analysis is complete and has no blocking diagnostics'
52
+ : graph.analysisMode === 'focused' && graph.skippedSourceCount === 0
53
+ ? 'Whole-workspace helper projections and lifecycle passes were deferred; the requested event analysis retains direct source evidence'
54
+ : graph.analysisMode === 'focused'
55
+ ? `${graph.skippedSourceCount} event-analysis source(s) were skipped; linked evidence records the partial boundary`
56
+ : graph.issues.some(({ code }) => code === 'EVENT_HELPER_DEPTH_LIMIT' ||
57
+ code === 'EVENT_HELPER_PROJECTION_LIMIT' ||
58
+ code === 'EVENT_HELPER_STATE_PROJECTION_LIMIT')
59
+ ? 'Helper expansion reached a depth or materialization boundary; helper_expansion mode provides bounded source-linked continuation'
60
+ : `${graph.skippedSourceCount} event-analysis source(s) were skipped; full evidence is linked`,
55
61
  },
56
62
  ],
57
63
  };
@@ -655,6 +655,9 @@ function validateReferencesAndScript(graph, scene, diagnostics) {
655
655
  const elementsById = new Map(graph.elements.map((element) => [element.id, element]));
656
656
  for (const button of scene.elements.filter(({ elementType }) => /button/iu.test(elementType))) {
657
657
  const source = elementsById.get(button.sourceId);
658
+ const referencedByScript = effects.has(button.name) || triggers.has(button.name);
659
+ if ((source === undefined || !/button/iu.test(source.elementType)) && !referencedByScript)
660
+ continue;
658
661
  const tooltipOnly = !effects.has(button.name) &&
659
662
  !triggers.has(button.name) &&
660
663
  (typeof source?.attributes.pdx_tooltip === 'string' ||
@@ -13,6 +13,22 @@ const numericEffects = new Map([
13
13
  ['add_war_support', { key: 'war_support', mode: 'add', min: 0, max: 1 }],
14
14
  ]);
15
15
  const harmlessEffects = new Set(['hidden_effect', 'custom_effect_tooltip', 'log', 'tooltip']);
16
+ const variableEffects = new Map([
17
+ ['set_variable', 'set'],
18
+ ['set_temp_variable', 'set'],
19
+ ['add_to_variable', 'add'],
20
+ ['add_to_temp_variable', 'add'],
21
+ ['subtract_from_variable', 'subtract'],
22
+ ['subtract_from_temp_variable', 'subtract'],
23
+ ['multiply_variable', 'multiply'],
24
+ ['multiply_temp_variable', 'multiply'],
25
+ ['divide_variable', 'divide'],
26
+ ['divide_temp_variable', 'divide'],
27
+ ['clamp_variable', 'clamp'],
28
+ ['clamp_temp_variable', 'clamp'],
29
+ ['round_variable', 'round'],
30
+ ['round_temp_variable', 'round'],
31
+ ]);
16
32
  function numeric(value) {
17
33
  if (typeof value === 'number' && Number.isFinite(value))
18
34
  return value;
@@ -175,6 +191,40 @@ export class MechanicInterpreter {
175
191
  return (numeric(scenarioValue(this.state, key, targetScope)) ??
176
192
  numeric(scenarioValue(this.state, `variable.${key}`, targetScope)));
177
193
  }
194
+ scopeReference(expression, scope, stack) {
195
+ const scopeId = (name) => {
196
+ if (name === 'ROOT')
197
+ return this.state.scenario.actor ?? 'ROOT';
198
+ if (name === 'THIS')
199
+ return scope === 'ROOT'
200
+ ? (this.state.scenario.actor ?? 'ROOT')
201
+ : this.state.scenario.scopes?.[scope]?.id;
202
+ if (name === 'PREV') {
203
+ const previous = stack.findLast((item) => item.startsWith('scope:'))?.slice(6);
204
+ return previous === undefined
205
+ ? undefined
206
+ : previous === 'ROOT'
207
+ ? (this.state.scenario.actor ?? 'ROOT')
208
+ : this.state.scenario.scopes?.[previous]?.id;
209
+ }
210
+ return this.state.scenario.scopes?.[name]?.id;
211
+ };
212
+ if (expression.startsWith('event_target:'))
213
+ return this.state.scenario.eventTargets?.[expression.slice(13)];
214
+ const identifier = /^([A-Za-z0-9_:]+)(?:\.id)?$/u.exec(expression)?.[1];
215
+ return identifier === undefined ? undefined : scopeId(identifier.toUpperCase());
216
+ }
217
+ value(expression, path, scope, stack) {
218
+ const numberValue = this.number(expression, path, scope);
219
+ if (numberValue !== undefined)
220
+ return numberValue;
221
+ const dotted = /^(ROOT|THIS|FROM|PREV)\.(.+)$/iu.exec(expression);
222
+ const targetScope = dotted?.[1]?.toUpperCase() === 'THIS' ? scope : (dotted?.[1]?.toUpperCase() ?? scope);
223
+ const key = dotted?.[2] ?? expression;
224
+ const existing = scenarioValue(this.state, key, targetScope) ??
225
+ scenarioValue(this.state, `variable.${key}`, targetScope);
226
+ return existing ?? this.scopeReference(expression, scope, stack);
227
+ }
178
228
  mathExpression(block, path, scope) {
179
229
  const entries = entriesOnly(block);
180
230
  if (entries === undefined || entries[0]?.key.value !== 'value')
@@ -410,42 +460,53 @@ export class MechanicInterpreter {
410
460
  }
411
461
  return;
412
462
  }
413
- if ([
414
- 'set_variable',
415
- 'add_to_variable',
416
- 'subtract_from_variable',
417
- 'multiply_variable',
418
- 'divide_variable',
419
- 'clamp_variable',
420
- ].includes(key)) {
463
+ const variableMode = variableEffects.get(key);
464
+ if (variableMode !== undefined) {
465
+ const variableScope = key.includes('temp_variable') ? 'ROOT' : scope;
466
+ const scalarVariable = asText(entry.value);
467
+ if (variableMode === 'round') {
468
+ const variable = scalarVariable ?? (child === undefined ? undefined : firstScalar(child, 'var')?.value);
469
+ const before = variable === undefined
470
+ ? undefined
471
+ : numeric(scenarioValue(this.state, variable, variableScope));
472
+ const after = before === undefined ? undefined : Math.round(before);
473
+ if (variable === undefined) {
474
+ this.unknown(key, path, scope, 'Variable must resolve');
475
+ return;
476
+ }
477
+ setScenarioValue(this.state, variable, after, variableScope);
478
+ this.note(key, path, scope, after === undefined ? 'unresolved' : 'applied', before, after, after === undefined ? 'Variable value is not numeric' : undefined, variable);
479
+ return;
480
+ }
421
481
  if (child === undefined) {
422
482
  this.unknown(key, path, scope, 'Variable effect requires a block');
423
483
  return;
424
484
  }
425
485
  const short = assignments(child).find((item) => !['var', 'value', 'min', 'max', 'tooltip'].includes(item.key.value));
426
486
  const variable = firstScalar(child, 'var')?.value ?? short?.key.value;
487
+ if (variable === undefined) {
488
+ this.unknown(key, path, scope, 'Variable must resolve');
489
+ return;
490
+ }
427
491
  const expression = firstScalar(child, 'value')?.value ??
428
492
  (short === undefined ? undefined : asText(short.value));
429
- if (variable === undefined || expression === undefined) {
430
- if (variable === undefined || short?.value.type !== 'block') {
431
- this.unknown(key, path, scope, 'Variable and operand must resolve');
432
- return;
433
- }
434
- }
435
- const value = expression === undefined
436
- ? this.mathExpression(short.value, path, scope)
437
- : this.number(expression, path, scope);
438
- const before = numeric(scenarioValue(this.state, variable, scope)) ?? 0;
439
- let after = value;
440
- if (value !== undefined && key === 'add_to_variable')
441
- after = before + value;
442
- if (value !== undefined && key === 'subtract_from_variable')
443
- after = before - value;
444
- if (value !== undefined && key === 'multiply_variable')
445
- after = before * value;
446
- if (value !== undefined && key === 'divide_variable')
447
- after = value === 0 ? undefined : before / value;
448
- if (value !== undefined && key === 'clamp_variable') {
493
+ const operand = expression === undefined
494
+ ? variableMode === 'set' && short?.value.type === 'block'
495
+ ? this.mathExpression(short.value, path, scope)
496
+ : undefined
497
+ : this.value(expression, path, scope, stack);
498
+ const previous = scenarioValue(this.state, variable, variableScope);
499
+ const before = numeric(previous) ?? 0;
500
+ let after = variableMode === 'set' ? operand : numeric(operand);
501
+ if (variableMode === 'add' && typeof after === 'number')
502
+ after = before + after;
503
+ if (variableMode === 'subtract' && typeof after === 'number')
504
+ after = before - after;
505
+ if (variableMode === 'multiply' && typeof after === 'number')
506
+ after = before * after;
507
+ if (variableMode === 'divide' && typeof after === 'number')
508
+ after = after === 0 ? undefined : before / after;
509
+ if (variableMode === 'clamp') {
449
510
  const minimum = firstScalar(child, 'min')?.value;
450
511
  const maximum = firstScalar(child, 'max')?.value;
451
512
  const min = minimum === undefined ? -Infinity : this.number(minimum, path, scope);
@@ -453,8 +514,8 @@ export class MechanicInterpreter {
453
514
  after =
454
515
  min === undefined || max === undefined ? undefined : Math.max(min, Math.min(max, before));
455
516
  }
456
- setScenarioValue(this.state, variable, after, scope);
457
- this.note(key, path, scope, after === undefined ? 'unresolved' : 'applied', before, after, after === undefined ? 'Numeric operand is unknown or division by zero' : undefined, variable);
517
+ setScenarioValue(this.state, variable, after, variableScope);
518
+ this.note(key, path, scope, after === undefined ? 'unresolved' : 'applied', previous, after, after === undefined ? 'Numeric operand is unknown or division by zero' : undefined, variable);
458
519
  return;
459
520
  }
460
521
  if (key === 'set_country_flag' ||
@@ -77,13 +77,28 @@ export function selectMechanicSource(snapshot, selector) {
77
77
  }
78
78
  export function scriptedEffectSources(snapshot) {
79
79
  const result = new Map();
80
+ const symbolsByPath = new Map();
80
81
  for (const symbol of snapshot.index.symbols) {
81
82
  if (symbol.kind !== 'scripted_effect' || symbol.overridden || symbol.sourceShadowed)
82
83
  continue;
83
- const document = activeDocument(snapshot, symbol.path);
84
- const block = document === undefined ? undefined : assignments(document.root, symbol.id)[0]?.value;
85
- if (block?.type === 'block')
86
- result.set(symbol.id, { id: symbol.id, path: symbol.path, block });
84
+ const symbols = symbolsByPath.get(symbol.path) ?? [];
85
+ symbols.push(symbol);
86
+ symbolsByPath.set(symbol.path, symbols);
87
+ }
88
+ for (const [sourcePath, symbols] of symbolsByPath) {
89
+ const document = activeDocument(snapshot, sourcePath);
90
+ if (document === undefined)
91
+ continue;
92
+ const blocks = new Map();
93
+ for (const definition of assignments(document.root)) {
94
+ if (definition.value.type === 'block' && !blocks.has(definition.key.value))
95
+ blocks.set(definition.key.value, definition.value);
96
+ }
97
+ for (const symbol of symbols) {
98
+ const block = blocks.get(symbol.id);
99
+ if (block !== undefined)
100
+ result.set(symbol.id, { id: symbol.id, path: symbol.path, block });
101
+ }
87
102
  }
88
103
  return result;
89
104
  }
@@ -58,6 +58,34 @@ export function technologyValidation(graph) {
58
58
  ],
59
59
  };
60
60
  }
61
+ export function technologyRenderValidation(graph, render) {
62
+ const omitted = render.omittedNodeCount;
63
+ const unresolvedSprites = render.unresolvedIconSprites.length;
64
+ const sourcePlacementPassed = render.view !== 'folder' || render.sourceAccurate;
65
+ const analysisBoundaryPassed = graph.complete || (graph.analysisMode === 'focused' && graph.skippedSourceCount === 0);
66
+ const renderPassed = sourcePlacementPassed && omitted === 0 && unresolvedSprites === 0;
67
+ return {
68
+ passed: renderPassed && analysisBoundaryPassed,
69
+ checks: [
70
+ {
71
+ id: 'technology-render',
72
+ passed: renderPassed,
73
+ message: renderPassed
74
+ ? 'The requested technology view rendered with complete node and sprite coverage'
75
+ : `${sourcePlacementPassed ? 0 : 1} source-placement failure(s), ${omitted} omitted node(s), and ${unresolvedSprites} unresolved sprite(s) affect the requested technology view`,
76
+ },
77
+ {
78
+ id: 'technology-analysis-boundary',
79
+ passed: analysisBoundaryPassed,
80
+ message: graph.complete
81
+ ? 'The supporting technology analysis is complete'
82
+ : analysisBoundaryPassed
83
+ ? 'Whole-workspace helper projections were deferred; the requested render retains direct source evidence'
84
+ : `${graph.skippedSourceCount} technology source(s) were skipped; linked evidence records the partial analysis boundary`,
85
+ },
86
+ ],
87
+ };
88
+ }
61
89
  export async function inspectTechnologies(viewer, input) {
62
90
  const output = await viewer.analyze(input);
63
91
  const result = emptyServiceResult(input.workspaceId, {
@@ -93,7 +121,7 @@ export async function renderTechnologies(viewer, input) {
93
121
  result.code = output.graph.complete ? 'TECH_RENDERED' : 'TECH_RENDERED_PARTIAL';
94
122
  setInlineFilesScanned(result, output.graph.filesScanned);
95
123
  result.artifacts = output.artifacts.map(publicArtifactLink);
96
- result.validation = technologyValidation(output.graph);
124
+ result.validation = technologyRenderValidation(output.graph, output.render);
97
125
  return result;
98
126
  }
99
127
  export async function compareTechnologies(viewer, input) {
@@ -72,9 +72,10 @@ function technologyNode(graph, technologyId, placement) {
72
72
  subTechnologies: technology.subTechnologies.map((id, index) => {
73
73
  const definition = graph.technologies.find((candidate) => candidate.id === id);
74
74
  const slot = itemLayout?.subTechnologySlots?.find((candidate) => candidate.index === index);
75
+ const iconSprite = slot?.sprite ?? definition?.icon.sprite;
75
76
  return {
76
77
  id,
77
- ...(definition === undefined ? {} : { iconSprite: definition.icon.sprite }),
78
+ ...(iconSprite === undefined ? {} : { iconSprite }),
78
79
  placementStatus: slot === undefined ? 'missing_source_slot' : 'source_slot',
79
80
  ...(slot === undefined ? {} : { slot }),
80
81
  };
@@ -487,6 +487,7 @@ export class TechnologyTreeViewer {
487
487
  });
488
488
  const guiSprites = [
489
489
  ...preliminary.itemLayouts.flatMap(({ designTeamIcon }) => designTeamIcon === undefined ? [] : [designTeamIcon.sprite]),
490
+ ...preliminary.itemLayouts.flatMap(({ subTechnologySlots }) => (subTechnologySlots ?? []).flatMap(({ sprite }) => (sprite === undefined ? [] : [sprite]))),
490
491
  ...preliminary.itemLayouts.flatMap(({ backgroundSprite }) => backgroundSprite === undefined
491
492
  ? []
492
493
  : [
@@ -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.4.0';
3
+ export const PACKAGE_VERSION = '3.4.1';
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/events.md CHANGED
@@ -19,7 +19,7 @@ Use the three event tools directly from the target mod.
19
19
 
20
20
  Use narrow identifiers, direction, and depth limits when the task concerns one chain.
21
21
  Broad scans build the structural event graph without expanding every scripted helper into duplicate paths.
22
- Focused trace, path, and selector-based inventory requests may defer workspace-wide helper projections and lifecycle passes; their linked evidence records the partial boundary.
22
+ Focused trace, path, and selector-based inventory requests may defer workspace-wide helper projections and lifecycle passes. When every event source was scanned and no blocking diagnostics remain, validation accepts the requested direct-source evidence while the linked artifacts continue to record that deferred boundary.
23
23
  Focused trace and path calls collapse only the selected bounded route.
24
24
  For a large helper closure, use [bounded helper expansion](helper-expansion.md) to receive resumable pages without first materializing every projection.
25
25
 
package/docs/gui.md CHANGED
@@ -51,6 +51,7 @@ The scene records the selected shader source, hash and entry point.
51
51
  Additional shader operations, including animated end-cap sampling, remain explicit unsupported findings rather than accepted pixel-parity evidence.
52
52
 
53
53
  Container scrollbars use referenced or inline `extendedScrollbarType` templates, including their backgrounds, inline track, thumb and end-button artwork.
54
+ The template's slider and end-button roles are engine-owned compound controls, so scripted click-effect checks apply to authored source buttons and explicitly referenced role names rather than requiring effects for the native scrollbar internals.
54
55
  Each attachment has independent instance IDs and source provenance even when multiple containers reuse one template.
55
56
  The renderer measures direct content bounds and populated grid/list rows, retaining declared list bounds as a minimum, clamps scroll offsets, and applies `autohide_scrollbars`.
56
57
  A nested container contributes its declared bounds rather than the overflowing artwork of its descendants.
package/docs/jobs.md CHANGED
@@ -57,6 +57,7 @@ Event and technology helper expansion also saves an authenticated intermediate f
57
57
  A replacement validates the saved paths, adjacency cursor, graph identity, and checkpoint bytes before continuing.
58
58
  Source changes before intermediate-frontier recovery cause a fresh analysis of the current revision; old and new graph evidence are never combined.
59
59
  An active host allows at most two replacement attempts after proven worker death, and never retries the same unchanged frontier twice.
60
+ If another bounded host has already claimed an interrupted job, the initiating host follows that live replacement until it publishes a terminal outcome; it does not expose the replacement's intermediate `running` record as the synchronous result.
60
61
  Checkpoint recovery never overrides cancellation or live/unverifiable ownership, and never replays a rewrite.
61
62
  Event and technology scan artifacts retain revision-addressed graphs for later comparisons, and bounded responses state their requested depth, node, edge, render, helper-expansion, or candidate coverage.
62
63
  Their opt-in `helper_expansion` mode also returns [bounded continuation pages](helper-expansion.md) through both ordinary calls and persistent jobs.
package/docs/mechanics.md CHANGED
@@ -41,7 +41,8 @@ An `advance_days` step only expires modeled timed flags; it never runs on action
41
41
 
42
42
  The linked trace records applied, skipped, and unresolved source operations with before and after values.
43
43
  The `single_payment` assertion checks the named balance's final change and the number and size of its traced deductions, so a compensating grant cannot hide a second charge.
44
- Supported operations include documented variable arithmetic, flags, event targets, arrays, conditionals, finite declared scope iteration, scripted helpers, safe meta substitutions, and selected balance effects.
44
+ Supported operations include documented persistent and unscoped temporary-variable arithmetic, exact `has_variable` presence checks, flags, event targets, arrays, conditionals, finite declared scope iteration, scripted helpers, safe meta substitutions, and selected balance effects.
45
+ Active scripted-effect documents are parsed once per source file and their helper blocks share that bounded source model.
45
46
  Decision engine political-power costs are applied once before `complete_effect`; custom cost triggers check affordability and their payment remains in `complete_effect`.
46
47
  Unresolved eligibility, unsupported effects, dynamic names that cannot be resolved, missing finite scope catalogs, and bounded-work limits remain unresolved rather than guessed.
47
48
  An effect with an unknown write set conservatively makes state assertions unresolved.
@@ -56,10 +56,14 @@ It preserves separate conditional call sites and identifies source, cycle, and d
56
56
 
57
57
  `hoi4.tech_render` returns authoritative JSON with deterministic SVG and PNG resources; set `includeHtml` when a bundled static report is useful. Folder renders read the actual `techtree_<folder>_item` and `techtree_<folder>_small_item` geometry, choose the small item for technologies that do not unlock equipment or explicitly force it, and place the source GUI's year labels. Available views are `summary`, `folder`, `dependencies`, `technology`, `doctrine`, `exclusive`, `memberships`, `bonuses`, `grants`, `unlocks`, `metadata`, `assets`, and `unresolved`.
58
58
 
59
- Folder renders use folder assignments, gridbox geometry, technology coordinates, and item dimensions found in source. File-local `@` constants in technology coordinates are resolved before placement. A technology with multiple prerequisite roots can use its own declared gridbox. The item template supplies its unavailable, available, or researched skin, icon center, name position, and design-team icon position when the corresponding sprites resolve. `sub_technology_slot_<index>` geometry positions each declared subtechnology icon; a missing slot stays visible as unresolved placement metadata. The folder's tiled panel sprite fills the canvas, and source `*_techtree_bg` sprites render behind technology nodes at their declared GUI positions; when the GUI omits a sprite size, the decoded dimensions supply it. Year labels retain their source font, box dimensions, alignment, orientation, and colour declarations in the graph. Sprite declarations and texture files resolve through the active load order across the mod, configured dependencies, and the detected or configured game installation. The authoritative JSON and SVG expose `layoutSize`, rendered sprite coverage, and unresolved sprite names without embedding image data in JSON. `sourceAccurate` describes source pixel placement only; it is not a percentage of visual agreement with a game screenshot. The game may change the visibility of design-team icons and status corners at runtime. Dependency and other semantic views are labelled as generated analysis layouts.
59
+ Folder renders use folder assignments, gridbox geometry, technology coordinates, and item dimensions found in source. File-local `@` constants in technology coordinates are resolved before placement. A technology with multiple prerequisite roots can use its own declared gridbox. The item template supplies its unavailable, available, or researched skin, icon center, name position, and design-team icon position when the corresponding sprites resolve. `sub_technology_slot_<index>` geometry and its source `picture` sprite position each declared subtechnology icon; the technology-id icon is a fallback when the slot does not declare art, and a missing slot stays visible as unresolved placement metadata. The folder's tiled panel sprite fills the canvas, and source `*_techtree_bg` sprites render behind technology nodes at their declared GUI positions; when the GUI omits a sprite size, the decoded dimensions supply it. Year labels retain their source font, box dimensions, alignment, orientation, and colour declarations in the graph. Sprite declarations and texture files resolve through the active load order across the mod, configured dependencies, and the detected or configured game installation. The authoritative JSON and SVG expose `layoutSize`, rendered sprite coverage, and unresolved sprite names without embedding image data in JSON. `sourceAccurate` describes source pixel placement only; it is not a percentage of visual agreement with a game screenshot. The game may change the visibility of design-team icons and status corners at runtime. Dependency and other semantic views are labelled as generated analysis layouts.
60
60
 
61
61
  A `small` 72-pixel item can still display its source `GFX_*_medium` artwork at full card size; the layout name does not select smaller icon art.
62
62
 
63
+ Render validation is scoped to the requested view: folder source placement, omitted nodes, and unresolved sprites determine whether that render passes. Graph-wide semantic diagnostics remain available through technology inspection and do not falsely invalidate an otherwise complete source-backed folder render. Deferred whole-workspace helper projection remains an explicit analysis boundary and does not by itself invalidate a complete direct-source render.
64
+
65
+ Subtechnology cards use the sprite declared by their source GUI slot before any technology-id fallback. Slot-declared textures are included in the bounded asset scan even when no ordinary technology icon references the same file.
66
+
63
67
  Example folder render:
64
68
 
65
69
  ```json
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.4.0",
4
+ "version": "3.4.1",
5
5
  "description": "Analyze HOI4 events, decisions, impact, tech, AI, and MTTH; build focus trees, GUIs, and maps.",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/configuration.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/configuration.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/continuous-focus-palette.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/continuous-focus-palette.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/custom-weighted-pool.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/custom-weighted-pool.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/decision-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/decision-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/event-feature-manifest.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/event-feature-manifest.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/focus-plan.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/focus-plan.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/focus-planning-sidecar.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/focus-planning-sidecar.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/gui-animation-source.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/gui-animation-source.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/gui-helper.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/gui-helper.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/gui-scenario.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/gui-scenario.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/helper-expansion-request.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/helper-expansion-request.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/helper-expansion-summary.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/helper-expansion-summary.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/impact-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/impact-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/map-operation.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/map-operation.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "oneOf": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/mechanic-test-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/mechanic-test-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/operation-result.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/operation-result.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/package-check-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/package-check-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/package-manifest.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/package-manifest.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-analysis-result.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-analysis-result.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-compare-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-compare-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-evaluate-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-evaluate-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-render-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-render-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-scenario-set.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-scenario-set.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-sequence-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-sequence-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-simulate-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-simulate-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/probability-sweep-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/probability-sweep-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/scenario-suite.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/scenario-suite.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/scenario-test-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/scenario-test-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.0/schemas/shared-scenario.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.4.1/schemas/shared-scenario.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.klimPaskov/hoi4-agent-tools",
4
4
  "title": "HOI4 Agent Tools",
5
5
  "description": "Analyze HOI4 events, decisions, impact, tech, AI, and MTTH; build focus trees, GUIs, and maps.",
6
- "version": "3.4.0",
6
+ "version": "3.4.1",
7
7
  "repository": {
8
8
  "url": "https://github.com/klimPaskov/hoi4-agent-tools",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "hoi4-agent-tools",
16
- "version": "3.4.0",
16
+ "version": "3.4.1",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }