hoi4-agent-tools 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/README.md +4 -3
- package/dist/hoi4_agent_tools/core/artifacts.js +109 -4
- package/dist/hoi4_agent_tools/core/diagnostic-collector.js +28 -8
- package/dist/hoi4_agent_tools/core/diff.js +162 -4
- package/dist/hoi4_agent_tools/core/engine.js +2 -0
- package/dist/hoi4_agent_tools/core/index.js +17 -6
- package/dist/hoi4_agent_tools/core/render-budget.js +5 -3
- package/dist/hoi4_agent_tools/core/scanner.js +3 -2
- package/dist/hoi4_agent_tools/core/source/limits.js +4 -1
- package/dist/hoi4_agent_tools/event/graph.js +28 -6
- package/dist/hoi4_agent_tools/event/limits.js +1 -1
- package/dist/hoi4_agent_tools/event/service.js +103 -7
- package/dist/hoi4_agent_tools/focus/continuous-render.js +17 -8
- package/dist/hoi4_agent_tools/focus/presentation.js +96 -41
- package/dist/hoi4_agent_tools/focus/render.js +27 -16
- package/dist/hoi4_agent_tools/gui/assets.js +9 -2
- package/dist/hoi4_agent_tools/gui/inspection-artifact.js +99 -0
- package/dist/hoi4_agent_tools/gui/layout.js +7 -3
- package/dist/hoi4_agent_tools/gui/source-graph.js +61 -28
- package/dist/hoi4_agent_tools/gui/studio.js +23 -8
- package/dist/hoi4_agent_tools/map/diagnostic-limit.js +90 -9
- package/dist/hoi4_agent_tools/map/model.js +22 -4
- package/dist/hoi4_agent_tools/map/service.js +4 -3
- package/dist/hoi4_agent_tools/map/validation.js +3 -2
- package/dist/hoi4_agent_tools/mcp/server/create.js +1 -1
- package/dist/hoi4_agent_tools/mcp/server/output-schemas.js +1 -1
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +145 -223
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +29 -22
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/events.md +4 -5
- package/docs/focus.md +5 -4
- package/docs/gui.md +4 -2
- package/docs/map.md +1 -1
- package/docs/setup.md +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { boundedSourceHashEvidence, publicArtifactLink } from '../../core/artifacts.js';
|
|
3
|
-
import { compareCodeUnits, canonicalJson
|
|
4
|
-
import { comparePngImages, createTransparentPng } from '../../core/image-diff.js';
|
|
3
|
+
import { compareCodeUnits, canonicalJson } from '../../core/canonical.js';
|
|
5
4
|
import { RenderBudget } from '../../core/render-budget.js';
|
|
6
5
|
import { emptyServiceResult, ServiceError } from '../../core/result.js';
|
|
7
6
|
import { parseClausewitz } from '../../core/source/index.js';
|
|
8
7
|
import { readDependenciesFromScannedFiles } from '../../core/transactions.js';
|
|
9
|
-
import { FocusWorkbench, FOCUS_RENDER_MAX_OUTPUT_SCALE, FOCUS_RENDER_MIN_OUTPUT_SCALE, assertCompactLayoutQuality, compactFocusTreePlanAsync, focusPlanHash, focusPresentationEvidence, focusPlanningSidecarPath, importContinuousFocusPalettes, importFocusTrees, linkContinuousFocusPalettes, parseFocusPlanningSidecar,
|
|
8
|
+
import { FocusWorkbench, FOCUS_RENDER_MAX_OUTPUT_SCALE, FOCUS_RENDER_MIN_OUTPUT_SCALE, assertCompactLayoutQuality, compactFocusTreePlanAsync, focusPlanHash, focusPresentationEvidence, focusPlanningSidecarPath, importContinuousFocusPalettes, importFocusTrees, linkContinuousFocusPalettes, parseFocusPlanningSidecar, resolveFocusPresentation, enrichFocusPlanFromSidecar, } from '../../focus/index.js';
|
|
10
9
|
import { continuousFocusPaletteSchema, focusLayoutMetricsSchema, focusLayoutSchema, focusTreePlanSchema, } from '../../schemas/focus.js';
|
|
11
10
|
import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
|
|
12
11
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
@@ -308,6 +307,124 @@ const LARGE_FOCUS_RENDER_SCALE = 0.5;
|
|
|
308
307
|
function automaticFocusRenderScale(focusCount) {
|
|
309
308
|
return focusCount >= LARGE_FOCUS_RENDER_NODE_THRESHOLD ? LARGE_FOCUS_RENDER_SCALE : 1;
|
|
310
309
|
}
|
|
310
|
+
async function executeFocusVisualTool(engine, workbench, context, input, extra, rasterize) {
|
|
311
|
+
const { workspaceId: requestedWorkspaceId, relativePath } = input;
|
|
312
|
+
const workspaceId = engine.resolver.resolveWorkspaceId(requestedWorkspaceId, context.principal);
|
|
313
|
+
const outputName = rasterize ? 'raster' : 'render';
|
|
314
|
+
try {
|
|
315
|
+
const progress = progressReporter(extra);
|
|
316
|
+
await progress.report(0, 4, 'Importing and indexing focus source');
|
|
317
|
+
const snapshot = await engine.scan(workspaceId, {}, context.principal, progress.signal);
|
|
318
|
+
await progress.report(1, 4, 'Focus source index complete');
|
|
319
|
+
const workspace = engine.resolver.get(workspaceId, context.principal);
|
|
320
|
+
const renderBudget = new RenderBudget();
|
|
321
|
+
if (input.mode === 'continuous') {
|
|
322
|
+
selectedContinuousFocusFile(snapshot, relativePath);
|
|
323
|
+
const imported = await workbench.importContinuousPath(workspaceId, relativePath, context.principal);
|
|
324
|
+
const plan = selectContinuousPlan(imported.result, input.paletteId);
|
|
325
|
+
const presentation = await resolveFocusPresentation({
|
|
326
|
+
plans: [],
|
|
327
|
+
palettes: [plan],
|
|
328
|
+
files: snapshot.files,
|
|
329
|
+
index: snapshot.index,
|
|
330
|
+
scanner: engine.scanner,
|
|
331
|
+
workspace,
|
|
332
|
+
decodeIcons: rasterize,
|
|
333
|
+
budget: renderBudget,
|
|
334
|
+
signal: progress.signal,
|
|
335
|
+
});
|
|
336
|
+
await progress.report(2, 4, `Producing continuous focus ${outputName} artifacts`);
|
|
337
|
+
const rendered = await workbench.renderContinuousAndStore(workspaceId, plan, {
|
|
338
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
339
|
+
presentation,
|
|
340
|
+
sourceHashes: presentation.sourceHashes,
|
|
341
|
+
...(input.columns === undefined ? {} : { columns: input.columns }),
|
|
342
|
+
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
343
|
+
renderProfile: { sourceRevision: snapshot.revision, output: outputName },
|
|
344
|
+
rasterize,
|
|
345
|
+
budget: renderBudget,
|
|
346
|
+
signal: progress.signal,
|
|
347
|
+
});
|
|
348
|
+
const diagnostics = [...imported.result.diagnostics, ...rendered.diagnostics];
|
|
349
|
+
const result = emptyServiceResult(workspaceId, {
|
|
350
|
+
mode: 'continuous',
|
|
351
|
+
paletteId: plan.id,
|
|
352
|
+
focusCount: plan.focuses.length,
|
|
353
|
+
hashes: rendered.bundle.hashes,
|
|
354
|
+
width: rendered.bundle.width,
|
|
355
|
+
height: rendered.bundle.height,
|
|
356
|
+
});
|
|
357
|
+
result.code = rasterize ? 'CONTINUOUS_FOCUS_RASTERIZED' : 'CONTINUOUS_FOCUS_RENDERED';
|
|
358
|
+
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
359
|
+
result.diagnostics = diagnostics.slice(0, 100);
|
|
360
|
+
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
361
|
+
result.validation = validationFromDiagnostics(diagnostics);
|
|
362
|
+
await progress.report(4, 4, `Continuous focus ${outputName} complete`);
|
|
363
|
+
return toolResult(result);
|
|
364
|
+
}
|
|
365
|
+
const paletteImport = continuousPalettes(snapshot);
|
|
366
|
+
const imported = importFocusFile(snapshot, selectedFocusFile(snapshot, workspace.registration.roots.focus, relativePath), paletteImport.palettes);
|
|
367
|
+
const plan = selectPlan(imported, input.treeId);
|
|
368
|
+
const linkedPalettes = paletteImport.palettes.filter(({ id }) => plan.continuousFocusPaletteIds.includes(id));
|
|
369
|
+
const presentation = await resolveFocusPresentation({
|
|
370
|
+
plans: [plan],
|
|
371
|
+
palettes: linkedPalettes,
|
|
372
|
+
files: snapshot.files,
|
|
373
|
+
index: snapshot.index,
|
|
374
|
+
scanner: engine.scanner,
|
|
375
|
+
workspace,
|
|
376
|
+
decodeIcons: rasterize,
|
|
377
|
+
budget: renderBudget,
|
|
378
|
+
signal: progress.signal,
|
|
379
|
+
});
|
|
380
|
+
await progress.report(2, 4, `Producing focus ${outputName} artifacts`);
|
|
381
|
+
const effectiveReviewScale = input.reviewScale ?? automaticFocusRenderScale(plan.focuses.length);
|
|
382
|
+
const rendered = await workbench.renderAndStore(workspaceId, plan, {
|
|
383
|
+
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
384
|
+
index: snapshot.index,
|
|
385
|
+
references: referenceCatalog(snapshot),
|
|
386
|
+
presentation,
|
|
387
|
+
sourceHashes: presentation.sourceHashes,
|
|
388
|
+
...(input.horizontalSpacing === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: { horizontalSpacing: input.horizontalSpacing }),
|
|
391
|
+
...(input.verticalSpacing === undefined ? {} : { verticalSpacing: input.verticalSpacing }),
|
|
392
|
+
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
393
|
+
outputScale: effectiveReviewScale,
|
|
394
|
+
rasterize,
|
|
395
|
+
renderProfile: {
|
|
396
|
+
sourceRevision: snapshot.revision,
|
|
397
|
+
reviewScale: effectiveReviewScale,
|
|
398
|
+
output: outputName,
|
|
399
|
+
},
|
|
400
|
+
budget: renderBudget,
|
|
401
|
+
signal: progress.signal,
|
|
402
|
+
});
|
|
403
|
+
const diagnostics = [
|
|
404
|
+
...paletteImport.diagnostics,
|
|
405
|
+
...imported.diagnostics,
|
|
406
|
+
...rendered.diagnostics,
|
|
407
|
+
];
|
|
408
|
+
const result = emptyServiceResult(workspaceId, {
|
|
409
|
+
mode: 'national',
|
|
410
|
+
treeId: plan.id,
|
|
411
|
+
layoutHash: rendered.layout.layoutHash,
|
|
412
|
+
hashes: rendered.bundle.hashes,
|
|
413
|
+
width: rendered.bundle.width,
|
|
414
|
+
height: rendered.bundle.height,
|
|
415
|
+
});
|
|
416
|
+
result.code = rasterize ? 'FOCUS_RASTERIZED' : 'FOCUS_RENDERED';
|
|
417
|
+
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
418
|
+
result.diagnostics = diagnostics.slice(0, 100);
|
|
419
|
+
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
420
|
+
result.validation = validationFromDiagnostics(diagnostics);
|
|
421
|
+
await progress.report(4, 4, `Focus ${outputName} complete`);
|
|
422
|
+
return toolResult(result);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
return errorResult(error, workspaceId);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
311
428
|
function compactDrift(drift) {
|
|
312
429
|
return {
|
|
313
430
|
status: drift.status,
|
|
@@ -490,6 +607,7 @@ export function registerFocusTools(server, engine, context) {
|
|
|
490
607
|
index: snapshot.index,
|
|
491
608
|
scanner: engine.scanner,
|
|
492
609
|
workspace,
|
|
610
|
+
decodeIcons: false,
|
|
493
611
|
signal: progress.signal,
|
|
494
612
|
});
|
|
495
613
|
diagnostics.push(...presentation.diagnostics);
|
|
@@ -589,6 +707,7 @@ export function registerFocusTools(server, engine, context) {
|
|
|
589
707
|
index: snapshot.index,
|
|
590
708
|
scanner: engine.scanner,
|
|
591
709
|
workspace,
|
|
710
|
+
decodeIcons: false,
|
|
592
711
|
signal: progress.signal,
|
|
593
712
|
});
|
|
594
713
|
diagnostics.push(...presentation.diagnostics);
|
|
@@ -663,123 +782,18 @@ export function registerFocusTools(server, engine, context) {
|
|
|
663
782
|
});
|
|
664
783
|
server.registerTool('hoi4.focus_render', {
|
|
665
784
|
title: 'Render focus review artifacts',
|
|
666
|
-
description: 'Render a national tree or continuous palette
|
|
785
|
+
description: 'Render a national tree or continuous palette as fast deterministic HTML, SVG, JSON, and source-map artifacts. Use hoi4.focus_raster when decoded icons and PNG output are needed.',
|
|
667
786
|
inputSchema: focusRenderInput,
|
|
668
787
|
outputSchema: focusRenderOutputSchema,
|
|
669
788
|
annotations: artifactProducing,
|
|
670
|
-
},
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const renderBudget = new RenderBudget();
|
|
679
|
-
if (input.mode === 'continuous') {
|
|
680
|
-
selectedContinuousFocusFile(snapshot, relativePath);
|
|
681
|
-
const imported = await workbench.importContinuousPath(workspaceId, relativePath, context.principal);
|
|
682
|
-
const plan = selectContinuousPlan(imported.result, input.paletteId);
|
|
683
|
-
const presentation = await resolveFocusPresentation({
|
|
684
|
-
plans: [],
|
|
685
|
-
palettes: [plan],
|
|
686
|
-
files: snapshot.files,
|
|
687
|
-
index: snapshot.index,
|
|
688
|
-
scanner: engine.scanner,
|
|
689
|
-
workspace,
|
|
690
|
-
budget: renderBudget,
|
|
691
|
-
signal: progress.signal,
|
|
692
|
-
});
|
|
693
|
-
await progress.report(1, 3, 'Rendering continuous focus artifacts');
|
|
694
|
-
const rendered = await workbench.renderContinuousAndStore(workspaceId, plan, {
|
|
695
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
696
|
-
presentation,
|
|
697
|
-
sourceHashes: presentation.sourceHashes,
|
|
698
|
-
...(input.columns === undefined ? {} : { columns: input.columns }),
|
|
699
|
-
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
700
|
-
renderProfile: { sourceRevision: snapshot.revision },
|
|
701
|
-
budget: renderBudget,
|
|
702
|
-
signal: progress.signal,
|
|
703
|
-
});
|
|
704
|
-
const diagnostics = [...imported.result.diagnostics, ...rendered.diagnostics];
|
|
705
|
-
const result = emptyServiceResult(workspaceId, {
|
|
706
|
-
mode: 'continuous',
|
|
707
|
-
paletteId: plan.id,
|
|
708
|
-
focusCount: plan.focuses.length,
|
|
709
|
-
hashes: rendered.bundle.hashes,
|
|
710
|
-
width: rendered.bundle.width,
|
|
711
|
-
height: rendered.bundle.height,
|
|
712
|
-
});
|
|
713
|
-
result.code = 'CONTINUOUS_FOCUS_RENDERED';
|
|
714
|
-
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
715
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
716
|
-
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
717
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
718
|
-
await progress.report(3, 3, 'Continuous focus render complete');
|
|
719
|
-
return toolResult(result);
|
|
720
|
-
}
|
|
721
|
-
const paletteImport = continuousPalettes(snapshot);
|
|
722
|
-
const imported = importFocusFile(snapshot, selectedFocusFile(snapshot, workspace.registration.roots.focus, relativePath), paletteImport.palettes);
|
|
723
|
-
const plan = selectPlan(imported, input.treeId);
|
|
724
|
-
const linkedPalettes = paletteImport.palettes.filter(({ id }) => plan.continuousFocusPaletteIds.includes(id));
|
|
725
|
-
const presentation = await resolveFocusPresentation({
|
|
726
|
-
plans: [plan],
|
|
727
|
-
palettes: linkedPalettes,
|
|
728
|
-
files: snapshot.files,
|
|
729
|
-
index: snapshot.index,
|
|
730
|
-
scanner: engine.scanner,
|
|
731
|
-
workspace,
|
|
732
|
-
budget: renderBudget,
|
|
733
|
-
signal: progress.signal,
|
|
734
|
-
});
|
|
735
|
-
await progress.report(1, 3, 'Rendering focus artifacts');
|
|
736
|
-
const effectiveReviewScale = input.reviewScale ?? automaticFocusRenderScale(plan.focuses.length);
|
|
737
|
-
const rendered = await workbench.renderAndStore(workspaceId, plan, {
|
|
738
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
739
|
-
index: snapshot.index,
|
|
740
|
-
references: referenceCatalog(snapshot),
|
|
741
|
-
presentation,
|
|
742
|
-
sourceHashes: presentation.sourceHashes,
|
|
743
|
-
...(input.horizontalSpacing === undefined
|
|
744
|
-
? {}
|
|
745
|
-
: { horizontalSpacing: input.horizontalSpacing }),
|
|
746
|
-
...(input.verticalSpacing === undefined
|
|
747
|
-
? {}
|
|
748
|
-
: { verticalSpacing: input.verticalSpacing }),
|
|
749
|
-
...(input.padding === undefined ? {} : { padding: input.padding }),
|
|
750
|
-
outputScale: effectiveReviewScale,
|
|
751
|
-
renderProfile: {
|
|
752
|
-
sourceRevision: snapshot.revision,
|
|
753
|
-
reviewScale: effectiveReviewScale,
|
|
754
|
-
},
|
|
755
|
-
budget: renderBudget,
|
|
756
|
-
signal: progress.signal,
|
|
757
|
-
});
|
|
758
|
-
const diagnostics = [
|
|
759
|
-
...paletteImport.diagnostics,
|
|
760
|
-
...imported.diagnostics,
|
|
761
|
-
...rendered.diagnostics,
|
|
762
|
-
];
|
|
763
|
-
const result = emptyServiceResult(workspaceId, {
|
|
764
|
-
mode: 'national',
|
|
765
|
-
treeId: plan.id,
|
|
766
|
-
layoutHash: rendered.layout.layoutHash,
|
|
767
|
-
hashes: rendered.bundle.hashes,
|
|
768
|
-
width: rendered.bundle.width,
|
|
769
|
-
height: rendered.bundle.height,
|
|
770
|
-
});
|
|
771
|
-
result.code = 'FOCUS_RENDERED';
|
|
772
|
-
setInlineFilesScanned(result, [...new Set([plan.provenance.sourcePath, ...presentation.filesScanned])].sort((left, right) => compareCodeUnits(left, right)));
|
|
773
|
-
result.diagnostics = diagnostics.slice(0, 100);
|
|
774
|
-
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
775
|
-
result.validation = validationFromDiagnostics(diagnostics);
|
|
776
|
-
await progress.report(3, 3, 'Focus render complete');
|
|
777
|
-
return toolResult(result);
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
return errorResult(error, workspaceId);
|
|
781
|
-
}
|
|
782
|
-
});
|
|
789
|
+
}, (input, extra) => executeFocusVisualTool(engine, workbench, context, input, extra, false));
|
|
790
|
+
server.registerTool('hoi4.focus_raster', {
|
|
791
|
+
title: 'Rasterize focus review artifacts',
|
|
792
|
+
description: 'Produce the high-fidelity focus review with decoded source icons and deterministic PNG output. Use focus_render for the faster structural HTML, SVG, and JSON view.',
|
|
793
|
+
inputSchema: focusRenderInput,
|
|
794
|
+
outputSchema: focusRenderOutputSchema,
|
|
795
|
+
annotations: artifactProducing,
|
|
796
|
+
}, (input, extra) => executeFocusVisualTool(engine, workbench, context, input, extra, true));
|
|
783
797
|
server.registerTool('hoi4.focus_rewrite', {
|
|
784
798
|
title: 'Create or clean up focus content',
|
|
785
799
|
description: 'Create or clean up a national tree or continuous palette, validate it, and apply it in one call. Supply a complete plan; set layoutMode compact for automatic arrangement. Existing national trees can omit the plan and use treeId plus layoutMode compact. Set createIfMissing for a new file.',
|
|
@@ -831,20 +845,11 @@ export function registerFocusTools(server, engine, context) {
|
|
|
831
845
|
index: snapshot.index,
|
|
832
846
|
scanner: engine.scanner,
|
|
833
847
|
workspace,
|
|
848
|
+
decodeIcons: false,
|
|
834
849
|
budget: renderBudget,
|
|
835
850
|
signal: progress.signal,
|
|
836
851
|
});
|
|
837
|
-
|
|
838
|
-
? []
|
|
839
|
-
: [...(imported?.result.diagnostics ?? []), ...workbench.lintContinuous(currentPlan)];
|
|
840
|
-
await progress.report(1, 5, 'Rendering before and proposed continuous focus plans');
|
|
841
|
-
const currentRender = currentPlan === undefined
|
|
842
|
-
? undefined
|
|
843
|
-
: await renderContinuousFocusPalette(currentPlan, currentDiagnostics, {
|
|
844
|
-
presentation,
|
|
845
|
-
signal: progress.signal,
|
|
846
|
-
budget: renderBudget,
|
|
847
|
-
});
|
|
852
|
+
await progress.report(1, 5, 'Building proposed continuous focus review');
|
|
848
853
|
const proposedPlanHash = focusPlanHash(plan);
|
|
849
854
|
const reviewPlan = {
|
|
850
855
|
...plan,
|
|
@@ -858,47 +863,16 @@ export function registerFocusTools(server, engine, context) {
|
|
|
858
863
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
859
864
|
presentation,
|
|
860
865
|
sourceHashes: presentation.sourceHashes,
|
|
861
|
-
renderProfile: {
|
|
866
|
+
renderProfile: {
|
|
867
|
+
sourceRevision: snapshot.revision,
|
|
868
|
+
proposedPlanHash,
|
|
869
|
+
output: 'vector',
|
|
870
|
+
},
|
|
871
|
+
rasterize: false,
|
|
862
872
|
budget: renderBudget,
|
|
863
873
|
signal: progress.signal,
|
|
864
874
|
});
|
|
865
|
-
const
|
|
866
|
-
(await createTransparentPng(proposed.bundle.width, proposed.bundle.height, 'new continuous focus baseline', renderBudget));
|
|
867
|
-
const comparison = await comparePngImages(beforePng, proposed.bundle.png, 8, progress.signal, renderBudget);
|
|
868
|
-
const stem = plan.id.replace(/[^A-Za-z0-9._-]/gu, '_').slice(0, 80) || 'continuous';
|
|
869
|
-
const provenance = {
|
|
870
|
-
toolVersion: PACKAGE_VERSION,
|
|
871
|
-
schemaVersion: 'continuous-focus-visual-diff.v1',
|
|
872
|
-
sourceHashes: {
|
|
873
|
-
before: sourceFile?.sha256 ?? sha256Bytes(Buffer.alloc(0)),
|
|
874
|
-
proposedPlan: proposedPlanHash,
|
|
875
|
-
},
|
|
876
|
-
renderProfile: { offline: true, kind: 'continuous-focus-palette' },
|
|
877
|
-
};
|
|
878
|
-
const comparisonArtifacts = await engine.artifacts.withAtomicWrites(workspace, [
|
|
879
|
-
{
|
|
880
|
-
name: `${stem}.continuous-focus-before.png`,
|
|
881
|
-
mimeType: 'image/png',
|
|
882
|
-
content: beforePng,
|
|
883
|
-
provenance: { ...provenance, kind: 'continuous-focus-before-render' },
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
name: `${stem}.continuous-focus-visual-diff.png`,
|
|
887
|
-
mimeType: 'image/png',
|
|
888
|
-
content: comparison.png,
|
|
889
|
-
provenance: { ...provenance, kind: 'continuous-focus-visual-diff' },
|
|
890
|
-
},
|
|
891
|
-
{
|
|
892
|
-
name: `${stem}.continuous-focus-visual-diff.json`,
|
|
893
|
-
mimeType: 'application/json',
|
|
894
|
-
content: comparison.json,
|
|
895
|
-
provenance: { ...provenance, kind: 'continuous-focus-visual-diff-json' },
|
|
896
|
-
},
|
|
897
|
-
], (stored) => Promise.resolve([...stored]), progress.signal);
|
|
898
|
-
const reviewArtifacts = [
|
|
899
|
-
...proposed.artifacts.map(publicArtifactLink),
|
|
900
|
-
...comparisonArtifacts.map(publicArtifactLink),
|
|
901
|
-
];
|
|
875
|
+
const reviewArtifacts = proposed.artifacts.map(publicArtifactLink);
|
|
902
876
|
await progress.report(3, 5, 'Preparing the continuous focus rewrite');
|
|
903
877
|
const planned = await workbench.planContinuousChanges({
|
|
904
878
|
workspaceId,
|
|
@@ -1029,6 +1003,7 @@ export function registerFocusTools(server, engine, context) {
|
|
|
1029
1003
|
index: snapshot.index,
|
|
1030
1004
|
scanner: engine.scanner,
|
|
1031
1005
|
workspace,
|
|
1006
|
+
decodeIcons: false,
|
|
1032
1007
|
budget: renderBudget,
|
|
1033
1008
|
signal: progress.signal,
|
|
1034
1009
|
});
|
|
@@ -1037,22 +1012,7 @@ export function registerFocusTools(server, engine, context) {
|
|
|
1037
1012
|
: currentPlan === compactSourcePlan && compactPlanning !== undefined
|
|
1038
1013
|
? compactPlanning.currentLayout
|
|
1039
1014
|
: await workbench.layoutAsync(currentPlan, { signal: progress.signal });
|
|
1040
|
-
|
|
1041
|
-
? []
|
|
1042
|
-
: workbench.lint(currentPlan, {
|
|
1043
|
-
index: snapshot.index,
|
|
1044
|
-
references: catalog,
|
|
1045
|
-
layout: currentLayout,
|
|
1046
|
-
});
|
|
1047
|
-
await progress.report(1, 5, 'Rendering before and proposed focus plans');
|
|
1048
|
-
const currentRender = currentPlan === undefined || currentLayout === undefined
|
|
1049
|
-
? undefined
|
|
1050
|
-
: await renderFocusTree(currentPlan, currentLayout, currentDiagnostics, {
|
|
1051
|
-
presentation,
|
|
1052
|
-
...reviewRenderOptions,
|
|
1053
|
-
signal: progress.signal,
|
|
1054
|
-
budget: renderBudget,
|
|
1055
|
-
});
|
|
1015
|
+
await progress.report(1, 5, 'Building proposed focus review');
|
|
1056
1016
|
const proposedPlanHash = focusPlanHash(plan);
|
|
1057
1017
|
const reviewPlan = {
|
|
1058
1018
|
...plan,
|
|
@@ -1074,56 +1034,18 @@ export function registerFocusTools(server, engine, context) {
|
|
|
1074
1034
|
presentation,
|
|
1075
1035
|
sourceHashes: presentation.sourceHashes,
|
|
1076
1036
|
...reviewRenderOptions,
|
|
1037
|
+
rasterize: false,
|
|
1077
1038
|
renderProfile: {
|
|
1078
1039
|
sourceRevision: snapshot.revision,
|
|
1079
1040
|
proposedPlanHash,
|
|
1080
1041
|
reviewRenderProfile,
|
|
1042
|
+
output: 'vector',
|
|
1081
1043
|
},
|
|
1082
1044
|
budget: renderBudget,
|
|
1083
1045
|
signal: progress.signal,
|
|
1084
1046
|
layout: proposedLayout,
|
|
1085
1047
|
});
|
|
1086
|
-
const
|
|
1087
|
-
(await createTransparentPng(proposed.bundle.width, proposed.bundle.height, 'new national focus baseline', renderBudget));
|
|
1088
|
-
const comparison = await comparePngImages(beforePng, proposed.bundle.png, 8, progress.signal, renderBudget);
|
|
1089
|
-
const stem = plan.id.replace(/[^A-Za-z0-9._-]/gu, '_').slice(0, 80) || 'focus-tree';
|
|
1090
|
-
const provenance = {
|
|
1091
|
-
toolVersion: PACKAGE_VERSION,
|
|
1092
|
-
schemaVersion: 'focus-visual-diff.v1',
|
|
1093
|
-
sourceHashes: {
|
|
1094
|
-
before: focusFile?.sha256 ?? sha256Bytes(Buffer.alloc(0)),
|
|
1095
|
-
proposedPlan: proposedPlanHash,
|
|
1096
|
-
},
|
|
1097
|
-
renderProfile: {
|
|
1098
|
-
offline: true,
|
|
1099
|
-
layoutHash: proposed.layout.layoutHash,
|
|
1100
|
-
reviewRenderProfile,
|
|
1101
|
-
},
|
|
1102
|
-
};
|
|
1103
|
-
const comparisonArtifacts = await engine.artifacts.withAtomicWrites(workspace, [
|
|
1104
|
-
{
|
|
1105
|
-
name: `${stem}.focus-before.png`,
|
|
1106
|
-
mimeType: 'image/png',
|
|
1107
|
-
content: beforePng,
|
|
1108
|
-
provenance: { ...provenance, kind: 'focus-before-render' },
|
|
1109
|
-
},
|
|
1110
|
-
{
|
|
1111
|
-
name: `${stem}.focus-visual-diff.png`,
|
|
1112
|
-
mimeType: 'image/png',
|
|
1113
|
-
content: comparison.png,
|
|
1114
|
-
provenance: { ...provenance, kind: 'focus-visual-diff' },
|
|
1115
|
-
},
|
|
1116
|
-
{
|
|
1117
|
-
name: `${stem}.focus-visual-diff.json`,
|
|
1118
|
-
mimeType: 'application/json',
|
|
1119
|
-
content: comparison.json,
|
|
1120
|
-
provenance: { ...provenance, kind: 'focus-visual-diff-json' },
|
|
1121
|
-
},
|
|
1122
|
-
], (stored) => Promise.resolve([...stored]), progress.signal);
|
|
1123
|
-
const reviewArtifacts = [
|
|
1124
|
-
...proposed.artifacts.map(publicArtifactLink),
|
|
1125
|
-
...comparisonArtifacts.map(publicArtifactLink),
|
|
1126
|
-
];
|
|
1048
|
+
const reviewArtifacts = proposed.artifacts.map(publicArtifactLink);
|
|
1127
1049
|
await progress.report(3, 5, 'Preparing the focus-tree rewrite');
|
|
1128
1050
|
const planned = await workbench.planChanges({
|
|
1129
1051
|
workspaceId,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { boundedSourceHashEvidence, publicArtifactLink } from '../../core/artifacts.js';
|
|
3
|
-
import { canonicalJson, compareCodeUnits } from '../../core/canonical.js';
|
|
3
|
+
import { canonicalJson, compareCodeUnits, hashCanonical } from '../../core/canonical.js';
|
|
4
4
|
import { renderDimensionViolation, RenderBudget, RENDER_MAX_DIMENSION, } from '../../core/render-budget.js';
|
|
5
5
|
import { emptyServiceResult, ServiceError } from '../../core/result.js';
|
|
6
6
|
import { SOURCE_MAX_BYTES } from '../../core/source/index.js';
|
|
7
7
|
import { GuiHelperDocumentSchema, GuiPreviewScenarioSchema, GUI_TEXT_PACKAGE_MAX_FILES, ScriptedGuiStudio, compileGuiHelpers, } from '../../gui/index.js';
|
|
8
|
+
import { encodeGuiInspectionArtifact, projectGuiGraphForArtifact, } from '../../gui/inspection-artifact.js';
|
|
8
9
|
import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
|
|
9
10
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
10
11
|
import { requireServerScope } from '../server/base-tools.js';
|
|
@@ -153,7 +154,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
153
154
|
const workspaceId = engine.resolver.resolveWorkspaceId(requestedWorkspaceId, context.principal);
|
|
154
155
|
try {
|
|
155
156
|
const progress = progressReporter(extra);
|
|
156
|
-
await progress.report(0, 3, 'Building
|
|
157
|
+
await progress.report(0, 3, 'Building GUI source graph');
|
|
157
158
|
const inspection = windowName === undefined || scenario === undefined
|
|
158
159
|
? studio
|
|
159
160
|
.scan(workspaceId, context.principal, progress.signal)
|
|
@@ -168,18 +169,21 @@ export function registerGuiTools(server, engine, context) {
|
|
|
168
169
|
signal: progress.signal,
|
|
169
170
|
})
|
|
170
171
|
.then((linted) => ({ graph: linted.graph, linted }));
|
|
171
|
-
const
|
|
172
|
-
engine.scan(workspaceId, {}, context.principal, progress.signal),
|
|
173
|
-
inspection,
|
|
174
|
-
]);
|
|
172
|
+
const inspected = await inspection;
|
|
175
173
|
const { graph, linted } = inspected;
|
|
174
|
+
const sharedRevision = hashCanonical(graph.sourceHashes);
|
|
176
175
|
const workspace = engine.resolver.get(workspaceId, context.principal);
|
|
177
176
|
const sourceEvidence = boundedSourceHashEvidence(graph.sourceHashes);
|
|
178
|
-
const
|
|
177
|
+
const artifactGraph = projectGuiGraphForArtifact(graph, linted?.scene.elements.map(({ sourceId }) => sourceId));
|
|
178
|
+
const inspectionName = `gui-inspect.${sharedRevision.slice(0, 16)}.json`;
|
|
179
|
+
const inspectionJson = `${canonicalJson({
|
|
179
180
|
schemaVersion: 1,
|
|
180
|
-
sharedRevision
|
|
181
|
+
sharedRevision,
|
|
181
182
|
offline: true,
|
|
182
|
-
graph,
|
|
183
|
+
graph: artifactGraph.graph,
|
|
184
|
+
...(artifactGraph.projection === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: { graphProjection: artifactGraph.projection }),
|
|
183
187
|
...(linted === undefined
|
|
184
188
|
? {}
|
|
185
189
|
: {
|
|
@@ -187,25 +191,29 @@ export function registerGuiTools(server, engine, context) {
|
|
|
187
191
|
fidelity: linted.scene.fidelity,
|
|
188
192
|
validation: linted.validation,
|
|
189
193
|
}),
|
|
190
|
-
})}\n
|
|
194
|
+
})}\n`;
|
|
195
|
+
const encodedInspection = await encodeGuiInspectionArtifact(inspectionName, inspectionJson);
|
|
196
|
+
const artifact = await engine.artifacts.putChunked(workspace, encodedInspection.name, encodedInspection.mimeType, encodedInspection.content, {
|
|
191
197
|
kind: 'gui-inspect',
|
|
192
198
|
toolVersion: PACKAGE_VERSION,
|
|
193
199
|
schemaVersion: 'gui-inspect.v1',
|
|
194
200
|
sourceHashes: sourceEvidence.sourceHashes,
|
|
195
201
|
metadata: {
|
|
196
|
-
sharedRevision
|
|
202
|
+
sharedRevision,
|
|
197
203
|
offline: true,
|
|
198
204
|
complete: graph.complete,
|
|
199
205
|
skippedSourceCount: graph.skippedSourceCount,
|
|
200
206
|
sourceHashInventory: sourceEvidence.inventory,
|
|
207
|
+
compressed: encodedInspection.compressed,
|
|
208
|
+
uncompressedBytes: encodedInspection.uncompressedBytes,
|
|
209
|
+
graphProjection: artifactGraph.projection?.mode ?? 'full',
|
|
201
210
|
},
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
];
|
|
211
|
+
}, encodedInspection.compressed
|
|
212
|
+
? 'Gzip-compressed scripted GUI graph, diagnostics, and optional scenario fidelity inspection'
|
|
213
|
+
: 'Scripted GUI graph, diagnostics, and optional scenario fidelity inspection', progress.signal);
|
|
214
|
+
const diagnostics = linted === undefined ? graph.diagnostics : linted.validation.diagnostics;
|
|
207
215
|
const result = emptyServiceResult(workspaceId, {
|
|
208
|
-
sharedRevision
|
|
216
|
+
sharedRevision,
|
|
209
217
|
complete: graph.complete,
|
|
210
218
|
skippedSourceCount: graph.skippedSourceCount,
|
|
211
219
|
skippedSources: graph.skippedSources,
|
|
@@ -258,9 +266,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
258
266
|
const { windowName } = input;
|
|
259
267
|
try {
|
|
260
268
|
const progress = progressReporter(extra);
|
|
261
|
-
await progress.report(0, 4, 'Building
|
|
262
|
-
const shared = await engine.scan(workspaceId, {}, context.principal, progress.signal);
|
|
263
|
-
await progress.report(1, 4, 'Rendering offline GUI artifacts');
|
|
269
|
+
await progress.report(0, 4, 'Building GUI source graph');
|
|
264
270
|
const rendered = await studio.renderAndStore({
|
|
265
271
|
workspaceId,
|
|
266
272
|
windowName,
|
|
@@ -281,7 +287,8 @@ export function registerGuiTools(server, engine, context) {
|
|
|
281
287
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
282
288
|
signal: progress.signal,
|
|
283
289
|
});
|
|
284
|
-
|
|
290
|
+
await progress.report(3, 4, 'Preparing GUI result');
|
|
291
|
+
const diagnostics = rendered.validation.diagnostics;
|
|
285
292
|
const result = emptyServiceResult(workspaceId, {
|
|
286
293
|
windowName,
|
|
287
294
|
scenarioId: rendered.render.scene.scenario.id,
|
|
@@ -302,7 +309,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
302
309
|
offlineRepresentation: true,
|
|
303
310
|
});
|
|
304
311
|
result.code = code;
|
|
305
|
-
setInlineFilesScanned(result,
|
|
312
|
+
setInlineFilesScanned(result, rendered.filesScanned);
|
|
306
313
|
result.diagnostics = diagnostics.slice(0, 100);
|
|
307
314
|
result.artifacts = rendered.artifacts.map(publicArtifactLink);
|
|
308
315
|
result.validation = validationSummary({
|
|
@@ -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 = '2.
|
|
3
|
+
export const PACKAGE_VERSION = '2.1.1';
|
|
4
4
|
export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
|
|
5
5
|
export const CONFIG_VERSION = 1;
|
|
6
6
|
export const TRANSACTION_VERSION = 1;
|
package/docs/events.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The Event Chain Viewer analyzes event definitions and call sites across the active mod, configured dependencies, and vanilla sources. It is read-only: it does not rewrite event files or simulate the game.
|
|
4
4
|
|
|
5
|
-
Use the three event tools directly
|
|
5
|
+
Use the three event tools directly from the target mod.
|
|
6
6
|
|
|
7
7
|
## Inspect
|
|
8
8
|
|
|
@@ -16,7 +16,9 @@ Use the three event tools directly; omit `workspaceId` when the MCP working dire
|
|
|
16
16
|
- `lint`: find missing targets, unreachable events, suspicious cycles, conflicting definitions, invalid timing, and unresolved dynamic calls.
|
|
17
17
|
- `impact`: identify callers, descendants, state dependencies, and files affected by a proposed event change.
|
|
18
18
|
|
|
19
|
-
Use narrow identifiers, direction, and depth limits when the task concerns one chain.
|
|
19
|
+
Use narrow identifiers, direction, and depth limits when the task concerns one chain. Broad scans build the structural event graph without expanding every scripted helper into duplicate paths. Focused trace, path, state, impact, and render calls expand helper relationships when needed.
|
|
20
|
+
|
|
21
|
+
The compact response summarizes the result. Small scan resources contain the complete graph; very large scan resources contain exact totals, grouped inventories, representative diagnostics, and the revision needed for focused follow-up queries. This keeps an agent's prompt and artifact storage bounded without reducing the results of those focused queries.
|
|
20
22
|
|
|
21
23
|
Selectors use one of these forms:
|
|
22
24
|
|
|
@@ -37,7 +39,6 @@ Example `hoi4.event_inspect` arguments:
|
|
|
37
39
|
|
|
38
40
|
```json
|
|
39
41
|
{
|
|
40
|
-
"workspaceId": "my-mod",
|
|
41
42
|
"mode": "trace",
|
|
42
43
|
"selector": { "kind": "event", "eventId": "namespace.1" },
|
|
43
44
|
"direction": "downstream",
|
|
@@ -61,7 +62,6 @@ Example `hoi4.event_render` arguments:
|
|
|
61
62
|
|
|
62
63
|
```json
|
|
63
64
|
{
|
|
64
|
-
"workspaceId": "my-mod",
|
|
65
65
|
"view": "neighborhood",
|
|
66
66
|
"selector": { "kind": "event", "eventId": "namespace.1" },
|
|
67
67
|
"direction": "both",
|
|
@@ -83,7 +83,6 @@ Example `hoi4.event_compare` arguments:
|
|
|
83
83
|
|
|
84
84
|
```json
|
|
85
85
|
{
|
|
86
|
-
"workspaceId": "my-mod",
|
|
87
86
|
"proposedSources": [
|
|
88
87
|
{
|
|
89
88
|
"relativePath": "events/example.txt",
|