hoi4-agent-tools 3.3.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/hoi4_agent_tools/core/condition-evaluator.js +13 -8
  3. package/dist/hoi4_agent_tools/core/helper-expansion.js +3 -0
  4. package/dist/hoi4_agent_tools/event/comparison-selection.js +78 -0
  5. package/dist/hoi4_agent_tools/event/helper-expansion.js +48 -0
  6. package/dist/hoi4_agent_tools/event/operations.js +16 -0
  7. package/dist/hoi4_agent_tools/event/service.js +21 -2
  8. package/dist/hoi4_agent_tools/focus/compact.js +241 -27
  9. package/dist/hoi4_agent_tools/focus/layout.js +119 -8
  10. package/dist/hoi4_agent_tools/focus/render.js +62 -0
  11. package/dist/hoi4_agent_tools/focus/scenario.js +99 -0
  12. package/dist/hoi4_agent_tools/focus/tool-operations.js +30 -4
  13. package/dist/hoi4_agent_tools/gui/assets.js +54 -4
  14. package/dist/hoi4_agent_tools/gui/layout.js +935 -104
  15. package/dist/hoi4_agent_tools/gui/renderer.js +47 -17
  16. package/dist/hoi4_agent_tools/gui/shader.js +515 -0
  17. package/dist/hoi4_agent_tools/gui/source-graph.js +166 -6
  18. package/dist/hoi4_agent_tools/gui/studio.js +245 -19
  19. package/dist/hoi4_agent_tools/gui/tool-operations.js +9 -0
  20. package/dist/hoi4_agent_tools/gui/validators.js +38 -12
  21. package/dist/hoi4_agent_tools/map/operations.js +70 -1
  22. package/dist/hoi4_agent_tools/map/render.js +70 -1
  23. package/dist/hoi4_agent_tools/map/service.js +30 -6
  24. package/dist/hoi4_agent_tools/map/tool-operations.js +67 -1
  25. package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -1
  26. package/dist/hoi4_agent_tools/mcp/tools/focus.js +14 -4
  27. package/dist/hoi4_agent_tools/mcp/tools/gui.js +5 -1
  28. package/dist/hoi4_agent_tools/mcp/tools/map.js +12 -2
  29. package/dist/hoi4_agent_tools/mcp/tools/probability.js +5 -0
  30. package/dist/hoi4_agent_tools/probability/evaluation.js +3 -0
  31. package/dist/hoi4_agent_tools/probability/operations.js +8 -0
  32. package/dist/hoi4_agent_tools/probability/service.js +33 -12
  33. package/dist/hoi4_agent_tools/probability/source-analysis.js +1 -0
  34. package/dist/hoi4_agent_tools/schemas/event.js +8 -0
  35. package/dist/hoi4_agent_tools/schemas/focus-requests.js +40 -1
  36. package/dist/hoi4_agent_tools/schemas/gui-requests.js +8 -0
  37. package/dist/hoi4_agent_tools/schemas/map-requests.js +22 -1
  38. package/dist/hoi4_agent_tools/schemas/technology.js +7 -0
  39. package/dist/hoi4_agent_tools/technology/graph.js +25 -10
  40. package/dist/hoi4_agent_tools/technology/render.js +286 -31
  41. package/dist/hoi4_agent_tools/technology/service.js +55 -8
  42. package/dist/hoi4_agent_tools/technology/source-analysis.js +166 -3
  43. package/dist/hoi4_agent_tools/version.js +1 -1
  44. package/docs/events.md +3 -1
  45. package/docs/focus.md +10 -0
  46. package/docs/gui.md +49 -3
  47. package/docs/map.md +8 -0
  48. package/docs/probability.md +3 -1
  49. package/docs/technology.md +7 -2
  50. package/package.json +1 -1
  51. package/schemas/configuration.schema.json +1 -1
  52. package/schemas/continuous-focus-palette.schema.json +1 -1
  53. package/schemas/custom-weighted-pool.schema.json +1 -1
  54. package/schemas/decision-inspect-input.schema.json +1 -1
  55. package/schemas/event-feature-manifest.schema.json +1 -1
  56. package/schemas/focus-plan.schema.json +1 -1
  57. package/schemas/focus-planning-sidecar.schema.json +1 -1
  58. package/schemas/gui-animation-source.schema.json +1 -1
  59. package/schemas/gui-helper.schema.json +1 -1
  60. package/schemas/gui-scenario.schema.json +1 -1
  61. package/schemas/helper-expansion-request.schema.json +1 -1
  62. package/schemas/helper-expansion-summary.schema.json +1 -1
  63. package/schemas/impact-inspect-input.schema.json +1 -1
  64. package/schemas/map-operation.schema.json +1 -1
  65. package/schemas/mechanic-test-input.schema.json +1 -1
  66. package/schemas/operation-result.schema.json +1 -1
  67. package/schemas/package-check-input.schema.json +1 -1
  68. package/schemas/package-manifest.schema.json +1 -1
  69. package/schemas/probability-analysis-result.schema.json +1 -1
  70. package/schemas/probability-compare-input.schema.json +1 -1
  71. package/schemas/probability-evaluate-input.schema.json +1 -1
  72. package/schemas/probability-inspect-input.schema.json +1 -1
  73. package/schemas/probability-render-input.schema.json +1 -1
  74. package/schemas/probability-scenario-set.schema.json +1 -1
  75. package/schemas/probability-sequence-input.schema.json +1 -1
  76. package/schemas/probability-simulate-input.schema.json +1 -1
  77. package/schemas/probability-sweep-input.schema.json +1 -1
  78. package/schemas/scenario-suite.schema.json +1 -1
  79. package/schemas/scenario-test-input.schema.json +1 -1
  80. package/schemas/shared-scenario.schema.json +1 -1
  81. package/server.json +2 -2
@@ -17,6 +17,10 @@ export const focusInspectRequestSchema = z
17
17
  previous: compactFocusLayoutSchema.optional(),
18
18
  laneSpacing: z.number().int().min(1).max(100).optional(),
19
19
  nodeSpacing: z.number().int().min(1).max(100).optional(),
20
+ scenario: z
21
+ .object({ completedFocusIds: z.array(z.string().min(1).max(256)).max(10_000) })
22
+ .strict()
23
+ .optional(),
20
24
  })
21
25
  .strict()
22
26
  .superRefine((value, context) => {
@@ -34,7 +38,7 @@ export const focusInspectRequestSchema = z
34
38
  message: 'treeId is only valid in national mode',
35
39
  });
36
40
  if (mode === 'continuous') {
37
- for (const field of ['previous', 'laneSpacing', 'nodeSpacing']) {
41
+ for (const field of ['previous', 'laneSpacing', 'nodeSpacing', 'scenario']) {
38
42
  if (value[field] !== undefined)
39
43
  context.addIssue({
40
44
  code: 'custom',
@@ -49,6 +53,12 @@ export const focusInspectRequestSchema = z
49
53
  path: ['previous'],
50
54
  message: 'previous requires treeId so it applies to exactly one tree',
51
55
  });
56
+ if (value.scenario !== undefined && value.treeId === undefined)
57
+ context.addIssue({
58
+ code: 'custom',
59
+ path: ['scenario'],
60
+ message: 'scenario requires treeId so it applies to exactly one tree',
61
+ });
52
62
  });
53
63
  export const focusRenderRequestSchema = z
54
64
  .object({
@@ -64,6 +74,7 @@ export const focusRenderRequestSchema = z
64
74
  .min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
65
75
  .max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
66
76
  .optional(),
77
+ cropFocusIds: z.array(z.string().min(1).max(256)).max(16).optional(),
67
78
  columns: z.number().int().min(1).max(12).optional(),
68
79
  padding: z.number().int().min(0).max(1000).optional(),
69
80
  })
@@ -80,6 +91,7 @@ export const focusRenderRequestSchema = z
80
91
  ['horizontalSpacing', value.horizontalSpacing],
81
92
  ['verticalSpacing', value.verticalSpacing],
82
93
  ['reviewScale', value.reviewScale],
94
+ ['cropFocusIds', value.cropFocusIds],
83
95
  ];
84
96
  for (const [field, fieldValue] of invalid) {
85
97
  if (fieldValue !== undefined)
@@ -103,6 +115,25 @@ export const focusRewriteRequestSchema = z
103
115
  relativePath: workspaceRelativePathSchema,
104
116
  treeId: z.string().min(1).max(256).optional(),
105
117
  layoutMode: z.enum(['authored', 'compact']).default('authored'),
118
+ compactFocusIds: z.array(z.string().min(1).max(256)).min(1).max(10_000).optional(),
119
+ pinnedFocusIds: z.array(z.string().min(1).max(256)).max(10_000).optional(),
120
+ symmetryGroups: z
121
+ .array(z
122
+ .object({
123
+ centerFocusId: z.string().min(1).max(256),
124
+ pairs: z
125
+ .array(z
126
+ .object({
127
+ leftFocusId: z.string().min(1).max(256),
128
+ rightFocusId: z.string().min(1).max(256),
129
+ })
130
+ .strict())
131
+ .min(1)
132
+ .max(10_000),
133
+ })
134
+ .strict())
135
+ .max(64)
136
+ .optional(),
106
137
  plan: compactFocusPlanSchema.optional(),
107
138
  createIfMissing: z.boolean().default(false),
108
139
  horizontalSpacing: z.number().int().min(80).max(1000).optional(),
@@ -117,6 +148,14 @@ export const focusRewriteRequestSchema = z
117
148
  })
118
149
  .strict()
119
150
  .superRefine((value, context) => {
151
+ if (value.layoutMode !== 'compact')
152
+ for (const field of ['compactFocusIds', 'pinnedFocusIds', 'symmetryGroups'])
153
+ if (value[field] !== undefined)
154
+ context.addIssue({
155
+ code: 'custom',
156
+ path: [field],
157
+ message: `${field} requires compact layout mode`,
158
+ });
120
159
  const mode = value.mode ?? 'national';
121
160
  const schema = mode === 'continuous' ? continuousFocusPaletteSchema : focusTreePlanSchema;
122
161
  if (value.plan !== undefined && !schema.safeParse(value.plan).success)
@@ -82,6 +82,14 @@ export const guiRenderRequestSchema = z
82
82
  relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
83
83
  generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
84
84
  comparisonScenario: compactGuiScenarioSchema.optional(),
85
+ sourceBaseline: z
86
+ .object({
87
+ relativePath: workspaceRelativePathSchema,
88
+ source: z.string().max(SOURCE_MAX_BYTES),
89
+ })
90
+ .strict()
91
+ .optional()
92
+ .describe('Previous mod-owned .gui source for a matched source comparison using the requested scenario.'),
85
93
  })
86
94
  .strict()
87
95
  .superRefine(({ scenario, states, resolutions, relatedScenarios, generatedScenarios }, context) => {
@@ -63,6 +63,7 @@ export const mapInspectRequestSchema = z
63
63
  ]))
64
64
  .max(100)
65
65
  .default([]),
66
+ lookupOnly: z.boolean().default(false),
66
67
  includeOverview: z.boolean().default(true),
67
68
  allocationRequests: z.array(mapAllocationRequestSchema).max(100).default([]),
68
69
  })
@@ -73,8 +74,28 @@ export const mapRenderRequestSchema = z
73
74
  layer: mapLayerSchema.optional(),
74
75
  overlays: z.array(mapOverlaySchema).max(12).optional(),
75
76
  scale: z.number().int().min(1).max(16).optional(),
77
+ tile: z
78
+ .object({
79
+ x: z.number().int().min(0),
80
+ y: z.number().int().min(0),
81
+ width: z.number().int().min(1).max(2_048),
82
+ height: z.number().int().min(1).max(2_048),
83
+ })
84
+ .strict()
85
+ .optional(),
86
+ area: z
87
+ .object({
88
+ provinceIds: z.array(z.number().int().nonnegative()).max(256).optional(),
89
+ stateIds: z.array(z.number().int().nonnegative()).max(64).optional(),
90
+ regionIds: z.array(z.number().int().nonnegative()).max(64).optional(),
91
+ padding: z.number().int().min(0).max(256).optional(),
92
+ })
93
+ .strict()
94
+ .refine(({ provinceIds, stateIds, regionIds }) => (provinceIds?.length ?? 0) + (stateIds?.length ?? 0) + (regionIds?.length ?? 0) > 0, 'Map area requires at least one province, state, or region ID')
95
+ .optional(),
76
96
  })
77
- .strict();
97
+ .strict()
98
+ .refine(({ tile, area }) => tile === undefined || area === undefined, 'Choose tile or area, not both');
78
99
  export const mapRewriteRequestSchema = z
79
100
  .object({
80
101
  workspaceId: workspaceIdSchema,
@@ -132,6 +132,13 @@ export const technologyRenderRequestSchema = z
132
132
  categoryId: technologyIdSchema.optional(),
133
133
  targetId: technologyIdSchema.optional(),
134
134
  maxNodes: z.number().int().min(1).max(2_000).optional(),
135
+ scenario: z
136
+ .object({
137
+ year: z.number().int().min(1).max(9999),
138
+ researchedTechnologyIds: z.array(technologyIdSchema).max(25_000),
139
+ })
140
+ .strict()
141
+ .optional(),
135
142
  includeHtml: z.boolean().optional(),
136
143
  refresh: z.boolean().optional(),
137
144
  })
@@ -11,6 +11,7 @@ const MAX_EXTERNAL_REFERENCES = 1_000_000;
11
11
  const MAX_ISSUES = 100_000;
12
12
  const MAX_HELPER_DEPTH = 64;
13
13
  const MAX_HELPER_PROJECTIONS = 500_000;
14
+ const MAX_FULL_HELPER_CALLS = 4_096;
14
15
  const confidenceRank = {
15
16
  confirmed: 0,
16
17
  high: 1,
@@ -188,6 +189,20 @@ function activeYearMarkers(values) {
188
189
  left.year - right.year ||
189
190
  compareCodeUnits(left.name, right.name));
190
191
  }
192
+ function activeBackgrounds(values) {
193
+ const groups = new Map();
194
+ for (const background of values) {
195
+ const key = `${background.folderId}\0${background.name}`;
196
+ const group = groups.get(key) ?? [];
197
+ group.push(background);
198
+ groups.set(key, group);
199
+ }
200
+ return [...groups.values()]
201
+ .map((group) => group.sort((left, right) => right.loadOrder - left.loadOrder ||
202
+ compareCodeUnits(left.sourcePath, right.sourcePath) ||
203
+ left.location.start.offset - right.location.start.offset)[0])
204
+ .sort((left, right) => compareCodeUnits(left.folderId, right.folderId) || compareCodeUnits(left.name, right.name));
205
+ }
191
206
  function enrichPlacements(placements, edges, gridboxes, technologies, unlocks, itemLayouts, signal) {
192
207
  const technologyById = new Map(technologies.map((technology) => [technology.id, technology]));
193
208
  const unlockKindsByTechnology = new Map();
@@ -238,11 +253,8 @@ function enrichPlacements(placements, edges, gridboxes, technologies, unlocks, i
238
253
  return placements.map((placement) => {
239
254
  const roots = rootsFor(placement.technologyId, placement.folderId);
240
255
  const branchRootId = roots.length === 1 ? roots[0] : undefined;
241
- const expectedGridbox = branchRootId === undefined ? undefined : `${branchRootId}_tree`;
242
- const candidates = expectedGridbox === undefined
243
- ? []
244
- : gridboxes.filter(({ name, folderId }) => name === expectedGridbox &&
245
- (folderId === undefined || folderId === placement.folderId));
256
+ const expectedGridbox = `${branchRootId ?? placement.technologyId}_tree`;
257
+ const candidates = gridboxes.filter(({ name, folderId }) => name === expectedGridbox && (folderId === undefined || folderId === placement.folderId));
246
258
  const gridbox = candidates[0];
247
259
  const technology = technologyById.get(placement.technologyId);
248
260
  const folderLayouts = layoutsByFolder.get(placement.folderId);
@@ -1155,6 +1167,7 @@ function* technologyGraphAnalysis(snapshot, options) {
1155
1167
  const gridboxes = activeGridboxes(fragments.flatMap(({ gridboxes }) => gridboxes));
1156
1168
  const itemLayouts = activeItemLayouts(fragments.flatMap(({ itemLayouts }) => itemLayouts));
1157
1169
  const yearMarkers = activeYearMarkers(fragments.flatMap(({ yearMarkers }) => yearMarkers));
1170
+ const backgrounds = activeBackgrounds(fragments.flatMap(({ backgrounds }) => backgrounds ?? []));
1158
1171
  const categories = activeCategories(fragments.flatMap(({ categories }) => categories));
1159
1172
  const selectedDoctrines = activeDefinitions(fragments.flatMap(({ doctrineDefinitions }) => doctrineDefinitions));
1160
1173
  const doctrineDefinitions = localiseDoctrines(selectedDoctrines.active, snapshot.index, assets);
@@ -1168,10 +1181,11 @@ function* technologyGraphAnalysis(snapshot, options) {
1168
1181
  const placements = enrichPlacements(basePlacements, edges, gridboxes, technologies, unlocks, itemLayouts, options.signal);
1169
1182
  const unresolved = fragments.flatMap(({ unresolved }) => unresolved);
1170
1183
  const helperCalls = fragments.flatMap(({ helperCalls }) => helperCalls);
1171
- const walk = options.analysisMode === 'focused'
1184
+ const analysisMode = options.analysisMode ?? (helperCalls.length > MAX_FULL_HELPER_CALLS ? 'focused' : 'full');
1185
+ const walk = analysisMode === 'focused'
1172
1186
  ? undefined
1173
1187
  : yield technologyHelperWalk(helperCalls, snapshot.revision);
1174
- const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved, options.analysisMode ?? 'full', walk, options.signal);
1188
+ const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved, analysisMode, walk, options.signal);
1175
1189
  const issues = diagnose(technologies, folders, placements, gridboxes, edges, categories, doctrineDefinitions, unlocks, externalReferences, selectedTechnologies.duplicates.map((group) => group.map(({ iconSprite, effectSignatureFields, ...technology }) => ({
1176
1190
  ...technology,
1177
1191
  icon: iconStatus(iconSprite, snapshot.index, assets),
@@ -1197,14 +1211,14 @@ function* technologyGraphAnalysis(snapshot, options) {
1197
1211
  const graph = {
1198
1212
  schemaVersion: TECHNOLOGY_GRAPH_SCHEMA_VERSION,
1199
1213
  parserVersion: TECHNOLOGY_PARSER_VERSION,
1200
- analysisMode: options.analysisMode ?? 'full',
1214
+ analysisMode,
1201
1215
  workspaceId: snapshot.workspaceId,
1202
1216
  workspaceIdentity: options.workspaceIdentity,
1203
1217
  revision,
1204
1218
  complete: snapshot.complete &&
1205
1219
  unresolved.every(({ kind }) => kind !== 'partial_source') &&
1206
1220
  !unresolved.some(({ blockers }) => blockers.some(({ code }) => code === 'TECH_HELPER_DEPTH_BLOCKED' || code === 'TECH_HELPER_PROJECTION_LIMIT')) &&
1207
- !(options.analysisMode === 'focused' && helperCalls.length > 0),
1221
+ !(analysisMode === 'focused' && helperCalls.length > 0),
1208
1222
  analysisBoundary: {
1209
1223
  staticAnalysis: true,
1210
1224
  language: 'l_english',
@@ -1215,7 +1229,7 @@ function* technologyGraphAnalysis(snapshot, options) {
1215
1229
  'meta-generated or variable technology identifiers',
1216
1230
  'exact AI research choice and runtime research time',
1217
1231
  'dynamic scripted localisation without supplied runtime values',
1218
- ...(options.analysisMode === 'focused' && helperCalls.length > 0
1232
+ ...(analysisMode === 'focused' && helperCalls.length > 0
1219
1233
  ? ['large-workspace scripted-effect helper projections are deferred']
1220
1234
  : []),
1221
1235
  ],
@@ -1235,6 +1249,7 @@ function* technologyGraphAnalysis(snapshot, options) {
1235
1249
  gridboxes,
1236
1250
  itemLayouts,
1237
1251
  yearMarkers,
1252
+ backgrounds,
1238
1253
  edges,
1239
1254
  categories,
1240
1255
  doctrineDefinitions,