hoi4-agent-tools 4.0.1 → 4.0.4
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 +23 -0
- package/README.md +2 -4
- package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +5 -3
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +5 -1
- package/dist/hoi4_agent_tools/core/index.js +11 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +3 -1
- package/dist/hoi4_agent_tools/core/job-worker-host.js +1 -1
- package/dist/hoi4_agent_tools/core/render-progress.js +7 -0
- package/dist/hoi4_agent_tools/gui/layout.js +15 -8
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +3 -3
- package/dist/hoi4_agent_tools/gui/source-graph.js +58 -26
- package/dist/hoi4_agent_tools/gui/studio.js +97 -31
- package/dist/hoi4_agent_tools/gui/tool-operations.js +8 -2
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +2 -1
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +3 -1
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +12 -0
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +2 -2
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +5 -2
- package/dist/hoi4_agent_tools/technology/job-operations.js +1 -0
- package/dist/hoi4_agent_tools/technology/service.js +18 -37
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/gui.md +8 -0
- package/docs/technology.md +2 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- 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/decision-inspect-input.schema.json +1 -1
- package/schemas/error-log-input.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 +1 -1
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/mod-index-input.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.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/schemas/reference-context-input.schema.json +1 -1
- package/schemas/reference-read-input.schema.json +1 -1
- package/schemas/reference-search-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +1 -1
- package/schemas/scenario-test-input.schema.json +1 -1
- package/schemas/script-validate-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/schemas/source-lookup-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -9,6 +9,7 @@ import { CoreEngine } from '../core/engine.js';
|
|
|
9
9
|
import { ClausewitzEvaluationDefinitions } from '../core/clausewitz-evaluation.js';
|
|
10
10
|
import { assertRenderDimensions, RenderBudget, RENDER_MAX_PIXELS } from '../core/render-budget.js';
|
|
11
11
|
import { escapeFileGlob } from '../core/file-glob.js';
|
|
12
|
+
import { renderStage } from '../core/render-progress.js';
|
|
12
13
|
import { ServiceError } from '../core/result.js';
|
|
13
14
|
import { WorkspaceScanner } from '../core/scanner.js';
|
|
14
15
|
import { applyReplacements, decodeSource, encodeSource, parseClausewitz, parseLocalisation, SOURCE_MAX_BYTES, sourcePartialLimitDiagnostics, } from '../core/source/index.js';
|
|
@@ -22,7 +23,7 @@ import { projectGuiGraphForArtifact } from './inspection-artifact.js';
|
|
|
22
23
|
import { buildGuiScene } from './layout.js';
|
|
23
24
|
import { GUI_VALIDATION_MAX_DIAGNOSTICS } from './limits.js';
|
|
24
25
|
import { compareGuiImages, galleryDimensions, renderGallerySvg, renderGuiScene, } from './renderer.js';
|
|
25
|
-
import { generateGuiPreviewScenarios, resolveScenarioScriptedLocalisation, } from './scenario-generator.js';
|
|
26
|
+
import { generatedCountryTags, generateGuiPreviewScenarios, resolveScenarioScriptedLocalisation, } from './scenario-generator.js';
|
|
26
27
|
import { parseGeneratedScenarioOptions, parsePreviewScenario } from './scenario.js';
|
|
27
28
|
import { buildGuiSourceGraph } from './source-graph.js';
|
|
28
29
|
import { assertGuiSourcePatchesSafe } from './source-patch.js';
|
|
@@ -242,13 +243,17 @@ function referenceVariants(value) {
|
|
|
242
243
|
return candidates;
|
|
243
244
|
}
|
|
244
245
|
const FALLBACK_NAMES_PER_PATTERN = 100;
|
|
245
|
-
function basenameFallbackPatterns(exactPaths, exactFiles) {
|
|
246
|
+
function basenameFallbackPatterns(exactPaths, exactFiles, assetRoots = ['gfx', 'interface', 'fonts']) {
|
|
246
247
|
const resolved = new Set(exactFiles
|
|
247
248
|
.filter(({ shadowedBy }) => shadowedBy === undefined)
|
|
248
249
|
.map(({ relativePath }) => relativePath.replaceAll('\\', '/').toLowerCase()));
|
|
250
|
+
const stem = (value) => value.toLowerCase().replace(/\.(?:png|bmp|tga|dds|svg|fnt|ttf|otf|woff2?)$/u, '');
|
|
251
|
+
const resolvedStems = new Set([...resolved].map(stem));
|
|
249
252
|
const names = [
|
|
250
253
|
...new Set(exactPaths
|
|
251
|
-
.filter((candidate) => !/[*?[\]{}!]/u.test(candidate) &&
|
|
254
|
+
.filter((candidate) => !/[*?[\]{}!]/u.test(candidate) &&
|
|
255
|
+
!resolved.has(candidate.toLowerCase()) &&
|
|
256
|
+
!resolvedStems.has(stem(candidate)))
|
|
252
257
|
.map((candidate) => path.posix.basename(candidate))),
|
|
253
258
|
].sort((left, right) => compareCodeUnits(left, right));
|
|
254
259
|
// One brace group searches many names in a single bounded wildcard pattern, so a window
|
|
@@ -261,7 +266,9 @@ function basenameFallbackPatterns(exactPaths, exactFiles) {
|
|
|
261
266
|
const chunk = grouped.slice(start, start + FALLBACK_NAMES_PER_PATTERN).map(escapeFileGlob);
|
|
262
267
|
patterns.push(chunk.length === 1 ? `**/${chunk[0]}` : `**/{${chunk.join(',')}}`);
|
|
263
268
|
}
|
|
264
|
-
return
|
|
269
|
+
return [...new Set(assetRoots)]
|
|
270
|
+
.flatMap((root) => patterns.map((pattern) => `${escapeFileGlob(normalizeConfiguredRoot(root))}/${pattern}`))
|
|
271
|
+
.sort(compareCodeUnits);
|
|
265
272
|
}
|
|
266
273
|
function selectedElements(graph, windowName, additionalTemplateNames = []) {
|
|
267
274
|
const byId = new Map(graph.elements.map((element) => [element.id, element]));
|
|
@@ -549,10 +556,14 @@ export function scenarioCountryFlagPatterns(scenarios) {
|
|
|
549
556
|
for (const values of [
|
|
550
557
|
scenario.values,
|
|
551
558
|
scenario.scriptedGui,
|
|
559
|
+
scenario.country ?? {},
|
|
552
560
|
...Object.values(scenario.lists).flat(),
|
|
553
561
|
]) {
|
|
554
562
|
for (const [key, value] of Object.entries(values)) {
|
|
555
|
-
if (key.endsWith('.countryTag')
|
|
563
|
+
if ((key.endsWith('.countryTag') ||
|
|
564
|
+
key.endsWith('.tag') ||
|
|
565
|
+
/(?:^|\.)GetFlag$/u.test(key) ||
|
|
566
|
+
['countryTag', 'GetTag', 'tag'].includes(key)) &&
|
|
556
567
|
typeof value === 'string' &&
|
|
557
568
|
/^[A-Za-z0-9_]{2,64}$/u.test(value))
|
|
558
569
|
tags.add(value.toUpperCase());
|
|
@@ -573,8 +584,14 @@ export function scenarioCountryFlagPatterns(scenarios) {
|
|
|
573
584
|
function scenarioCountryHistoryPatterns(scenarios) {
|
|
574
585
|
return scenarios.some(scenarioNeedsCountryHistory) ? ['history/countries/*.txt'] : [];
|
|
575
586
|
}
|
|
576
|
-
function scenarioCountryAssetPatterns(scenarios) {
|
|
577
|
-
|
|
587
|
+
function scenarioCountryAssetPatterns(scenarios, generated = false) {
|
|
588
|
+
const flags = generated
|
|
589
|
+
? [
|
|
590
|
+
...scenarios,
|
|
591
|
+
...generatedCountryTags.map((tag) => parsePreviewScenario({ id: `generated-${tag}`, country: { tag } })),
|
|
592
|
+
]
|
|
593
|
+
: scenarios;
|
|
594
|
+
return [...scenarioCountryFlagPatterns(flags), ...scenarioCountryHistoryPatterns(scenarios)];
|
|
578
595
|
}
|
|
579
596
|
function bmFontPagePatterns(files) {
|
|
580
597
|
const references = files.flatMap((file) => {
|
|
@@ -972,13 +989,9 @@ export class ScriptedGuiStudio {
|
|
|
972
989
|
return cached;
|
|
973
990
|
}
|
|
974
991
|
const retainedFiles = [...files];
|
|
975
|
-
// The previous graph of this scope still indexes every unchanged file.
|
|
976
|
-
const previous = [...this.#graphCache].find(([existing]) => existing.startsWith(`${scope}:`))?.[1].index;
|
|
977
|
-
const index = this.engine.indexFiles(retainedFiles, previous);
|
|
978
992
|
const scanned = {
|
|
979
993
|
files: retainedFiles,
|
|
980
|
-
graph: buildGuiSourceGraph(retainedFiles,
|
|
981
|
-
index,
|
|
994
|
+
graph: buildGuiSourceGraph(retainedFiles, undefined, additionalLocalisationKeys),
|
|
982
995
|
};
|
|
983
996
|
// One graph per scope: a preview builds layout, game-layout and definition graphs, and a
|
|
984
997
|
// single shared slot made each repeat call rebuild all of them.
|
|
@@ -1003,24 +1016,33 @@ export class ScriptedGuiStudio {
|
|
|
1003
1016
|
signal?.throwIfAborted();
|
|
1004
1017
|
const workspace = this.resolver.get(workspaceId, principal);
|
|
1005
1018
|
const [snapshot, localisationFiles] = await Promise.all([
|
|
1006
|
-
this.
|
|
1019
|
+
this.scanner.scan(workspace, {
|
|
1020
|
+
patterns: guiDefinitionPatterns(workspace),
|
|
1021
|
+
...(signal === undefined ? {} : { signal }),
|
|
1022
|
+
}),
|
|
1007
1023
|
this.scanner.scan(workspace, {
|
|
1008
1024
|
patterns: guiLocalisationPatterns(workspace, languages),
|
|
1009
1025
|
...(signal === undefined ? {} : { signal }),
|
|
1010
1026
|
}),
|
|
1011
1027
|
]);
|
|
1012
1028
|
signal?.throwIfAborted();
|
|
1013
|
-
const files = mergeScannedFiles(snapshot
|
|
1029
|
+
const files = mergeScannedFiles(snapshot, localisationFiles);
|
|
1014
1030
|
return this.graphForFiles(files, `workspace:${workspaceId}`);
|
|
1015
1031
|
}
|
|
1016
|
-
async scanWindow(workspaceId, windowName, principal, signal, languages = ['l_english'], additionalSpriteNames = [], additionalTemplateNames = [], additionalAssetPatterns = [], additionalLocalisationKeys = [], includeImmediateTooltip = false) {
|
|
1032
|
+
async scanWindow(workspaceId, windowName, principal, signal, languages = ['l_english'], additionalSpriteNames = [], additionalTemplateNames = [], additionalAssetPatterns = [], additionalLocalisationKeys = [], includeImmediateTooltip = false, progress) {
|
|
1033
|
+
const stage = (phase) => renderStage({
|
|
1034
|
+
...(signal === undefined ? {} : { signal }),
|
|
1035
|
+
...(progress === undefined ? {} : { progress }),
|
|
1036
|
+
}, phase);
|
|
1037
|
+
await stage('Scanning GUI layouts');
|
|
1017
1038
|
const workspace = this.resolver.get(workspaceId, principal);
|
|
1018
1039
|
const layoutPatterns = guiLayoutPatterns(workspace);
|
|
1019
|
-
const overlayLayouts = await this.
|
|
1040
|
+
const overlayLayouts = await this.scanner.scan(workspace, {
|
|
1020
1041
|
patterns: layoutPatterns,
|
|
1021
1042
|
rootKinds: ['mod', 'dependency', 'fixture'],
|
|
1022
|
-
|
|
1023
|
-
|
|
1043
|
+
...(signal === undefined ? {} : { signal }),
|
|
1044
|
+
});
|
|
1045
|
+
let layoutFiles = overlayLayouts;
|
|
1024
1046
|
let layoutGraph = this.graphForFiles(layoutFiles, `layout:${workspaceId}`).graph;
|
|
1025
1047
|
const selectedLayoutElements = selectedElements(layoutGraph, windowName, additionalTemplateNames);
|
|
1026
1048
|
const requiredNames = new Set([windowName, ...additionalTemplateNames]);
|
|
@@ -1028,25 +1050,38 @@ export class ScriptedGuiStudio {
|
|
|
1028
1050
|
collectNamedAttributes(element.attributes, new Set(['verticalScrollbar', 'horizontalScrollbar', 'scrollbarType', 'scrollbartype']), requiredNames);
|
|
1029
1051
|
const availableNames = new Set(layoutGraph.elements.map(({ name }) => name));
|
|
1030
1052
|
if ([...requiredNames].some((name) => !availableNames.has(name))) {
|
|
1031
|
-
const gameLayouts = await this.
|
|
1032
|
-
|
|
1053
|
+
const gameLayouts = await this.scanner.scan(workspace, {
|
|
1054
|
+
patterns: layoutPatterns,
|
|
1055
|
+
rootKinds: ['game', 'dlc'],
|
|
1056
|
+
...(signal === undefined ? {} : { signal }),
|
|
1057
|
+
});
|
|
1058
|
+
const gameGraph = this.graphForFiles(gameLayouts, `game-layout:${workspaceId}`).graph;
|
|
1033
1059
|
const selectedPaths = new Set(selectedElements(gameGraph, windowName, [...requiredNames]).map(({ sourcePath }) => sourcePath));
|
|
1034
|
-
layoutFiles = mergeScannedFiles(layoutFiles, gameLayouts.
|
|
1060
|
+
layoutFiles = mergeScannedFiles(layoutFiles, gameLayouts.filter(({ displayPath }) => selectedPaths.has(displayPath)));
|
|
1035
1061
|
layoutGraph = this.graphForFiles(layoutFiles, `merged-layout:${workspaceId}`).graph;
|
|
1036
1062
|
}
|
|
1037
1063
|
if (!layoutGraph.elements.some(({ name }) => name === windowName))
|
|
1038
1064
|
assertTargetWindowAvailable(layoutGraph, windowName);
|
|
1065
|
+
await stage('Scanning GUI definitions and localisation');
|
|
1039
1066
|
const [linkedDefinitions, localisation] = await Promise.all([
|
|
1040
|
-
this.
|
|
1041
|
-
|
|
1067
|
+
this.scanner.scan(workspace, {
|
|
1068
|
+
patterns: guiLinkedDefinitionPatterns(workspace),
|
|
1069
|
+
...(signal === undefined ? {} : { signal }),
|
|
1070
|
+
}),
|
|
1071
|
+
this.scanner.scan(workspace, {
|
|
1072
|
+
patterns: guiLocalisationPatterns(workspace, languages),
|
|
1073
|
+
...(signal === undefined ? {} : { signal }),
|
|
1074
|
+
}),
|
|
1042
1075
|
]);
|
|
1043
|
-
const definitionFiles = mergeScannedFiles(layoutFiles, linkedDefinitions
|
|
1076
|
+
const definitionFiles = mergeScannedFiles(layoutFiles, linkedDefinitions, localisation);
|
|
1077
|
+
await stage('Building GUI source graph');
|
|
1044
1078
|
const definitions = this.graphForFiles(definitionFiles, `definitions:${workspaceId}`, additionalLocalisationKeys);
|
|
1045
1079
|
assertTargetWindowAvailable(definitions.graph, windowName);
|
|
1046
1080
|
const referencedPatterns = [
|
|
1047
|
-
...referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames, languages, additionalTemplateNames, includeImmediateTooltip),
|
|
1081
|
+
...referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames, languages, additionalTemplateNames, includeImmediateTooltip).filter((pattern) => !pattern.startsWith('gfx/flags/')),
|
|
1048
1082
|
...additionalAssetPatterns,
|
|
1049
1083
|
];
|
|
1084
|
+
await stage('Loading selected GUI assets');
|
|
1050
1085
|
const exactReferenced = referencedPatterns.length === 0
|
|
1051
1086
|
? []
|
|
1052
1087
|
: await this.scanner.scan(workspace, {
|
|
@@ -1054,7 +1089,11 @@ export class ScriptedGuiStudio {
|
|
|
1054
1089
|
...(signal === undefined ? {} : { signal }),
|
|
1055
1090
|
});
|
|
1056
1091
|
signal?.throwIfAborted();
|
|
1057
|
-
const fallbackPatterns = basenameFallbackPatterns(referencedPatterns, exactReferenced
|
|
1092
|
+
const fallbackPatterns = basenameFallbackPatterns(referencedPatterns, exactReferenced, [
|
|
1093
|
+
...workspace.registration.roots.gfx,
|
|
1094
|
+
...workspace.registration.roots.interface,
|
|
1095
|
+
'fonts',
|
|
1096
|
+
]);
|
|
1058
1097
|
const fallbackReferenced = fallbackPatterns.length === 0
|
|
1059
1098
|
? []
|
|
1060
1099
|
: await this.scanner.scan(workspace, {
|
|
@@ -1070,7 +1109,11 @@ export class ScriptedGuiStudio {
|
|
|
1070
1109
|
...(signal === undefined ? {} : { signal }),
|
|
1071
1110
|
});
|
|
1072
1111
|
signal?.throwIfAborted();
|
|
1073
|
-
const fontPageFallbackPatterns = basenameFallbackPatterns(fontPagePatterns, exactFontPages
|
|
1112
|
+
const fontPageFallbackPatterns = basenameFallbackPatterns(fontPagePatterns, exactFontPages, [
|
|
1113
|
+
...workspace.registration.roots.gfx,
|
|
1114
|
+
...workspace.registration.roots.interface,
|
|
1115
|
+
'fonts',
|
|
1116
|
+
]);
|
|
1074
1117
|
const fallbackFontPages = fontPageFallbackPatterns.length === 0
|
|
1075
1118
|
? []
|
|
1076
1119
|
: await this.scanner.scan(workspace, {
|
|
@@ -1079,7 +1122,26 @@ export class ScriptedGuiStudio {
|
|
|
1079
1122
|
});
|
|
1080
1123
|
const fontPages = mergeScannedFiles(exactFontPages, fallbackFontPages);
|
|
1081
1124
|
signal?.throwIfAborted();
|
|
1082
|
-
const
|
|
1125
|
+
const windowElements = selectedElements(definitions.graph, windowName, additionalTemplateNames);
|
|
1126
|
+
const selectedPaths = new Set(windowElements.map(({ sourcePath }) => sourcePath));
|
|
1127
|
+
const namedSprites = new Set(additionalSpriteNames);
|
|
1128
|
+
const namedFonts = new Set();
|
|
1129
|
+
for (const element of windowElements) {
|
|
1130
|
+
collectNamedAttributes(element.attributes, new Set(['spriteType', 'quadTextureSprite']), namedSprites);
|
|
1131
|
+
collectNamedAttributes(element.attributes, new Set(['font', 'buttonFont']), namedFonts);
|
|
1132
|
+
}
|
|
1133
|
+
const assetPaths = new Set(referencedPatterns.map((value) => value.toLowerCase()));
|
|
1134
|
+
for (const sprite of definitions.graph.sprites)
|
|
1135
|
+
if (namedSprites.has(sprite.name) ||
|
|
1136
|
+
[sprite.texturePath, sprite.texturePath2, sprite.effectFile].some((value) => value !== undefined &&
|
|
1137
|
+
referenceVariants(value).some((variant) => assetPaths.has(variant.toLowerCase()))))
|
|
1138
|
+
selectedPaths.add(sprite.sourcePath);
|
|
1139
|
+
for (const font of definitions.graph.fonts)
|
|
1140
|
+
if (namedFonts.has(font.name) ||
|
|
1141
|
+
font.assetPaths.some((value) => referenceVariants(value).some((variant) => assetPaths.has(variant.toLowerCase()))))
|
|
1142
|
+
selectedPaths.add(font.sourcePath);
|
|
1143
|
+
const files = mergeScannedFiles(definitions.files.filter((file) => !/\.(?:gui|gfx)$/iu.test(file.relativePath) || selectedPaths.has(file.displayPath)), referenced, fontPages);
|
|
1144
|
+
await stage('Resolving selected GUI assets');
|
|
1083
1145
|
return this.graphForFiles(files, `window:${workspaceId}:${windowName}`, additionalLocalisationKeys);
|
|
1084
1146
|
}
|
|
1085
1147
|
async lint(input) {
|
|
@@ -1089,7 +1151,7 @@ export class ScriptedGuiStudio {
|
|
|
1089
1151
|
const generatedOptions = input.generatedScenarios === undefined
|
|
1090
1152
|
? undefined
|
|
1091
1153
|
: parseGeneratedScenarioOptions(input.generatedScenarios);
|
|
1092
|
-
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [placeholderScenario.language, ...explicitRelatedScenarios.map(({ language }) => language)], scenarioSpriteNames([placeholderScenario, ...explicitRelatedScenarios], generatedOptions?.textSamples), scenarioTemplateNames([placeholderScenario, ...explicitRelatedScenarios]), scenarioCountryAssetPatterns([placeholderScenario, ...explicitRelatedScenarios]), scenarioLocalisationKeys([placeholderScenario, ...explicitRelatedScenarios]), [placeholderScenario, ...explicitRelatedScenarios].some(({ tooltip }) => tooltip !== undefined));
|
|
1154
|
+
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [placeholderScenario.language, ...explicitRelatedScenarios.map(({ language }) => language)], scenarioSpriteNames([placeholderScenario, ...explicitRelatedScenarios], generatedOptions?.textSamples), scenarioTemplateNames([placeholderScenario, ...explicitRelatedScenarios]), scenarioCountryAssetPatterns([placeholderScenario, ...explicitRelatedScenarios], generatedOptions?.enabled === true), scenarioLocalisationKeys([placeholderScenario, ...explicitRelatedScenarios]), [placeholderScenario, ...explicitRelatedScenarios].some(({ tooltip }) => tooltip !== undefined), input.progress);
|
|
1093
1155
|
let builtDefinitions;
|
|
1094
1156
|
const evaluationDefinitions = () => (builtDefinitions ??= ClausewitzEvaluationDefinitions.build(scanned));
|
|
1095
1157
|
const resolveLocalisation = (target) => resolveScenarioScriptedLocalisation(scanned.graph, input.windowName, target, evaluationDefinitions);
|
|
@@ -1108,6 +1170,7 @@ export class ScriptedGuiStudio {
|
|
|
1108
1170
|
assertUniqueScenarioIds([scenario, ...relatedScenarios]);
|
|
1109
1171
|
const budget = new RenderBudget();
|
|
1110
1172
|
const catalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, scenario.language);
|
|
1173
|
+
await renderStage(input, 'Laying out GUI and decoding visible assets');
|
|
1111
1174
|
const scene = await buildGuiScene(scanned.graph, scanned.files, input.windowName, scenario, catalog);
|
|
1112
1175
|
const relatedScenes = [];
|
|
1113
1176
|
for (const related of relatedScenarios) {
|
|
@@ -1179,7 +1242,7 @@ export class ScriptedGuiStudio {
|
|
|
1179
1242
|
placeholderScenario,
|
|
1180
1243
|
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
1181
1244
|
...explicitRelatedScenarios,
|
|
1182
|
-
]), scenarioLocalisationKeys([
|
|
1245
|
+
], generatedOptions?.enabled === true), scenarioLocalisationKeys([
|
|
1183
1246
|
placeholderScenario,
|
|
1184
1247
|
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
1185
1248
|
...explicitRelatedScenarios,
|
|
@@ -1187,7 +1250,7 @@ export class ScriptedGuiStudio {
|
|
|
1187
1250
|
placeholderScenario,
|
|
1188
1251
|
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
1189
1252
|
...explicitRelatedScenarios,
|
|
1190
|
-
].some(({ tooltip }) => tooltip !== undefined));
|
|
1253
|
+
].some(({ tooltip }) => tooltip !== undefined), input.progress);
|
|
1191
1254
|
let builtDefinitions;
|
|
1192
1255
|
const evaluationDefinitions = () => (builtDefinitions ??= ClausewitzEvaluationDefinitions.build(scanned));
|
|
1193
1256
|
const resolveLocalisation = (target) => resolveScenarioScriptedLocalisation(scanned.graph, input.windowName, target, evaluationDefinitions);
|
|
@@ -1207,7 +1270,9 @@ export class ScriptedGuiStudio {
|
|
|
1207
1270
|
const baselineScenario = requestedBaselineScenario ??
|
|
1208
1271
|
parsePreviewScenario({ ...scenario, id: `${scenario.id}-comparison`, state: 'normal' });
|
|
1209
1272
|
const catalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, scenario.language);
|
|
1273
|
+
await renderStage(input, 'Laying out GUI and decoding visible assets');
|
|
1210
1274
|
const scene = await buildGuiScene(scanned.graph, scanned.files, input.windowName, scenario, catalog);
|
|
1275
|
+
await renderStage(input, 'Rasterizing GUI views');
|
|
1211
1276
|
const render = await renderGuiScene(scene, undefined, input.signal, budget);
|
|
1212
1277
|
const fullRenderCache = new Map([
|
|
1213
1278
|
[visualSceneKey(scene), fullImage(render)],
|
|
@@ -1319,6 +1384,7 @@ export class ScriptedGuiStudio {
|
|
|
1319
1384
|
validation.diagnostics.push(...stateValidation.diagnostics, ...resolutionValidation.diagnostics);
|
|
1320
1385
|
validation.checks.push(...stateValidation.checks, ...resolutionValidation.checks);
|
|
1321
1386
|
const completeValidation = withGraphDiagnostics(validationGraph, validation);
|
|
1387
|
+
await renderStage(input, 'Writing GUI galleries and evidence');
|
|
1322
1388
|
const scenarioGallerySvg = renderGallerySvg(`${input.windowName} scripted scenario matrix`, scenarioItems);
|
|
1323
1389
|
const stateGallerySvg = renderGallerySvg(`${input.windowName} state matrix`, stateItems);
|
|
1324
1390
|
const resolutionGallerySvg = renderGallerySvg(`${input.windowName} resolution and UI-scale matrix`, resolutionItems);
|
|
@@ -120,11 +120,14 @@ export async function inspectGui(engine, studio, input, context) {
|
|
|
120
120
|
const linted = await studio.lint({
|
|
121
121
|
workspaceId: context.workspaceId,
|
|
122
122
|
windowName: input.windowName,
|
|
123
|
+
progress: (phase) => context.progress(1, 3, phase),
|
|
123
124
|
scenario: input.scenario,
|
|
124
125
|
...(input.relatedScenarios === undefined
|
|
125
126
|
? {}
|
|
126
127
|
: { relatedScenarios: input.relatedScenarios }),
|
|
127
|
-
|
|
128
|
+
...(input.generatedScenarios === undefined
|
|
129
|
+
? {}
|
|
130
|
+
: { generatedScenarios: input.generatedScenarios }),
|
|
128
131
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
129
132
|
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
130
133
|
});
|
|
@@ -217,6 +220,7 @@ export async function inspectGui(engine, studio, input, context) {
|
|
|
217
220
|
export async function renderGui(studio, input, context) {
|
|
218
221
|
await context.progress(0, 4, 'Building GUI source graph');
|
|
219
222
|
const rendered = await studio.renderAndStore({
|
|
223
|
+
progress: (phase) => context.progress(1, 4, phase),
|
|
220
224
|
workspaceId: context.workspaceId,
|
|
221
225
|
windowName: input.windowName,
|
|
222
226
|
scenario: input.scenario,
|
|
@@ -231,7 +235,9 @@ export async function renderGui(studio, input, context) {
|
|
|
231
235
|
})),
|
|
232
236
|
}),
|
|
233
237
|
...(input.relatedScenarios === undefined ? {} : { relatedScenarios: input.relatedScenarios }),
|
|
234
|
-
|
|
238
|
+
...(input.generatedScenarios === undefined
|
|
239
|
+
? {}
|
|
240
|
+
: { generatedScenarios: input.generatedScenarios }),
|
|
235
241
|
...(input.comparisonScenario === undefined
|
|
236
242
|
? {}
|
|
237
243
|
: { comparisonScenario: input.comparisonScenario }),
|
|
@@ -17,7 +17,7 @@ import { referenceTools } from '../tools/reference.js';
|
|
|
17
17
|
import { isReferenceTool, ReferenceToolService } from '../../reference/operations.js';
|
|
18
18
|
import { ModernTaskAdapter, TASKS_EXTENSION } from './modern-tasks.js';
|
|
19
19
|
import { SERVER_INSTRUCTIONS } from './instructions.js';
|
|
20
|
-
import { modernProgressReporter, withProgressHeartbeat } from './progress.js';
|
|
20
|
+
import { jobProgressReporter, modernProgressReporter, withProgressHeartbeat } from './progress.js';
|
|
21
21
|
import { taskToolCatalog } from './task-tool-catalog.js';
|
|
22
22
|
const rootsResponseSchema = z
|
|
23
23
|
.object({
|
|
@@ -187,6 +187,7 @@ export function createModernOperationServer(engine, context = {}, tasks = new Op
|
|
|
187
187
|
const progress = modernProgressReporter(extra);
|
|
188
188
|
const result = await withProgressHeartbeat(async () => {
|
|
189
189
|
const completed = await adapter.call({ name: definition.name, arguments: arguments_ }, capabilities, operationContext, {
|
|
190
|
+
progress: jobProgressReporter(progress),
|
|
190
191
|
requestId: extra.mcpReq.id,
|
|
191
192
|
signal: extra.mcpReq.signal,
|
|
192
193
|
...(extra.sessionId === undefined ? {} : { sessionId: extra.sessionId }),
|
|
@@ -2,7 +2,7 @@ import { CallToolResultSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { jobTaskTiming } from '../../core/job-store.js';
|
|
3
3
|
import { OperationTaskService, operationTaskCallSchema, operationTaskId, operationTaskMessage, operationTaskStatus, } from '../../core/operation-tasks.js';
|
|
4
4
|
import { ServiceError } from '../../core/result.js';
|
|
5
|
-
import { currentTaskRequestSignal } from './task-request-context.js';
|
|
5
|
+
import { currentTaskRequestSignal, currentTaskRequestProgress } from './task-request-context.js';
|
|
6
6
|
function legacyTask(record) {
|
|
7
7
|
if (record.request.protocolTask === undefined)
|
|
8
8
|
throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
|
|
@@ -44,6 +44,8 @@ export class PersistentJobTaskStore {
|
|
|
44
44
|
}
|
|
45
45
|
async getTask(value, _sessionId) {
|
|
46
46
|
const record = await this.tasks.get(value, this.context());
|
|
47
|
+
if (record?.progress !== undefined)
|
|
48
|
+
await currentTaskRequestProgress()?.(record.progress.completed, record.progress.total ?? 1, record.progress.message);
|
|
47
49
|
return record === null ? null : legacyTask(record);
|
|
48
50
|
}
|
|
49
51
|
async storeTaskResult(value, status, result, _sessionId) {
|
|
@@ -112,6 +112,8 @@ export class ModernTaskAdapter {
|
|
|
112
112
|
options.signal?.throwIfAborted();
|
|
113
113
|
await delay(100, undefined, { signal: options.signal });
|
|
114
114
|
latest = await this.known(taskId, context);
|
|
115
|
+
if (latest.progress !== undefined)
|
|
116
|
+
await options.progress?.(latest.progress.completed, latest.progress.total ?? 1, latest.progress.message);
|
|
115
117
|
}
|
|
116
118
|
return modernToolResult(latest, await this.tasks.result(operationTaskId(record), context));
|
|
117
119
|
}
|
|
@@ -101,3 +101,15 @@ export async function withProgressHeartbeat(operation, reporter, message, interv
|
|
|
101
101
|
heartbeatOwners.delete(reporter);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/** Reserve the outer request's final two steps for retrieval/completion while forwarding worker phases. */
|
|
105
|
+
export function jobProgressReporter(reporter) {
|
|
106
|
+
let previous;
|
|
107
|
+
return async (completed, total, message) => {
|
|
108
|
+
const identity = JSON.stringify([completed, total, message]);
|
|
109
|
+
if (identity === previous)
|
|
110
|
+
return;
|
|
111
|
+
previous = identity;
|
|
112
|
+
await reporter.report(Math.min(1, completed / Math.max(1, total)), 3, message);
|
|
113
|
+
await reporter.pulse(message);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -5,7 +5,7 @@ import { referenceTools } from '../tools/reference.js';
|
|
|
5
5
|
import { jobControlTools } from '../tools/job.js';
|
|
6
6
|
import { invalidArgumentsResult } from './argument-errors.js';
|
|
7
7
|
import { taskToolCatalog } from './task-tool-catalog.js';
|
|
8
|
-
import { progressReporter, withProgressHeartbeat } from './progress.js';
|
|
8
|
+
import { jobProgressReporter, progressReporter, withProgressHeartbeat } from './progress.js';
|
|
9
9
|
import { withTaskRequestSignal } from './task-request-context.js';
|
|
10
10
|
import { slimToolList } from './tool-listing.js';
|
|
11
11
|
// Durable task compatibility and cancellation are control traffic. They must
|
|
@@ -42,7 +42,7 @@ export function installRequestLifecycle(server, engine) {
|
|
|
42
42
|
return handler(request, extra);
|
|
43
43
|
if (taskCall.success && backgroundTools.has(taskCall.data.params.name)) {
|
|
44
44
|
return withProgressHeartbeat(async () => {
|
|
45
|
-
const result = await withTaskRequestSignal(extra.signal, () => handler(request, extra));
|
|
45
|
+
const result = await withTaskRequestSignal(extra.signal, () => handler(request, extra), jobProgressReporter(progress));
|
|
46
46
|
if (taskCall.data.params.task === undefined) {
|
|
47
47
|
await progress.report(2, 3, 'Retrieving persistent operation result');
|
|
48
48
|
await progress.report(3, 3, 'Persistent operation complete');
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
const taskRequestContext = new AsyncLocalStorage();
|
|
3
3
|
/** Bind transport cancellation to foreground-compatible task creation only. */
|
|
4
|
-
export function withTaskRequestSignal(signal, action) {
|
|
5
|
-
return taskRequestContext.run({ signal }, action);
|
|
4
|
+
export function withTaskRequestSignal(signal, action, progress) {
|
|
5
|
+
return taskRequestContext.run({ signal, ...(progress === undefined ? {} : { progress }) }, action);
|
|
6
6
|
}
|
|
7
7
|
export function currentTaskRequestSignal() {
|
|
8
8
|
return taskRequestContext.getStore()?.signal;
|
|
9
9
|
}
|
|
10
|
+
export function currentTaskRequestProgress() {
|
|
11
|
+
return taskRequestContext.getStore()?.progress;
|
|
12
|
+
}
|
|
@@ -22,6 +22,7 @@ export function registerTechnologyJobs(operations, engine) {
|
|
|
22
22
|
workspaceId: context.workspaceId,
|
|
23
23
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
24
24
|
signal: context.signal,
|
|
25
|
+
progress: (phase) => context.progress({ completed: 1, total: 3, message: phase }),
|
|
25
26
|
});
|
|
26
27
|
await context.progress({
|
|
27
28
|
completed: 3,
|
|
@@ -4,6 +4,7 @@ import { technologyHelperInventory } from './helper-expansion.js';
|
|
|
4
4
|
import { clearSemanticDependencies } from '../core/semantic-dependencies.js';
|
|
5
5
|
import { canonicalJson, compareCodeUnits, hashCanonical, sha256Bytes } from '../core/canonical.js';
|
|
6
6
|
import { boundedSourceHashEvidence, } from '../core/artifacts.js';
|
|
7
|
+
import { renderStage } from '../core/render-progress.js';
|
|
7
8
|
import { RenderBudget } from '../core/render-budget.js';
|
|
8
9
|
import { ServiceError } from '../core/result.js';
|
|
9
10
|
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
@@ -96,43 +97,17 @@ function technologyScanPatterns(workspace) {
|
|
|
96
97
|
...under(roots.localisation, '*.{yml,yaml}'),
|
|
97
98
|
].sort(compareCodeUnits);
|
|
98
99
|
}
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const key = file.relativePath.replaceAll('\\', '/').toLowerCase();
|
|
104
|
-
const group = groups.get(key) ?? [];
|
|
105
|
-
group.push(file);
|
|
106
|
-
groups.set(key, group);
|
|
107
|
-
}
|
|
108
|
-
for (const group of groups.values()) {
|
|
109
|
-
group.sort((left, right) => right.loadOrder - left.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
110
|
-
const active = group[0];
|
|
111
|
-
for (const shadowed of group.slice(1))
|
|
112
|
-
shadowed.shadowedBy = active.displayPath;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function scanTechnologySources(engine, workspaceId, workspace, principal, signal) {
|
|
116
|
-
const patterns = technologyScanPatterns(workspace);
|
|
117
|
-
const scans = await Promise.all([
|
|
118
|
-
engine.scan(workspaceId, { patterns, rootKinds: ['mod', 'dependency', 'fixture'] }, principal, signal),
|
|
119
|
-
...(workspace.roots.some(({ kind }) => kind === 'game')
|
|
120
|
-
? [engine.scan(workspaceId, { patterns, rootKinds: ['game', 'dlc'] }, principal, signal)]
|
|
121
|
-
: []),
|
|
122
|
-
]);
|
|
123
|
-
const files = scans.flatMap(({ files }) => files.map((file) => ({ ...file })));
|
|
124
|
-
recomputeTechnologyShadowing(files);
|
|
125
|
-
files.sort((left, right) => left.loadOrder - right.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
126
|
-
const index = engine.indexFiles(files);
|
|
100
|
+
async function scanTechnologySources(engine, workspaceId, workspace, principal, signal, renderOnly = false) {
|
|
101
|
+
const patterns = technologyScanPatterns(workspace).filter((pattern) => !renderOnly ||
|
|
102
|
+
!/^(?:events|history|common\/(?:national_focus|decisions|scripted_effects|on_actions|ideas|characters))\//u.test(pattern));
|
|
103
|
+
const snapshot = await engine.scan(workspaceId, { patterns }, principal, signal);
|
|
127
104
|
return {
|
|
128
|
-
|
|
129
|
-
revision: hashCanonical(files.map(({ displayPath, loadOrder, sha256 }) => ({
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
skippedSources: index.skippedSources,
|
|
135
|
-
diagnostics: [...scans.flatMap(({ diagnostics }) => diagnostics), ...index.diagnostics],
|
|
105
|
+
...snapshot,
|
|
106
|
+
revision: hashCanonical(snapshot.files.map(({ displayPath, loadOrder, sha256 }) => ({
|
|
107
|
+
displayPath,
|
|
108
|
+
loadOrder,
|
|
109
|
+
sha256,
|
|
110
|
+
}))),
|
|
136
111
|
};
|
|
137
112
|
}
|
|
138
113
|
function activeTechnologySprite(spriteName, spritePath, sprites) {
|
|
@@ -472,7 +447,7 @@ export class TechnologyTreeViewer {
|
|
|
472
447
|
const workspace = this.engine.resolver.get(workspaceId, options.principal);
|
|
473
448
|
const generation = this.engine.generation(workspaceId);
|
|
474
449
|
const cached = this.#state.current.get(workspaceId);
|
|
475
|
-
const snapshot = await scanTechnologySources(this.engine, workspaceId, workspace, options.principal, options.signal);
|
|
450
|
+
const snapshot = await scanTechnologySources(this.engine, workspaceId, workspace, options.principal, options.signal, options.renderOnly);
|
|
476
451
|
const analysisMode = workspace.roots.some(({ kind }) => kind === 'game') || snapshot.files.length > 1_000
|
|
477
452
|
? 'focused'
|
|
478
453
|
: undefined;
|
|
@@ -526,6 +501,8 @@ export class TechnologyTreeViewer {
|
|
|
526
501
|
...(analysisMode === undefined ? {} : { analysisMode }),
|
|
527
502
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
528
503
|
});
|
|
504
|
+
if (options.renderOnly === true)
|
|
505
|
+
graph.analysisBoundary.assumptions.push('Folder renders scan technology definitions, layouts, unlock targets and assets; inbound grants and references require technology inspection.');
|
|
529
506
|
this.#state.current.set(workspaceId, { generation, snapshot, assetFiles, graph });
|
|
530
507
|
rememberGraph(this.#state, graph);
|
|
531
508
|
return graph;
|
|
@@ -650,7 +627,9 @@ export class TechnologyTreeViewer {
|
|
|
650
627
|
};
|
|
651
628
|
}
|
|
652
629
|
async renderAndStore(input) {
|
|
630
|
+
await renderStage(input, 'Indexing technology view sources');
|
|
653
631
|
const graph = await this.scan(input.workspaceId, {
|
|
632
|
+
renderOnly: input.view === 'folder',
|
|
654
633
|
...(input.refresh === undefined ? {} : { refresh: input.refresh }),
|
|
655
634
|
...(input.principal === undefined ? {} : { principal: input.principal }),
|
|
656
635
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
@@ -684,9 +663,11 @@ export class TechnologyTreeViewer {
|
|
|
684
663
|
maxNodes: 1_000,
|
|
685
664
|
})),
|
|
686
665
|
]);
|
|
666
|
+
await renderStage(input, 'Decoding selected technology assets');
|
|
687
667
|
const { dataUris: iconDataUris, dimensions: spriteDimensions } = await resolveTechnologyIconDataUris(cached, requestedIconSprites, budget, input.signal);
|
|
688
668
|
renderOptions.iconDataUris = iconDataUris;
|
|
689
669
|
renderOptions.spriteDimensions = spriteDimensions;
|
|
670
|
+
await renderStage(input, 'Laying out and rasterizing technology view');
|
|
690
671
|
const render = await renderTechnologyGraph(graph, renderOptions);
|
|
691
672
|
const focused = [];
|
|
692
673
|
for (const folder of focusedFolders) {
|
|
@@ -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 = '4.0.
|
|
3
|
+
export const PACKAGE_VERSION = '4.0.4';
|
|
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/gui.md
CHANGED
|
@@ -11,6 +11,14 @@ Dashed amber regions are potential controls with unresolved visibility or enabli
|
|
|
11
11
|
The scene retains the reason so the agent can supply the missing facts instead of guessing.
|
|
12
12
|
These are source-model results, not proof of native click execution.
|
|
13
13
|
|
|
14
|
+
## Render one supplied state
|
|
15
|
+
|
|
16
|
+
For a focused render, use `states: []`, one `resolutions` entry matching `scenario.resolution`, and the flags, variables and list rows needed to open the window. Inspection and rendering preserve the supplied scenario; `generatedScenarios` is opt-in. A full state/resolution matrix still runs when those arrays are omitted.
|
|
17
|
+
|
|
18
|
+
Progress reports scanning, graph construction, selected asset loading, layout and rasterization. Deadline errors name the last phase. Missing texture fallback searches configured graphics/interface roots and fonts, never documentation or unrelated workspace folders. Move assets under a configured asset root if they previously depended on a workspace-wide basename search.
|
|
19
|
+
|
|
20
|
+
Nested `$KEY$` localisation references resolve across files. Scenario values can supply runtime tokens; cycles, more than 16 nested substitutions, and missing values remain unresolved. Animated sprites use `animationTimeSeconds` (and `visibleTimeSeconds` for play-on-show timing); each PNG is a deterministic frame at that time, not an animation.
|
|
21
|
+
|
|
14
22
|
## Preview immediate tooltip text
|
|
15
23
|
|
|
16
24
|
An explicit tooltip request uses a visible element's `pdx_tooltip` localisation key. Supply `tooltip.target` with its inspected instance ID or a unique element name, plus viewport `x`, `y`, `width` and `padding` values.
|
package/docs/technology.md
CHANGED
|
@@ -127,3 +127,5 @@ The comparison render is produced unless `render` is `false`; `maxRenderNodes` r
|
|
|
127
127
|
For an unfamiliar technology system, an agent can scan it, discover folders, trace or explain the relevant path, render only the needed views, edit Clausewitz source through its normal repository workflow, and compare the result. The same graph and revision back every query and artifact, so folder placement, dependency structure, unlock impact, and cross-file references stay aligned.
|
|
128
128
|
|
|
129
129
|
This is static analysis. Exact runtime research time, AI choices, balance, arbitrary dynamic grants, and unsupported visibility behavior are not inferred. Dynamic expressions remain in the results with their confidence and blockers, and no destination is invented.
|
|
130
|
+
|
|
131
|
+
Folder rendering scans technology definitions, GUI layouts, unlock targets, localisation and assets. It omits inbound event, decision, focus and history references; use `hoi4.tech_inspect` for grants, bonuses, impact and complete reference evidence. The render manifest records this source scope.
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hoi4-agent-tools",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "hoi4-agent-tools",
|
|
9
|
-
"version": "4.0.
|
|
9
|
+
"version": "4.0.4",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@fontsource-variable/roboto": "5.3.0",
|
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": "4.0.
|
|
4
|
+
"version": "4.0.4",
|
|
5
5
|
"description": "Build HOI4 mods: focus trees, events, decisions, worlds, GUI previews, docs and AI balance.",
|
|
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/v4.0.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.0.4/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/v4.0.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.0.4/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/v4.0.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.0.4/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/v4.0.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.0.4/schemas/decision-inspect-input.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|