hoi4-agent-tools 3.9.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +115 -147
- package/dist/bin/http.js +12 -0
- package/dist/bin/setup.js +3 -2
- package/dist/bin/stdio.js +12 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +40 -5
- package/dist/hoi4_agent_tools/core/canonical.js +18 -4
- package/dist/hoi4_agent_tools/core/configuration.js +7 -0
- package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +12 -0
- package/dist/hoi4_agent_tools/core/domain-tools.js +65 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +5 -2
- package/dist/hoi4_agent_tools/core/engine.js +56 -10
- package/dist/hoi4_agent_tools/core/index.js +45 -20
- package/dist/hoi4_agent_tools/core/job-executor.js +14 -1
- package/dist/hoi4_agent_tools/core/job-operation-registration.js +15 -7
- package/dist/hoi4_agent_tools/core/job-store.js +78 -5
- package/dist/hoi4_agent_tools/core/job-transactions.js +31 -2
- package/dist/hoi4_agent_tools/core/job-worker-host.js +215 -116
- package/dist/hoi4_agent_tools/core/job-worker.js +90 -36
- package/dist/hoi4_agent_tools/core/operation-result.js +123 -10
- package/dist/hoi4_agent_tools/core/operation-tasks.js +49 -89
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +25 -21
- package/dist/hoi4_agent_tools/core/server-registry.js +155 -0
- package/dist/hoi4_agent_tools/core/server-status-resource.js +22 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +26 -1
- package/dist/hoi4_agent_tools/core/source/cache.js +108 -29
- package/dist/hoi4_agent_tools/core/source/lexer.js +11 -3
- package/dist/hoi4_agent_tools/core/transactions.js +114 -23
- package/dist/hoi4_agent_tools/core/workspace.js +84 -8
- package/dist/hoi4_agent_tools/decision/ai.js +1 -1
- package/dist/hoi4_agent_tools/decision/service.js +28 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +7 -5
- package/dist/hoi4_agent_tools/event/job-operations.js +9 -5
- package/dist/hoi4_agent_tools/event/operations.js +14 -1
- package/dist/hoi4_agent_tools/event/service.js +7 -0
- package/dist/hoi4_agent_tools/focus/job-operations.js +1 -2
- package/dist/hoi4_agent_tools/focus/tool-operations.js +1 -1
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +11 -3
- package/dist/hoi4_agent_tools/gui/studio.js +20 -7
- package/dist/hoi4_agent_tools/gui/validators.js +8 -6
- package/dist/hoi4_agent_tools/map/bmp.js +1 -1
- package/dist/hoi4_agent_tools/map/derived.js +748 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +7 -3
- package/dist/hoi4_agent_tools/map/model.js +103 -0
- package/dist/hoi4_agent_tools/map/operations.js +427 -58
- package/dist/hoi4_agent_tools/map/references.js +483 -0
- package/dist/hoi4_agent_tools/map/service.js +136 -61
- package/dist/hoi4_agent_tools/map/tool-operations.js +2 -1
- package/dist/hoi4_agent_tools/map/validation.js +3 -0
- package/dist/hoi4_agent_tools/map/world.js +1280 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +13 -1
- package/dist/hoi4_agent_tools/mcp/resources/register.js +6 -0
- package/dist/hoi4_agent_tools/mcp/server/argument-errors.js +72 -0
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +23 -5
- package/dist/hoi4_agent_tools/mcp/server/create.js +7 -0
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +24 -30
- package/dist/hoi4_agent_tools/mcp/server/tool-listing.js +45 -0
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +25 -5
- package/dist/hoi4_agent_tools/mcp/tools/event.js +18 -29
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +4 -12
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +3 -3
- package/dist/hoi4_agent_tools/mcp/tools/job.js +2 -2
- package/dist/hoi4_agent_tools/mcp/tools/map.js +13 -16
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +42 -48
- package/dist/hoi4_agent_tools/mcp/tools/reference.js +22 -5
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +16 -26
- package/dist/hoi4_agent_tools/mcp/transports/http.js +28 -4
- package/dist/hoi4_agent_tools/mcp/transports/stdio-lifetime.js +37 -18
- package/dist/hoi4_agent_tools/probability/job-operations.js +4 -9
- package/dist/hoi4_agent_tools/probability/operations.js +175 -13
- package/dist/hoi4_agent_tools/probability/source-analysis.js +24 -1
- package/dist/hoi4_agent_tools/reference/error-log.js +359 -0
- package/dist/hoi4_agent_tools/reference/known-mistakes.js +307 -0
- package/dist/hoi4_agent_tools/reference/known-mistakes.json +154 -0
- package/dist/hoi4_agent_tools/reference/mod-index.js +101 -0
- package/dist/hoi4_agent_tools/reference/operations.js +88 -19
- package/dist/hoi4_agent_tools/reference/player-text.js +4 -3
- package/dist/hoi4_agent_tools/reference/script-validation.js +19 -1
- package/dist/hoi4_agent_tools/reference/service.js +6 -0
- package/dist/hoi4_agent_tools/schemas/event.js +78 -19
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +10 -0
- package/dist/hoi4_agent_tools/schemas/lenient-arguments.js +266 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +63 -20
- package/dist/hoi4_agent_tools/schemas/map.js +122 -3
- package/dist/hoi4_agent_tools/schemas/mod-tools.js +181 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +119 -9
- package/dist/hoi4_agent_tools/schemas/reference.js +13 -0
- package/dist/hoi4_agent_tools/schemas/scenarios.js +1 -3
- package/dist/hoi4_agent_tools/schemas/script-validation.js +13 -6
- package/dist/hoi4_agent_tools/schemas/technology.js +83 -23
- package/dist/hoi4_agent_tools/technology/job-operations.js +3 -4
- package/dist/hoi4_agent_tools/technology/operations.js +14 -1
- package/dist/hoi4_agent_tools/technology/service.js +7 -0
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +7 -4
- package/docs/analysis.md +13 -0
- package/docs/configuration.md +281 -0
- package/docs/development.md +24 -2
- package/docs/events.md +27 -3
- package/docs/examples.md +6 -6
- package/docs/focus.md +125 -4
- package/docs/gui.md +98 -17
- package/docs/helper-expansion.md +8 -8
- package/docs/http.md +3 -1
- package/docs/jobs.md +19 -13
- package/docs/map.md +197 -6
- package/docs/mechanics.md +4 -0
- package/docs/probability.md +42 -15
- package/docs/reference.md +71 -4
- package/docs/setup.md +45 -11
- package/docs/technology.md +26 -1
- package/docs/third-party-notices.md +11 -0
- package/docs/tools.md +151 -42
- package/docs/troubleshooting.md +61 -0
- package/examples/clients/codex-config.toml +5 -2
- package/examples/clients/generic.json +2 -2
- package/examples/config/config.json +7 -0
- package/npm-shrinkwrap.json +161 -162
- package/package.json +13 -8
- package/schemas/configuration.schema.json +144 -128
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +1 -1
- package/schemas/error-log-input.schema.json +55 -0
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +597 -126
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/mod-index-input.schema.json +51 -0
- package/schemas/operation-result.schema.json +2 -2
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/schemas/reference-context-input.schema.json +1 -1
- package/schemas/reference-read-input.schema.json +1 -1
- package/schemas/reference-search-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +3 -5
- package/schemas/scenario-test-input.schema.json +3 -5
- package/schemas/script-validate-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/schemas/source-lookup-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
|
-
import { mapInspectRequestSchema,
|
|
3
|
+
import { mapInspectRequestSchema, mapRewriteRequestSchema } from '../../schemas/map-requests.js';
|
|
4
4
|
import { allocationEvidenceSchema, bitmapRenderHashesSchema, nonNegativeIntegerSchema, operationBlockerDataSchema, sha256Schema, } from '../server/output-schemas.js';
|
|
5
5
|
import { strictOperationResultSchema } from '../server/result.js';
|
|
6
6
|
const changedBoundsSchema = z
|
|
@@ -55,7 +55,7 @@ const mapSemanticDiffSchema = z
|
|
|
55
55
|
normalAdjacenciesChanged: z.boolean(),
|
|
56
56
|
})
|
|
57
57
|
.strict();
|
|
58
|
-
const
|
|
58
|
+
const mapWorkspaceInspectDataSchema = z
|
|
59
59
|
.object({
|
|
60
60
|
revision: sha256Schema,
|
|
61
61
|
sharedRevision: sha256Schema,
|
|
@@ -78,9 +78,13 @@ const mapWorkspaceInspectOutputSchema = strictOperationResultSchema(z
|
|
|
78
78
|
lookupOnly: z.boolean().optional(),
|
|
79
79
|
unknownProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
80
80
|
missingGeometryProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
81
|
+
scriptReferences: z
|
|
82
|
+
.object({ references: nonNegativeIntegerSchema, missing: nonNegativeIntegerSchema })
|
|
83
|
+
.strict()
|
|
84
|
+
.optional(),
|
|
81
85
|
})
|
|
82
|
-
.strict()
|
|
83
|
-
const
|
|
86
|
+
.strict();
|
|
87
|
+
const mapRenderDataSchema = z
|
|
84
88
|
.object({
|
|
85
89
|
revision: sha256Schema,
|
|
86
90
|
width: nonNegativeIntegerSchema,
|
|
@@ -97,7 +101,8 @@ const mapRenderOutputSchema = strictOperationResultSchema(z
|
|
|
97
101
|
.strict()
|
|
98
102
|
.optional(),
|
|
99
103
|
})
|
|
100
|
-
.strict()
|
|
104
|
+
.strict();
|
|
105
|
+
const mapInspectOutputSchema = strictOperationResultSchema(z.union([mapWorkspaceInspectDataSchema, mapRenderDataSchema]));
|
|
101
106
|
const mapPlanOutputSchema = strictOperationResultSchema(z
|
|
102
107
|
.object({
|
|
103
108
|
execution: z.enum(['applied', 'blocked', 'unchanged']),
|
|
@@ -123,23 +128,15 @@ export const mapTaskTools = [
|
|
|
123
128
|
{
|
|
124
129
|
name: 'hoi4.map_inspect',
|
|
125
130
|
title: 'Inspect HOI4 map',
|
|
126
|
-
description: 'Validate
|
|
131
|
+
description: 'Validate the map with a searchable navigator, look up provinces, states, regions, names or coordinates (lookupOnly for speed), render a layer, tile or area, or check script references (references:true). Example: {"query":"Brandenburg","lookupOnly":true}. Returns counts and matches inline; PNG, HTML and the catalog are artifacts.',
|
|
127
132
|
inputSchema: mapInspectRequestSchema,
|
|
128
|
-
outputSchema:
|
|
129
|
-
annotations: artifactProducing,
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: 'hoi4.map_render',
|
|
133
|
-
title: 'Render map inspection artifacts',
|
|
134
|
-
description: 'Render searchable full-map PNG, JSON, and HTML, or a reusable source-coordinate tile.',
|
|
135
|
-
inputSchema: mapRenderRequestSchema,
|
|
136
|
-
outputSchema: mapRenderOutputSchema,
|
|
133
|
+
outputSchema: mapInspectOutputSchema,
|
|
137
134
|
annotations: artifactProducing,
|
|
138
135
|
},
|
|
139
136
|
{
|
|
140
137
|
name: 'hoi4.map_rewrite',
|
|
141
138
|
title: 'Create or clean up map content',
|
|
142
|
-
description: '
|
|
139
|
+
description: 'Create a world, or create, split, merge, move and renumber states and provinces, regenerate positions and supply, and remap script references. Example: {"operations":[{"id":"s1","kind":"create_state","provinceIds":[120,121],"displayName":"New State"}]}. All or nothing: a failed operation leaves no partial files. Returns changed IDs, semantic diff and a render.',
|
|
143
140
|
inputSchema: mapRewriteRequestSchema,
|
|
144
141
|
outputSchema: mapPlanOutputSchema,
|
|
145
142
|
annotations: {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
2
2
|
import { probabilityPrompt, probabilityPromptArguments, probabilityPromptDefinition, } from '../prompts/probability.js';
|
|
3
3
|
import { z } from 'zod/v4';
|
|
4
|
-
import {
|
|
4
|
+
import { probabilityAnalyzeRequestSchema, probabilityInspectRequestSchema, } from '../../schemas/probability-requests.js';
|
|
5
5
|
import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
|
|
6
6
|
import { strictOperationResultSchema } from '../server/result.js';
|
|
7
|
+
const rankedCandidateSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
id: z.string().max(512),
|
|
10
|
+
eligible: z.enum(['true', 'false', 'unresolved']),
|
|
11
|
+
weight: z.number().nullable(),
|
|
12
|
+
weightRange: z.array(z.number().nullable()).length(2).optional(),
|
|
13
|
+
probability: z.number().nullable(),
|
|
14
|
+
mtthDays: z.number().nullable().optional(),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
7
17
|
const analysisDataSchema = z
|
|
8
18
|
.object({
|
|
9
19
|
operation: z.enum(['evaluate', 'sweep', 'simulate', 'sequence', 'compare', 'render']),
|
|
@@ -30,6 +40,29 @@ const analysisDataSchema = z
|
|
|
30
40
|
.optional(),
|
|
31
41
|
comparisonChanges: nonNegativeIntegerSchema.optional(),
|
|
32
42
|
visualResources: nonNegativeIntegerSchema.optional(),
|
|
43
|
+
ranking: z
|
|
44
|
+
.array(z
|
|
45
|
+
.object({
|
|
46
|
+
scenarioId: z.string().max(512),
|
|
47
|
+
poolComplete: z.boolean(),
|
|
48
|
+
candidates: z.array(rankedCandidateSchema).max(12),
|
|
49
|
+
omittedCandidates: nonNegativeIntegerSchema.optional(),
|
|
50
|
+
})
|
|
51
|
+
.strict())
|
|
52
|
+
.max(4),
|
|
53
|
+
missingInputs: z.array(z.string().max(1024)).max(16),
|
|
54
|
+
changes: z
|
|
55
|
+
.array(z
|
|
56
|
+
.object({
|
|
57
|
+
scenarioId: z.string().max(512),
|
|
58
|
+
candidateId: z.string().max(512),
|
|
59
|
+
weightDelta: z.number().nullable().optional(),
|
|
60
|
+
probabilityDelta: z.number().nullable().optional(),
|
|
61
|
+
eligibility: z.string().max(64).optional(),
|
|
62
|
+
})
|
|
63
|
+
.strict())
|
|
64
|
+
.max(12)
|
|
65
|
+
.optional(),
|
|
33
66
|
})
|
|
34
67
|
.strict();
|
|
35
68
|
const inspectDataSchema = z
|
|
@@ -66,11 +99,12 @@ const inspectDataSchema = z
|
|
|
66
99
|
requiredInputs: nonNegativeIntegerSchema,
|
|
67
100
|
requiredInputPaths: z.array(z.string().max(1024)).max(32),
|
|
68
101
|
requiredInputPathsTruncated: z.boolean(),
|
|
102
|
+
adapterRequiredInputPaths: z.array(z.string().max(1024)).max(32).optional(),
|
|
69
103
|
unresolved: nonNegativeIntegerSchema,
|
|
70
104
|
})
|
|
71
105
|
.strict();
|
|
72
106
|
const analysisOutput = strictOperationResultSchema(analysisDataSchema);
|
|
73
|
-
const inspectOutput = strictOperationResultSchema(inspectDataSchema);
|
|
107
|
+
const inspectOutput = strictOperationResultSchema(z.union([analysisDataSchema, inspectDataSchema]));
|
|
74
108
|
const readOnly = {
|
|
75
109
|
readOnlyHint: true,
|
|
76
110
|
destructiveHint: false,
|
|
@@ -80,57 +114,17 @@ const readOnly = {
|
|
|
80
114
|
export const probabilityTaskTools = [
|
|
81
115
|
{
|
|
82
116
|
name: 'hoi4.probability_inspect',
|
|
83
|
-
title: 'Inspect AI and MTTH
|
|
84
|
-
description: '
|
|
117
|
+
title: 'Inspect and evaluate AI weights and MTTH',
|
|
118
|
+
description: 'Find weighted logic (ai_will_do, ai_chance, MTTH, random_list, strategy factors) by path, ID or candidate IDs and evaluate it under declared scenarios, or none: weights, probabilities and the facts still missing. Example: {"candidatePool":["my_decision"],"scenarioSet":[{"actor":"GER","state":{"has_war":false}}]}. Ranks the strongest candidates inline.',
|
|
85
119
|
inputSchema: probabilityInspectRequestSchema,
|
|
86
120
|
outputSchema: inspectOutput,
|
|
87
121
|
annotations: readOnly,
|
|
88
122
|
},
|
|
89
123
|
{
|
|
90
|
-
name: 'hoi4.
|
|
91
|
-
title: '
|
|
92
|
-
description: '
|
|
93
|
-
inputSchema:
|
|
94
|
-
outputSchema: analysisOutput,
|
|
95
|
-
annotations: readOnly,
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
name: 'hoi4.probability_sweep',
|
|
99
|
-
title: 'Sweep AI and MTTH parameters',
|
|
100
|
-
description: 'Evaluate declared ranges, breakpoints, and rank reversals.',
|
|
101
|
-
inputSchema: probabilitySweepRequestSchema,
|
|
102
|
-
outputSchema: analysisOutput,
|
|
103
|
-
annotations: readOnly,
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'hoi4.probability_simulate',
|
|
107
|
-
title: 'Simulate uncertain weighted scenarios',
|
|
108
|
-
description: 'Run seeded simulations with distributions and confidence intervals.',
|
|
109
|
-
inputSchema: probabilitySimulateRequestSchema,
|
|
110
|
-
outputSchema: analysisOutput,
|
|
111
|
-
annotations: readOnly,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
name: 'hoi4.probability_sequence',
|
|
115
|
-
title: 'Analyze a declared weighted sequence',
|
|
116
|
-
description: 'Analyze a manifest-declared dynamic weighted sequence.',
|
|
117
|
-
inputSchema: probabilitySequenceRequestSchema,
|
|
118
|
-
outputSchema: analysisOutput,
|
|
119
|
-
annotations: readOnly,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: 'hoi4.probability_compare',
|
|
123
|
-
title: 'Compare weighted source patches',
|
|
124
|
-
description: 'Compare sources or declared pools under identical scenarios.',
|
|
125
|
-
inputSchema: probabilityCompareRequestSchema,
|
|
126
|
-
outputSchema: analysisOutput,
|
|
127
|
-
annotations: readOnly,
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
name: 'hoi4.probability_render',
|
|
131
|
-
title: 'Render AI and MTTH analysis',
|
|
132
|
-
description: 'Render retained probability analysis as deterministic resources.',
|
|
133
|
-
inputSchema: probabilityRenderRequestSchema,
|
|
124
|
+
name: 'hoi4.probability_analyze',
|
|
125
|
+
title: 'Compare, sweep, simulate, or sequence weighted logic',
|
|
126
|
+
description: 'Deeper weighted analysis: compare before and after source under the same scenarios, sweep input ranges for breakpoints and rank reversals, simulate uncertain inputs, run a declared pool over time, or render a retained analysis. Example: {"analysis":"sweep","source":"common/decisions/my_decisions.txt","candidatePool":["my_decision"],"scenarioSet":[{"actor":"GER","state":{"variable.pressure":10}}],"sweep":{"paths":["variable.pressure"],"steps":10}}.',
|
|
127
|
+
inputSchema: probabilityAnalyzeRequestSchema,
|
|
134
128
|
outputSchema: analysisOutput,
|
|
135
129
|
annotations: readOnly,
|
|
136
130
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { errorResult, strictOperationResultSchema } from '../../core/operation-result.js';
|
|
2
2
|
import { ReferenceToolService } from '../../reference/operations.js';
|
|
3
3
|
import { referenceContextRequestSchema, referenceReadRequestSchema, referenceSearchRequestSchema, sourceLookupRequestSchema, referenceContextDataSchema, referenceReadDataSchema, referenceSearchDataSchema, sourceLookupDataSchema, } from '../../schemas/reference.js';
|
|
4
|
+
import { errorLogDataSchema, errorLogRequestSchema, modIndexDataSchema, modIndexRequestSchema, } from '../../schemas/mod-tools.js';
|
|
4
5
|
import { scriptValidateDataSchema, scriptValidateRequestSchema, } from '../../schemas/script-validation.js';
|
|
5
6
|
const readOnly = {
|
|
6
7
|
readOnlyHint: true,
|
|
@@ -12,7 +13,7 @@ export const referenceTools = [
|
|
|
12
13
|
{
|
|
13
14
|
name: 'hoi4.reference_search',
|
|
14
15
|
title: 'Search local HOI4 references',
|
|
15
|
-
description: 'Search
|
|
16
|
+
description: 'Search the offline wiki and installed game documentation by words or script names; each hit gives its file, lines, revision and answering line. Example: {"query":"add_timed_idea"}. Returns 6 hits by default (up to 12); read one with hoi4.reference_read using its id and revision.',
|
|
16
17
|
inputSchema: referenceSearchRequestSchema,
|
|
17
18
|
outputSchema: strictOperationResultSchema(referenceSearchDataSchema),
|
|
18
19
|
annotations: readOnly,
|
|
@@ -20,7 +21,7 @@ export const referenceTools = [
|
|
|
20
21
|
{
|
|
21
22
|
name: 'hoi4.reference_read',
|
|
22
23
|
title: 'Read cited HOI4 reference section',
|
|
23
|
-
description: 'Read at most 80 lines
|
|
24
|
+
description: 'Read one cited documentation section: pass the id and revision of a reference_search or reference_context hit. Example: {"id":"<hit id>","revision":"<hit revision>"}. Returns at most 80 lines (40 by default) and 8,000 bytes, with a line to continue from.',
|
|
24
25
|
inputSchema: referenceReadRequestSchema,
|
|
25
26
|
outputSchema: strictOperationResultSchema(referenceReadDataSchema),
|
|
26
27
|
annotations: readOnly,
|
|
@@ -28,7 +29,7 @@ export const referenceTools = [
|
|
|
28
29
|
{
|
|
29
30
|
name: 'hoi4.reference_context',
|
|
30
31
|
title: 'Get HOI4 task references',
|
|
31
|
-
description: '
|
|
32
|
+
description: 'Cited wiki and installed-documentation sections for one modding surface (event, decision, idea, focus, technology, gui, map, localisation, ai, general). Example: {"surface":"event","question":"fire an event after a delay"}. Returns up to 16 short sections; read more with hoi4.reference_read.',
|
|
32
33
|
inputSchema: referenceContextRequestSchema,
|
|
33
34
|
outputSchema: strictOperationResultSchema(referenceContextDataSchema),
|
|
34
35
|
annotations: readOnly,
|
|
@@ -36,7 +37,7 @@ export const referenceTools = [
|
|
|
36
37
|
{
|
|
37
38
|
name: 'hoi4.source_lookup',
|
|
38
39
|
title: 'Look up HOI4 source symbol',
|
|
39
|
-
description: 'Find
|
|
40
|
+
description: 'Find where a script symbol is defined across game, DLC and mod, and where it is used; or pass path and line for the definition containing that line. Example: {"symbol":"my_scripted_effect"}. Returns up to 3 definitions with source text and 10 usages; keyPath and view=structure navigate nested blocks.',
|
|
40
41
|
inputSchema: sourceLookupRequestSchema,
|
|
41
42
|
outputSchema: strictOperationResultSchema(sourceLookupDataSchema),
|
|
42
43
|
annotations: readOnly,
|
|
@@ -44,11 +45,27 @@ export const referenceTools = [
|
|
|
44
45
|
{
|
|
45
46
|
name: 'hoi4.script_validate',
|
|
46
47
|
title: 'Check HOI4 script commands and scopes',
|
|
47
|
-
description: 'Check an effect or trigger snippet
|
|
48
|
+
description: 'Check an effect or trigger snippet, or a whole mod file, against the installed documentation: syntax, command kinds and scopes. Unknown helpers and native argument blocks stay marked unresolved, never valid. Example: {"source":"add_political_power = 10","kind":"effect","scope":"country"} or {"path":"events/a.txt"}. Returns up to 12 findings.',
|
|
48
49
|
inputSchema: scriptValidateRequestSchema,
|
|
49
50
|
outputSchema: strictOperationResultSchema(scriptValidateDataSchema),
|
|
50
51
|
annotations: readOnly,
|
|
51
52
|
},
|
|
53
|
+
{
|
|
54
|
+
name: 'hoi4.error_log',
|
|
55
|
+
title: 'Read the game error log',
|
|
56
|
+
description: 'Group the game\'s logs/error.log from its last launch by category, with each entry\'s source file, line, layer and whether that file changed since; mod entries first. Example: {"scope":"mod","category":"effect"}. Returns at most 20 entries (limit 50) with fix hints and which entries are new since the previous launch.',
|
|
57
|
+
inputSchema: errorLogRequestSchema,
|
|
58
|
+
outputSchema: strictOperationResultSchema(errorLogDataSchema),
|
|
59
|
+
annotations: readOnly,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'hoi4.mod_index',
|
|
63
|
+
title: 'Index mod definitions and free IDs',
|
|
64
|
+
description: 'overview: the mod descriptor, definition counts by kind, folders and event namespace ranges. next_id: the next free numbered ID for an event namespace or prefix across every layer. Example: {"mode":"next_id","kind":"event","namespace":"my_mod"}. Returns a compact summary; the first call indexes the mod, which takes seconds on a large one.',
|
|
65
|
+
inputSchema: modIndexRequestSchema,
|
|
66
|
+
outputSchema: strictOperationResultSchema(modIndexDataSchema),
|
|
67
|
+
annotations: readOnly,
|
|
68
|
+
},
|
|
52
69
|
];
|
|
53
70
|
export function registerReferenceTools(server, engine, context) {
|
|
54
71
|
const service = new ReferenceToolService(engine);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
3
|
import { helperExpansionRequestSchema, helperExpansionSummarySchema, } from '../../schemas/helper-expansion.js';
|
|
4
|
-
import { technologyAnalysisModeSchema,
|
|
4
|
+
import { technologyAnalysisModeSchema, technologyGraphReferenceSchema, technologyImpactSchema, technologyInspectRequestSchema, technologyProposedSourceSchema, technologyRenderRequestSchema, validateTechnologyRenderRequest, technologyRenderViewSchema, validateTechnologyInspectToolRequest, } from '../../schemas/technology.js';
|
|
5
5
|
import { compactValidatedInputSchema } from '../server/context-schemas.js';
|
|
6
6
|
import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
|
|
7
7
|
import { strictOperationResultSchema } from '../server/result.js';
|
|
@@ -10,23 +10,20 @@ const inspectInputSchema = z
|
|
|
10
10
|
...technologyInspectRequestSchema.shape,
|
|
11
11
|
helperExpansion: compactValidatedInputSchema(helperExpansionRequestSchema, 'Bounded helper paths; opaque continuationUri resumes the same source and roots. See docs/technology.md.').optional(),
|
|
12
12
|
impact: compactValidatedInputSchema(technologyImpactSchema, 'Rename or removal subject.').optional(),
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
.superRefine(validateTechnologyInspectRequest);
|
|
16
|
-
const renderInputSchema = z.object({ ...technologyRenderRequestSchema.shape }).strict();
|
|
17
|
-
const compareInputSchema = z
|
|
18
|
-
.object({
|
|
19
|
-
...technologyCompareRequestSchema.shape,
|
|
20
|
-
before: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
|
|
21
|
-
after: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
|
|
13
|
+
before: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Mode compare: revision or graph resource.').optional(),
|
|
14
|
+
after: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Mode compare: revision or graph resource.').optional(),
|
|
22
15
|
proposedSources: z
|
|
23
|
-
.array(compactValidatedInputSchema(technologyProposedSourceSchema, '
|
|
16
|
+
.array(compactValidatedInputSchema(technologyProposedSourceSchema, 'Mode compare: in-memory source overlay.'))
|
|
24
17
|
.min(1)
|
|
25
18
|
.max(128)
|
|
26
19
|
.optional(),
|
|
27
20
|
})
|
|
28
21
|
.strict()
|
|
29
|
-
.superRefine(
|
|
22
|
+
.superRefine(validateTechnologyInspectToolRequest);
|
|
23
|
+
const renderInputSchema = z
|
|
24
|
+
.object({ ...technologyRenderRequestSchema.shape })
|
|
25
|
+
.strict()
|
|
26
|
+
.superRefine(validateTechnologyRenderRequest);
|
|
30
27
|
const countsSchema = z
|
|
31
28
|
.object({
|
|
32
29
|
technologies: nonNegativeIntegerSchema,
|
|
@@ -41,7 +38,7 @@ const countsSchema = z
|
|
|
41
38
|
artifacts: nonNegativeIntegerSchema,
|
|
42
39
|
})
|
|
43
40
|
.strict();
|
|
44
|
-
const
|
|
41
|
+
const analysisDataSchema = z
|
|
45
42
|
.object({
|
|
46
43
|
mode: technologyAnalysisModeSchema,
|
|
47
44
|
revision: sha256Schema,
|
|
@@ -49,7 +46,7 @@ const analysisOutputSchema = strictOperationResultSchema(z
|
|
|
49
46
|
counts: countsSchema,
|
|
50
47
|
helperExpansion: helperExpansionSummarySchema.optional(),
|
|
51
48
|
})
|
|
52
|
-
.strict()
|
|
49
|
+
.strict();
|
|
53
50
|
const renderOutputSchema = strictOperationResultSchema(z
|
|
54
51
|
.object({
|
|
55
52
|
view: technologyRenderViewSchema,
|
|
@@ -69,7 +66,7 @@ const renderOutputSchema = strictOperationResultSchema(z
|
|
|
69
66
|
sourceAccurate: z.boolean(),
|
|
70
67
|
})
|
|
71
68
|
.strict());
|
|
72
|
-
const
|
|
69
|
+
const compareDataSchema = z
|
|
73
70
|
.object({
|
|
74
71
|
beforeRevision: sha256Schema,
|
|
75
72
|
afterRevision: sha256Schema,
|
|
@@ -84,7 +81,8 @@ const compareOutputSchema = strictOperationResultSchema(z
|
|
|
84
81
|
.strict()
|
|
85
82
|
.optional(),
|
|
86
83
|
})
|
|
87
|
-
.strict()
|
|
84
|
+
.strict();
|
|
85
|
+
const analysisOutputSchema = strictOperationResultSchema(z.union([analysisDataSchema, compareDataSchema]));
|
|
88
86
|
const readOnlyTechnologyTool = {
|
|
89
87
|
readOnlyHint: true,
|
|
90
88
|
destructiveHint: false,
|
|
@@ -95,7 +93,7 @@ export const technologyTaskTools = [
|
|
|
95
93
|
{
|
|
96
94
|
name: 'hoi4.tech_inspect',
|
|
97
95
|
title: 'Inspect technology trees',
|
|
98
|
-
description: 'Scan,
|
|
96
|
+
description: 'Scan, trace, explain, list unlocks and bonus coverage, lint, assess impact or compare (mode compare) technology and doctrine graphs. Example: {"mode":"explain","technologyId":"my_tech"}. Returns counts and the main findings inline; the full report is a linked resource.',
|
|
99
97
|
inputSchema: inspectInputSchema,
|
|
100
98
|
outputSchema: analysisOutputSchema,
|
|
101
99
|
annotations: readOnlyTechnologyTool,
|
|
@@ -103,19 +101,11 @@ export const technologyTaskTools = [
|
|
|
103
101
|
{
|
|
104
102
|
name: 'hoi4.tech_render',
|
|
105
103
|
title: 'Render technology trees',
|
|
106
|
-
description: 'Render
|
|
104
|
+
description: 'Render a technology folder, dependencies, doctrine, bonuses, grants, unlocks or unresolved view as JSON, SVG and PNG with real item sizes and year guides. Example: {"view":"folder","folderId":"my_folder"}. Images are linked artifacts.',
|
|
107
105
|
inputSchema: renderInputSchema,
|
|
108
106
|
outputSchema: renderOutputSchema,
|
|
109
107
|
annotations: readOnlyTechnologyTool,
|
|
110
108
|
},
|
|
111
|
-
{
|
|
112
|
-
name: 'hoi4.tech_compare',
|
|
113
|
-
title: 'Compare technology trees',
|
|
114
|
-
description: 'Compare cached, resource-backed, current, or proposed source graphs without writes.',
|
|
115
|
-
inputSchema: compareInputSchema,
|
|
116
|
-
outputSchema: compareOutputSchema,
|
|
117
|
-
annotations: readOnlyTechnologyTool,
|
|
118
|
-
},
|
|
119
109
|
];
|
|
120
110
|
export function registerTechnologyTools(server) {
|
|
121
111
|
registerLegacyTaskTools(server, technologyTaskTools);
|
|
@@ -54,10 +54,18 @@ export function rateLimitClientAddress(request, configuration) {
|
|
|
54
54
|
const forwarded = request.headers['x-forwarded-for'];
|
|
55
55
|
if (typeof forwarded !== 'string')
|
|
56
56
|
return undefined;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// Proxies append to the right, so only the entries after the last untrusted hop are
|
|
58
|
+
// trustworthy; the leftmost entry is whatever the client sent.
|
|
59
|
+
const hops = forwarded.split(',').map((hop) => hop.trim());
|
|
60
|
+
for (let index = hops.length - 1; index >= 0; index -= 1) {
|
|
61
|
+
const hop = hops[index];
|
|
62
|
+
if (isIP(hop) === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const normalized = normalizeIpAddress(hop);
|
|
65
|
+
if (!trusted.has(normalized))
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
61
69
|
}
|
|
62
70
|
export function requiredScopesForMcpRequest(configuration, body) {
|
|
63
71
|
const scopes = [...(configuration.http.oauth?.requiredScopes ?? ['hoi4:read'])];
|
|
@@ -502,6 +510,22 @@ export async function startHttpServer(engine, configuration, serverFactory) {
|
|
|
502
510
|
// Supplying a defined parsed body to handleRequest prevents the SDK from falling back to
|
|
503
511
|
// an unbounded Request.json() parse for alternate media types or empty established-session
|
|
504
512
|
// requests. GET and DELETE deliberately remain bodyless.
|
|
513
|
+
// Authenticate before reading the body, so an unauthenticated caller cannot hold a request
|
|
514
|
+
// slot and body budget while it sends one slowly. Scopes that depend on the parsed body are
|
|
515
|
+
// checked again afterwards.
|
|
516
|
+
app.post('/mcp', async (request, response, next) => {
|
|
517
|
+
const baseScopes = requiredScopesForMcpRequest(configuration, undefined);
|
|
518
|
+
try {
|
|
519
|
+
const authorization = request.headers.authorization;
|
|
520
|
+
await authenticate(Array.isArray(authorization) ? undefined : authorization, configuration, baseScopes);
|
|
521
|
+
next();
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
sendAuthError(response, configuration, error instanceof HttpAuthError
|
|
525
|
+
? error
|
|
526
|
+
: new HttpAuthError(401, 'AUTH_INVALID', 'Authentication failed', 'invalid_token', baseScopes));
|
|
527
|
+
}
|
|
528
|
+
});
|
|
505
529
|
app.post('/mcp', express.json({
|
|
506
530
|
limit: http.maxBodyBytes,
|
|
507
531
|
strict: true,
|
|
@@ -5,20 +5,14 @@
|
|
|
5
5
|
* pipe, and a client keeps an idle connection it will never use again. The first is detected
|
|
6
6
|
* by polling the parent process; the second by an optional idle limit.
|
|
7
7
|
*/
|
|
8
|
+
import { processAlive } from '../../core/server-registry.js';
|
|
8
9
|
const PARENT_POLL_MS = 5_000;
|
|
9
|
-
|
|
10
|
-
export function processAlive(pid) {
|
|
11
|
-
try {
|
|
12
|
-
process.kill(pid, 0);
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
return error.code === 'EPERM';
|
|
17
|
-
}
|
|
18
|
-
}
|
|
10
|
+
export { processAlive };
|
|
19
11
|
export class StdioLifetime {
|
|
20
12
|
#options;
|
|
21
|
-
|
|
13
|
+
/** Open client requests and when each arrived. */
|
|
14
|
+
#open = new Map();
|
|
15
|
+
#parentGone = false;
|
|
22
16
|
#lastActivity;
|
|
23
17
|
#timer;
|
|
24
18
|
#stopped = false;
|
|
@@ -48,7 +42,11 @@ export class StdioLifetime {
|
|
|
48
42
|
this.#lastActivity = this.#options.now();
|
|
49
43
|
const id = requestId(message, 'method');
|
|
50
44
|
if (id !== undefined)
|
|
51
|
-
this.#open.
|
|
45
|
+
this.#open.set(id, this.#lastActivity);
|
|
46
|
+
// A cancelled request gets no response, so the cancellation closes it.
|
|
47
|
+
const cancelled = cancelledRequestId(message);
|
|
48
|
+
if (cancelled !== undefined)
|
|
49
|
+
this.#open.delete(cancelled);
|
|
52
50
|
}
|
|
53
51
|
/** A server message: a response closes its request. */
|
|
54
52
|
sent(message) {
|
|
@@ -57,24 +55,45 @@ export class StdioLifetime {
|
|
|
57
55
|
if (id !== undefined)
|
|
58
56
|
this.#open.delete(id);
|
|
59
57
|
}
|
|
58
|
+
/** When the client last sent or received a message, in epoch milliseconds. */
|
|
59
|
+
lastActivity() {
|
|
60
|
+
return this.#lastActivity;
|
|
61
|
+
}
|
|
60
62
|
check() {
|
|
61
63
|
if (this.#stopped)
|
|
62
64
|
return;
|
|
63
|
-
const { parentPid, isAlive, idleExitMinutes, now, onExit } = this.#options;
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const { parentPid, isAlive, idleExitMinutes, now, onExit, pollMs } = this.#options;
|
|
66
|
+
const idleMs = idleExitMinutes * 60_000;
|
|
67
|
+
// A request open for twice the idle limit was abandoned by its client.
|
|
68
|
+
if (idleMs > 0)
|
|
69
|
+
for (const [id, openedAt] of this.#open)
|
|
70
|
+
if (now() - openedAt > 2 * idleMs)
|
|
71
|
+
this.#open.delete(id);
|
|
72
|
+
// A parent pid of 0 or 1 means the server was already reparented at start. Once the
|
|
73
|
+
// parent is gone, the server finishes open requests and waits for a quiet poll first,
|
|
74
|
+
// since some launchers exit while the client keeps using the pipes.
|
|
75
|
+
if (parentPid > 1 && !this.#parentGone && !isAlive(parentPid))
|
|
76
|
+
this.#parentGone = true;
|
|
77
|
+
if (this.#parentGone && this.#open.size === 0 && now() - this.#lastActivity >= pollMs) {
|
|
66
78
|
this.stop();
|
|
67
79
|
onExit('parent_exited');
|
|
68
80
|
return;
|
|
69
81
|
}
|
|
70
|
-
if (
|
|
71
|
-
this.#open.size === 0 &&
|
|
72
|
-
now() - this.#lastActivity > idleExitMinutes * 60_000) {
|
|
82
|
+
if (idleMs > 0 && this.#open.size === 0 && now() - this.#lastActivity > idleMs) {
|
|
73
83
|
this.stop();
|
|
74
84
|
onExit('idle');
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
}
|
|
88
|
+
function cancelledRequestId(message) {
|
|
89
|
+
if (typeof message !== 'object' || message === null)
|
|
90
|
+
return undefined;
|
|
91
|
+
const { method, params } = message;
|
|
92
|
+
if (method !== 'notifications/cancelled')
|
|
93
|
+
return undefined;
|
|
94
|
+
const id = params?.requestId;
|
|
95
|
+
return typeof id === 'string' || typeof id === 'number' ? id : undefined;
|
|
96
|
+
}
|
|
78
97
|
function requestId(message, field) {
|
|
79
98
|
if (typeof message !== 'object' || message === null || !(field in message))
|
|
80
99
|
return undefined;
|
|
@@ -2,8 +2,8 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
import { IdleCacheLifetime } from '../core/idle-cache-lifetime.js';
|
|
3
3
|
import { errorResult, toolResult } from '../core/operation-result.js';
|
|
4
4
|
import { ServiceError } from '../core/result.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { probabilityAnalyzeRequestSchema, probabilityInspectRequestSchema, } from '../schemas/probability-requests.js';
|
|
6
|
+
import { analyzeProbabilities, inspectProbabilities, normalizeProbabilityAnalyzeRequest, normalizeProbabilityInspectRequest, } from './operations.js';
|
|
7
7
|
import { ProbabilityAnalyzer } from './service.js';
|
|
8
8
|
const wireResult = z.record(z.string(), z.json());
|
|
9
9
|
/** Registers probability operations without invoking transport handlers. */
|
|
@@ -48,11 +48,6 @@ export function registerProbabilityJobs(operations, engine) {
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
register('hoi4.probability_inspect', probabilityInspectRequestSchema, normalizeProbabilityInspectRequest, inspectProbabilities, 'Inspecting weighted source');
|
|
52
|
-
register('hoi4.
|
|
53
|
-
register('hoi4.probability_sweep', probabilitySweepRequestSchema, normalizeProbabilitySweepRequest, sweepProbabilities, 'Evaluating parameter sweep');
|
|
54
|
-
register('hoi4.probability_simulate', probabilitySimulateRequestSchema, normalizeProbabilitySimulateRequest, simulateProbabilities, 'Sampling uncertain scenarios');
|
|
55
|
-
register('hoi4.probability_sequence', probabilitySequenceRequestSchema, normalizeProbabilitySequenceRequest, sequenceProbabilities, 'Analyzing declared sequence');
|
|
56
|
-
register('hoi4.probability_compare', probabilityCompareRequestSchema, normalizeProbabilityCompareRequest, compareProbabilities, 'Comparing weighted source');
|
|
57
|
-
register('hoi4.probability_render', probabilityRenderRequestSchema, normalizeProbabilityRenderRequest, renderProbabilities, 'Rendering analysis resources');
|
|
51
|
+
register('hoi4.probability_inspect', probabilityInspectRequestSchema, normalizeProbabilityInspectRequest, inspectProbabilities, 'Inspecting and evaluating weighted source');
|
|
52
|
+
register('hoi4.probability_analyze', probabilityAnalyzeRequestSchema, normalizeProbabilityAnalyzeRequest, analyzeProbabilities, 'Analyzing weighted source');
|
|
58
53
|
}
|