hoi4-agent-tools 3.0.9 → 3.1.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +4 -0
  3. package/dist/bin/stdio.js +23 -12
  4. package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
  5. package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
  6. package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
  7. package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
  8. package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
  9. package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
  10. package/dist/hoi4_agent_tools/core/engine.js +48 -5
  11. package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
  12. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  13. package/dist/hoi4_agent_tools/core/index.js +47 -4
  14. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  15. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  16. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  17. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  18. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  19. package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
  20. package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
  21. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  22. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  23. package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
  24. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  25. package/dist/hoi4_agent_tools/core/result.js +1 -0
  26. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  27. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  28. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
  29. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  30. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  31. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  32. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  33. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  34. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  35. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  36. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  37. package/dist/hoi4_agent_tools/event/service.js +67 -15
  38. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  39. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  40. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  41. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  42. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  43. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  44. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  45. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  46. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  47. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  48. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  49. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  50. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  51. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  52. package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
  53. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  54. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  55. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  56. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  57. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  58. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
  59. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  60. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  61. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
  62. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  63. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  64. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  65. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  66. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  67. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  68. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  69. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  70. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  71. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  72. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  73. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  74. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  75. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  76. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  77. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  78. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  79. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  80. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  81. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  82. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  83. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  84. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  85. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  86. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  87. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  88. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  89. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  90. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  91. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  92. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  93. package/dist/hoi4_agent_tools/version.js +1 -1
  94. package/docs/README.md +2 -0
  95. package/docs/development.md +2 -0
  96. package/docs/events.md +7 -2
  97. package/docs/helper-expansion.md +78 -0
  98. package/docs/http.md +2 -0
  99. package/docs/jobs.md +85 -0
  100. package/docs/technology.md +6 -2
  101. package/package.json +11 -5
  102. package/schemas/configuration.schema.json +1 -1
  103. package/schemas/continuous-focus-palette.schema.json +1 -1
  104. package/schemas/custom-weighted-pool.schema.json +1 -1
  105. package/schemas/event-feature-manifest.schema.json +1 -1
  106. package/schemas/focus-plan.schema.json +1 -1
  107. package/schemas/focus-planning-sidecar.schema.json +1 -1
  108. package/schemas/gui-animation-source.schema.json +1 -1
  109. package/schemas/gui-helper.schema.json +1 -1
  110. package/schemas/gui-scenario.schema.json +1 -1
  111. package/schemas/helper-expansion-request.schema.json +44 -0
  112. package/schemas/helper-expansion-summary.schema.json +70 -0
  113. package/schemas/map-operation.schema.json +1 -1
  114. package/schemas/operation-result.schema.json +1 -1
  115. package/schemas/probability-analysis-result.schema.json +1 -1
  116. package/schemas/probability-compare-input.schema.json +1 -1
  117. package/schemas/probability-evaluate-input.schema.json +1 -1
  118. package/schemas/probability-inspect-input.schema.json +1 -1
  119. package/schemas/probability-render-input.schema.json +1 -1
  120. package/schemas/probability-scenario-set.schema.json +1 -1
  121. package/schemas/probability-sequence-input.schema.json +1 -1
  122. package/schemas/probability-simulate-input.schema.json +1 -1
  123. package/schemas/probability-sweep-input.schema.json +1 -1
  124. package/server.json +2 -2
@@ -0,0 +1,493 @@
1
+ import { boundedSourceHashEvidence, publicArtifactLink, } from '../core/artifacts.js';
2
+ import { canonicalJson, hashCanonical } from '../core/canonical.js';
3
+ import { autonomousResultArtifacts, } from '../core/transaction-execution.js';
4
+ import { emptyServiceResult } from '../core/result.js';
5
+ import { setInlineFilesScanned } from '../core/operation-result.js';
6
+ import { PACKAGE_VERSION } from '../version.js';
7
+ import { allocateMapIdentifiersAsync, } from './operations.js';
8
+ import { buildMapCatalog, lookupMapCoordinate, searchMapCatalog } from './catalog.js';
9
+ import { renderMap } from './render.js';
10
+ import { exportProvinceGeometryRowRuns } from './service.js';
11
+ const jsonObject = z.record(z.string(), z.json());
12
+ const mapRewriteRecipeSchema = z
13
+ .object({
14
+ data: jsonObject,
15
+ filesScanned: z.array(z.string().min(1).max(4096)),
16
+ planBlockers: z
17
+ .array(z
18
+ .object({
19
+ code: z.string().min(1).max(256),
20
+ message: z.string().max(4096),
21
+ })
22
+ .strict())
23
+ .max(100),
24
+ })
25
+ .strict();
26
+ const mapInlineDiffLimit = 20;
27
+ const mapInlineMembershipIdLimit = 32;
28
+ const mapInlineTextLimit = 512;
29
+ function compactAllocationEvidence(evidence) {
30
+ return {
31
+ ...evidence,
32
+ rootCount: evidence.roots.length,
33
+ roots: evidence.roots.slice(0, 16),
34
+ rootsTruncated: evidence.roots.length > 16,
35
+ };
36
+ }
37
+ function compactNullableText(value) {
38
+ return value === null ? null : value.slice(0, mapInlineTextLimit);
39
+ }
40
+ function compactRecordDiffs(entries) {
41
+ return entries.slice(0, mapInlineDiffLimit).map(({ key, before, after }) => ({
42
+ key: key.slice(0, 256),
43
+ before: compactNullableText(before),
44
+ after: compactNullableText(after),
45
+ }));
46
+ }
47
+ function compactMapSemanticDiff(semantic) {
48
+ const counts = {
49
+ definitions: semantic.definitions.length,
50
+ stateMembership: semantic.stateMembership.length,
51
+ regionMembership: semantic.regionMembership.length,
52
+ states: semantic.states.length,
53
+ ports: semantic.ports.length,
54
+ buildingPositions: semantic.buildingPositions.length,
55
+ unitPositions: semantic.unitPositions.length,
56
+ weatherPositions: semantic.weatherPositions.length,
57
+ entityLocators: semantic.entityLocators.length,
58
+ supplyNodes: semantic.supplyNodes.length,
59
+ railways: semantic.railways.length,
60
+ adjacencies: semantic.adjacencies.length,
61
+ normalAdjacencies: semantic.normalAdjacencies.length,
62
+ };
63
+ return {
64
+ semantic: {
65
+ definitions: semantic.definitions
66
+ .slice(0, mapInlineDiffLimit)
67
+ .map(({ id, before, after }) => ({
68
+ id,
69
+ before: compactNullableText(before),
70
+ after: compactNullableText(after),
71
+ })),
72
+ stateMembership: semantic.stateMembership.slice(0, mapInlineDiffLimit).map((entry) => ({
73
+ ...entry,
74
+ before: entry.before.slice(0, mapInlineMembershipIdLimit),
75
+ after: entry.after.slice(0, mapInlineMembershipIdLimit),
76
+ })),
77
+ regionMembership: semantic.regionMembership.slice(0, mapInlineDiffLimit).map((entry) => ({
78
+ ...entry,
79
+ before: entry.before.slice(0, mapInlineMembershipIdLimit),
80
+ after: entry.after.slice(0, mapInlineMembershipIdLimit),
81
+ })),
82
+ states: compactRecordDiffs(semantic.states),
83
+ ports: compactRecordDiffs(semantic.ports),
84
+ buildingPositions: compactRecordDiffs(semantic.buildingPositions),
85
+ unitPositions: compactRecordDiffs(semantic.unitPositions),
86
+ weatherPositions: compactRecordDiffs(semantic.weatherPositions),
87
+ entityLocators: compactRecordDiffs(semantic.entityLocators),
88
+ supplyNodes: compactRecordDiffs(semantic.supplyNodes),
89
+ railways: compactRecordDiffs(semantic.railways),
90
+ adjacencies: compactRecordDiffs(semantic.adjacencies),
91
+ normalAdjacencies: compactRecordDiffs(semantic.normalAdjacencies),
92
+ supplyNodesChanged: semantic.supplyNodesChanged,
93
+ railwaysChanged: semantic.railwaysChanged,
94
+ adjacenciesChanged: semantic.adjacenciesChanged,
95
+ normalAdjacenciesChanged: semantic.normalAdjacenciesChanged,
96
+ },
97
+ counts,
98
+ truncated: Object.values(counts).some((count) => count > mapInlineDiffLimit) ||
99
+ [...semantic.stateMembership, ...semantic.regionMembership].some(({ before, after }) => before.length > mapInlineMembershipIdLimit || after.length > mapInlineMembershipIdLimit) ||
100
+ semantic.definitions.some(({ before, after }) => (before?.length ?? 0) > mapInlineTextLimit || (after?.length ?? 0) > mapInlineTextLimit) ||
101
+ [
102
+ ...semantic.states,
103
+ ...semantic.ports,
104
+ ...semantic.buildingPositions,
105
+ ...semantic.unitPositions,
106
+ ...semantic.weatherPositions,
107
+ ...semantic.entityLocators,
108
+ ...semantic.supplyNodes,
109
+ ...semantic.railways,
110
+ ...semantic.adjacencies,
111
+ ...semantic.normalAdjacencies,
112
+ ].some(({ key, before, after }) => key.length > 256 ||
113
+ (before?.length ?? 0) > mapInlineTextLimit ||
114
+ (after?.length ?? 0) > mapInlineTextLimit),
115
+ };
116
+ }
117
+ function validationSummary(validation) {
118
+ return { passed: validation.passed, checks: validation.checks };
119
+ }
120
+ function mapArtifactSourceEvidence(files) {
121
+ const complete = Object.fromEntries(files.map(({ displayPath, sha256 }) => [displayPath, sha256]));
122
+ return { complete, bounded: boundedSourceHashEvidence(complete) };
123
+ }
124
+ function selectedMapEntities(snapshot, provinceIds, stateIds, regionIds) {
125
+ const provinces = [...new Set(provinceIds)]
126
+ .sort((a, b) => a - b)
127
+ .map((id) => {
128
+ const definition = snapshot.index.definitionsById.get(id);
129
+ const geometry = snapshot.index.raster?.geometry.get(id);
130
+ return {
131
+ id,
132
+ definition: definition === undefined
133
+ ? null
134
+ : {
135
+ color: definition.color,
136
+ type: definition.type,
137
+ coastal: definition.coastal,
138
+ terrain: definition.terrain,
139
+ continent: definition.continent,
140
+ },
141
+ geometry: geometry ?? null,
142
+ stateIds: snapshot.index.stateForProvince(id).map(({ id: state }) => state),
143
+ regionIds: snapshot.index.regionForProvince(id).map(({ id: region }) => region),
144
+ victoryPoints: snapshot.index.victoryPointsByProvince
145
+ .get(id)
146
+ ?.map(({ stateId: state, value }) => ({ stateId: state, value })) ?? [],
147
+ provinceBuildings: snapshot.index.provinceBuildingsByProvince
148
+ .get(id)
149
+ ?.map(({ stateId: state, buildings }) => ({
150
+ stateId: state,
151
+ buildings: Object.fromEntries(buildings),
152
+ })) ?? [],
153
+ port: (() => {
154
+ const port = snapshot.index.ports.find(({ provinceId }) => provinceId === id);
155
+ return port === undefined
156
+ ? null
157
+ : {
158
+ stateId: port.stateId,
159
+ provinceId: port.provinceId,
160
+ level: port.level,
161
+ coastal: port.coastal,
162
+ adjacentSeaProvinceIds: port.adjacentSeaProvinceIds,
163
+ positions: port.positions.map(({ stateId, building, x, y, z, rotation, adjacentSeaProvince }) => ({
164
+ stateId,
165
+ building,
166
+ x,
167
+ y,
168
+ z,
169
+ rotation,
170
+ adjacentSeaProvince,
171
+ })),
172
+ };
173
+ })(),
174
+ };
175
+ });
176
+ const states = [...new Set(stateIds)]
177
+ .sort((a, b) => a - b)
178
+ .map((id) => {
179
+ const state = snapshot.index.statesById.get(id);
180
+ return state === undefined
181
+ ? { id, value: null }
182
+ : {
183
+ id,
184
+ value: {
185
+ name: state.name,
186
+ capital: state.capital ?? null,
187
+ manpower: state.manpower,
188
+ category: state.category,
189
+ provinces: state.provinces,
190
+ resources: Object.fromEntries(state.resources),
191
+ owner: state.owner ?? null,
192
+ controller: state.controller ?? null,
193
+ cores: state.cores,
194
+ claims: state.claims,
195
+ victoryPoints: state.victoryPoints.map(({ provinceId, value }) => ({
196
+ provinceId,
197
+ value,
198
+ })),
199
+ stateBuildings: Object.fromEntries(state.stateBuildings),
200
+ provinceBuildings: Object.fromEntries([...state.provinceBuildings].map(([provinceId, buildings]) => [
201
+ String(provinceId),
202
+ Object.fromEntries(buildings),
203
+ ])),
204
+ },
205
+ };
206
+ });
207
+ const regions = [...new Set(regionIds)]
208
+ .sort((a, b) => a - b)
209
+ .map((id) => {
210
+ const region = snapshot.index.regionsById.get(id);
211
+ return region === undefined
212
+ ? { id, value: null }
213
+ : {
214
+ id,
215
+ value: {
216
+ name: region.name,
217
+ provinces: region.provinces,
218
+ navalTerrain: region.navalTerrain ?? null,
219
+ },
220
+ };
221
+ });
222
+ return { provinces, states, regions };
223
+ }
224
+ export function normalizeMapInspectRequest(input) {
225
+ return input;
226
+ }
227
+ export function normalizeMapRenderRequest(input) {
228
+ return input;
229
+ }
230
+ export function normalizeMapRewriteRequest(input) {
231
+ return input;
232
+ }
233
+ export async function prepareMapRewrite(nudger, input, context) {
234
+ await context.progress(0, 5, 'Planning map operations and visual diff');
235
+ const planned = await nudger.planRewriteWithDiff({
236
+ workspaceId: context.workspaceId,
237
+ operations: input.operations,
238
+ ...(input.diffScale === undefined ? {} : { diffScale: input.diffScale }),
239
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
240
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
241
+ });
242
+ const compactSemantic = compactMapSemanticDiff(planned.bundle.semantic);
243
+ await context.progress(3, 5, 'Map rewrite prepared for journaled application');
244
+ return {
245
+ transaction: planned.transaction,
246
+ recipe: mapRewriteRecipeSchema.parse({
247
+ data: jsonObject.parse({
248
+ allocations: planned.plan.allocations.slice(0, 100).map(compactAllocationEvidence),
249
+ operationBlockers: planned.plan.blockers
250
+ .slice(0, 100)
251
+ .map(({ code, message, operationId }) => ({ code, message, operationId })),
252
+ expectedChangedBounds: planned.plan.expectedChangedBounds ?? null,
253
+ changedProvinceIds: planned.bundle.changedProvinceIds.slice(0, 256),
254
+ changedProvinceCount: planned.bundle.changedProvinceIds.length,
255
+ semanticDiff: compactSemantic.semantic,
256
+ semanticDiffCounts: compactSemantic.counts,
257
+ semanticDiffTruncated: compactSemantic.truncated,
258
+ }),
259
+ filesScanned: planned.filesScanned,
260
+ planBlockers: planned.plan.blockers
261
+ .slice(0, 100)
262
+ .map(({ code, message }) => ({ code, message })),
263
+ }),
264
+ };
265
+ }
266
+ export function completeMapRewrite(workspaceId, execution, recipe) {
267
+ const parsed = mapRewriteRecipeSchema.parse(recipe);
268
+ const transaction = execution.transaction;
269
+ const result = emptyServiceResult(workspaceId, {
270
+ execution: execution.outcome,
271
+ ...parsed.data,
272
+ fileCount: transaction.files.length,
273
+ artifactCount: transaction.artifacts.length,
274
+ });
275
+ result.status = transaction.validation.passed ? 'ok' : 'blocked';
276
+ result.code =
277
+ execution.outcome === 'applied'
278
+ ? 'MAP_CHANGES_APPLIED'
279
+ : execution.outcome === 'unchanged'
280
+ ? 'MAP_CHANGES_UNCHANGED'
281
+ : 'MAP_CHANGES_BLOCKED';
282
+ setInlineFilesScanned(result, parsed.filesScanned);
283
+ result.proposedFiles = transaction.files.slice(0, 100).map(({ relativePath }) => relativePath);
284
+ result.changedFiles = transaction.appliedFiles.slice(0, 100);
285
+ result.diagnostics = transaction.diagnostics.slice(0, 100);
286
+ result.artifacts = autonomousResultArtifacts(execution);
287
+ result.validation = transaction.validation;
288
+ result.blockers = [
289
+ ...parsed.planBlockers,
290
+ ...transaction.diagnostics
291
+ .filter(({ severity }) => severity === 'error' || severity === 'blocker')
292
+ .map(({ code, message, details }) => ({
293
+ code,
294
+ message,
295
+ ...(details === undefined ? {} : { details }),
296
+ })),
297
+ ].slice(0, 100);
298
+ return result;
299
+ }
300
+ export async function inspectMap(engine, nudger, input, context) {
301
+ await context.progress(0, 3, 'Inspecting and validating the map');
302
+ const { snapshot, validation } = await nudger.validate(context.workspaceId, context.principal, context.signal);
303
+ const sharedRevision = snapshot.revision;
304
+ const catalog = buildMapCatalog(snapshot.index);
305
+ const queryMatches = input.query === undefined ? [] : searchMapCatalog(catalog, input.query, input.queryLimit);
306
+ const coordinateMatches = input.coordinates.map((coordinate) => lookupMapCoordinate(snapshot.index, coordinate));
307
+ const overviewRendered = input.includeOverview && snapshot.index.raster !== undefined;
308
+ const selected = selectedMapEntities(snapshot, input.provinceIds, input.stateIds, input.regionIds);
309
+ const provinceGeometry = input.provinceIds.length === 0
310
+ ? undefined
311
+ : await exportProvinceGeometryRowRuns(snapshot.index, input.provinceIds, context.signal);
312
+ const allocationPreviews = [];
313
+ for (const request of input.allocationRequests) {
314
+ allocationPreviews.push({
315
+ request,
316
+ allocation: await allocateMapIdentifiersAsync(snapshot.index, request, context.signal),
317
+ });
318
+ }
319
+ const workspace = engine.resolver.get(context.workspaceId, context.principal);
320
+ const sourceEvidence = mapArtifactSourceEvidence(snapshot.files);
321
+ const inspectionProvenance = {
322
+ kind: 'map-inspect',
323
+ toolVersion: PACKAGE_VERSION,
324
+ schemaVersion: 'map-inspect.v2',
325
+ sourceHashes: sourceEvidence.bounded.sourceHashes,
326
+ metadata: {
327
+ sharedRevision,
328
+ sourceHashInventory: sourceEvidence.bounded.inventory,
329
+ },
330
+ };
331
+ const artifactWrites = [
332
+ {
333
+ name: `map-inspect.${snapshot.revision.slice(0, 16)}.json`,
334
+ mimeType: 'application/json',
335
+ content: `${canonicalJson({
336
+ schemaVersion: 2,
337
+ revision: snapshot.revision,
338
+ sharedRevision,
339
+ dimensions: snapshot.index.raster === undefined
340
+ ? null
341
+ : {
342
+ width: snapshot.index.raster.width,
343
+ height: snapshot.index.raster.height,
344
+ bitsPerPixel: snapshot.index.provinceBitmap?.bitsPerPixel,
345
+ dibSize: snapshot.index.provinceBitmap?.dibSize,
346
+ },
347
+ counts: {
348
+ definitions: snapshot.index.definitions.length,
349
+ states: snapshot.index.states.length,
350
+ regions: snapshot.index.regions.length,
351
+ adjacencies: snapshot.index.adjacencies.length,
352
+ supplyNodes: snapshot.index.supplyNodes.length,
353
+ railways: snapshot.index.railways.length,
354
+ ports: snapshot.index.ports.length,
355
+ locators: snapshot.index.entityLocators.length,
356
+ },
357
+ catalog,
358
+ query: input.query ?? null,
359
+ queryMatches,
360
+ coordinateMatches,
361
+ selected,
362
+ allocationPreviews,
363
+ validation,
364
+ sourceHashes: sourceEvidence.complete,
365
+ })}\n`,
366
+ provenance: inspectionProvenance,
367
+ description: 'Complete searchable map catalog, inspection, validation, selected records, and allocation previews',
368
+ },
369
+ ];
370
+ if (overviewRendered) {
371
+ const overviewLayer = 'state';
372
+ const overviewOverlays = [
373
+ 'coastlines',
374
+ 'ports',
375
+ 'victory-points',
376
+ 'supply-nodes',
377
+ 'railways',
378
+ ];
379
+ const overview = await renderMap(snapshot.index, {
380
+ layer: overviewLayer,
381
+ overlays: overviewOverlays,
382
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
383
+ });
384
+ const overviewProvenance = {
385
+ kind: 'map-inspect-overview',
386
+ toolVersion: PACKAGE_VERSION,
387
+ schemaVersion: 'map-inspect-overview.v1',
388
+ sourceHashes: sourceEvidence.bounded.sourceHashes,
389
+ renderProfile: {
390
+ layer: overviewLayer,
391
+ overlays: [...new Set(overviewOverlays)].sort(),
392
+ scale: 1,
393
+ },
394
+ metadata: { sourceHashInventory: sourceEvidence.bounded.inventory },
395
+ };
396
+ artifactWrites.push({
397
+ name: `map-overview.${snapshot.revision.slice(0, 16)}.png`,
398
+ mimeType: 'image/png',
399
+ content: overview.png,
400
+ provenance: overviewProvenance,
401
+ description: 'Complete rendered map overview',
402
+ }, {
403
+ name: `map-overview.${snapshot.revision.slice(0, 16)}.html`,
404
+ mimeType: 'text/html',
405
+ content: overview.html,
406
+ provenance: overviewProvenance,
407
+ description: 'Searchable and clickable full-map navigator with exact IDs, names, coordinates, and linked records',
408
+ });
409
+ }
410
+ if (provinceGeometry !== undefined) {
411
+ const { width, height, requestedProvinceIds, unknownProvinceIds, missingGeometryProvinceIds, pixelCount, rowRunCount, provinces, } = provinceGeometry;
412
+ const selectorHash = hashCanonical(requestedProvinceIds).slice(0, 16);
413
+ artifactWrites.push({
414
+ name: `map-province-geometry.${snapshot.revision.slice(0, 16)}.${selectorHash}.json`,
415
+ mimeType: 'application/json',
416
+ content: `${canonicalJson({
417
+ schemaVersion: 1,
418
+ revision: snapshot.revision,
419
+ dimensions: { width, height },
420
+ coordinateSystem: { origin: 'top-left', xDirection: 'right', yDirection: 'down' },
421
+ rowRunFormat: ['y', 'startX', 'endXExclusive'],
422
+ requestedProvinceIds,
423
+ unknownProvinceIds,
424
+ missingGeometryProvinceIds,
425
+ pixelCount,
426
+ rowRunCount,
427
+ provinces,
428
+ sourceHashes: sourceEvidence.complete,
429
+ })}\n`,
430
+ provenance: {
431
+ kind: 'map-province-geometry',
432
+ toolVersion: PACKAGE_VERSION,
433
+ schemaVersion: 'map-province-geometry.v1',
434
+ sourceHashes: sourceEvidence.bounded.sourceHashes,
435
+ metadata: { requestedProvinceIds, sourceHashInventory: sourceEvidence.bounded.inventory },
436
+ },
437
+ description: 'Exact canonical province row runs for deriving bounded split or create geometry',
438
+ });
439
+ }
440
+ const artifacts = await engine.artifacts.withAtomicChunkedWrites(workspace, artifactWrites, (stored) => Promise.resolve([...stored]), context.signal);
441
+ const result = emptyServiceResult(context.workspaceId, {
442
+ revision: snapshot.revision,
443
+ sharedRevision,
444
+ width: snapshot.index.raster?.width ?? null,
445
+ height: snapshot.index.raster?.height ?? null,
446
+ definitions: snapshot.index.definitions.length,
447
+ states: snapshot.index.states.length,
448
+ regions: snapshot.index.regions.length,
449
+ ports: snapshot.index.ports.length,
450
+ inspectedProvinceCount: selected.provinces.length,
451
+ inspectedStateCount: selected.states.length,
452
+ inspectedRegionCount: selected.regions.length,
453
+ allocationCount: allocationPreviews.length,
454
+ queryMatchCount: queryMatches.length,
455
+ coordinateMatchCount: coordinateMatches.length,
456
+ overviewRendered,
457
+ provinceGeometryCount: provinceGeometry?.provinces.length ?? 0,
458
+ provinceGeometryPixelCount: provinceGeometry?.pixelCount ?? 0,
459
+ provinceGeometryRowRunCount: provinceGeometry?.rowRunCount ?? 0,
460
+ unknownProvinceIds: provinceGeometry?.unknownProvinceIds ?? [],
461
+ missingGeometryProvinceIds: provinceGeometry?.missingGeometryProvinceIds ?? [],
462
+ });
463
+ result.code = 'MAP_INSPECTED';
464
+ setInlineFilesScanned(result, snapshot.files.map(({ displayPath }) => displayPath));
465
+ result.diagnostics = validation.diagnostics.slice(0, 100);
466
+ result.artifacts = artifacts.map(publicArtifactLink);
467
+ result.validation = validationSummary(validation);
468
+ await context.progress(3, 3, 'Map inspection complete');
469
+ return result;
470
+ }
471
+ export async function renderMapView(nudger, input, context) {
472
+ await context.progress(0, 3, 'Rendering offline map layer');
473
+ const rendered = await nudger.renderAndStore(context.workspaceId, {
474
+ ...(input.layer === undefined ? {} : { layer: input.layer }),
475
+ ...(input.overlays === undefined ? {} : { overlays: input.overlays }),
476
+ ...(input.scale === undefined ? {} : { scale: input.scale }),
477
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
478
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
479
+ });
480
+ const result = emptyServiceResult(context.workspaceId, {
481
+ revision: rendered.revision,
482
+ width: rendered.bundle.width,
483
+ height: rendered.bundle.height,
484
+ hashes: rendered.bundle.hashes,
485
+ offlineRepresentation: true,
486
+ });
487
+ result.code = 'MAP_RENDERED';
488
+ setInlineFilesScanned(result, rendered.filesScanned);
489
+ result.artifacts = rendered.artifacts.map(publicArtifactLink);
490
+ await context.progress(3, 3, 'Map render complete');
491
+ return result;
492
+ }
493
+ import { z } from 'zod/v4';
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod/v4';
2
+ export const probabilityPromptArguments = z.object({
3
+ objective: z.string().min(1).max(4096),
4
+ sourceHint: z.string().max(1024).optional(),
5
+ });
6
+ export const probabilityPromptDefinition = {
7
+ name: 'hoi4.probability_analysis',
8
+ title: 'Analyze HOI4 weighted logic',
9
+ description: 'Plan one source-linked AI, MTTH, random, or declared-pool analysis and return to the owning modding workflow.',
10
+ };
11
+ /** One prompt body for both protocol generations; it does not run an analysis. */
12
+ export function probabilityPrompt(input) {
13
+ const { objective, sourceHint } = probabilityPromptArguments.parse(input);
14
+ return {
15
+ messages: [
16
+ {
17
+ role: 'user',
18
+ content: {
19
+ type: 'text',
20
+ text: `Analyze this HOI4 weighted-logic task: ${objective}${sourceHint === undefined ? '' : `\nSource hint: ${sourceHint}`}\nIdentify the exact weighted surface and adapter, declare representative world-state scenarios and every required candidate or external factor, inspect first, run the narrowest useful evaluate/sweep/simulate/sequence/compare operation, review linked uncertainty and provenance, then return the findings to the normal owning modding workflow. Do not infer missing state, execute effects, or edit source through probability tools.`,
21
+ },
22
+ },
23
+ ],
24
+ };
25
+ }
@@ -0,0 +1,29 @@
1
+ import { GetPromptResultSchema } from '@modelcontextprotocol/core';
2
+ import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
3
+ import { probabilityPrompt, probabilityPromptArguments, probabilityPromptDefinition, } from './probability.js';
4
+ export function registerModernPrompts(server) {
5
+ server.setRequestHandler('prompts/list', () => ({
6
+ prompts: [
7
+ {
8
+ ...probabilityPromptDefinition,
9
+ arguments: Object.entries(probabilityPromptArguments.shape).map(([name, schema]) => ({
10
+ name,
11
+ required: !schema.safeParse(undefined).success,
12
+ })),
13
+ },
14
+ ],
15
+ }));
16
+ server.setRequestHandler('prompts/get', (request) => {
17
+ if (request.params.name !== probabilityPromptDefinition.name)
18
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Unknown prompt');
19
+ const parsed = probabilityPromptArguments.safeParse(request.params.arguments ?? {});
20
+ if (!parsed.success)
21
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Invalid prompt arguments');
22
+ return GetPromptResultSchema.parse({
23
+ ...probabilityPrompt(parsed.data),
24
+ resultType: 'complete',
25
+ ttlMs: 0,
26
+ cacheScope: 'private',
27
+ });
28
+ });
29
+ }
@@ -0,0 +1,34 @@
1
+ import { ReadResourceResultSchema } from '@modelcontextprotocol/core';
2
+ import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError, } from '@modelcontextprotocol/server';
3
+ import { artifactResourceDefinition, readArtifactResource } from '../../core/artifact-resource.js';
4
+ import { ServiceError } from '../../core/result.js';
5
+ /** Modern wire projection of the same non-enumerating, authorization-bound resource reader. */
6
+ export function registerModernResources(server, engine, context) {
7
+ server.setRequestHandler('resources/list', () => ({ resources: [] }));
8
+ server.setRequestHandler('resources/templates/list', () => ({
9
+ resourceTemplates: [artifactResourceDefinition],
10
+ }));
11
+ server.setRequestHandler('resources/read', async (request, extra) => {
12
+ try {
13
+ return ReadResourceResultSchema.parse({
14
+ ...(await readArtifactResource(engine, request.params.uri, context, extra.mcpReq.signal)),
15
+ resultType: 'complete',
16
+ ttlMs: 0,
17
+ cacheScope: 'private',
18
+ });
19
+ }
20
+ catch (error) {
21
+ if (extra.mcpReq.signal.aborted)
22
+ throw error;
23
+ if (error instanceof ServiceError) {
24
+ if (error.code.startsWith('RESOURCE_') ||
25
+ ['ARTIFACT_URI_INVALID', 'ARTIFACT_RANGE_INVALID', 'ARTIFACT_METADATA_INVALID'].includes(error.code))
26
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message, {
27
+ code: error.code,
28
+ });
29
+ throw new ResourceNotFoundError(request.params.uri);
30
+ }
31
+ throw new ProtocolError(ProtocolErrorCode.InternalError, 'The resource could not be read');
32
+ }
33
+ });
34
+ }