hoi4-agent-tools 2.3.0 → 2.3.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 +18 -0
- package/dist/bin/stdio.js +4 -0
- package/dist/hoi4_agent_tools/event/algorithms.js +178 -1
- package/dist/hoi4_agent_tools/event/graph.js +38 -9
- package/dist/hoi4_agent_tools/event/limits.js +4 -1
- package/dist/hoi4_agent_tools/event/service.js +200 -46
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +480 -0
- package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -3
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +4 -1
- package/dist/hoi4_agent_tools/runtime.js +109 -7
- 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/events.md +2 -2
- package/docs/setup.md +2 -0
- package/docs/technology.md +4 -2
- package/package.json +3 -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/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/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -42,14 +42,19 @@ 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
|
+
}
|
|
48
|
+
function isGameBackedWorkspace(workspace) {
|
|
49
|
+
return workspace.roots.some(({ kind }) => kind === 'game');
|
|
47
50
|
}
|
|
48
51
|
const serviceState = new WeakMap();
|
|
49
52
|
const EVENT_GRAPH_ARTIFACT_MAX_BYTES = 67_108_864;
|
|
50
53
|
const EVENT_GRAPH_ARTIFACT_MAX_CHUNKS = 1_024;
|
|
51
54
|
const EVENT_SCAN_FULL_GRAPH_RECORD_LIMIT = 100_000;
|
|
52
55
|
const EVENT_SCAN_SUMMARY_SAMPLE_LIMIT = 100;
|
|
56
|
+
const EVENT_FOCUSED_EVIDENCE_LIMIT = 2_000;
|
|
57
|
+
const EVENT_FOCUSED_GRAPH_FILE_THRESHOLD = 1_000;
|
|
53
58
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
54
59
|
const graphHashCache = new WeakMap();
|
|
55
60
|
function groupedCounts(values, key) {
|
|
@@ -75,6 +80,7 @@ export function eventScanReport(graph, fullGraphRecordLimit = EVENT_SCAN_FULL_GR
|
|
|
75
80
|
graphSummary: {
|
|
76
81
|
schemaVersion: graph.schemaVersion,
|
|
77
82
|
parserVersion: graph.parserVersion,
|
|
83
|
+
analysisMode: graph.analysisMode ?? 'full',
|
|
78
84
|
workspaceId: graph.workspaceId,
|
|
79
85
|
workspaceIdentity: graph.workspaceIdentity,
|
|
80
86
|
revision: graph.revision,
|
|
@@ -116,7 +122,17 @@ function cachedEventGraphHash(graph, signal) {
|
|
|
116
122
|
const cached = graphHashCache.get(graph);
|
|
117
123
|
if (cached !== undefined)
|
|
118
124
|
return cached;
|
|
119
|
-
const computed =
|
|
125
|
+
const computed = graph.analysisMode === 'focused'
|
|
126
|
+
? hashCanonical({
|
|
127
|
+
schemaVersion: graph.schemaVersion,
|
|
128
|
+
parserVersion: graph.parserVersion,
|
|
129
|
+
analysisMode: graph.analysisMode ?? 'full',
|
|
130
|
+
workspaceIdentity: graph.workspaceIdentity,
|
|
131
|
+
revision: graph.revision,
|
|
132
|
+
statistics: graph.statistics,
|
|
133
|
+
sourceHashes: graph.sourceHashes,
|
|
134
|
+
})
|
|
135
|
+
: eventGraphHash(graph, signal);
|
|
120
136
|
graphHashCache.set(graph, computed);
|
|
121
137
|
return computed;
|
|
122
138
|
}
|
|
@@ -135,6 +151,61 @@ function sharedState(engine) {
|
|
|
135
151
|
function engineGeneration(engine, workspaceId) {
|
|
136
152
|
return engine.generation(workspaceId);
|
|
137
153
|
}
|
|
154
|
+
function eventScanPatterns(workspace, focused = false) {
|
|
155
|
+
const roots = workspace.registration.roots;
|
|
156
|
+
const under = (values, glob) => values.map((root) => `${root.replaceAll('\\', '/').replace(/\/$/u, '')}/${glob}`);
|
|
157
|
+
if (focused) {
|
|
158
|
+
return ['events/**/*.txt', 'common/on_actions/**/*.txt'].sort(compareCodeUnits);
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
'events/**/*.txt',
|
|
162
|
+
'history/countries/**/*.txt',
|
|
163
|
+
...under(roots.states, '**/*.txt'),
|
|
164
|
+
'common/continuous_focus/**/*.txt',
|
|
165
|
+
'common/decisions/**/*.txt',
|
|
166
|
+
'common/ideas/**/*.txt',
|
|
167
|
+
'common/characters/**/*.txt',
|
|
168
|
+
'common/operations/**/*.txt',
|
|
169
|
+
'common/raids/**/*.txt',
|
|
170
|
+
'common/bop/**/*.txt',
|
|
171
|
+
'common/resistance_compliance_modifiers/**/*.txt',
|
|
172
|
+
'common/abilities/**/*.txt',
|
|
173
|
+
'common/on_actions/**/*.txt',
|
|
174
|
+
'common/special_projects/**/*.txt',
|
|
175
|
+
'common/scripted_effects/**/*.txt',
|
|
176
|
+
...under(roots.focus, '**/*.txt'),
|
|
177
|
+
...under(roots.scriptedGui, '**/*.txt'),
|
|
178
|
+
...under(roots.localisation, 'english/**/*.{yml,yaml}'),
|
|
179
|
+
...under(roots.localisation, '*.{yml,yaml}'),
|
|
180
|
+
].sort(compareCodeUnits);
|
|
181
|
+
}
|
|
182
|
+
async function scanEventSources(engine, workspaceId, workspace, focused = false, principal, signal) {
|
|
183
|
+
const patterns = eventScanPatterns(workspace, focused);
|
|
184
|
+
const gamePatterns = eventScanPatterns(workspace, focused);
|
|
185
|
+
const sourceKinds = ['mod', 'dependency', 'fixture'];
|
|
186
|
+
const scans = await Promise.all([
|
|
187
|
+
engine.scan(workspaceId, { patterns, rootKinds: sourceKinds }, principal, signal),
|
|
188
|
+
...(workspace.roots.some(({ kind }) => kind === 'game')
|
|
189
|
+
? [
|
|
190
|
+
engine.scan(workspaceId, { patterns: gamePatterns, rootKinds: ['game'] }, principal, signal),
|
|
191
|
+
]
|
|
192
|
+
: []),
|
|
193
|
+
]);
|
|
194
|
+
const files = scans.flatMap(({ files }) => files.map((file) => ({ ...file })));
|
|
195
|
+
recomputeShadowing(files);
|
|
196
|
+
files.sort((left, right) => left.loadOrder - right.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
197
|
+
const index = engine.indexFiles(files);
|
|
198
|
+
return {
|
|
199
|
+
workspaceId,
|
|
200
|
+
revision: snapshotRevision(files),
|
|
201
|
+
files,
|
|
202
|
+
index,
|
|
203
|
+
complete: scans.every(({ complete }) => complete) && index.complete,
|
|
204
|
+
skippedSourceCount: index.skippedSourceCount,
|
|
205
|
+
skippedSources: index.skippedSources,
|
|
206
|
+
diagnostics: [...scans.flatMap(({ diagnostics }) => diagnostics), ...index.diagnostics],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
138
209
|
function rememberGraph(state, workspaceId, graph) {
|
|
139
210
|
const history = state.history.get(workspaceId) ?? new Map();
|
|
140
211
|
history.delete(graph.revision);
|
|
@@ -163,6 +234,20 @@ function eventIssueDiagnostic(issue) {
|
|
|
163
234
|
},
|
|
164
235
|
};
|
|
165
236
|
}
|
|
237
|
+
function boundedEvidence(values, limit = EVENT_FOCUSED_EVIDENCE_LIMIT) {
|
|
238
|
+
if (values.length <= limit)
|
|
239
|
+
return { values: [...values] };
|
|
240
|
+
return {
|
|
241
|
+
values: values.slice(0, limit),
|
|
242
|
+
inventory: { count: values.length, retainedCount: limit, truncated: true },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function boundedUnresolved(graph, selectedNodeIds) {
|
|
246
|
+
const relevant = selectedNodeIds === undefined
|
|
247
|
+
? graph.unresolved
|
|
248
|
+
: graph.unresolved.filter(({ id, ownerId }) => selectedNodeIds.has(id) || (ownerId !== undefined && selectedNodeIds.has(ownerId)));
|
|
249
|
+
return boundedEvidence(relevant);
|
|
250
|
+
}
|
|
166
251
|
export function eventDiagnostics(graph) {
|
|
167
252
|
const existing = new Set(graph.diagnostics.map(({ code, location }) => `${code}:${location?.path ?? ''}:${location?.start.offset ?? -1}`));
|
|
168
253
|
return [
|
|
@@ -617,38 +702,63 @@ export class EventChainViewer {
|
|
|
617
702
|
// principal bypass the resolver check for a later caller.
|
|
618
703
|
const workspace = this.engine.resolver.get(workspaceId, options.principal);
|
|
619
704
|
const generation = engineGeneration(this.engine, workspaceId);
|
|
620
|
-
const
|
|
621
|
-
const
|
|
705
|
+
const requestedProjectHelpers = options.projectHelpers ?? true;
|
|
706
|
+
const requestedAnalysisMode = options.analysisMode ?? 'full';
|
|
707
|
+
const autoFocused = isGameBackedWorkspace(workspace) && requestedAnalysisMode === 'full';
|
|
708
|
+
const cacheAnalysisMode = autoFocused ? 'focused' : requestedAnalysisMode;
|
|
709
|
+
const cacheProjectHelpers = cacheAnalysisMode === 'focused' ? false : requestedProjectHelpers;
|
|
710
|
+
const cacheKey = eventGraphCacheKey(workspaceId, cacheProjectHelpers, cacheAnalysisMode);
|
|
622
711
|
const cached = this.#state.current.get(cacheKey);
|
|
623
712
|
if (options.refresh !== true && cached?.generation === generation) {
|
|
624
713
|
return cached.graph;
|
|
625
714
|
}
|
|
626
|
-
const sibling = this.#state.current.get(eventGraphCacheKey(workspaceId, !
|
|
715
|
+
const sibling = this.#state.current.get(eventGraphCacheKey(workspaceId, !cacheProjectHelpers, cacheAnalysisMode)) ??
|
|
716
|
+
this.#state.current.get(eventGraphCacheKey(workspaceId, requestedProjectHelpers, requestedAnalysisMode)) ??
|
|
717
|
+
this.#state.current.get(eventGraphCacheKey(workspaceId, !requestedProjectHelpers)) ??
|
|
718
|
+
this.#state.current.get(eventGraphCacheKey(workspaceId, requestedProjectHelpers));
|
|
719
|
+
const scanFocused = autoFocused || requestedAnalysisMode === 'focused';
|
|
627
720
|
const snapshot = options.refresh !== true && sibling?.generation === generation
|
|
628
721
|
? sibling.snapshot
|
|
629
|
-
: await this.engine
|
|
722
|
+
: await scanEventSources(this.engine, workspaceId, workspace, scanFocused, options.principal, options.signal);
|
|
630
723
|
options.signal?.throwIfAborted();
|
|
631
724
|
if (cached?.snapshot.revision === snapshot.revision && cached.generation === generation) {
|
|
632
725
|
return cached.graph;
|
|
633
726
|
}
|
|
727
|
+
const buildAnalysisMode = autoFocused || snapshot.files.length > EVENT_FOCUSED_GRAPH_FILE_THRESHOLD
|
|
728
|
+
? 'focused'
|
|
729
|
+
: 'full';
|
|
730
|
+
const projectHelpers = buildAnalysisMode === 'focused' ? false : requestedProjectHelpers;
|
|
634
731
|
const graph = buildEventGraph(snapshot, {
|
|
635
732
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
636
733
|
cache: this.#state.fragments,
|
|
637
734
|
workspaceIdentity: workspace.workspaceIdentity,
|
|
638
735
|
projectHelpers,
|
|
736
|
+
analysisMode: buildAnalysisMode,
|
|
639
737
|
});
|
|
640
738
|
this.#state.current.set(cacheKey, { generation, snapshot, graph });
|
|
641
|
-
|
|
739
|
+
if (buildAnalysisMode === 'full')
|
|
740
|
+
rememberGraph(this.#state, workspaceId, graph);
|
|
642
741
|
return graph;
|
|
643
742
|
}
|
|
644
743
|
async inspect(input) {
|
|
645
744
|
input.signal?.throwIfAborted();
|
|
646
|
-
const
|
|
745
|
+
const scannedGraph = await this.scan(input.workspaceId, {
|
|
647
746
|
refresh: input.refresh ?? input.mode === 'scan',
|
|
648
|
-
|
|
747
|
+
// Focused routes traverse helper nodes directly and collapse only the
|
|
748
|
+
// selected path. Avoid materialising the full workspace projection first.
|
|
749
|
+
projectHelpers: input.mode !== 'scan' &&
|
|
750
|
+
input.mode !== 'roots' &&
|
|
751
|
+
input.mode !== 'trace' &&
|
|
752
|
+
input.mode !== 'explain_path',
|
|
753
|
+
...(input.mode === 'trace' || input.mode === 'explain_path'
|
|
754
|
+
? { analysisMode: 'focused' }
|
|
755
|
+
: {}),
|
|
649
756
|
...(input.principal === undefined ? {} : { principal: input.principal }),
|
|
650
757
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
651
758
|
});
|
|
759
|
+
// Keep the service result's graph identity distinct from a prior scan while
|
|
760
|
+
// reusing its structural cache and avoiding a second large graph build.
|
|
761
|
+
const graph = input.mode === 'trace' || input.mode === 'explain_path' ? { ...scannedGraph } : scannedGraph;
|
|
652
762
|
const maxDepth = input.maxDepth ?? 8;
|
|
653
763
|
const maxNodes = input.maxNodes ?? 500;
|
|
654
764
|
const maxEdges = input.maxEdges ?? 2_000;
|
|
@@ -671,28 +781,41 @@ export class EventChainViewer {
|
|
|
671
781
|
report = eventScanReport(graph);
|
|
672
782
|
break;
|
|
673
783
|
case 'roots':
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
784
|
+
{
|
|
785
|
+
const unresolved = boundedUnresolved(graph);
|
|
786
|
+
report = {
|
|
787
|
+
graphRevision: graph.revision,
|
|
788
|
+
sourceHashes: graph.sourceHashes,
|
|
789
|
+
roots: discoverEventRoots(graph, input.signal),
|
|
790
|
+
unresolved: unresolved.values,
|
|
791
|
+
...(unresolved.inventory === undefined
|
|
792
|
+
? {}
|
|
793
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
794
|
+
};
|
|
795
|
+
}
|
|
680
796
|
break;
|
|
681
797
|
case 'trace':
|
|
682
798
|
if (input.selector === undefined)
|
|
683
799
|
throw new ServiceError('EVENT_SELECTOR_REQUIRED', 'Trace requires a selector');
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
sourceHashes: graph.sourceHashes,
|
|
687
|
-
trace: traceSelectedEvents(graph, input.selector, {
|
|
800
|
+
{
|
|
801
|
+
const trace = traceSelectedEvents(graph, input.selector, {
|
|
688
802
|
maxDepth,
|
|
689
803
|
maxNodes,
|
|
690
804
|
maxEdges,
|
|
691
805
|
direction: input.direction ?? 'both',
|
|
692
806
|
expandHelpers: input.expandHelpers ?? false,
|
|
693
|
-
}, input.signal)
|
|
694
|
-
unresolved
|
|
695
|
-
|
|
807
|
+
}, input.signal);
|
|
808
|
+
const unresolved = boundedUnresolved(graph, new Set(trace.nodes.map(({ id }) => id)));
|
|
809
|
+
report = {
|
|
810
|
+
graphRevision: graph.revision,
|
|
811
|
+
sourceHashes: graph.sourceHashes,
|
|
812
|
+
trace,
|
|
813
|
+
unresolved: unresolved.values,
|
|
814
|
+
...(unresolved.inventory === undefined
|
|
815
|
+
? {}
|
|
816
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
817
|
+
};
|
|
818
|
+
}
|
|
696
819
|
break;
|
|
697
820
|
case 'explain_path':
|
|
698
821
|
if (input.from === undefined || input.to === undefined)
|
|
@@ -704,31 +827,54 @@ export class EventChainViewer {
|
|
|
704
827
|
};
|
|
705
828
|
break;
|
|
706
829
|
case 'state_flow':
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
830
|
+
{
|
|
831
|
+
const unresolved = boundedUnresolved(graph);
|
|
832
|
+
const issues = boundedEvidence(lintEventGraph(graph, input.selector, input.signal).filter(({ code }) => /(?:STATE|FLAG|VARIABLE|ARRAY|TARGET|SCOPE)/u.test(code)));
|
|
833
|
+
report = {
|
|
834
|
+
graphRevision: graph.revision,
|
|
835
|
+
sourceHashes: graph.sourceHashes,
|
|
836
|
+
flow: inspectEventStateFlow(graph, input.selector, input.stateSubject, input.signal),
|
|
837
|
+
issues: issues.values,
|
|
838
|
+
unresolved: unresolved.values,
|
|
839
|
+
...(issues.inventory === undefined ? {} : { issueInventory: issues.inventory }),
|
|
840
|
+
...(unresolved.inventory === undefined
|
|
841
|
+
? {}
|
|
842
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
713
845
|
break;
|
|
714
846
|
case 'lint':
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
847
|
+
{
|
|
848
|
+
const issues = boundedEvidence(lintEventGraph(graph, input.selector, input.signal));
|
|
849
|
+
const unresolved = boundedUnresolved(graph);
|
|
850
|
+
report = {
|
|
851
|
+
graphRevision: graph.revision,
|
|
852
|
+
sourceHashes: graph.sourceHashes,
|
|
853
|
+
issues: issues.values,
|
|
854
|
+
complete: graph.complete,
|
|
855
|
+
unresolved: unresolved.values,
|
|
856
|
+
...(issues.inventory === undefined ? {} : { issueInventory: issues.inventory }),
|
|
857
|
+
...(unresolved.inventory === undefined
|
|
858
|
+
? {}
|
|
859
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
860
|
+
};
|
|
861
|
+
}
|
|
722
862
|
break;
|
|
723
863
|
case 'impact':
|
|
724
864
|
if (input.impactSubject === undefined)
|
|
725
865
|
throw new ServiceError('EVENT_IMPACT_SUBJECT_REQUIRED', 'Impact analysis requires a subject');
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
866
|
+
{
|
|
867
|
+
const unresolved = boundedUnresolved(graph);
|
|
868
|
+
report = {
|
|
869
|
+
graphRevision: graph.revision,
|
|
870
|
+
sourceHashes: graph.sourceHashes,
|
|
871
|
+
impact: analyzeEventImpact(graph, input.impactSubject, input.signal),
|
|
872
|
+
unresolved: unresolved.values,
|
|
873
|
+
...(unresolved.inventory === undefined
|
|
874
|
+
? {}
|
|
875
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
732
878
|
break;
|
|
733
879
|
}
|
|
734
880
|
const graphHash = cachedEventGraphHash(graph, input.signal);
|
|
@@ -742,7 +888,7 @@ export class EventChainViewer {
|
|
|
742
888
|
: undefined;
|
|
743
889
|
const artifactUnresolved = projectedScan
|
|
744
890
|
? graph.unresolved.slice(0, EVENT_SCAN_SUMMARY_SAMPLE_LIMIT)
|
|
745
|
-
: graph.
|
|
891
|
+
: boundedUnresolved(graph).values;
|
|
746
892
|
const reportJson = `${canonicalJson({
|
|
747
893
|
schemaVersion: 'event-analysis.v1',
|
|
748
894
|
graphSchemaVersion: graph.schemaVersion,
|
|
@@ -759,12 +905,12 @@ export class EventChainViewer {
|
|
|
759
905
|
complete: graph.complete,
|
|
760
906
|
filters: analysisFilters,
|
|
761
907
|
unresolved: artifactUnresolved,
|
|
762
|
-
...(
|
|
908
|
+
...(artifactUnresolved.length !== graph.unresolved.length
|
|
763
909
|
? {
|
|
764
910
|
unresolvedInventory: {
|
|
765
911
|
count: graph.unresolved.length,
|
|
766
912
|
retainedCount: artifactUnresolved.length,
|
|
767
|
-
truncated:
|
|
913
|
+
truncated: true,
|
|
768
914
|
},
|
|
769
915
|
}
|
|
770
916
|
: {}),
|
|
@@ -950,7 +1096,15 @@ export class EventChainViewer {
|
|
|
950
1096
|
includeHtml: input.includeHtml ?? false,
|
|
951
1097
|
refresh: input.refresh ?? false,
|
|
952
1098
|
},
|
|
953
|
-
|
|
1099
|
+
...(() => {
|
|
1100
|
+
const unresolved = boundedUnresolved(graph, new Set(render.selectedNodeIds));
|
|
1101
|
+
return {
|
|
1102
|
+
unresolved: unresolved.values,
|
|
1103
|
+
...(unresolved.inventory === undefined
|
|
1104
|
+
? {}
|
|
1105
|
+
: { unresolvedInventory: unresolved.inventory }),
|
|
1106
|
+
};
|
|
1107
|
+
})(),
|
|
954
1108
|
resources: { ...resourceNames, artifacts: resourceLinks },
|
|
955
1109
|
overview: {
|
|
956
1110
|
selectedNodeIds: render.selectedNodeIds,
|