hoi4-agent-tools 2.2.0 → 2.3.3
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 +28 -0
- package/README.md +30 -21
- package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +90 -0
- package/dist/hoi4_agent_tools/core/engine.js +5 -0
- package/dist/hoi4_agent_tools/core/index.js +57 -1
- package/dist/hoi4_agent_tools/core/source/ast-path.js +41 -0
- package/dist/hoi4_agent_tools/core/source/index.js +1 -0
- package/dist/hoi4_agent_tools/event/algorithms.js +178 -1
- package/dist/hoi4_agent_tools/event/graph.js +24 -9
- package/dist/hoi4_agent_tools/event/limits.js +5 -2
- package/dist/hoi4_agent_tools/event/service.js +182 -45
- package/dist/hoi4_agent_tools/mcp/server/create.js +3 -1
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +13 -63
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +413 -0
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +4 -1
- package/dist/hoi4_agent_tools/probability/adapters.js +344 -0
- package/dist/hoi4_agent_tools/probability/compare.js +178 -0
- package/dist/hoi4_agent_tools/probability/evaluation.js +649 -0
- package/dist/hoi4_agent_tools/probability/index.js +13 -0
- package/dist/hoi4_agent_tools/probability/model.js +5 -0
- package/dist/hoi4_agent_tools/probability/rational.js +145 -0
- package/dist/hoi4_agent_tools/probability/render.js +307 -0
- package/dist/hoi4_agent_tools/probability/sequence.js +858 -0
- package/dist/hoi4_agent_tools/probability/service.js +1168 -0
- package/dist/hoi4_agent_tools/probability/simulation.js +545 -0
- package/dist/hoi4_agent_tools/probability/source-analysis.js +458 -0
- package/dist/hoi4_agent_tools/probability/sweep.js +265 -0
- package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +280 -0
- package/dist/hoi4_agent_tools/probability/value-evaluator.js +357 -0
- package/dist/hoi4_agent_tools/runtime.js +109 -7
- package/dist/hoi4_agent_tools/schemas/probability.js +930 -0
- package/dist/hoi4_agent_tools/technology/graph.js +30 -3
- package/dist/hoi4_agent_tools/technology/queries.js +70 -3
- package/dist/hoi4_agent_tools/technology/service.js +38 -2
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +1 -0
- package/docs/development.md +2 -0
- package/docs/events.md +2 -2
- package/docs/probability.md +80 -0
- package/docs/research/probability-adapter-evidence.md +64 -0
- package/docs/setup.md +2 -0
- package/docs/technology.md +4 -2
- package/examples/clients/codex-config.toml +3 -0
- package/examples/clients/generic.json +8 -0
- package/examples/probability/adaptive-event-pool.json +92 -0
- package/examples/probability/adaptive-event-sequence-input.json +57 -0
- package/examples/probability/event-mtth-scenarios.json +57 -0
- package/examples/probability/event-mtth-simulate-input.json +37 -0
- package/examples/probability/focus-route-scenarios.json +61 -0
- package/examples/probability/focus-route-sweep-input.json +36 -0
- package/package.json +19 -8
- package/schemas/configuration.schema.json +747 -0
- package/schemas/continuous-focus-palette.schema.json +325 -0
- package/schemas/custom-weighted-pool.schema.json +298 -0
- package/schemas/event-feature-manifest.schema.json +63 -0
- package/schemas/focus-plan.schema.json +927 -0
- package/schemas/focus-planning-sidecar.schema.json +407 -0
- package/schemas/gui-animation-source.schema.json +176 -0
- package/schemas/gui-helper.schema.json +301 -0
- package/schemas/gui-scenario.schema.json +298 -0
- package/schemas/map-operation.schema.json +2284 -0
- package/schemas/operation-result.schema.json +327 -0
- package/schemas/probability-analysis-result.schema.json +2211 -0
- package/schemas/probability-compare-input.schema.json +664 -0
- package/schemas/probability-evaluate-input.schema.json +665 -0
- package/schemas/probability-inspect-input.schema.json +97 -0
- package/schemas/probability-render-input.schema.json +141 -0
- package/schemas/probability-scenario-set.schema.json +430 -0
- package/schemas/probability-sequence-input.schema.json +886 -0
- package/schemas/probability-simulate-input.schema.json +691 -0
- package/schemas/probability-sweep-input.schema.json +702 -0
- package/server.json +3 -3
|
@@ -42,14 +42,16 @@ class BoundedEventFragmentCache {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
function eventGraphCacheKey(workspaceId, projectHelpers) {
|
|
46
|
-
return `${workspaceId}\0${projectHelpers ? 'expanded' : 'structural'}`;
|
|
45
|
+
function eventGraphCacheKey(workspaceId, projectHelpers, analysisMode = 'full') {
|
|
46
|
+
return `${workspaceId}\0${projectHelpers ? 'expanded' : 'structural'}\0${analysisMode}`;
|
|
47
47
|
}
|
|
48
48
|
const serviceState = new WeakMap();
|
|
49
49
|
const EVENT_GRAPH_ARTIFACT_MAX_BYTES = 67_108_864;
|
|
50
50
|
const EVENT_GRAPH_ARTIFACT_MAX_CHUNKS = 1_024;
|
|
51
51
|
const EVENT_SCAN_FULL_GRAPH_RECORD_LIMIT = 100_000;
|
|
52
52
|
const EVENT_SCAN_SUMMARY_SAMPLE_LIMIT = 100;
|
|
53
|
+
const EVENT_FOCUSED_EVIDENCE_LIMIT = 2_000;
|
|
54
|
+
const EVENT_FOCUSED_GRAPH_FILE_THRESHOLD = 1_000;
|
|
53
55
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
54
56
|
const graphHashCache = new WeakMap();
|
|
55
57
|
function groupedCounts(values, key) {
|
|
@@ -116,7 +118,16 @@ function cachedEventGraphHash(graph, signal) {
|
|
|
116
118
|
const cached = graphHashCache.get(graph);
|
|
117
119
|
if (cached !== undefined)
|
|
118
120
|
return cached;
|
|
119
|
-
const computed =
|
|
121
|
+
const computed = graph.analysisMode === 'focused'
|
|
122
|
+
? hashCanonical({
|
|
123
|
+
schemaVersion: graph.schemaVersion,
|
|
124
|
+
parserVersion: graph.parserVersion,
|
|
125
|
+
workspaceIdentity: graph.workspaceIdentity,
|
|
126
|
+
revision: graph.revision,
|
|
127
|
+
statistics: graph.statistics,
|
|
128
|
+
sourceHashes: graph.sourceHashes,
|
|
129
|
+
})
|
|
130
|
+
: eventGraphHash(graph, signal);
|
|
120
131
|
graphHashCache.set(graph, computed);
|
|
121
132
|
return computed;
|
|
122
133
|
}
|
|
@@ -135,6 +146,55 @@ function sharedState(engine) {
|
|
|
135
146
|
function engineGeneration(engine, workspaceId) {
|
|
136
147
|
return engine.generation(workspaceId);
|
|
137
148
|
}
|
|
149
|
+
function eventScanPatterns(workspace) {
|
|
150
|
+
const roots = workspace.registration.roots;
|
|
151
|
+
const under = (values, glob) => values.map((root) => `${root.replaceAll('\\', '/').replace(/\/$/u, '')}/${glob}`);
|
|
152
|
+
return [
|
|
153
|
+
'events/**/*.txt',
|
|
154
|
+
'history/countries/**/*.txt',
|
|
155
|
+
...under(roots.states, '**/*.txt'),
|
|
156
|
+
'common/continuous_focus/**/*.txt',
|
|
157
|
+
'common/decisions/**/*.txt',
|
|
158
|
+
'common/ideas/**/*.txt',
|
|
159
|
+
'common/characters/**/*.txt',
|
|
160
|
+
'common/operations/**/*.txt',
|
|
161
|
+
'common/raids/**/*.txt',
|
|
162
|
+
'common/bop/**/*.txt',
|
|
163
|
+
'common/resistance_compliance_modifiers/**/*.txt',
|
|
164
|
+
'common/abilities/**/*.txt',
|
|
165
|
+
'common/on_actions/**/*.txt',
|
|
166
|
+
'common/special_projects/**/*.txt',
|
|
167
|
+
'common/scripted_effects/**/*.txt',
|
|
168
|
+
...under(roots.focus, '**/*.txt'),
|
|
169
|
+
...under(roots.scriptedGui, '**/*.txt'),
|
|
170
|
+
...under(roots.localisation, 'english/**/*.{yml,yaml}'),
|
|
171
|
+
...under(roots.localisation, '*.{yml,yaml}'),
|
|
172
|
+
].sort(compareCodeUnits);
|
|
173
|
+
}
|
|
174
|
+
async function scanEventSources(engine, workspaceId, workspace, principal, signal) {
|
|
175
|
+
const patterns = eventScanPatterns(workspace);
|
|
176
|
+
const sourceKinds = ['mod', 'dependency', 'fixture'];
|
|
177
|
+
const scans = await Promise.all([
|
|
178
|
+
engine.scan(workspaceId, { patterns, rootKinds: sourceKinds }, principal, signal),
|
|
179
|
+
...(workspace.roots.some(({ kind }) => kind === 'game')
|
|
180
|
+
? [engine.scan(workspaceId, { patterns, rootKinds: ['game'] }, principal, signal)]
|
|
181
|
+
: []),
|
|
182
|
+
]);
|
|
183
|
+
const files = scans.flatMap(({ files }) => files.map((file) => ({ ...file })));
|
|
184
|
+
recomputeShadowing(files);
|
|
185
|
+
files.sort((left, right) => left.loadOrder - right.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
186
|
+
const index = engine.indexFiles(files);
|
|
187
|
+
return {
|
|
188
|
+
workspaceId,
|
|
189
|
+
revision: snapshotRevision(files),
|
|
190
|
+
files,
|
|
191
|
+
index,
|
|
192
|
+
complete: scans.every(({ complete }) => complete) && index.complete,
|
|
193
|
+
skippedSourceCount: index.skippedSourceCount,
|
|
194
|
+
skippedSources: index.skippedSources,
|
|
195
|
+
diagnostics: [...scans.flatMap(({ diagnostics }) => diagnostics), ...index.diagnostics],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
138
198
|
function rememberGraph(state, workspaceId, graph) {
|
|
139
199
|
const history = state.history.get(workspaceId) ?? new Map();
|
|
140
200
|
history.delete(graph.revision);
|
|
@@ -163,6 +223,20 @@ function eventIssueDiagnostic(issue) {
|
|
|
163
223
|
},
|
|
164
224
|
};
|
|
165
225
|
}
|
|
226
|
+
function boundedEvidence(values, limit = EVENT_FOCUSED_EVIDENCE_LIMIT) {
|
|
227
|
+
if (values.length <= limit)
|
|
228
|
+
return { values: [...values] };
|
|
229
|
+
return {
|
|
230
|
+
values: values.slice(0, limit),
|
|
231
|
+
inventory: { count: values.length, retainedCount: limit, truncated: true },
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function boundedUnresolved(graph, selectedNodeIds) {
|
|
235
|
+
const relevant = selectedNodeIds === undefined
|
|
236
|
+
? graph.unresolved
|
|
237
|
+
: graph.unresolved.filter(({ id, ownerId }) => selectedNodeIds.has(id) || (ownerId !== undefined && selectedNodeIds.has(ownerId)));
|
|
238
|
+
return boundedEvidence(relevant);
|
|
239
|
+
}
|
|
166
240
|
export function eventDiagnostics(graph) {
|
|
167
241
|
const existing = new Set(graph.diagnostics.map(({ code, location }) => `${code}:${location?.path ?? ''}:${location?.start.offset ?? -1}`));
|
|
168
242
|
return [
|
|
@@ -618,37 +692,56 @@ export class EventChainViewer {
|
|
|
618
692
|
const workspace = this.engine.resolver.get(workspaceId, options.principal);
|
|
619
693
|
const generation = engineGeneration(this.engine, workspaceId);
|
|
620
694
|
const projectHelpers = options.projectHelpers ?? true;
|
|
621
|
-
const
|
|
695
|
+
const analysisMode = options.analysisMode ?? 'full';
|
|
696
|
+
const cacheKey = eventGraphCacheKey(workspaceId, projectHelpers, analysisMode);
|
|
622
697
|
const cached = this.#state.current.get(cacheKey);
|
|
623
698
|
if (options.refresh !== true && cached?.generation === generation) {
|
|
624
699
|
return cached.graph;
|
|
625
700
|
}
|
|
626
|
-
const sibling = this.#state.current.get(eventGraphCacheKey(workspaceId, !projectHelpers))
|
|
701
|
+
const sibling = this.#state.current.get(eventGraphCacheKey(workspaceId, !projectHelpers, analysisMode)) ??
|
|
702
|
+
this.#state.current.get(eventGraphCacheKey(workspaceId, !projectHelpers)) ??
|
|
703
|
+
this.#state.current.get(eventGraphCacheKey(workspaceId, projectHelpers));
|
|
627
704
|
const snapshot = options.refresh !== true && sibling?.generation === generation
|
|
628
705
|
? sibling.snapshot
|
|
629
|
-
: await this.engine
|
|
706
|
+
: await scanEventSources(this.engine, workspaceId, workspace, options.principal, options.signal);
|
|
630
707
|
options.signal?.throwIfAborted();
|
|
631
708
|
if (cached?.snapshot.revision === snapshot.revision && cached.generation === generation) {
|
|
632
709
|
return cached.graph;
|
|
633
710
|
}
|
|
711
|
+
const buildAnalysisMode = analysisMode === 'focused' && snapshot.files.length > EVENT_FOCUSED_GRAPH_FILE_THRESHOLD
|
|
712
|
+
? 'focused'
|
|
713
|
+
: 'full';
|
|
634
714
|
const graph = buildEventGraph(snapshot, {
|
|
635
715
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
636
716
|
cache: this.#state.fragments,
|
|
637
717
|
workspaceIdentity: workspace.workspaceIdentity,
|
|
638
718
|
projectHelpers,
|
|
719
|
+
analysisMode: buildAnalysisMode,
|
|
639
720
|
});
|
|
640
721
|
this.#state.current.set(cacheKey, { generation, snapshot, graph });
|
|
641
|
-
|
|
722
|
+
if (analysisMode === 'full')
|
|
723
|
+
rememberGraph(this.#state, workspaceId, graph);
|
|
642
724
|
return graph;
|
|
643
725
|
}
|
|
644
726
|
async inspect(input) {
|
|
645
727
|
input.signal?.throwIfAborted();
|
|
646
|
-
const
|
|
728
|
+
const scannedGraph = await this.scan(input.workspaceId, {
|
|
647
729
|
refresh: input.refresh ?? input.mode === 'scan',
|
|
648
|
-
|
|
730
|
+
// Focused routes traverse helper nodes directly and collapse only the
|
|
731
|
+
// selected path. Avoid materialising the full workspace projection first.
|
|
732
|
+
projectHelpers: input.mode !== 'scan' &&
|
|
733
|
+
input.mode !== 'roots' &&
|
|
734
|
+
input.mode !== 'trace' &&
|
|
735
|
+
input.mode !== 'explain_path',
|
|
736
|
+
...(input.mode === 'trace' || input.mode === 'explain_path'
|
|
737
|
+
? { analysisMode: 'focused' }
|
|
738
|
+
: {}),
|
|
649
739
|
...(input.principal === undefined ? {} : { principal: input.principal }),
|
|
650
740
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
651
741
|
});
|
|
742
|
+
// Keep the service result's graph identity distinct from a prior scan while
|
|
743
|
+
// reusing its structural cache and avoiding a second large graph build.
|
|
744
|
+
const graph = input.mode === 'trace' || input.mode === 'explain_path' ? { ...scannedGraph } : scannedGraph;
|
|
652
745
|
const maxDepth = input.maxDepth ?? 8;
|
|
653
746
|
const maxNodes = input.maxNodes ?? 500;
|
|
654
747
|
const maxEdges = input.maxEdges ?? 2_000;
|
|
@@ -671,28 +764,41 @@ export class EventChainViewer {
|
|
|
671
764
|
report = eventScanReport(graph);
|
|
672
765
|
break;
|
|
673
766
|
case 'roots':
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
767
|
+
{
|
|
768
|
+
const unresolved = boundedUnresolved(graph);
|
|
769
|
+
report = {
|
|
770
|
+
graphRevision: graph.revision,
|
|
771
|
+
sourceHashes: graph.sourceHashes,
|
|
772
|
+
roots: discoverEventRoots(graph, input.signal),
|
|
773
|
+
unresolved: unresolved.values,
|
|
774
|
+
...(unresolved.inventory === undefined
|
|
775
|
+
? {}
|
|
776
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
777
|
+
};
|
|
778
|
+
}
|
|
680
779
|
break;
|
|
681
780
|
case 'trace':
|
|
682
781
|
if (input.selector === undefined)
|
|
683
782
|
throw new ServiceError('EVENT_SELECTOR_REQUIRED', 'Trace requires a selector');
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
sourceHashes: graph.sourceHashes,
|
|
687
|
-
trace: traceSelectedEvents(graph, input.selector, {
|
|
783
|
+
{
|
|
784
|
+
const trace = traceSelectedEvents(graph, input.selector, {
|
|
688
785
|
maxDepth,
|
|
689
786
|
maxNodes,
|
|
690
787
|
maxEdges,
|
|
691
788
|
direction: input.direction ?? 'both',
|
|
692
789
|
expandHelpers: input.expandHelpers ?? false,
|
|
693
|
-
}, input.signal)
|
|
694
|
-
unresolved
|
|
695
|
-
|
|
790
|
+
}, input.signal);
|
|
791
|
+
const unresolved = boundedUnresolved(graph, new Set(trace.nodes.map(({ id }) => id)));
|
|
792
|
+
report = {
|
|
793
|
+
graphRevision: graph.revision,
|
|
794
|
+
sourceHashes: graph.sourceHashes,
|
|
795
|
+
trace,
|
|
796
|
+
unresolved: unresolved.values,
|
|
797
|
+
...(unresolved.inventory === undefined
|
|
798
|
+
? {}
|
|
799
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
800
|
+
};
|
|
801
|
+
}
|
|
696
802
|
break;
|
|
697
803
|
case 'explain_path':
|
|
698
804
|
if (input.from === undefined || input.to === undefined)
|
|
@@ -704,31 +810,54 @@ export class EventChainViewer {
|
|
|
704
810
|
};
|
|
705
811
|
break;
|
|
706
812
|
case 'state_flow':
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
813
|
+
{
|
|
814
|
+
const unresolved = boundedUnresolved(graph);
|
|
815
|
+
const issues = boundedEvidence(lintEventGraph(graph, input.selector, input.signal).filter(({ code }) => /(?:STATE|FLAG|VARIABLE|ARRAY|TARGET|SCOPE)/u.test(code)));
|
|
816
|
+
report = {
|
|
817
|
+
graphRevision: graph.revision,
|
|
818
|
+
sourceHashes: graph.sourceHashes,
|
|
819
|
+
flow: inspectEventStateFlow(graph, input.selector, input.stateSubject, input.signal),
|
|
820
|
+
issues: issues.values,
|
|
821
|
+
unresolved: unresolved.values,
|
|
822
|
+
...(issues.inventory === undefined ? {} : { issueInventory: issues.inventory }),
|
|
823
|
+
...(unresolved.inventory === undefined
|
|
824
|
+
? {}
|
|
825
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
713
828
|
break;
|
|
714
829
|
case 'lint':
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
830
|
+
{
|
|
831
|
+
const issues = boundedEvidence(lintEventGraph(graph, input.selector, input.signal));
|
|
832
|
+
const unresolved = boundedUnresolved(graph);
|
|
833
|
+
report = {
|
|
834
|
+
graphRevision: graph.revision,
|
|
835
|
+
sourceHashes: graph.sourceHashes,
|
|
836
|
+
issues: issues.values,
|
|
837
|
+
complete: graph.complete,
|
|
838
|
+
unresolved: unresolved.values,
|
|
839
|
+
...(issues.inventory === undefined ? {} : { issueInventory: issues.inventory }),
|
|
840
|
+
...(unresolved.inventory === undefined
|
|
841
|
+
? {}
|
|
842
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
722
845
|
break;
|
|
723
846
|
case 'impact':
|
|
724
847
|
if (input.impactSubject === undefined)
|
|
725
848
|
throw new ServiceError('EVENT_IMPACT_SUBJECT_REQUIRED', 'Impact analysis requires a subject');
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
849
|
+
{
|
|
850
|
+
const unresolved = boundedUnresolved(graph);
|
|
851
|
+
report = {
|
|
852
|
+
graphRevision: graph.revision,
|
|
853
|
+
sourceHashes: graph.sourceHashes,
|
|
854
|
+
impact: analyzeEventImpact(graph, input.impactSubject, input.signal),
|
|
855
|
+
unresolved: unresolved.values,
|
|
856
|
+
...(unresolved.inventory === undefined
|
|
857
|
+
? {}
|
|
858
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
859
|
+
};
|
|
860
|
+
}
|
|
732
861
|
break;
|
|
733
862
|
}
|
|
734
863
|
const graphHash = cachedEventGraphHash(graph, input.signal);
|
|
@@ -742,7 +871,7 @@ export class EventChainViewer {
|
|
|
742
871
|
: undefined;
|
|
743
872
|
const artifactUnresolved = projectedScan
|
|
744
873
|
? graph.unresolved.slice(0, EVENT_SCAN_SUMMARY_SAMPLE_LIMIT)
|
|
745
|
-
: graph.
|
|
874
|
+
: boundedUnresolved(graph).values;
|
|
746
875
|
const reportJson = `${canonicalJson({
|
|
747
876
|
schemaVersion: 'event-analysis.v1',
|
|
748
877
|
graphSchemaVersion: graph.schemaVersion,
|
|
@@ -759,12 +888,12 @@ export class EventChainViewer {
|
|
|
759
888
|
complete: graph.complete,
|
|
760
889
|
filters: analysisFilters,
|
|
761
890
|
unresolved: artifactUnresolved,
|
|
762
|
-
...(
|
|
891
|
+
...(artifactUnresolved.length !== graph.unresolved.length
|
|
763
892
|
? {
|
|
764
893
|
unresolvedInventory: {
|
|
765
894
|
count: graph.unresolved.length,
|
|
766
895
|
retainedCount: artifactUnresolved.length,
|
|
767
|
-
truncated:
|
|
896
|
+
truncated: true,
|
|
768
897
|
},
|
|
769
898
|
}
|
|
770
899
|
: {}),
|
|
@@ -950,7 +1079,15 @@ export class EventChainViewer {
|
|
|
950
1079
|
includeHtml: input.includeHtml ?? false,
|
|
951
1080
|
refresh: input.refresh ?? false,
|
|
952
1081
|
},
|
|
953
|
-
|
|
1082
|
+
...(() => {
|
|
1083
|
+
const unresolved = boundedUnresolved(graph, new Set(render.selectedNodeIds));
|
|
1084
|
+
return {
|
|
1085
|
+
unresolved: unresolved.values,
|
|
1086
|
+
...(unresolved.inventory === undefined
|
|
1087
|
+
? {}
|
|
1088
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
1089
|
+
};
|
|
1090
|
+
})(),
|
|
954
1091
|
resources: { ...resourceNames, artifacts: resourceLinks },
|
|
955
1092
|
overview: {
|
|
956
1093
|
selectedNodeIds: render.selectedNodeIds,
|
|
@@ -5,8 +5,9 @@ import { registerGuiTools } from '../tools/gui.js';
|
|
|
5
5
|
import { registerMapTools } from '../tools/map.js';
|
|
6
6
|
import { registerEventTools } from '../tools/event.js';
|
|
7
7
|
import { registerTechnologyTools } from '../tools/technology.js';
|
|
8
|
+
import { registerProbabilityTools } from '../tools/probability.js';
|
|
8
9
|
import { registerMcpResources } from '../resources/register.js';
|
|
9
|
-
export const SERVER_INSTRUCTIONS = 'Use focus tools for focus trees, GUI tools for interfaces, and map tools for map data.
|
|
10
|
+
export const SERVER_INSTRUCTIONS = 'Use focus tools for focus trees, GUI tools for interfaces, and map tools for map data. Start unfamiliar event chains with hoi4.event_inspect. Event tools are read-only. Start technology and doctrine work with hoi4.tech_inspect. Technology tools are read-only. Start weighted AI, MTTH, random, and declared-pool analysis with hoi4.probability_inspect. Probability tools are read-only. Large evidence is linked as resources.';
|
|
10
11
|
export function createMcpServer(engine, context = {}) {
|
|
11
12
|
const server = new McpServer({
|
|
12
13
|
name: PACKAGE_NAME,
|
|
@@ -21,6 +22,7 @@ export function createMcpServer(engine, context = {}) {
|
|
|
21
22
|
registerMapTools(server, engine, context);
|
|
22
23
|
registerEventTools(server, engine, context);
|
|
23
24
|
registerTechnologyTools(server, engine, context);
|
|
25
|
+
registerProbabilityTools(server, engine, context);
|
|
24
26
|
registerMcpResources(server, engine, context);
|
|
25
27
|
return server;
|
|
26
28
|
}
|
|
@@ -14,7 +14,6 @@ import { compactValidatedInputSchema } from '../server/context-schemas.js';
|
|
|
14
14
|
import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
|
|
15
15
|
import { progressReporter } from '../server/progress.js';
|
|
16
16
|
import { errorResult, setInlineFilesScanned, strictOperationResultSchema, toolResult, } from '../server/result.js';
|
|
17
|
-
import { computeFocusVisualRevisions } from './focus.js';
|
|
18
17
|
const countryTagSchema = z
|
|
19
18
|
.string()
|
|
20
19
|
.regex(/^[A-Z0-9]{3}$/u)
|
|
@@ -43,12 +42,6 @@ const chaosxCountryAssetsOutput = strictOperationResultSchema(z
|
|
|
43
42
|
artifactCount: nonNegativeIntegerSchema,
|
|
44
43
|
})
|
|
45
44
|
.strict());
|
|
46
|
-
const visualFocusSelectorSchema = z
|
|
47
|
-
.object({
|
|
48
|
-
relativePath: z.string().min(1).max(1024),
|
|
49
|
-
treeId: z.string().min(1).max(256),
|
|
50
|
-
})
|
|
51
|
-
.strict();
|
|
52
45
|
const visualGuiSelectorSchema = z
|
|
53
46
|
.object({
|
|
54
47
|
windowName: z.string().min(1).max(256),
|
|
@@ -58,27 +51,20 @@ const visualGuiSelectorSchema = z
|
|
|
58
51
|
const chaosxVisualRevisionInput = compactValidatedInputSchema(z
|
|
59
52
|
.object({
|
|
60
53
|
workspaceId: workspaceIdSchema,
|
|
61
|
-
|
|
62
|
-
guiWindows: z.array(visualGuiSelectorSchema).max(3).default([]),
|
|
63
|
-
countryTags: z.array(countryTagSchema).max(12).default([]),
|
|
64
|
-
eventId: z.number().int().min(0).max(999).optional(),
|
|
54
|
+
guiWindows: z.array(visualGuiSelectorSchema).min(1).max(3),
|
|
65
55
|
})
|
|
66
|
-
.strict(), 'ChaosX-only selectors for fast
|
|
56
|
+
.strict(), 'ChaosX-only scripted-GUI selectors for fast cache revision checks');
|
|
67
57
|
const visualRevisionEntrySchema = z
|
|
68
58
|
.object({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
windowName: z.string().min(1).max(256).optional(),
|
|
72
|
-
guiId: z.string().min(1).max(256).optional(),
|
|
59
|
+
windowName: z.string().min(1).max(256),
|
|
60
|
+
guiId: z.string().min(1).max(256),
|
|
73
61
|
revision: sha256Schema,
|
|
74
62
|
})
|
|
75
63
|
.strict();
|
|
76
64
|
const chaosxVisualRevisionOutput = strictOperationResultSchema(z
|
|
77
65
|
.object({
|
|
78
66
|
workspaceRevision: sha256Schema,
|
|
79
|
-
focusRevisions: z.array(visualRevisionEntrySchema).max(3),
|
|
80
67
|
guiRevisions: z.array(visualRevisionEntrySchema).max(3),
|
|
81
|
-
countryRevision: sha256Schema,
|
|
82
68
|
dependencyFileCount: nonNegativeIntegerSchema,
|
|
83
69
|
})
|
|
84
70
|
.strict());
|
|
@@ -344,35 +330,12 @@ async function resolveGuiRevision(engine, context, studio, cache, workspaceId, s
|
|
|
344
330
|
filesScanned: Object.keys(sourceHashes).sort(compareCodeUnits),
|
|
345
331
|
};
|
|
346
332
|
}
|
|
347
|
-
function countryRevision(snapshot, selections, catalog) {
|
|
348
|
-
const sourceHashes = {};
|
|
349
|
-
for (const selection of selections) {
|
|
350
|
-
for (const sourcePath of [selection.discoverySourcePath, selection.spriteSourcePath]) {
|
|
351
|
-
if (sourcePath === undefined)
|
|
352
|
-
continue;
|
|
353
|
-
const source = snapshot.files.find(({ displayPath }) => displayPath === sourcePath);
|
|
354
|
-
if (source !== undefined)
|
|
355
|
-
sourceHashes[source.displayPath] = source.sha256;
|
|
356
|
-
}
|
|
357
|
-
for (const assetPath of [selection.flagPath, selection.leaderTexturePath]) {
|
|
358
|
-
if (assetPath === undefined)
|
|
359
|
-
continue;
|
|
360
|
-
const source = catalog.resolveFile(assetPath);
|
|
361
|
-
if (source !== undefined)
|
|
362
|
-
sourceHashes[source.displayPath] = source.sha256;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
revision: hashCanonical({ selections, sourceHashes }),
|
|
367
|
-
filesScanned: Object.keys(sourceHashes).sort(compareCodeUnits),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
333
|
export function registerChaosxTools(server, engine, context) {
|
|
371
334
|
const guiStudio = new ScriptedGuiStudio(engine);
|
|
372
335
|
const guiRevisionCache = new Map();
|
|
373
336
|
server.registerTool('chaosx.visual_revision', {
|
|
374
337
|
title: 'Check ChaosX visual revisions',
|
|
375
|
-
description: 'Private ChaosX cache-coherency endpoint. Computes exact source revisions
|
|
338
|
+
description: 'Private ChaosX cache-coherency endpoint. Computes exact scripted-GUI source revisions without rendering PNG artifacts.',
|
|
376
339
|
inputSchema: chaosxVisualRevisionInput,
|
|
377
340
|
outputSchema: chaosxVisualRevisionOutput,
|
|
378
341
|
annotations: readOnly,
|
|
@@ -380,41 +343,28 @@ export function registerChaosxTools(server, engine, context) {
|
|
|
380
343
|
const workspaceId = engine.resolver.resolveWorkspaceId(input.workspaceId, context.principal);
|
|
381
344
|
try {
|
|
382
345
|
const progress = progressReporter(extra);
|
|
383
|
-
await progress.report(0,
|
|
384
|
-
const snapshot = await engine.scan(workspaceId, {}, context.principal, progress.signal);
|
|
385
|
-
const focusRevisions = await computeFocusVisualRevisions(engine, context, workspaceId, snapshot, input.focusTrees, progress.signal);
|
|
386
|
-
await progress.report(1, 4, 'Checking ChaosX GUI sources');
|
|
346
|
+
await progress.report(0, 2, 'Checking ChaosX scripted-GUI sources');
|
|
387
347
|
const resolvedGuiRevisions = [];
|
|
388
348
|
for (const selector of input.guiWindows)
|
|
389
349
|
resolvedGuiRevisions.push(await resolveGuiRevision(engine, context, guiStudio, guiRevisionCache, workspaceId, selector, progress.signal));
|
|
350
|
+
const filesScanned = new Set(resolvedGuiRevisions.flatMap(({ filesScanned }) => filesScanned));
|
|
390
351
|
const guiRevisions = resolvedGuiRevisions.map(({ windowName, guiId, revision }) => ({
|
|
391
352
|
windowName,
|
|
392
353
|
guiId,
|
|
393
354
|
revision,
|
|
394
355
|
}));
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const filesScanned = new Set([
|
|
400
|
-
...focusRevisions.flatMap(({ filesScanned }) => filesScanned),
|
|
401
|
-
...resolvedGuiRevisions.flatMap(({ filesScanned }) => filesScanned),
|
|
402
|
-
...countries.filesScanned,
|
|
403
|
-
]);
|
|
356
|
+
const workspaceRevision = hashCanonical(resolvedGuiRevisions.map(({ windowName, workspaceRevision: revision }) => ({
|
|
357
|
+
windowName,
|
|
358
|
+
revision,
|
|
359
|
+
})));
|
|
404
360
|
const result = emptyServiceResult(workspaceId, {
|
|
405
|
-
workspaceRevision
|
|
406
|
-
focusRevisions: focusRevisions.map(({ relativePath, treeId, revision }) => ({
|
|
407
|
-
relativePath,
|
|
408
|
-
treeId,
|
|
409
|
-
revision,
|
|
410
|
-
})),
|
|
361
|
+
workspaceRevision,
|
|
411
362
|
guiRevisions,
|
|
412
|
-
countryRevision: countries.revision,
|
|
413
363
|
dependencyFileCount: filesScanned.size,
|
|
414
364
|
});
|
|
415
365
|
result.code = 'CHAOSX_VISUAL_REVISION_CHECKED';
|
|
416
366
|
setInlineFilesScanned(result, [...filesScanned].sort(compareCodeUnits));
|
|
417
|
-
await progress.report(
|
|
367
|
+
await progress.report(2, 2, 'ChaosX scripted-GUI revisions complete');
|
|
418
368
|
return toolResult(result);
|
|
419
369
|
}
|
|
420
370
|
catch (error) {
|