hoi4-agent-tools 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  No unreleased changes.
6
6
 
7
+ ## 2.8.1 - 2026-08-27
8
+
9
+ - Added periodic MCP progress heartbeats for long GUI inspections, renders, and rewrites, with repeated progress notifications that preserve monotonic work values and still honor cancellation.
10
+
7
11
  ## 2.8.0 - 2026-08-26
8
12
 
9
13
  - Added structured `ROOT`, `THIS`, `PREV`, `FROM` chain, saved-scope, and event-target bindings to probability scenarios.
@@ -8,7 +8,7 @@ import { registerEventTools } from '../tools/event.js';
8
8
  import { registerTechnologyTools } from '../tools/technology.js';
9
9
  import { registerProbabilityTools } from '../tools/probability.js';
10
10
  import { registerMcpResources } from '../resources/register.js';
11
- 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 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. Large evidence is linked as resources.';
11
+ 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 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. Long GUI calls emit periodic progress heartbeats when the client requests progress. Large evidence is linked as resources.';
12
12
  export function createMcpServer(engine, context = {}) {
13
13
  const server = new McpServer({
14
14
  name: PACKAGE_NAME,
@@ -1,9 +1,18 @@
1
+ /**
2
+ * Interval used for long-running MCP operations. It is shorter than the request timeout used by
3
+ * common MCP clients, while still keeping notification traffic negligible for ordinary calls.
4
+ */
5
+ export const PROGRESS_HEARTBEAT_INTERVAL_MS = 10_000;
1
6
  export function progressReporter(extra) {
2
7
  let latest = Number.NEGATIVE_INFINITY;
8
+ let latestTotal = 1;
9
+ let latestMessage = '';
3
10
  return {
4
11
  signal: extra.signal,
5
12
  async report(progress, total, message) {
6
13
  extra.signal.throwIfAborted();
14
+ latestTotal = total;
15
+ latestMessage = message;
7
16
  const normalized = Math.max(latest, Math.min(progress, total));
8
17
  const progressToken = extra._meta?.progressToken;
9
18
  if (progressToken === undefined)
@@ -16,5 +25,53 @@ export function progressReporter(extra) {
16
25
  params: { progressToken, progress: normalized, total, message },
17
26
  });
18
27
  },
28
+ async pulse(message = latestMessage) {
29
+ extra.signal.throwIfAborted();
30
+ const progressToken = extra._meta?.progressToken;
31
+ if (progressToken === undefined)
32
+ return;
33
+ await extra.sendNotification({
34
+ method: 'notifications/progress',
35
+ params: {
36
+ progressToken,
37
+ progress: Number.isFinite(latest) ? latest : 0,
38
+ total: latestTotal,
39
+ message,
40
+ },
41
+ });
42
+ },
43
+ };
44
+ }
45
+ /**
46
+ * Keep an MCP request alive while a domain service is in a long, opaque asynchronous stage.
47
+ * Progress notifications are best-effort: a disconnected client must not turn a successful
48
+ * server operation into a partial result, while the request's AbortSignal still cancels work.
49
+ */
50
+ export async function withProgressHeartbeat(operation, reporter, message, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
51
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
52
+ throw new RangeError('Progress heartbeat interval must be a positive finite number');
53
+ }
54
+ reporter.signal.throwIfAborted();
55
+ let active = true;
56
+ let inFlight = false;
57
+ const tick = () => {
58
+ if (!active || inFlight || reporter.signal.aborted)
59
+ return;
60
+ inFlight = true;
61
+ void reporter
62
+ .pulse(message)
63
+ .catch(() => undefined)
64
+ .finally(() => {
65
+ inFlight = false;
66
+ });
19
67
  };
68
+ const timer = setInterval(tick, intervalMs);
69
+ timer.unref();
70
+ try {
71
+ return await (typeof operation === 'function' ? operation() : operation);
72
+ }
73
+ finally {
74
+ active = false;
75
+ clearInterval(timer);
76
+ }
20
77
  }
@@ -12,7 +12,7 @@ import { requireServerScope, resolveServerWorkspaceId, } from '../server/base-to
12
12
  import { compactValidatedInputSchema } from '../server/context-schemas.js';
13
13
  import { autonomousFailureContext, autonomousResultArtifacts, executePlannedTransaction, } from '../server/transaction-execution.js';
14
14
  import { indexSkippedSourceSchema, nonNegativeIntegerSchema, sha256Schema, } from '../server/output-schemas.js';
15
- import { progressReporter } from '../server/progress.js';
15
+ import { progressReporter, withProgressHeartbeat } from '../server/progress.js';
16
16
  import { errorResult, setInlineFilesScanned, strictOperationResultSchema, toolResult, } from '../server/result.js';
17
17
  const countRecordSchema = z.record(z.string().max(256), nonNegativeIntegerSchema);
18
18
  const guiScanOutputSchema = strictOperationResultSchema(z
@@ -156,21 +156,21 @@ export function registerGuiTools(server, engine, context) {
156
156
  try {
157
157
  const progress = progressReporter(extra);
158
158
  await progress.report(0, 3, 'Building GUI source graph');
159
- const inspection = windowName === undefined || scenario === undefined
160
- ? studio
161
- .scan(workspaceId, context.principal, progress.signal)
162
- .then(({ graph }) => ({ graph, linted: undefined }))
163
- : studio
164
- .lint({
159
+ const inspected = await withProgressHeartbeat(async () => {
160
+ if (windowName === undefined || scenario === undefined) {
161
+ const { graph } = await studio.scan(workspaceId, context.principal, progress.signal);
162
+ return { graph, linted: undefined };
163
+ }
164
+ const linted = await studio.lint({
165
165
  workspaceId,
166
166
  windowName,
167
167
  scenario,
168
168
  ...(relatedScenarios === undefined ? {} : { relatedScenarios }),
169
169
  ...(context.principal === undefined ? {} : { principal: context.principal }),
170
170
  signal: progress.signal,
171
- })
172
- .then((linted) => ({ graph: linted.graph, linted }));
173
- const inspected = await inspection;
171
+ });
172
+ return { graph: linted.graph, linted };
173
+ }, progress, 'Inspecting GUI sources and linked assets');
174
174
  const { graph, linted } = inspected;
175
175
  const sharedRevision = hashCanonical(graph.sourceHashes);
176
176
  const workspace = engine.resolver.get(workspaceId, context.principal);
@@ -268,7 +268,7 @@ export function registerGuiTools(server, engine, context) {
268
268
  try {
269
269
  const progress = progressReporter(extra);
270
270
  await progress.report(0, 4, 'Building GUI source graph');
271
- const rendered = await studio.renderAndStore({
271
+ const rendered = await withProgressHeartbeat(() => studio.renderAndStore({
272
272
  workspaceId,
273
273
  windowName,
274
274
  scenario: input.scenario,
@@ -290,7 +290,7 @@ export function registerGuiTools(server, engine, context) {
290
290
  : { comparisonScenario: input.comparisonScenario }),
291
291
  ...(context.principal === undefined ? {} : { principal: context.principal }),
292
292
  signal: progress.signal,
293
- });
293
+ }), progress, 'Rendering GUI variants and visual diagnostics');
294
294
  await progress.report(3, 4, 'Preparing GUI result');
295
295
  const diagnostics = rendered.validation.diagnostics;
296
296
  const result = emptyServiceResult(workspaceId, {
@@ -471,7 +471,7 @@ export function registerGuiTools(server, engine, context) {
471
471
  const progress = progressReporter(extra);
472
472
  await progress.report(0, 3, 'Validating the requested GUI result');
473
473
  const compilation = input.mode === 'helpers' ? compileGuiHelpers(input.helper) : undefined;
474
- const plannedTransaction = await studio.planSource({
474
+ const plannedTransaction = await withProgressHeartbeat(() => studio.planSource({
475
475
  workspaceId,
476
476
  relativePath,
477
477
  ...(input.mode === 'patches'
@@ -489,9 +489,9 @@ export function registerGuiTools(server, engine, context) {
489
489
  scenario,
490
490
  ...(context.principal === undefined ? {} : { principal: context.principal }),
491
491
  signal: progress.signal,
492
- });
492
+ }), progress, 'Preparing GUI rewrite');
493
493
  await progress.report(2, 3, 'Applying and validating the GUI rewrite');
494
- const execution = await executePlannedTransaction(engine, plannedTransaction, context.principal, progress.signal);
494
+ const execution = await withProgressHeartbeat(() => executePlannedTransaction(engine, plannedTransaction, context.principal, progress.signal), progress, 'Applying GUI rewrite');
495
495
  const transaction = execution.transaction;
496
496
  const planned = {
497
497
  transaction,
@@ -1,6 +1,6 @@
1
1
  import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
2
2
  export const PACKAGE_NAME = 'hoi4-agent-tools';
3
- export const PACKAGE_VERSION = '2.8.0';
3
+ export const PACKAGE_VERSION = '2.8.1';
4
4
  export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
5
5
  export const CONFIG_VERSION = 1;
6
6
  export const TRANSACTION_VERSION = 1;
package/docs/gui.md CHANGED
@@ -58,6 +58,8 @@ Broad inspection indexes localisation actually referenced by GUI source and retu
58
58
 
59
59
  GUI rewrites keep complete validation evidence in a linked JSON resource and retain the highest-priority findings in the transaction result. Large surrounding interface inventories therefore do not turn a small scalar patch into a transaction structure-limit error.
60
60
 
61
+ Large inspections, renders, and rewrites send periodic MCP progress heartbeats while a service stage is still running. Clients that request progress can reset their idle request timer from these notifications; there is no 180-second GUI operation limit in the server, and cancellation still follows the request signal.
62
+
61
63
  ## Offline preview limits
62
64
 
63
65
  The renderer does not run the game engine. Each render includes a fidelity report that separates fields it models from fields it approximates, ignores, cannot resolve, or does not support. Treat that report as part of the review.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
3
  "mcpName": "io.github.klimPaskov/hoi4-agent-tools",
4
- "version": "2.8.0",
4
+ "version": "2.8.1",
5
5
  "description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/configuration.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/configuration.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/continuous-focus-palette.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/continuous-focus-palette.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/custom-weighted-pool.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/custom-weighted-pool.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/event-feature-manifest.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/event-feature-manifest.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/focus-plan.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/focus-plan.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/focus-planning-sidecar.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/focus-planning-sidecar.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/gui-animation-source.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-animation-source.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/gui-helper.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-helper.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/gui-scenario.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/gui-scenario.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/map-operation.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/map-operation.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "oneOf": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/operation-result.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/operation-result.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-analysis-result.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-analysis-result.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-compare-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-compare-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-evaluate-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-evaluate-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-render-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-render-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-scenario-set.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-scenario-set.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-sequence-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-sequence-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-simulate-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-simulate-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.0/schemas/probability-sweep-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.1/schemas/probability-sweep-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.klimPaskov/hoi4-agent-tools",
4
4
  "title": "HOI4 Agent Tools",
5
5
  "description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
6
- "version": "2.8.0",
6
+ "version": "2.8.1",
7
7
  "repository": {
8
8
  "url": "https://github.com/klimPaskov/hoi4-agent-tools",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "hoi4-agent-tools",
16
- "version": "2.8.0",
16
+ "version": "2.8.1",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }