hoi4-agent-tools 3.2.0 → 3.4.0
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 +19 -0
- package/README.md +4 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +11 -1
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +18 -9
- package/dist/hoi4_agent_tools/core/helper-expansion.js +3 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +3 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +7 -0
- package/dist/hoi4_agent_tools/core/scenario-model.js +85 -0
- package/dist/hoi4_agent_tools/event/comparison-selection.js +78 -0
- package/dist/hoi4_agent_tools/event/helper-expansion.js +48 -0
- package/dist/hoi4_agent_tools/event/operations.js +16 -0
- package/dist/hoi4_agent_tools/event/service.js +21 -2
- package/dist/hoi4_agent_tools/focus/compact.js +241 -27
- package/dist/hoi4_agent_tools/focus/layout.js +119 -8
- package/dist/hoi4_agent_tools/focus/render.js +62 -0
- package/dist/hoi4_agent_tools/focus/scenario.js +99 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +30 -4
- package/dist/hoi4_agent_tools/gui/assets.js +54 -4
- package/dist/hoi4_agent_tools/gui/layout.js +935 -104
- package/dist/hoi4_agent_tools/gui/renderer.js +47 -17
- package/dist/hoi4_agent_tools/gui/scenario-conditions.js +2 -23
- package/dist/hoi4_agent_tools/gui/shader.js +515 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js +166 -6
- package/dist/hoi4_agent_tools/gui/studio.js +245 -19
- package/dist/hoi4_agent_tools/gui/tool-operations.js +9 -0
- package/dist/hoi4_agent_tools/gui/validators.js +38 -12
- package/dist/hoi4_agent_tools/map/operations.js +70 -1
- package/dist/hoi4_agent_tools/map/render.js +70 -1
- package/dist/hoi4_agent_tools/map/service.js +30 -6
- package/dist/hoi4_agent_tools/map/tool-operations.js +67 -1
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +1 -1
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +3 -0
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +90 -0
- package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -1
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +14 -4
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +5 -1
- package/dist/hoi4_agent_tools/mcp/tools/map.js +12 -2
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +5 -0
- package/dist/hoi4_agent_tools/mechanic/assertions.js +112 -0
- package/dist/hoi4_agent_tools/mechanic/interpreter.js +619 -0
- package/dist/hoi4_agent_tools/mechanic/service.js +145 -0
- package/dist/hoi4_agent_tools/mechanic/source.js +89 -0
- package/dist/hoi4_agent_tools/package-check/service.js +231 -0
- package/dist/hoi4_agent_tools/probability/evaluation.js +6 -1
- package/dist/hoi4_agent_tools/probability/operations.js +8 -0
- package/dist/hoi4_agent_tools/probability/service.js +33 -12
- package/dist/hoi4_agent_tools/probability/source-analysis.js +1 -0
- package/dist/hoi4_agent_tools/scenario-suite/service.js +341 -0
- package/dist/hoi4_agent_tools/schemas/event.js +8 -0
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +40 -1
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +8 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +22 -1
- package/dist/hoi4_agent_tools/schemas/scenarios.js +264 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +7 -0
- package/dist/hoi4_agent_tools/technology/graph.js +25 -10
- package/dist/hoi4_agent_tools/technology/render.js +286 -31
- package/dist/hoi4_agent_tools/technology/service.js +55 -8
- package/dist/hoi4_agent_tools/technology/source-analysis.js +166 -3
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +1 -0
- package/docs/events.md +3 -1
- package/docs/focus.md +10 -0
- package/docs/gui.md +49 -3
- package/docs/map.md +8 -0
- package/docs/mechanics.md +70 -0
- package/docs/probability.md +3 -1
- package/docs/technology.md +7 -2
- package/package.json +2 -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/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 +650 -0
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +164 -0
- package/schemas/package-manifest.schema.json +142 -0
- 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/scenario-suite.schema.json +1004 -0
- package/schemas/scenario-test-input.schema.json +1057 -0
- package/schemas/shared-scenario.schema.json +237 -0
- package/server.json +2 -2
|
@@ -357,7 +357,50 @@ export async function renderMap(index, options = {}) {
|
|
|
357
357
|
throw new ServiceError('MAP_RENDER_SCALE_INVALID', 'Map render scale must be an integer from 1 through 16');
|
|
358
358
|
const budget = options.budget ?? new RenderBudget();
|
|
359
359
|
assertRenderDimensions(raster.width, raster.height, 'map RGBA source plane');
|
|
360
|
-
|
|
360
|
+
let tile = options.tile;
|
|
361
|
+
if (options.area !== undefined) {
|
|
362
|
+
if (tile !== undefined)
|
|
363
|
+
throw new ServiceError('MAP_RENDER_AREA_CONFLICT', 'Choose a map tile or entity area');
|
|
364
|
+
const ids = new Set(options.area.provinceIds ?? []);
|
|
365
|
+
for (const stateId of options.area.stateIds ?? []) {
|
|
366
|
+
const state = index.statesById.get(stateId);
|
|
367
|
+
if (state === undefined)
|
|
368
|
+
throw new ServiceError('MAP_AREA_STATE_MISSING', 'Map area state ID is missing', {
|
|
369
|
+
stateId,
|
|
370
|
+
});
|
|
371
|
+
for (const id of state.provinces)
|
|
372
|
+
ids.add(id);
|
|
373
|
+
}
|
|
374
|
+
for (const regionId of options.area.regionIds ?? []) {
|
|
375
|
+
const region = index.regionsById.get(regionId);
|
|
376
|
+
if (region === undefined)
|
|
377
|
+
throw new ServiceError('MAP_AREA_REGION_MISSING', 'Map area region ID is missing', {
|
|
378
|
+
regionId,
|
|
379
|
+
});
|
|
380
|
+
for (const id of region.provinces)
|
|
381
|
+
ids.add(id);
|
|
382
|
+
}
|
|
383
|
+
if (ids.size === 0)
|
|
384
|
+
throw new ServiceError('MAP_AREA_EMPTY', 'Map area has no provinces');
|
|
385
|
+
const geometry = [...ids].map((id) => {
|
|
386
|
+
const province = raster.geometry.get(id);
|
|
387
|
+
if (province === undefined)
|
|
388
|
+
throw new ServiceError('MAP_AREA_PROVINCE_MISSING', 'Map area province ID has no raster geometry', { provinceId: id });
|
|
389
|
+
return province;
|
|
390
|
+
});
|
|
391
|
+
const padding = options.area.padding ?? 32;
|
|
392
|
+
const x = Math.max(0, Math.min(...geometry.map(({ minX }) => minX)) - padding);
|
|
393
|
+
const y = Math.max(0, Math.min(...geometry.map(({ minY }) => minY)) - padding);
|
|
394
|
+
const right = Math.min(raster.width, Math.max(...geometry.map(({ maxX }) => maxX)) + padding + 1);
|
|
395
|
+
const bottom = Math.min(raster.height, Math.max(...geometry.map(({ maxY }) => maxY)) + padding + 1);
|
|
396
|
+
tile = { x, y, width: right - x, height: bottom - y };
|
|
397
|
+
if (tile.width > 2_048 || tile.height > 2_048)
|
|
398
|
+
throw new ServiceError('MAP_AREA_TOO_LARGE', 'Map area exceeds the tile limit; select fewer entities', { tile });
|
|
399
|
+
}
|
|
400
|
+
if (tile !== undefined &&
|
|
401
|
+
(tile.x + tile.width > raster.width || tile.y + tile.height > raster.height))
|
|
402
|
+
throw new ServiceError('MAP_TILE_OUT_OF_BOUNDS', 'Map tile extends beyond the province raster', { tile, rasterWidth: raster.width, rasterHeight: raster.height });
|
|
403
|
+
const outputDimensions = budget.reserve((tile?.width ?? raster.width) * resolved.scale, (tile?.height ?? raster.height) * resolved.scale, 'map PNG output');
|
|
361
404
|
const raw = Buffer.alloc(raster.width * raster.height * 4);
|
|
362
405
|
for (let offset = 0; offset < raster.provinceIds.length; offset += 1) {
|
|
363
406
|
if (offset % Math.max(raster.width * 32, 1) === 0)
|
|
@@ -371,6 +414,32 @@ export async function renderMap(index, options = {}) {
|
|
|
371
414
|
}
|
|
372
415
|
await applyOverlays(index, raw, new Set(resolved.overlays), options.signal);
|
|
373
416
|
options.signal?.throwIfAborted();
|
|
417
|
+
if (tile !== undefined) {
|
|
418
|
+
const tileRaw = Buffer.alloc(tile.width * tile.height * 4);
|
|
419
|
+
const provinceIds = new Set();
|
|
420
|
+
for (let row = 0; row < tile.height; row += 1) {
|
|
421
|
+
if (row % 32 === 0)
|
|
422
|
+
await renderCheckpoint(options.signal);
|
|
423
|
+
raw.copy(tileRaw, row * tile.width * 4, ((tile.y + row) * raster.width + tile.x) * 4, ((tile.y + row) * raster.width + tile.x + tile.width) * 4);
|
|
424
|
+
for (let column = 0; column < tile.width; column += 1) {
|
|
425
|
+
const id = raster.provinceIds[(tile.y + row) * raster.width + tile.x + column];
|
|
426
|
+
if (id !== undefined && id >= 0)
|
|
427
|
+
provinceIds.add(id);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const png = await encodePng(tileRaw, tile.width, tile.height, resolved.scale);
|
|
431
|
+
const json = `${canonicalJson({ schemaVersion: 'map-tile.v1', layer: resolved.layer, overlays: resolved.overlays, scale: resolved.scale, tile, raster: { width: raster.width, height: raster.height }, provinceIds: [...provinceIds].sort((left, right) => left - right) })}\n`;
|
|
432
|
+
const html = diffHtmlDocument(`HOI4 map tile - ${resolved.layer}`, png, json);
|
|
433
|
+
return {
|
|
434
|
+
width: outputDimensions.width,
|
|
435
|
+
height: outputDimensions.height,
|
|
436
|
+
tile,
|
|
437
|
+
png,
|
|
438
|
+
json,
|
|
439
|
+
html,
|
|
440
|
+
hashes: { png: sha256Bytes(png), json: sha256Bytes(json), html: sha256Bytes(html) },
|
|
441
|
+
};
|
|
442
|
+
}
|
|
374
443
|
const png = await encodePng(raw, raster.width, raster.height, resolved.scale);
|
|
375
444
|
options.signal?.throwIfAborted();
|
|
376
445
|
const json = `${canonicalJson(await renderMetadata(index, resolved, options.signal))}\n`;
|
|
@@ -481,7 +481,7 @@ export class AgentNudger {
|
|
|
481
481
|
this.scanner = this.engine.scanner;
|
|
482
482
|
this.transactions = this.engine.transactions;
|
|
483
483
|
}
|
|
484
|
-
async scan(workspaceId, principal, signal) {
|
|
484
|
+
async scan(workspaceId, principal, signal, includeExternalScriptReferences = false) {
|
|
485
485
|
signal?.throwIfAborted();
|
|
486
486
|
const workspace = this.resolver.get(workspaceId, principal);
|
|
487
487
|
const sourceRoots = {
|
|
@@ -493,8 +493,27 @@ export class AgentNudger {
|
|
|
493
493
|
const bitmapPatterns = provinceBitmapPatterns(selectorSnapshot.files, sourceRoots.map);
|
|
494
494
|
const contentSnapshot = await this.engine.scan(workspaceId, { patterns: [...sourceTextPatterns(sourceRoots), ...bitmapPatterns] }, principal, signal);
|
|
495
495
|
const locatorSnapshot = await this.engine.scan(workspaceId, { patterns: ['**/*.asset'], rootKinds: ['mod', 'fixture'] }, principal, signal);
|
|
496
|
+
const externalScriptSnapshot = includeExternalScriptReferences
|
|
497
|
+
? await this.engine.scan(workspaceId, {
|
|
498
|
+
patterns: [
|
|
499
|
+
'events/**/*.txt',
|
|
500
|
+
'common/national_focus/**/*.txt',
|
|
501
|
+
'common/decisions/**/*.txt',
|
|
502
|
+
'common/scripted_effects/**/*.txt',
|
|
503
|
+
'common/scripted_triggers/**/*.txt',
|
|
504
|
+
'common/on_actions/**/*.txt',
|
|
505
|
+
'common/ideas/**/*.txt',
|
|
506
|
+
'common/characters/**/*.txt',
|
|
507
|
+
'history/countries/**/*.txt',
|
|
508
|
+
],
|
|
509
|
+
}, principal, signal)
|
|
510
|
+
: undefined;
|
|
496
511
|
signal?.throwIfAborted();
|
|
497
|
-
const files = [
|
|
512
|
+
const files = [
|
|
513
|
+
...contentSnapshot.files,
|
|
514
|
+
...locatorSnapshot.files,
|
|
515
|
+
...(externalScriptSnapshot?.files ?? []),
|
|
516
|
+
].sort((left, right) => left.loadOrder - right.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
498
517
|
const revision = hashCanonical(files.map(({ displayPath, loadOrder, sha256 }) => ({ displayPath, loadOrder, sha256 })));
|
|
499
518
|
return {
|
|
500
519
|
workspaceId,
|
|
@@ -512,7 +531,7 @@ export class AgentNudger {
|
|
|
512
531
|
}
|
|
513
532
|
async prepareOperations(workspaceId, operations, principal, signal) {
|
|
514
533
|
signal?.throwIfAborted();
|
|
515
|
-
const snapshot = await this.scan(workspaceId, principal, signal);
|
|
534
|
+
const snapshot = await this.scan(workspaceId, principal, signal, operations.some(({ kind }) => kind === 'renumber_map_entity'));
|
|
516
535
|
const plan = await planMapOperationsAsync(snapshot.index, operations, signal);
|
|
517
536
|
const baselineValidation = await validateMapAsync(snapshot.index, signal === undefined ? {} : { signal });
|
|
518
537
|
const rawValidation = await validateMapAsync(plan.finalIndex, {
|
|
@@ -614,6 +633,9 @@ export class AgentNudger {
|
|
|
614
633
|
const bundle = mapBundleWithSourceHashes(rawBundle, completeSourceHashes);
|
|
615
634
|
const workspace = this.resolver.get(workspaceId, options.principal);
|
|
616
635
|
const layer = options.layer ?? 'province';
|
|
636
|
+
const tileSuffix = options.tile === undefined && options.area === undefined
|
|
637
|
+
? ''
|
|
638
|
+
: `-tile-${hashCanonical({ tile: bundle.tile, area: options.area, overlays: [...new Set(options.overlays ?? [])].sort(), scale: options.scale ?? 1 }).slice(0, 12)}`;
|
|
617
639
|
const provenance = {
|
|
618
640
|
kind: 'map-render',
|
|
619
641
|
toolVersion: PACKAGE_VERSION,
|
|
@@ -624,24 +646,26 @@ export class AgentNudger {
|
|
|
624
646
|
layer,
|
|
625
647
|
overlays: [...new Set(options.overlays ?? [])].sort(),
|
|
626
648
|
scale: options.scale ?? 1,
|
|
649
|
+
...(bundle.tile === undefined ? {} : { tile: bundle.tile }),
|
|
650
|
+
...(options.area === undefined ? {} : { area: options.area }),
|
|
627
651
|
},
|
|
628
652
|
metadata: { sourceHashInventory: sourceEvidence.inventory },
|
|
629
653
|
};
|
|
630
654
|
const writes = [
|
|
631
655
|
{
|
|
632
|
-
name: `map-${layer}.png`,
|
|
656
|
+
name: `map-${layer}${tileSuffix}.png`,
|
|
633
657
|
mimeType: 'image/png',
|
|
634
658
|
content: bundle.png,
|
|
635
659
|
provenance,
|
|
636
660
|
},
|
|
637
661
|
{
|
|
638
|
-
name: `map-${layer}.json`,
|
|
662
|
+
name: `map-${layer}${tileSuffix}.json`,
|
|
639
663
|
mimeType: 'application/json',
|
|
640
664
|
content: bundle.json,
|
|
641
665
|
provenance,
|
|
642
666
|
},
|
|
643
667
|
{
|
|
644
|
-
name: `map-${layer}.html`,
|
|
668
|
+
name: `map-${layer}${tileSuffix}.html`,
|
|
645
669
|
mimeType: 'text/html',
|
|
646
670
|
content: bundle.html,
|
|
647
671
|
provenance,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { boundedSourceHashEvidence, publicArtifactLink, } from '../core/artifacts.js';
|
|
2
2
|
import { canonicalJson, hashCanonical } from '../core/canonical.js';
|
|
3
3
|
import { autonomousResultArtifacts, } from '../core/transaction-execution.js';
|
|
4
|
-
import { emptyServiceResult } from '../core/result.js';
|
|
4
|
+
import { emptyServiceResult, ServiceError } from '../core/result.js';
|
|
5
5
|
import { setInlineFilesScanned } from '../core/operation-result.js';
|
|
6
6
|
import { PACKAGE_VERSION } from '../version.js';
|
|
7
7
|
import { allocateMapIdentifiersAsync, } from './operations.js';
|
|
@@ -299,6 +299,69 @@ export function completeMapRewrite(workspaceId, execution, recipe) {
|
|
|
299
299
|
}
|
|
300
300
|
export async function inspectMap(engine, nudger, input, context) {
|
|
301
301
|
await context.progress(0, 3, 'Inspecting and validating the map');
|
|
302
|
+
if (input.lookupOnly) {
|
|
303
|
+
if (input.allocationRequests.length > 0)
|
|
304
|
+
throw new ServiceError('MAP_LOOKUP_ALLOCATION_UNSUPPORTED', 'Use full map inspection for identifier allocation previews');
|
|
305
|
+
const snapshot = await nudger.scan(context.workspaceId, context.principal, context.signal);
|
|
306
|
+
const catalog = input.query === undefined ? undefined : buildMapCatalog(snapshot.index);
|
|
307
|
+
const queryMatches = catalog === undefined ? [] : searchMapCatalog(catalog, input.query, input.queryLimit);
|
|
308
|
+
const coordinateMatches = input.coordinates.map((coordinate) => lookupMapCoordinate(snapshot.index, coordinate));
|
|
309
|
+
const selected = selectedMapEntities(snapshot, input.provinceIds, input.stateIds, input.regionIds);
|
|
310
|
+
const workspace = engine.resolver.get(context.workspaceId, context.principal);
|
|
311
|
+
const sourceEvidence = mapArtifactSourceEvidence(snapshot.files);
|
|
312
|
+
const selectorHash = hashCanonical({
|
|
313
|
+
provinceIds: input.provinceIds,
|
|
314
|
+
stateIds: input.stateIds,
|
|
315
|
+
regionIds: input.regionIds,
|
|
316
|
+
query: input.query,
|
|
317
|
+
coordinates: input.coordinates,
|
|
318
|
+
}).slice(0, 16);
|
|
319
|
+
const artifact = await engine.artifacts.putChunked(workspace, `map-lookup.${snapshot.revision.slice(0, 16)}.${selectorHash}.json`, 'application/json', `${canonicalJson({ schemaVersion: 1, revision: snapshot.revision, lookupOnly: true, queryMatches, coordinateMatches, selected, sourceHashes: sourceEvidence.complete })}\n`, {
|
|
320
|
+
kind: 'map-lookup',
|
|
321
|
+
toolVersion: PACKAGE_VERSION,
|
|
322
|
+
schemaVersion: 'map-lookup.v1',
|
|
323
|
+
sourceHashes: sourceEvidence.bounded.sourceHashes,
|
|
324
|
+
metadata: { sourceHashInventory: sourceEvidence.bounded.inventory },
|
|
325
|
+
}, 'Bounded map coordinate, catalog, and entity lookup without full validation or overview rendering', context.signal);
|
|
326
|
+
const result = emptyServiceResult(context.workspaceId, {
|
|
327
|
+
revision: snapshot.revision,
|
|
328
|
+
sharedRevision: snapshot.revision,
|
|
329
|
+
width: snapshot.index.raster?.width ?? null,
|
|
330
|
+
height: snapshot.index.raster?.height ?? null,
|
|
331
|
+
definitions: snapshot.index.definitions.length,
|
|
332
|
+
states: snapshot.index.states.length,
|
|
333
|
+
regions: snapshot.index.regions.length,
|
|
334
|
+
ports: snapshot.index.ports.length,
|
|
335
|
+
inspectedProvinceCount: selected.provinces.length,
|
|
336
|
+
inspectedStateCount: selected.states.length,
|
|
337
|
+
inspectedRegionCount: selected.regions.length,
|
|
338
|
+
allocationCount: 0,
|
|
339
|
+
queryMatchCount: queryMatches.length,
|
|
340
|
+
coordinateMatchCount: coordinateMatches.length,
|
|
341
|
+
overviewRendered: false,
|
|
342
|
+
provinceGeometryCount: 0,
|
|
343
|
+
provinceGeometryPixelCount: 0,
|
|
344
|
+
provinceGeometryRowRunCount: 0,
|
|
345
|
+
unknownProvinceIds: [],
|
|
346
|
+
missingGeometryProvinceIds: [],
|
|
347
|
+
lookupOnly: true,
|
|
348
|
+
});
|
|
349
|
+
result.code = 'MAP_LOOKUP_COMPLETED';
|
|
350
|
+
result.validation = {
|
|
351
|
+
passed: true,
|
|
352
|
+
checks: [
|
|
353
|
+
{
|
|
354
|
+
id: 'lookup-only',
|
|
355
|
+
passed: true,
|
|
356
|
+
message: 'Lookup completed; full map validation was not requested',
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
setInlineFilesScanned(result, snapshot.files.map(({ displayPath }) => displayPath));
|
|
361
|
+
result.artifacts = [publicArtifactLink(artifact)];
|
|
362
|
+
await context.progress(3, 3, 'Map lookup complete');
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
302
365
|
const { snapshot, validation } = await nudger.validate(context.workspaceId, context.principal, context.signal);
|
|
303
366
|
const sharedRevision = snapshot.revision;
|
|
304
367
|
const catalog = buildMapCatalog(snapshot.index);
|
|
@@ -474,6 +537,8 @@ export async function renderMapView(nudger, input, context) {
|
|
|
474
537
|
...(input.layer === undefined ? {} : { layer: input.layer }),
|
|
475
538
|
...(input.overlays === undefined ? {} : { overlays: input.overlays }),
|
|
476
539
|
...(input.scale === undefined ? {} : { scale: input.scale }),
|
|
540
|
+
...(input.tile === undefined ? {} : { tile: input.tile }),
|
|
541
|
+
...(input.area === undefined ? {} : { area: input.area }),
|
|
477
542
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
478
543
|
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
479
544
|
});
|
|
@@ -483,6 +548,7 @@ export async function renderMapView(nudger, input, context) {
|
|
|
483
548
|
height: rendered.bundle.height,
|
|
484
549
|
hashes: rendered.bundle.hashes,
|
|
485
550
|
offlineRepresentation: true,
|
|
551
|
+
...(rendered.bundle.tile === undefined ? {} : { tile: rendered.bundle.tile }),
|
|
486
552
|
});
|
|
487
553
|
result.code = 'MAP_RENDERED';
|
|
488
554
|
setInlineFilesScanned(result, rendered.filesScanned);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Shared agent-facing guidance for both MCP protocol generations. */
|
|
2
|
-
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 cross-system source review with hoi4.impact_inspect and decision or mission scenarios with hoi4.decision_inspect.
|
|
2
|
+
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 cross-system source review with hoi4.impact_inspect and decision or mission scenarios with hoi4.decision_inspect. Test declared mechanic steps with hoi4.mechanic_test, package connections with hoi4.package_check, and named suites with hoi4.scenario_test. These analysis 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. Concurrent calls queue automatically and emit periodic progress when the client requests it. Long operations support negotiated MCP tasks; native background rewrites require a stable requestKey. Large evidence is linked as resources.';
|
|
@@ -7,6 +7,9 @@ const controlTools = new Set(['hoi4.job_inspect', 'hoi4.job_cancel']);
|
|
|
7
7
|
const backgroundTools = new Set([
|
|
8
8
|
'hoi4.impact_inspect',
|
|
9
9
|
'hoi4.decision_inspect',
|
|
10
|
+
'hoi4.mechanic_test',
|
|
11
|
+
'hoi4.package_check',
|
|
12
|
+
'hoi4.scenario_test',
|
|
10
13
|
'hoi4.event_inspect',
|
|
11
14
|
'hoi4.event_render',
|
|
12
15
|
'hoi4.event_compare',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { decisionInspectRequestSchema, impactInspectRequestSchema, } from '../../schemas/analysis.js';
|
|
3
|
+
import { mechanicCaseSchema, mechanicTestRequestSchema, packageManifestSchema, packageCheckRequestSchema, scenarioSuiteSchema, scenarioTestRequestSchema, } from '../../schemas/scenarios.js';
|
|
4
|
+
import { compactValidatedInputSchema } from '../server/context-schemas.js';
|
|
3
5
|
import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
|
|
4
6
|
import { strictOperationResultSchema } from '../server/result.js';
|
|
5
7
|
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
@@ -36,6 +38,70 @@ const decisionOutputSchema = strictOperationResultSchema(z
|
|
|
36
38
|
unresolvedDefinitions: nonNegativeIntegerSchema,
|
|
37
39
|
})
|
|
38
40
|
.strict());
|
|
41
|
+
const mechanicOutputSchema = strictOperationResultSchema(z
|
|
42
|
+
.object({
|
|
43
|
+
sourceRevision: sha256Schema,
|
|
44
|
+
reportHash: sha256Schema,
|
|
45
|
+
status: z.enum(['passed', 'failed', 'unresolved']),
|
|
46
|
+
assertions: nonNegativeIntegerSchema,
|
|
47
|
+
passed: nonNegativeIntegerSchema,
|
|
48
|
+
failed: nonNegativeIntegerSchema,
|
|
49
|
+
unresolved: nonNegativeIntegerSchema,
|
|
50
|
+
steps: nonNegativeIntegerSchema,
|
|
51
|
+
})
|
|
52
|
+
.strict());
|
|
53
|
+
const packageOutputSchema = strictOperationResultSchema(z
|
|
54
|
+
.object({
|
|
55
|
+
sourceRevision: sha256Schema,
|
|
56
|
+
reportHash: sha256Schema,
|
|
57
|
+
complete: z.boolean(),
|
|
58
|
+
items: nonNegativeIntegerSchema,
|
|
59
|
+
present: nonNegativeIntegerSchema,
|
|
60
|
+
absent: nonNegativeIntegerSchema,
|
|
61
|
+
shadowed: nonNegativeIntegerSchema,
|
|
62
|
+
unresolved: nonNegativeIntegerSchema,
|
|
63
|
+
})
|
|
64
|
+
.strict());
|
|
65
|
+
const scenarioOutputSchema = strictOperationResultSchema(z
|
|
66
|
+
.object({
|
|
67
|
+
sourceRevision: sha256Schema,
|
|
68
|
+
suiteHash: sha256Schema,
|
|
69
|
+
reportHash: sha256Schema,
|
|
70
|
+
start: nonNegativeIntegerSchema,
|
|
71
|
+
end: nonNegativeIntegerSchema,
|
|
72
|
+
total: nonNegativeIntegerSchema,
|
|
73
|
+
completed: nonNegativeIntegerSchema,
|
|
74
|
+
failed: nonNegativeIntegerSchema,
|
|
75
|
+
unresolved: nonNegativeIntegerSchema,
|
|
76
|
+
pending: nonNegativeIntegerSchema,
|
|
77
|
+
continuation: z.string().optional(),
|
|
78
|
+
})
|
|
79
|
+
.strict());
|
|
80
|
+
const mechanicInputSchema = z
|
|
81
|
+
.object({
|
|
82
|
+
...mechanicTestRequestSchema.shape,
|
|
83
|
+
test: compactValidatedInputSchema(mechanicCaseSchema, 'Versioned declared scenario, explicit effect steps, and typed assertions. See docs/mechanics.md.'),
|
|
84
|
+
})
|
|
85
|
+
.strict();
|
|
86
|
+
const packageInputSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
...packageCheckRequestSchema.shape,
|
|
89
|
+
manifest: compactValidatedInputSchema(packageManifestSchema, 'Declarative definitions, calls, registrations, localisation, assets, and required cases. See docs/mechanics.md.'),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
const scenarioInputSchema = z
|
|
93
|
+
.object({
|
|
94
|
+
...scenarioTestRequestSchema.shape,
|
|
95
|
+
suite: compactValidatedInputSchema(scenarioSuiteSchema, 'Named mechanic, package, or fixed read-only tool cases. See docs/mechanics.md.').optional(),
|
|
96
|
+
})
|
|
97
|
+
.strict()
|
|
98
|
+
.superRefine((input, context) => {
|
|
99
|
+
if ((input.suite === undefined) === (input.suitePath === undefined))
|
|
100
|
+
context.addIssue({
|
|
101
|
+
code: 'custom',
|
|
102
|
+
message: 'Specify exactly one inline suite or suitePath',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
39
105
|
const readOnlyAnalysisTool = {
|
|
40
106
|
readOnlyHint: true,
|
|
41
107
|
destructiveHint: false,
|
|
@@ -59,6 +125,30 @@ export const analysisTaskTools = [
|
|
|
59
125
|
outputSchema: decisionOutputSchema,
|
|
60
126
|
annotations: readOnlyAnalysisTool,
|
|
61
127
|
},
|
|
128
|
+
{
|
|
129
|
+
name: 'hoi4.mechanic_test',
|
|
130
|
+
title: 'Test declared mechanic steps',
|
|
131
|
+
description: 'Interpret bounded, source-backed effect steps on isolated declared state and check typed invariants. Unsupported effects remain unresolved; no game or mod source is changed.',
|
|
132
|
+
inputSchema: mechanicInputSchema,
|
|
133
|
+
outputSchema: mechanicOutputSchema,
|
|
134
|
+
annotations: readOnlyAnalysisTool,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'hoi4.package_check',
|
|
138
|
+
title: 'Check package connections',
|
|
139
|
+
description: 'Check a declarative package manifest against active definitions, references, registrations, localisation, assets, and required suite cases.',
|
|
140
|
+
inputSchema: packageInputSchema,
|
|
141
|
+
outputSchema: packageOutputSchema,
|
|
142
|
+
annotations: readOnlyAnalysisTool,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'hoi4.scenario_test',
|
|
146
|
+
title: 'Run scenario suite',
|
|
147
|
+
description: 'Run named inline or workspace-relative cases through bounded typed read-only domain services. Large suites return an authenticated continuation.',
|
|
148
|
+
inputSchema: scenarioInputSchema,
|
|
149
|
+
outputSchema: scenarioOutputSchema,
|
|
150
|
+
annotations: readOnlyAnalysisTool,
|
|
151
|
+
},
|
|
62
152
|
];
|
|
63
153
|
export function registerAnalysisTools(server) {
|
|
64
154
|
registerLegacyTaskTools(server, analysisTaskTools);
|
|
@@ -31,6 +31,7 @@ const eventRenderInputSchema = z
|
|
|
31
31
|
const eventCompareInputSchema = z
|
|
32
32
|
.object({
|
|
33
33
|
...eventCompareRequestSchema.shape,
|
|
34
|
+
selector: nestedSelectorSchema.optional(),
|
|
34
35
|
before: nestedGraphReferenceSchema.optional(),
|
|
35
36
|
after: nestedGraphReferenceSchema.optional(),
|
|
36
37
|
proposedSources: z.array(nestedProposedSourceSchema).min(1).max(64).optional(),
|
|
@@ -119,6 +120,11 @@ const eventCompareOutputSchema = strictOperationResultSchema(z
|
|
|
119
120
|
counts: z
|
|
120
121
|
.object({
|
|
121
122
|
changes: nonNegativeIntegerSchema,
|
|
123
|
+
selectedChanges: nonNegativeIntegerSchema.optional(),
|
|
124
|
+
omittedChanges: nonNegativeIntegerSchema.optional(),
|
|
125
|
+
unattributedChanges: nonNegativeIntegerSchema.optional(),
|
|
126
|
+
selectedBeforeNodes: nonNegativeIntegerSchema.optional(),
|
|
127
|
+
selectedAfterNodes: nonNegativeIntegerSchema.optional(),
|
|
122
128
|
addedNodes: nonNegativeIntegerSchema,
|
|
123
129
|
removedNodes: nonNegativeIntegerSchema,
|
|
124
130
|
changedNodes: nonNegativeIntegerSchema,
|
|
@@ -146,6 +152,9 @@ const eventCompareOutputSchema = strictOperationResultSchema(z
|
|
|
146
152
|
boundary: z
|
|
147
153
|
.object({
|
|
148
154
|
proposedSources: nonNegativeIntegerSchema,
|
|
155
|
+
selector: eventSelectorSchema.optional(),
|
|
156
|
+
maxChainNodes: nonNegativeIntegerSchema.optional(),
|
|
157
|
+
selectionTruncated: z.boolean().optional(),
|
|
149
158
|
render: z.boolean(),
|
|
150
159
|
maxRenderNodes: nonNegativeIntegerSchema,
|
|
151
160
|
refresh: z.boolean(),
|
|
@@ -179,7 +188,7 @@ export const eventTaskTools = [
|
|
|
179
188
|
{
|
|
180
189
|
name: 'hoi4.event_compare',
|
|
181
190
|
title: 'Compare event chains',
|
|
182
|
-
description: 'Compare cached, artifact-backed, current, or in-memory proposed event graphs
|
|
191
|
+
description: 'Compare cached, artifact-backed, current, or in-memory proposed event graphs, optionally selecting one downstream chain. Full changes and explicit selection coverage are resources.',
|
|
183
192
|
inputSchema: eventCompareInputSchema,
|
|
184
193
|
outputSchema: eventCompareOutputSchema,
|
|
185
194
|
annotations: readOnlyEventTool,
|
|
@@ -30,6 +30,16 @@ const focusInspectOutputSchema = strictOperationResultSchema(z
|
|
|
30
30
|
layoutDecisionCount: nonNegativeIntegerSchema,
|
|
31
31
|
layoutMetrics: focusLayoutMetricsSchema,
|
|
32
32
|
diagnosticCount: nonNegativeIntegerSchema,
|
|
33
|
+
scenarioSummary: z
|
|
34
|
+
.object({
|
|
35
|
+
completed: nonNegativeIntegerSchema,
|
|
36
|
+
blocked: nonNegativeIntegerSchema,
|
|
37
|
+
unresolved: nonNegativeIntegerSchema,
|
|
38
|
+
structuralCandidates: nonNegativeIntegerSchema,
|
|
39
|
+
choiceConflictCount: nonNegativeIntegerSchema,
|
|
40
|
+
})
|
|
41
|
+
.strict()
|
|
42
|
+
.optional(),
|
|
33
43
|
})
|
|
34
44
|
.strict())
|
|
35
45
|
.max(100),
|
|
@@ -125,7 +135,7 @@ export const focusTaskTools = [
|
|
|
125
135
|
{
|
|
126
136
|
name: 'hoi4.focus_inspect',
|
|
127
137
|
title: 'Inspect focus trees',
|
|
128
|
-
description: 'Inspect
|
|
138
|
+
description: 'Inspect focus trees and continuous palettes, including source references, diagnostics, and layout.',
|
|
129
139
|
inputSchema: focusInspectRequestSchema,
|
|
130
140
|
outputSchema: focusInspectOutputSchema,
|
|
131
141
|
annotations: artifactProducing,
|
|
@@ -133,7 +143,7 @@ export const focusTaskTools = [
|
|
|
133
143
|
{
|
|
134
144
|
name: 'hoi4.focus_render',
|
|
135
145
|
title: 'Render focus review artifacts',
|
|
136
|
-
description: 'Render
|
|
146
|
+
description: 'Render focus HTML, SVG, JSON, and source maps. Use hoi4.focus_raster for decoded icons and PNG.',
|
|
137
147
|
inputSchema: focusRenderRequestSchema,
|
|
138
148
|
outputSchema: focusRenderOutputSchema,
|
|
139
149
|
annotations: artifactProducing,
|
|
@@ -141,7 +151,7 @@ export const focusTaskTools = [
|
|
|
141
151
|
{
|
|
142
152
|
name: 'hoi4.focus_raster',
|
|
143
153
|
title: 'Rasterize focus review artifacts',
|
|
144
|
-
description: '
|
|
154
|
+
description: 'Render focus PNG with decoded source icons; also returns HTML, SVG, and JSON.',
|
|
145
155
|
inputSchema: focusRenderRequestSchema,
|
|
146
156
|
outputSchema: focusRenderOutputSchema,
|
|
147
157
|
annotations: artifactProducing,
|
|
@@ -149,7 +159,7 @@ export const focusTaskTools = [
|
|
|
149
159
|
{
|
|
150
160
|
name: 'hoi4.focus_rewrite',
|
|
151
161
|
title: 'Create or clean up focus content',
|
|
152
|
-
description: 'Create or
|
|
162
|
+
description: 'Create or compact a focus tree or palette from a complete plan. Use treeId for existing trees and createIfMissing for new files.',
|
|
153
163
|
inputSchema: focusRewriteRequestSchema,
|
|
154
164
|
outputSchema: focusPlanOutputSchema,
|
|
155
165
|
annotations: {
|
|
@@ -43,6 +43,10 @@ const guiRenderOutputSchema = strictOperationResultSchema(z
|
|
|
43
43
|
comparison: z
|
|
44
44
|
.object({ changedPixels: nonNegativeIntegerSchema, changedRatio: z.number().min(0).max(1) })
|
|
45
45
|
.strict(),
|
|
46
|
+
sourceComparison: z
|
|
47
|
+
.object({ changedPixels: nonNegativeIntegerSchema, changedRatio: z.number().min(0).max(1) })
|
|
48
|
+
.strict()
|
|
49
|
+
.optional(),
|
|
46
50
|
fidelityCounts: countRecordSchema,
|
|
47
51
|
offlineRepresentation: z.literal(true),
|
|
48
52
|
})
|
|
@@ -76,7 +80,7 @@ export const guiTaskTools = [
|
|
|
76
80
|
{
|
|
77
81
|
name: 'hoi4.gui_render',
|
|
78
82
|
title: 'Render scripted GUI artifacts',
|
|
79
|
-
description: 'Render GUI states, resolutions,
|
|
83
|
+
description: 'Render GUI states, resolutions, scenarios, hierarchy, comparisons, and diagnostics.',
|
|
80
84
|
inputSchema: guiRenderRequestSchema,
|
|
81
85
|
outputSchema: guiRenderOutputSchema,
|
|
82
86
|
annotations: artifactProducing,
|
|
@@ -75,6 +75,7 @@ const mapWorkspaceInspectOutputSchema = strictOperationResultSchema(z
|
|
|
75
75
|
provinceGeometryCount: nonNegativeIntegerSchema,
|
|
76
76
|
provinceGeometryPixelCount: nonNegativeIntegerSchema,
|
|
77
77
|
provinceGeometryRowRunCount: nonNegativeIntegerSchema,
|
|
78
|
+
lookupOnly: z.boolean().optional(),
|
|
78
79
|
unknownProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
79
80
|
missingGeometryProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
80
81
|
})
|
|
@@ -86,6 +87,15 @@ const mapRenderOutputSchema = strictOperationResultSchema(z
|
|
|
86
87
|
height: nonNegativeIntegerSchema,
|
|
87
88
|
hashes: bitmapRenderHashesSchema,
|
|
88
89
|
offlineRepresentation: z.literal(true),
|
|
90
|
+
tile: z
|
|
91
|
+
.object({
|
|
92
|
+
x: nonNegativeIntegerSchema,
|
|
93
|
+
y: nonNegativeIntegerSchema,
|
|
94
|
+
width: nonNegativeIntegerSchema,
|
|
95
|
+
height: nonNegativeIntegerSchema,
|
|
96
|
+
})
|
|
97
|
+
.strict()
|
|
98
|
+
.optional(),
|
|
89
99
|
})
|
|
90
100
|
.strict());
|
|
91
101
|
const mapPlanOutputSchema = strictOperationResultSchema(z
|
|
@@ -113,7 +123,7 @@ export const mapTaskTools = [
|
|
|
113
123
|
{
|
|
114
124
|
name: 'hoi4.map_inspect',
|
|
115
125
|
title: 'Inspect HOI4 map',
|
|
116
|
-
description: '
|
|
126
|
+
description: 'Validate and inspect the complete map, or use lookupOnly for bounded coordinate and entity lookups.',
|
|
117
127
|
inputSchema: mapInspectRequestSchema,
|
|
118
128
|
outputSchema: mapWorkspaceInspectOutputSchema,
|
|
119
129
|
annotations: artifactProducing,
|
|
@@ -121,7 +131,7 @@ export const mapTaskTools = [
|
|
|
121
131
|
{
|
|
122
132
|
name: 'hoi4.map_render',
|
|
123
133
|
title: 'Render map inspection artifacts',
|
|
124
|
-
description: 'Render searchable full-map PNG, JSON, and HTML.',
|
|
134
|
+
description: 'Render searchable full-map PNG, JSON, and HTML, or a reusable source-coordinate tile.',
|
|
125
135
|
inputSchema: mapRenderRequestSchema,
|
|
126
136
|
outputSchema: mapRenderOutputSchema,
|
|
127
137
|
annotations: artifactProducing,
|
|
@@ -16,6 +16,9 @@ const analysisDataSchema = z
|
|
|
16
16
|
cacheKey: sha256Schema,
|
|
17
17
|
scenarios: nonNegativeIntegerSchema,
|
|
18
18
|
candidates: nonNegativeIntegerSchema,
|
|
19
|
+
eligibleCandidates: nonNegativeIntegerSchema,
|
|
20
|
+
excludedCandidates: nonNegativeIntegerSchema,
|
|
21
|
+
unresolvedCandidates: nonNegativeIntegerSchema,
|
|
19
22
|
scopePools: nonNegativeIntegerSchema.optional(),
|
|
20
23
|
scopePoolCandidates: nonNegativeIntegerSchema.optional(),
|
|
21
24
|
unresolved: nonNegativeIntegerSchema,
|
|
@@ -61,6 +64,8 @@ const inspectDataSchema = z
|
|
|
61
64
|
candidateExamples: z.array(z.string().max(512)).max(10),
|
|
62
65
|
poolComplete: z.boolean().optional(),
|
|
63
66
|
requiredInputs: nonNegativeIntegerSchema,
|
|
67
|
+
requiredInputPaths: z.array(z.string().max(1024)).max(32),
|
|
68
|
+
requiredInputPathsTruncated: z.boolean(),
|
|
64
69
|
unresolved: nonNegativeIntegerSchema,
|
|
65
70
|
})
|
|
66
71
|
.strict();
|