hoi4-agent-tools 3.0.9 → 3.2.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 (145) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/README.md +10 -2
  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/analysis-evidence.js +9 -0
  6. package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -0
  7. package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
  8. package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
  9. package/dist/hoi4_agent_tools/core/condition-evaluator.js +16 -0
  10. package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
  11. package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
  12. package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
  13. package/dist/hoi4_agent_tools/core/engine.js +48 -5
  14. package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
  15. package/dist/hoi4_agent_tools/core/impact-comparison.js +49 -0
  16. package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
  17. package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
  18. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  19. package/dist/hoi4_agent_tools/core/index.js +47 -4
  20. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  21. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  22. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  23. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  24. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  25. package/dist/hoi4_agent_tools/core/job-worker-host.js +202 -0
  26. package/dist/hoi4_agent_tools/core/job-worker.js +64 -0
  27. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  28. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  29. package/dist/hoi4_agent_tools/core/operation-tasks.js +387 -0
  30. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  31. package/dist/hoi4_agent_tools/core/result.js +1 -0
  32. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  33. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  34. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
  35. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  36. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  37. package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
  38. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  39. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  40. package/dist/hoi4_agent_tools/decision/ai.js +104 -0
  41. package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
  42. package/dist/hoi4_agent_tools/decision/cost.js +177 -0
  43. package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
  44. package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
  45. package/dist/hoi4_agent_tools/decision/service.js +172 -0
  46. package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
  47. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  48. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  49. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  50. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  51. package/dist/hoi4_agent_tools/event/service.js +67 -15
  52. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  53. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  54. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  55. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  56. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  57. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  58. package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
  59. package/dist/hoi4_agent_tools/impact/service.js +127 -0
  60. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  61. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  62. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  63. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  64. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  65. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  66. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  67. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  68. package/dist/hoi4_agent_tools/mcp/server/create.js +18 -6
  69. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  70. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  71. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  72. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  73. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  74. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +45 -0
  75. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  76. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  77. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +16 -0
  78. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  79. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  80. package/dist/hoi4_agent_tools/mcp/tools/analysis.js +65 -0
  81. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  82. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  83. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  84. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  85. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  86. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  87. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  88. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  89. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  90. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  91. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  92. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  93. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  94. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  95. package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
  96. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  97. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  98. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  99. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  100. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  101. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  102. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  103. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  104. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  105. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  106. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  107. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  108. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  109. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  110. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  111. package/dist/hoi4_agent_tools/version.js +1 -1
  112. package/docs/README.md +3 -0
  113. package/docs/analysis.md +100 -0
  114. package/docs/development.md +2 -0
  115. package/docs/events.md +7 -2
  116. package/docs/helper-expansion.md +78 -0
  117. package/docs/http.md +2 -0
  118. package/docs/jobs.md +86 -0
  119. package/docs/technology.md +6 -2
  120. package/package.json +15 -6
  121. package/schemas/configuration.schema.json +1 -1
  122. package/schemas/continuous-focus-palette.schema.json +1 -1
  123. package/schemas/custom-weighted-pool.schema.json +1 -1
  124. package/schemas/decision-inspect-input.schema.json +290 -0
  125. package/schemas/event-feature-manifest.schema.json +1 -1
  126. package/schemas/focus-plan.schema.json +1 -1
  127. package/schemas/focus-planning-sidecar.schema.json +1 -1
  128. package/schemas/gui-animation-source.schema.json +1 -1
  129. package/schemas/gui-helper.schema.json +1 -1
  130. package/schemas/gui-scenario.schema.json +1 -1
  131. package/schemas/helper-expansion-request.schema.json +44 -0
  132. package/schemas/helper-expansion-summary.schema.json +70 -0
  133. package/schemas/impact-inspect-input.schema.json +203 -0
  134. package/schemas/map-operation.schema.json +1 -1
  135. package/schemas/operation-result.schema.json +1 -1
  136. package/schemas/probability-analysis-result.schema.json +1 -1
  137. package/schemas/probability-compare-input.schema.json +1 -1
  138. package/schemas/probability-evaluate-input.schema.json +1 -1
  139. package/schemas/probability-inspect-input.schema.json +1 -1
  140. package/schemas/probability-render-input.schema.json +1 -1
  141. package/schemas/probability-scenario-set.schema.json +1 -1
  142. package/schemas/probability-sequence-input.schema.json +1 -1
  143. package/schemas/probability-simulate-input.schema.json +1 -1
  144. package/schemas/probability-sweep-input.schema.json +1 -1
  145. package/server.json +3 -3
@@ -1,20 +1,8 @@
1
+ import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
1
2
  import { z } from 'zod/v4';
2
- import { boundedSourceHashEvidence, publicArtifactLink } from '../../core/artifacts.js';
3
- import { canonicalJson, compareCodeUnits, hashCanonical } from '../../core/canonical.js';
4
- import { IdleCacheLifetime } from '../../core/idle-cache-lifetime.js';
5
- import { renderDimensionViolation, RenderBudget, RENDER_MAX_DIMENSION, } from '../../core/render-budget.js';
6
- import { emptyServiceResult, ServiceError } from '../../core/result.js';
7
- import { SOURCE_MAX_BYTES } from '../../core/source/index.js';
8
- import { GuiHelperDocumentSchema, GuiGeneratedScenarioOptionsSchema, GuiPreviewScenarioSchema, GUI_TEXT_PACKAGE_MAX_FILES, ScriptedGuiStudio, compileGuiHelpers, } from '../../gui/index.js';
9
- import { encodeGuiInspectionArtifact, projectGuiGraphForArtifact, } from '../../gui/inspection-artifact.js';
10
- import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
11
- import { PACKAGE_VERSION } from '../../version.js';
12
- import { requireServerScope, resolveServerWorkspaceId, } from '../server/base-tools.js';
13
- import { compactValidatedInputSchema } from '../server/context-schemas.js';
14
- import { autonomousFailureContext, autonomousResultArtifacts, executePlannedTransaction, } from '../server/transaction-execution.js';
3
+ import { guiInspectRequestSchema, guiRenderRequestSchema, guiRewriteRequestSchema, } from '../../schemas/gui-requests.js';
15
4
  import { indexSkippedSourceSchema, nonNegativeIntegerSchema, sha256Schema, } from '../server/output-schemas.js';
16
- import { progressReporter, withProgressHeartbeat } from '../server/progress.js';
17
- import { errorResult, setInlineFilesScanned, strictOperationResultSchema, toolResult, } from '../server/result.js';
5
+ import { strictOperationResultSchema } from '../server/result.js';
18
6
  const countRecordSchema = z.record(z.string().max(256), nonNegativeIntegerSchema);
19
7
  const guiScanOutputSchema = strictOperationResultSchema(z
20
8
  .object({
@@ -76,418 +64,28 @@ const artifactProducing = {
76
64
  idempotentHint: true,
77
65
  openWorldHint: false,
78
66
  };
79
- const previewStateSchema = z.enum([
80
- 'normal',
81
- 'hover',
82
- 'selected',
83
- 'locked',
84
- 'disabled',
85
- 'warning',
86
- 'active',
87
- 'completed',
88
- 'empty-list',
89
- 'full-list',
90
- 'minimum-value',
91
- 'maximum-value',
92
- 'long-text',
93
- 'missing-localisation',
94
- ]);
95
- const compactGuiScenarioSchema = compactValidatedInputSchema(GuiPreviewScenarioSchema, `GUI preview scenario: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
96
- const compactGeneratedScenarioOptionsSchema = compactValidatedInputSchema(GuiGeneratedScenarioOptionsSchema, `Options: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
97
- const compactGuiHelperSchema = compactValidatedInputSchema(GuiHelperDocumentSchema, `GUI helper document: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
98
- const guiBaseInput = z
99
- .object({
100
- workspaceId: workspaceIdSchema,
101
- windowName: z.string().min(1).max(256),
102
- scenario: compactGuiScenarioSchema,
103
- })
104
- .strict();
105
- const guiInspectInput = z
106
- .object({
107
- workspaceId: workspaceIdSchema,
108
- windowName: z.string().min(1).max(256).optional(),
109
- scenario: compactGuiScenarioSchema.optional(),
110
- relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
111
- generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
112
- })
113
- .strict()
114
- .superRefine((value, context) => {
115
- if ((value.windowName === undefined) !== (value.scenario === undefined)) {
116
- context.addIssue({
117
- code: 'custom',
118
- message: 'windowName and scenario must be provided together',
119
- });
120
- }
121
- if (value.relatedScenarios !== undefined && value.scenario === undefined) {
122
- context.addIssue({
123
- code: 'custom',
124
- path: ['relatedScenarios'],
125
- message: 'relatedScenarios requires a window scenario',
126
- });
127
- }
128
- if (value.generatedScenarios !== undefined && value.scenario === undefined) {
129
- context.addIssue({
130
- code: 'custom',
131
- path: ['generatedScenarios'],
132
- message: 'generatedScenarios requires a window scenario',
133
- });
134
- }
135
- });
136
- const resolutionSchema = z
137
- .object({
138
- width: z.number().int().min(320).max(RENDER_MAX_DIMENSION),
139
- height: z.number().int().min(200).max(RENDER_MAX_DIMENSION),
140
- uiScale: z
141
- .number()
142
- .min(0.25)
143
- .max(4)
144
- .optional()
145
- .describe('In-game UI scale for this resolution. Use 1 at 1920x1080; do not use it to enlarge an artifact.'),
146
- })
147
- .strict()
148
- .superRefine(({ width, height }, context) => {
149
- const violation = renderDimensionViolation(width, height, 'GUI matrix resolution');
150
- if (violation !== undefined) {
151
- context.addIssue({ code: 'custom', message: `${violation.code}: ${violation.message}` });
152
- }
153
- });
154
- function validationSummary(validation) {
155
- return {
156
- passed: validation.checks.every(({ passed }) => passed) &&
157
- !validation.diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker'),
158
- checks: validation.checks,
159
- };
160
- }
161
- export function registerGuiTools(server, engine, context) {
162
- const studio = new ScriptedGuiStudio(engine);
163
- const cacheLifetime = new IdleCacheLifetime(() => studio.clearCaches());
164
- server.registerTool('hoi4.gui_inspect', {
67
+ export const guiTaskTools = [
68
+ {
69
+ name: 'hoi4.gui_inspect',
165
70
  title: 'Inspect scripted GUI',
166
71
  description: 'Inspect GUI sources and scenarios.',
167
- inputSchema: guiInspectInput,
72
+ inputSchema: guiInspectRequestSchema,
168
73
  outputSchema: guiScanOutputSchema,
169
74
  annotations: artifactProducing,
170
- }, async ({ workspaceId: requestedWorkspaceId, windowName, scenario, relatedScenarios, generatedScenarios, }, extra) => {
171
- const workspaceId = await resolveServerWorkspaceId(engine, context, requestedWorkspaceId, extra.signal);
172
- const releaseCaches = cacheLifetime.begin();
173
- try {
174
- const progress = progressReporter(extra);
175
- await progress.report(0, 3, 'Building GUI source graph');
176
- const inspected = await withProgressHeartbeat(async () => {
177
- if (windowName === undefined || scenario === undefined) {
178
- const { graph } = await studio.scan(workspaceId, context.principal, progress.signal);
179
- return { graph, linted: undefined };
180
- }
181
- const linted = await studio.lint({
182
- workspaceId,
183
- windowName,
184
- scenario,
185
- ...(relatedScenarios === undefined ? {} : { relatedScenarios }),
186
- generatedScenarios: generatedScenarios ?? {},
187
- ...(context.principal === undefined ? {} : { principal: context.principal }),
188
- signal: progress.signal,
189
- });
190
- return { graph: linted.graph, linted };
191
- }, progress, 'Inspecting GUI sources and linked assets');
192
- const { graph, linted } = inspected;
193
- const sharedRevision = hashCanonical(graph.sourceHashes);
194
- const workspace = engine.resolver.get(workspaceId, context.principal);
195
- const sourceEvidence = boundedSourceHashEvidence(graph.sourceHashes);
196
- const artifactGraph = projectGuiGraphForArtifact(graph, linted?.scene.elements.map(({ sourceId }) => sourceId));
197
- const inspectionName = `gui-inspect.${sharedRevision.slice(0, 16)}.json`;
198
- const inspectionJson = `${canonicalJson({
199
- schemaVersion: 1,
200
- sharedRevision,
201
- offline: true,
202
- graph: artifactGraph.graph,
203
- ...(artifactGraph.projection === undefined
204
- ? {}
205
- : { graphProjection: artifactGraph.projection }),
206
- ...(linted === undefined
207
- ? {}
208
- : {
209
- scenario: linted.scene.scenario,
210
- fidelity: linted.scene.fidelity,
211
- validation: linted.validation,
212
- }),
213
- })}\n`;
214
- const encodedInspection = await encodeGuiInspectionArtifact(inspectionName, inspectionJson);
215
- const artifact = await engine.artifacts.putChunked(workspace, encodedInspection.name, encodedInspection.mimeType, encodedInspection.content, {
216
- kind: 'gui-inspect',
217
- toolVersion: PACKAGE_VERSION,
218
- schemaVersion: 'gui-inspect.v1',
219
- sourceHashes: sourceEvidence.sourceHashes,
220
- metadata: {
221
- sharedRevision,
222
- offline: true,
223
- complete: graph.complete,
224
- skippedSourceCount: graph.skippedSourceCount,
225
- sourceHashInventory: sourceEvidence.inventory,
226
- compressed: encodedInspection.compressed,
227
- uncompressedBytes: encodedInspection.uncompressedBytes,
228
- graphProjection: artifactGraph.projection?.mode ?? 'full',
229
- },
230
- }, encodedInspection.compressed
231
- ? 'Gzip-compressed scripted GUI graph, diagnostics, and optional scenario fidelity inspection'
232
- : 'Scripted GUI graph, diagnostics, and optional scenario fidelity inspection', progress.signal);
233
- const diagnostics = linted === undefined ? graph.diagnostics : linted.validation.diagnostics;
234
- const result = emptyServiceResult(workspaceId, {
235
- sharedRevision,
236
- complete: graph.complete,
237
- skippedSourceCount: graph.skippedSourceCount,
238
- skippedSources: graph.skippedSources,
239
- nodes: graph.nodes.length,
240
- edges: graph.edges.length,
241
- elements: graph.elements.length,
242
- sprites: graph.sprites.length,
243
- fonts: graph.fonts.length,
244
- scriptedGuis: graph.scriptedGuis.length,
245
- ...(linted === undefined
246
- ? {}
247
- : {
248
- windowName,
249
- scenarioId: linted.scene.scenario.id,
250
- inspectedElementCount: linted.scene.elements.length,
251
- fidelityCounts: Object.fromEntries(Object.entries(linted.scene.fidelity).map(([key, values]) => [
252
- key,
253
- values.length,
254
- ])),
255
- }),
256
- });
257
- result.code = graph.complete ? 'GUI_INSPECTED' : 'GUI_INSPECTED_PARTIAL';
258
- setInlineFilesScanned(result, graph.filesScanned);
259
- result.diagnostics = diagnostics.slice(0, 100);
260
- result.artifacts = [publicArtifactLink(artifact)];
261
- result.validation =
262
- linted === undefined
263
- ? {
264
- passed: !diagnostics.some(({ severity }) => severity === 'error' || severity === 'blocker'),
265
- checks: [
266
- {
267
- id: 'gui-inspection',
268
- passed: true,
269
- message: graph.complete
270
- ? `${graph.nodes.length} GUI graph nodes connected from a complete inventory`
271
- : `${graph.nodes.length} GUI graph nodes connected; ${graph.skippedSourceCount} over-limit source(s) were skipped`,
272
- },
273
- ],
274
- }
275
- : validationSummary({ diagnostics, checks: linted.validation.checks });
276
- await progress.report(3, 3, 'GUI inspection complete');
277
- return toolResult(result);
278
- }
279
- catch (error) {
280
- return errorResult(error, workspaceId);
281
- }
282
- finally {
283
- releaseCaches();
284
- }
285
- });
286
- const render = async (input, extra, code) => {
287
- const workspaceId = await resolveServerWorkspaceId(engine, context, input.workspaceId, extra.signal);
288
- const { windowName } = input;
289
- const releaseCaches = cacheLifetime.begin();
290
- try {
291
- const progress = progressReporter(extra);
292
- await progress.report(0, 4, 'Building GUI source graph');
293
- const rendered = await withProgressHeartbeat(() => studio.renderAndStore({
294
- workspaceId,
295
- windowName,
296
- scenario: input.scenario,
297
- ...(input.states === undefined ? {} : { states: input.states }),
298
- ...(input.resolutions === undefined
299
- ? {}
300
- : {
301
- resolutions: input.resolutions.map(({ width, height, uiScale }) => ({
302
- width,
303
- height,
304
- ...(uiScale === undefined ? {} : { uiScale }),
305
- })),
306
- }),
307
- ...(input.relatedScenarios === undefined
308
- ? {}
309
- : { relatedScenarios: input.relatedScenarios }),
310
- generatedScenarios: input.generatedScenarios ?? {},
311
- ...(input.comparisonScenario === undefined
312
- ? {}
313
- : { comparisonScenario: input.comparisonScenario }),
314
- ...(context.principal === undefined ? {} : { principal: context.principal }),
315
- signal: progress.signal,
316
- }), progress, 'Rendering GUI variants and visual diagnostics');
317
- await progress.report(3, 4, 'Preparing GUI result');
318
- const diagnostics = rendered.validation.diagnostics;
319
- const result = emptyServiceResult(workspaceId, {
320
- windowName,
321
- scenarioId: rendered.render.scene.scenario.id,
322
- sourceRevision: rendered.render.scene.sourceRevision,
323
- variantCount: rendered.render.images.length,
324
- variants: rendered.render.images.slice(0, 64).map(({ variant, width, height }) => ({
325
- variant,
326
- width,
327
- height,
328
- })),
329
- stateCount: rendered.stateScenes.length,
330
- scenarioCount: rendered.scenarioScenes.length,
331
- resolutionCount: rendered.resolutionScenes.length,
332
- comparison: {
333
- changedPixels: rendered.comparison.changedPixels,
334
- changedRatio: rendered.comparison.changedRatio,
335
- },
336
- fidelityCounts: Object.fromEntries(Object.entries(rendered.render.fidelity).map(([key, values]) => [key, values.length])),
337
- offlineRepresentation: true,
338
- });
339
- result.code = code;
340
- setInlineFilesScanned(result, rendered.filesScanned);
341
- result.diagnostics = diagnostics.slice(0, 100);
342
- result.artifacts = rendered.artifacts.map(publicArtifactLink);
343
- result.validation = validationSummary({
344
- diagnostics,
345
- checks: rendered.validation.checks,
346
- });
347
- await progress.report(4, 4, 'GUI render complete');
348
- return toolResult(result);
349
- }
350
- catch (error) {
351
- return errorResult(error, workspaceId);
352
- }
353
- finally {
354
- releaseCaches();
355
- }
356
- };
357
- const renderInput = guiBaseInput
358
- .extend({
359
- states: z.array(previewStateSchema).max(14).optional(),
360
- resolutions: z.array(resolutionSchema).min(1).max(16).optional(),
361
- relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
362
- generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
363
- comparisonScenario: compactGuiScenarioSchema.optional(),
364
- })
365
- .strict()
366
- .superRefine(({ scenario, states, resolutions, relatedScenarios, generatedScenarios }, context) => {
367
- const budget = new RenderBudget();
368
- try {
369
- const stateCount = states?.length ?? 14;
370
- for (let index = 0; index < 9 + stateCount; index += 1) {
371
- budget.reserve(scenario.resolution.width, scenario.resolution.height, 'GUI request variant');
372
- }
373
- for (const resolution of resolutions ?? [
374
- { width: 1280, height: 720 },
375
- { width: 1920, height: 1080 },
376
- { width: 2560, height: 1440 },
377
- { width: 1920, height: 1080 },
378
- ]) {
379
- budget.reserve(resolution.width, resolution.height, 'GUI resolution variant');
380
- }
381
- const generatedCount = generatedScenarios?.enabled === false ? 0 : (generatedScenarios?.count ?? 1);
382
- const scenarioCount = generatedCount === 0
383
- ? 1 + (relatedScenarios?.length ?? 0)
384
- : generatedCount +
385
- (generatedScenarios?.preservePlaceholder === false ? 0 : 1) +
386
- (relatedScenarios?.length ?? 0);
387
- const stateColumns = Math.min(3, Math.max(1, stateCount));
388
- const stateRows = Math.max(1, Math.ceil(stateCount / stateColumns));
389
- budget.reserve(stateColumns * 420, 46 + stateRows * 280, 'GUI state gallery');
390
- const resolutionCount = resolutions?.length ?? 4;
391
- const resolutionColumns = Math.min(3, Math.max(1, resolutionCount));
392
- const resolutionRows = Math.max(1, Math.ceil(resolutionCount / resolutionColumns));
393
- budget.reserve(resolutionColumns * 420, 46 + resolutionRows * 280, 'GUI resolution gallery');
394
- const scenarioBudget = new RenderBudget();
395
- const scenarioColumns = Math.min(3, Math.max(1, scenarioCount));
396
- const scenarioRows = Math.max(1, Math.ceil(scenarioCount / scenarioColumns));
397
- scenarioBudget.reserve(scenarioColumns * 420, 46 + scenarioRows * 280, 'GUI scripted scenario gallery');
398
- }
399
- catch (error) {
400
- if (error instanceof ServiceError) {
401
- context.addIssue({ code: 'custom', message: `${error.code}: ${error.message}` });
402
- return;
403
- }
404
- throw error;
405
- }
406
- });
407
- server.registerTool('hoi4.gui_render', {
75
+ },
76
+ {
77
+ name: 'hoi4.gui_render',
408
78
  title: 'Render scripted GUI artifacts',
409
79
  description: 'Render GUI states, resolutions, generated or explicit scenarios, dynamic flags and text icons, hierarchy, comparisons, and diagnostics.',
410
- inputSchema: renderInput,
80
+ inputSchema: guiRenderRequestSchema,
411
81
  outputSchema: guiRenderOutputSchema,
412
82
  annotations: artifactProducing,
413
- }, (input, extra) => render(input, extra, 'GUI_RENDERED'));
414
- server.registerTool('hoi4.gui_rewrite', {
83
+ },
84
+ {
85
+ name: 'hoi4.gui_rewrite',
415
86
  title: 'Create or clean up scripted GUI',
416
87
  description: 'Apply one validated source, helper, or exact-patch GUI package. Text dependencies use additionalFiles; binary art stays workspace-referenced.',
417
- inputSchema: z
418
- .object({
419
- mode: z.enum(['source', 'helpers', 'patches']),
420
- workspaceId: workspaceIdSchema,
421
- relativePath: workspaceRelativePathSchema,
422
- windowName: z.string().min(1).max(256),
423
- scenario: compactGuiScenarioSchema,
424
- source: z.string().max(SOURCE_MAX_BYTES).optional(),
425
- helper: compactGuiHelperSchema.optional(),
426
- additionalFiles: z
427
- .array(z
428
- .object({
429
- relativePath: workspaceRelativePathSchema,
430
- source: z.string().max(SOURCE_MAX_BYTES),
431
- })
432
- .strict())
433
- .max(GUI_TEXT_PACKAGE_MAX_FILES - 1)
434
- .optional(),
435
- expectedSourceHash: z
436
- .string()
437
- .regex(/^[a-f0-9]{64}$/u)
438
- .optional(),
439
- patches: z
440
- .array(z
441
- .object({
442
- start: z.number().int().min(0),
443
- end: z.number().int().min(0),
444
- expectedText: z.string().max(5_000_000),
445
- text: z.string().max(5_000_000),
446
- description: z.string().min(1).max(1000),
447
- })
448
- .strict())
449
- .min(1)
450
- .max(1000)
451
- .optional(),
452
- })
453
- .strict()
454
- .superRefine((value, context) => {
455
- if (value.mode === 'source' && value.source === undefined) {
456
- context.addIssue({ code: 'custom', message: 'source is required in source mode' });
457
- }
458
- if (value.mode === 'helpers' && value.helper === undefined) {
459
- context.addIssue({ code: 'custom', message: 'helper is required in helpers mode' });
460
- }
461
- if (value.mode === 'patches' &&
462
- (value.patches === undefined || value.expectedSourceHash === undefined)) {
463
- context.addIssue({
464
- code: 'custom',
465
- message: 'patches and expectedSourceHash are required in patches mode',
466
- });
467
- }
468
- if (value.mode === 'source' && value.helper !== undefined) {
469
- context.addIssue({ code: 'custom', message: 'helper is forbidden in source mode' });
470
- }
471
- if (value.mode === 'helpers' && value.source !== undefined) {
472
- context.addIssue({ code: 'custom', message: 'source is forbidden in helpers mode' });
473
- }
474
- if (value.mode !== 'patches' &&
475
- (value.patches !== undefined || value.expectedSourceHash !== undefined)) {
476
- context.addIssue({
477
- code: 'custom',
478
- message: 'patch fields are accepted only in patches mode',
479
- });
480
- }
481
- if (value.mode === 'patches' &&
482
- (value.source !== undefined ||
483
- value.helper !== undefined ||
484
- value.additionalFiles !== undefined)) {
485
- context.addIssue({
486
- code: 'custom',
487
- message: 'source, helper, and additionalFiles are forbidden in patches mode',
488
- });
489
- }
490
- }),
88
+ inputSchema: guiRewriteRequestSchema,
491
89
  outputSchema: guiPlanOutputSchema,
492
90
  annotations: {
493
91
  readOnlyHint: false,
@@ -495,91 +93,8 @@ export function registerGuiTools(server, engine, context) {
495
93
  idempotentHint: false,
496
94
  openWorldHint: false,
497
95
  },
498
- }, async (input, extra) => {
499
- const workspaceId = await resolveServerWorkspaceId(engine, context, input.workspaceId, extra.signal);
500
- const { relativePath, windowName, scenario } = input;
501
- const releaseCaches = cacheLifetime.begin();
502
- try {
503
- requireServerScope(context, 'hoi4:write');
504
- const progress = progressReporter(extra);
505
- await progress.report(0, 3, 'Validating the requested GUI result');
506
- const compilation = input.mode === 'helpers' ? compileGuiHelpers(input.helper) : undefined;
507
- const plannedTransaction = await withProgressHeartbeat(() => studio.planSource({
508
- workspaceId,
509
- relativePath,
510
- ...(input.mode === 'patches'
511
- ? {
512
- expectedSourceHash: input.expectedSourceHash,
513
- patches: input.patches,
514
- }
515
- : {
516
- source: input.mode === 'source' ? input.source : compilation.source,
517
- ...(input.additionalFiles === undefined
518
- ? {}
519
- : { additionalFiles: input.additionalFiles }),
520
- }),
521
- windowName,
522
- scenario,
523
- ...(context.principal === undefined ? {} : { principal: context.principal }),
524
- signal: progress.signal,
525
- }), progress, 'Preparing GUI rewrite');
526
- await progress.report(2, 3, 'Applying and validating the GUI rewrite');
527
- const execution = await withProgressHeartbeat(() => executePlannedTransaction(engine, plannedTransaction, context.principal, progress.signal), progress, 'Applying GUI rewrite');
528
- const transaction = execution.transaction;
529
- const planned = {
530
- transaction,
531
- compilation,
532
- };
533
- const result = emptyServiceResult(workspaceId, {
534
- mode: input.mode,
535
- execution: execution.outcome,
536
- fileCount: transaction.files.length,
537
- artifactCount: transaction.artifacts.length,
538
- ...(planned.compilation === undefined
539
- ? {}
540
- : {
541
- nodeCount: planned.compilation.nodeCount,
542
- templateInstanceCount: planned.compilation.templateInstanceCount,
543
- rawEscapeCount: planned.compilation.rawEscapeCount,
544
- }),
545
- });
546
- result.status = transaction.validation.passed ? 'ok' : 'blocked';
547
- result.code =
548
- execution.outcome === 'applied'
549
- ? 'GUI_CHANGES_APPLIED'
550
- : execution.outcome === 'unchanged'
551
- ? 'GUI_CHANGES_UNCHANGED'
552
- : 'GUI_CHANGES_BLOCKED';
553
- setInlineFilesScanned(result, [
554
- relativePath,
555
- ...(input.additionalFiles ?? []).map(({ relativePath: file }) => file),
556
- ].sort((left, right) => compareCodeUnits(left, right)));
557
- result.proposedFiles = transaction.files
558
- .slice(0, 100)
559
- .map(({ relativePath: file }) => file);
560
- result.changedFiles = transaction.appliedFiles.slice(0, 100);
561
- result.diagnostics = transaction.diagnostics.slice(0, 100);
562
- result.artifacts = autonomousResultArtifacts(execution);
563
- result.validation = transaction.validation;
564
- result.blockers = transaction.diagnostics
565
- .filter(({ severity }) => severity === 'error' || severity === 'blocker')
566
- .map(({ code, message, details }) => ({
567
- code,
568
- message,
569
- ...(details === undefined ? {} : { details }),
570
- }));
571
- await progress.report(3, 3, execution.outcome === 'applied'
572
- ? 'GUI rewrite complete'
573
- : execution.outcome === 'unchanged'
574
- ? 'GUI content already satisfied the rewrite'
575
- : 'GUI rewrite blocked');
576
- return toolResult(result);
577
- }
578
- catch (error) {
579
- return errorResult(error, workspaceId, autonomousFailureContext(error));
580
- }
581
- finally {
582
- releaseCaches();
583
- }
584
- });
96
+ },
97
+ ];
98
+ export function registerGuiTools(server, _engine, _context) {
99
+ registerLegacyTaskTools(server, guiTaskTools);
585
100
  }
@@ -0,0 +1,46 @@
1
+ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { JobControlService, jobReferenceSchema, jobStatusOutputSchema, } from '../../core/job-controls.js';
3
+ import { errorResult } from '../../core/operation-result.js';
4
+ export const jobControlTools = [
5
+ {
6
+ name: 'hoi4.job_inspect',
7
+ title: 'Inspect persistent job',
8
+ description: 'Inspect a durable background operation. A completed job returns the original tool result; active and terminal non-result states return bounded status evidence.',
9
+ inputSchema: jobReferenceSchema,
10
+ annotations: {
11
+ readOnlyHint: true,
12
+ destructiveHint: false,
13
+ idempotentHint: true,
14
+ openWorldHint: false,
15
+ },
16
+ },
17
+ {
18
+ name: 'hoi4.job_cancel',
19
+ title: 'Cancel persistent job',
20
+ description: 'Durably request cancellation of an authorized background operation. Completed results remain completed.',
21
+ inputSchema: jobReferenceSchema,
22
+ outputSchema: jobStatusOutputSchema,
23
+ annotations: {
24
+ readOnlyHint: false,
25
+ destructiveHint: true,
26
+ idempotentHint: true,
27
+ openWorldHint: false,
28
+ },
29
+ },
30
+ ];
31
+ /** SDK-v1 projection of the shared, principal-private persistent-job controls. */
32
+ export function registerJobTools(server, engine, context, service) {
33
+ const controls = new JobControlService(engine, service);
34
+ for (const { name, ...definition } of jobControlTools) {
35
+ server.registerTool(name, definition, async (input, extra) => {
36
+ try {
37
+ return CallToolResultSchema.parse(await controls.call(name, input, context, extra.signal));
38
+ }
39
+ catch (error) {
40
+ if (extra.signal.aborted)
41
+ throw error;
42
+ return errorResult(error, input.workspaceId);
43
+ }
44
+ });
45
+ }
46
+ }