hoi4-agent-tools 3.10.0 → 4.0.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 +131 -0
- package/README.md +91 -147
- package/dist/bin/http.js +10 -0
- package/dist/bin/setup.js +3 -2
- package/dist/bin/stdio.js +10 -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/condition-evaluator.js +18 -0
- 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 +29 -2
- package/dist/hoi4_agent_tools/core/index.js +2 -2
- 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 +314 -127
- package/dist/hoi4_agent_tools/core/job-worker.js +96 -37
- 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 +22 -5
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +26 -1
- package/dist/hoi4_agent_tools/core/source/cache.js +20 -10
- package/dist/hoi4_agent_tools/core/source/lexer.js +11 -3
- package/dist/hoi4_agent_tools/core/temporary-directories.js +293 -0
- package/dist/hoi4_agent_tools/core/transactions.js +114 -23
- package/dist/hoi4_agent_tools/core/worker-exit.js +80 -0
- 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/focus/job-operations.js +1 -2
- package/dist/hoi4_agent_tools/focus/tool-operations.js +1 -1
- package/dist/hoi4_agent_tools/gui/assets.js +3 -0
- package/dist/hoi4_agent_tools/gui/dds.js +8 -4
- package/dist/hoi4_agent_tools/gui/layout.js +45 -5
- package/dist/hoi4_agent_tools/gui/native-state.js +244 -0
- package/dist/hoi4_agent_tools/gui/renderer.js +12 -7
- package/dist/hoi4_agent_tools/gui/source-graph.js +17 -4
- package/dist/hoi4_agent_tools/gui/studio.js +33 -21
- 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/prompts/probability.js +2 -2
- 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/instructions.js +1 -1
- 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 +28 -8
- package/dist/hoi4_agent_tools/mcp/tools/event.js +19 -30
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +5 -13
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +6 -6
- package/dist/hoi4_agent_tools/mcp/tools/job.js +4 -4
- package/dist/hoi4_agent_tools/mcp/tools/map.js +15 -18
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +42 -48
- package/dist/hoi4_agent_tools/mcp/tools/reference.js +26 -9
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +17 -30
- package/dist/hoi4_agent_tools/mcp/transports/http.js +28 -4
- package/dist/hoi4_agent_tools/mcp/transports/stdio-lifetime.js +31 -8
- 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/runtime.js +14 -1
- 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 +79 -21
- 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/version.js +1 -1
- package/docs/README.md +7 -4
- package/docs/analysis.md +15 -2
- package/docs/configuration.md +281 -0
- package/docs/development.md +26 -2
- package/docs/events.md +29 -6
- package/docs/examples.md +13 -28
- package/docs/focus.md +127 -5
- package/docs/gui.md +105 -21
- package/docs/helper-expansion.md +9 -9
- package/docs/http.md +3 -1
- package/docs/jobs.md +23 -14
- package/docs/map.md +199 -7
- package/docs/mechanics.md +6 -2
- package/docs/probability.md +46 -16
- package/docs/reference.md +73 -5
- package/docs/setup.md +45 -14
- package/docs/technology.md +28 -4
- package/docs/third-party-notices.md +11 -0
- package/docs/tools.md +150 -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 +151 -156
- 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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept the argument shorthands coding agents most often send and rewrite them into the
|
|
3
|
+
* canonical request before validation. Every rule maps one unambiguous shorthand to exactly one
|
|
4
|
+
* canonical value; anything ambiguous is left alone so validation can name the field.
|
|
5
|
+
*
|
|
6
|
+
* The rules come from failed calls observed in agent transcripts: an event selector given as a
|
|
7
|
+
* plain string or without its kind, a GUI scenario without an id, a decision inspection without
|
|
8
|
+
* a scenario, a probability scenario set without ids or state, a scenario set sent as a bare
|
|
9
|
+
* array, a namespace wildcard such as `chaosx.nr14.*`, a trace given `from` instead of `selector`,
|
|
10
|
+
* and a script file check that also names the kind and scope its file already fixes.
|
|
11
|
+
*/
|
|
12
|
+
function isObject(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
const EVENT_ID = /^[A-Za-z0-9_.-]+\.[0-9]+$/u;
|
|
16
|
+
/** A size limit above the accepted maximum means "as many as allowed", not an invalid call. */
|
|
17
|
+
function clampLimits(args, maximums) {
|
|
18
|
+
for (const [key, maximum] of Object.entries(maximums)) {
|
|
19
|
+
const value = args[key];
|
|
20
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > maximum)
|
|
21
|
+
args[key] = maximum;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** A selector object that names its kind but uses `id` or `path` for the kind's own field. */
|
|
25
|
+
function withKindField(value) {
|
|
26
|
+
const { kind, id, path, ...rest } = value;
|
|
27
|
+
const field = {
|
|
28
|
+
event: 'eventId',
|
|
29
|
+
namespace: 'namespace',
|
|
30
|
+
file: 'sourcePath',
|
|
31
|
+
source: 'sourcePath',
|
|
32
|
+
node: 'nodeId',
|
|
33
|
+
};
|
|
34
|
+
const target = typeof kind === 'string' ? field[kind] : undefined;
|
|
35
|
+
if (target === undefined || rest[target] !== undefined)
|
|
36
|
+
return value;
|
|
37
|
+
const given = target === 'sourcePath' ? (path ?? id) : (id ?? path);
|
|
38
|
+
if (typeof given !== 'string')
|
|
39
|
+
return value;
|
|
40
|
+
const extra = target === 'sourcePath' ? (path === undefined ? {} : { id }) : { path };
|
|
41
|
+
return Object.fromEntries(Object.entries({
|
|
42
|
+
kind,
|
|
43
|
+
[target]: kind === 'namespace' ? withoutWildcard(given) : given,
|
|
44
|
+
...rest,
|
|
45
|
+
...extra,
|
|
46
|
+
}).filter(([, entry]) => entry !== undefined));
|
|
47
|
+
}
|
|
48
|
+
/** `chaosx.nr14.*`, `chaosx.nr14.` and `chaosx.nr14*` all name the namespace `chaosx.nr14`. */
|
|
49
|
+
function withoutWildcard(value) {
|
|
50
|
+
return value.replace(/\.?\*+$|\.$/u, '');
|
|
51
|
+
}
|
|
52
|
+
/** An event selector as a string or an object without `kind`. */
|
|
53
|
+
export function normalizeEventSelector(value) {
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
const trimmed = value.trim();
|
|
56
|
+
if (EVENT_ID.test(trimmed))
|
|
57
|
+
return { kind: 'event', eventId: trimmed };
|
|
58
|
+
if (/\.txt$/iu.test(trimmed) || trimmed.includes('/'))
|
|
59
|
+
return { kind: 'file', sourcePath: trimmed };
|
|
60
|
+
return { kind: 'namespace', namespace: withoutWildcard(trimmed) };
|
|
61
|
+
}
|
|
62
|
+
if (isObject(value) && value.kind !== undefined)
|
|
63
|
+
return withKindField(value);
|
|
64
|
+
if (!isObject(value))
|
|
65
|
+
return value;
|
|
66
|
+
const keys = Object.keys(value);
|
|
67
|
+
const only = (...allowed) => keys.every((key) => allowed.includes(key));
|
|
68
|
+
if (typeof value.eventId === 'string' && only('eventId'))
|
|
69
|
+
return { kind: 'event', eventId: value.eventId };
|
|
70
|
+
if (typeof value.id === 'string' && only('id'))
|
|
71
|
+
return EVENT_ID.test(value.id)
|
|
72
|
+
? { kind: 'event', eventId: value.id }
|
|
73
|
+
: { kind: 'namespace', namespace: withoutWildcard(value.id) };
|
|
74
|
+
if (typeof value.namespace === 'string' && only('namespace'))
|
|
75
|
+
return { kind: 'namespace', namespace: withoutWildcard(value.namespace) };
|
|
76
|
+
const sourcePath = typeof value.sourcePath === 'string'
|
|
77
|
+
? value.sourcePath
|
|
78
|
+
: typeof value.path === 'string'
|
|
79
|
+
? value.path
|
|
80
|
+
: undefined;
|
|
81
|
+
if (sourcePath !== undefined && only('sourcePath', 'path', 'line', 'column')) {
|
|
82
|
+
if (typeof value.line === 'number')
|
|
83
|
+
return {
|
|
84
|
+
kind: 'source',
|
|
85
|
+
sourcePath,
|
|
86
|
+
line: value.line,
|
|
87
|
+
...(typeof value.column === 'number' ? { column: value.column } : {}),
|
|
88
|
+
};
|
|
89
|
+
return { kind: 'file', sourcePath };
|
|
90
|
+
}
|
|
91
|
+
if (typeof value.nodeId === 'string' && only('nodeId'))
|
|
92
|
+
return { kind: 'node', nodeId: value.nodeId };
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
/** A scenario object without `id` takes its `name` or `label`, or a positional id. */
|
|
96
|
+
function withScenarioId(scenario, fallback) {
|
|
97
|
+
if (!isObject(scenario) || scenario.id !== undefined)
|
|
98
|
+
return scenario;
|
|
99
|
+
const { name, ...rest } = scenario;
|
|
100
|
+
const id = typeof name === 'string' && name.length > 0
|
|
101
|
+
? name
|
|
102
|
+
: typeof scenario.label === 'string' && scenario.label.length > 0
|
|
103
|
+
? scenario.label
|
|
104
|
+
: fallback;
|
|
105
|
+
return { id, ...rest };
|
|
106
|
+
}
|
|
107
|
+
function normalizeGuiScenario(value, fallback) {
|
|
108
|
+
return withScenarioId(value, fallback);
|
|
109
|
+
}
|
|
110
|
+
function normalizeConditionScenario(value, index) {
|
|
111
|
+
const scenario = withScenarioId(value, `scenario-${index + 1}`);
|
|
112
|
+
if (!isObject(scenario))
|
|
113
|
+
return scenario;
|
|
114
|
+
return scenario.state === undefined ? { ...scenario, state: {} } : scenario;
|
|
115
|
+
}
|
|
116
|
+
/** A probability scenario set as a bare array, or without its id. */
|
|
117
|
+
function normalizeScenarioSet(value) {
|
|
118
|
+
const set = Array.isArray(value) ? { scenarios: value } : value;
|
|
119
|
+
if (!isObject(set) || !Array.isArray(set.scenarios))
|
|
120
|
+
return set;
|
|
121
|
+
return {
|
|
122
|
+
...set,
|
|
123
|
+
...(set.id === undefined ? { id: 'scenarios' } : {}),
|
|
124
|
+
scenarios: set.scenarios.map((scenario, index) => normalizeConditionScenario(scenario, index)),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function normalizeEventArguments(args) {
|
|
128
|
+
const result = { ...args };
|
|
129
|
+
// A top-level event ID, namespace or file stands for the selector.
|
|
130
|
+
if (result.selector === undefined)
|
|
131
|
+
for (const key of ['eventId', 'id', 'namespace', 'event'])
|
|
132
|
+
if (typeof result[key] === 'string') {
|
|
133
|
+
result.selector = result[key];
|
|
134
|
+
Reflect.deleteProperty(result, key);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
// Modes that start from one event take `selector`; `from` alone means the same event.
|
|
138
|
+
if (result.selector === undefined &&
|
|
139
|
+
result.to === undefined &&
|
|
140
|
+
result.from !== undefined &&
|
|
141
|
+
result.mode !== 'explain_path') {
|
|
142
|
+
result.selector = result.from;
|
|
143
|
+
delete result.from;
|
|
144
|
+
}
|
|
145
|
+
for (const key of ['selector', 'from', 'to'])
|
|
146
|
+
if (result[key] !== undefined)
|
|
147
|
+
result[key] = normalizeEventSelector(result[key]);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A file check reads each block's kind and scope from the file, so a kind or scope sent with
|
|
152
|
+
* `path` is redundant and dropped. `file` names the path and `snippet`, `code` or `text` the
|
|
153
|
+
* source; a snippet without a scope runs in country scope.
|
|
154
|
+
*/
|
|
155
|
+
function normalizeScriptValidateArguments(args) {
|
|
156
|
+
const result = { ...args };
|
|
157
|
+
clampLimits(result, { limit: 32 });
|
|
158
|
+
for (const alias of ['file', 'filePath', 'sourcePath'])
|
|
159
|
+
if (result.path === undefined && typeof result[alias] === 'string') {
|
|
160
|
+
result.path = result[alias];
|
|
161
|
+
Reflect.deleteProperty(result, alias);
|
|
162
|
+
}
|
|
163
|
+
for (const alias of ['snippet', 'code', 'text', 'script', 'body'])
|
|
164
|
+
if (result.source === undefined && typeof result[alias] === 'string') {
|
|
165
|
+
result.source = result[alias];
|
|
166
|
+
Reflect.deleteProperty(result, alias);
|
|
167
|
+
}
|
|
168
|
+
if (typeof result.path === 'string' && result.source === undefined) {
|
|
169
|
+
delete result.kind;
|
|
170
|
+
delete result.scope;
|
|
171
|
+
if (Array.isArray(result.bindings) && result.bindings.length === 0)
|
|
172
|
+
delete result.bindings;
|
|
173
|
+
}
|
|
174
|
+
if (typeof result.source === 'string' && result.path === undefined && result.scope === undefined)
|
|
175
|
+
result.scope = 'country';
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function normalizeGuiArguments(args, defaultScenario) {
|
|
179
|
+
const result = { ...args };
|
|
180
|
+
const fallback = typeof result.windowName === 'string' ? result.windowName : 'default';
|
|
181
|
+
if (result.scenario !== undefined)
|
|
182
|
+
result.scenario = normalizeGuiScenario(result.scenario, fallback);
|
|
183
|
+
// A window inspection without a scenario reads the window in its default state.
|
|
184
|
+
else if (defaultScenario || typeof result.windowName === 'string')
|
|
185
|
+
result.scenario = { id: fallback };
|
|
186
|
+
if (Array.isArray(result.relatedScenarios))
|
|
187
|
+
result.relatedScenarios = result.relatedScenarios.map((scenario, index) => normalizeGuiScenario(scenario, `${fallback}-${index + 1}`));
|
|
188
|
+
if (isObject(result.generatedScenarios)) {
|
|
189
|
+
const { maxScenarios, ...rest } = result.generatedScenarios;
|
|
190
|
+
result.generatedScenarios =
|
|
191
|
+
typeof maxScenarios === 'number' && rest.count === undefined
|
|
192
|
+
? { ...rest, count: maxScenarios }
|
|
193
|
+
: result.generatedScenarios;
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
function normalizeDecisionArguments(args) {
|
|
198
|
+
const result = { ...args };
|
|
199
|
+
for (const alias of ['decisionId', 'decision', 'missionId'])
|
|
200
|
+
if (result.id === undefined && typeof result[alias] === 'string') {
|
|
201
|
+
result.id = result[alias];
|
|
202
|
+
Reflect.deleteProperty(result, alias);
|
|
203
|
+
}
|
|
204
|
+
if (result.mode === undefined && typeof result.id === 'string')
|
|
205
|
+
result.mode = 'inspect';
|
|
206
|
+
if (Array.isArray(result.scenarios))
|
|
207
|
+
result.scenarios = result.scenarios.map((scenario, index) => normalizeConditionScenario(scenario, index));
|
|
208
|
+
const mode = result.mode ?? 'inventory';
|
|
209
|
+
if ((mode === 'inspect' || mode === 'compare') &&
|
|
210
|
+
(result.scenarios === undefined ||
|
|
211
|
+
(Array.isArray(result.scenarios) && result.scenarios.length === 0))) {
|
|
212
|
+
result.scenarios = [
|
|
213
|
+
{
|
|
214
|
+
id: 'no-facts',
|
|
215
|
+
state: {},
|
|
216
|
+
...(typeof result.actor === 'string' ? { actor: result.actor } : {}),
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
if (typeof result.actor === 'string')
|
|
220
|
+
delete result.actor;
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
function normalizeProbabilityArguments(args) {
|
|
225
|
+
const result = { ...args };
|
|
226
|
+
if (result.scenarioSet !== undefined)
|
|
227
|
+
result.scenarioSet = normalizeScenarioSet(result.scenarioSet);
|
|
228
|
+
if (typeof result.source === 'string')
|
|
229
|
+
result.source = /\.txt$/iu.test(result.source)
|
|
230
|
+
? { path: result.source }
|
|
231
|
+
: { identifier: result.source };
|
|
232
|
+
if (typeof result.candidatePool === 'string')
|
|
233
|
+
result.candidatePool = [result.candidatePool];
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
/** Rewrite known shorthands for one tool; unknown tools and non-object arguments pass through. */
|
|
237
|
+
export function normalizeToolArguments(name, args) {
|
|
238
|
+
if (!isObject(args))
|
|
239
|
+
return args;
|
|
240
|
+
switch (name) {
|
|
241
|
+
case 'hoi4.event_inspect': {
|
|
242
|
+
const result = normalizeEventArguments(args);
|
|
243
|
+
clampLimits(result, { maxDepth: 64, maxNodes: 5_000, maxEdges: 20_000 });
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
case 'hoi4.event_render': {
|
|
247
|
+
const result = normalizeEventArguments(args);
|
|
248
|
+
clampLimits(result, { maxDepth: 64, maxNodes: 240 });
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
case 'hoi4.gui_inspect':
|
|
252
|
+
return normalizeGuiArguments(args, false);
|
|
253
|
+
case 'hoi4.gui_render':
|
|
254
|
+
case 'hoi4.gui_rewrite':
|
|
255
|
+
return normalizeGuiArguments(args, true);
|
|
256
|
+
case 'hoi4.decision_inspect':
|
|
257
|
+
return normalizeDecisionArguments(args);
|
|
258
|
+
case 'hoi4.script_validate':
|
|
259
|
+
return normalizeScriptValidateArguments(args);
|
|
260
|
+
case 'hoi4.probability_inspect':
|
|
261
|
+
case 'hoi4.probability_analyze':
|
|
262
|
+
return normalizeProbabilityArguments(args);
|
|
263
|
+
default:
|
|
264
|
+
return args;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -32,6 +32,33 @@ export const mapOverlaySchema = z.enum([
|
|
|
32
32
|
'unit-positions',
|
|
33
33
|
'weather-positions',
|
|
34
34
|
]);
|
|
35
|
+
const mapTileSchema = z
|
|
36
|
+
.object({
|
|
37
|
+
x: z.number().int().min(0),
|
|
38
|
+
y: z.number().int().min(0),
|
|
39
|
+
width: z.number().int().min(1).max(2_048),
|
|
40
|
+
height: z.number().int().min(1).max(2_048),
|
|
41
|
+
})
|
|
42
|
+
.strict();
|
|
43
|
+
const mapAreaSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
provinceIds: z.array(z.number().int().nonnegative()).max(256).optional(),
|
|
46
|
+
stateIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
47
|
+
regionIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
48
|
+
padding: z.number().int().min(0).max(256).optional(),
|
|
49
|
+
})
|
|
50
|
+
.strict()
|
|
51
|
+
.refine(({ provinceIds, stateIds, regionIds }) => (provinceIds?.length ?? 0) + (stateIds?.length ?? 0) + (regionIds?.length ?? 0) > 0, 'Map area requires at least one province, state, or region ID');
|
|
52
|
+
/** Fields that select a rendered map view instead of validation and lookups. */
|
|
53
|
+
export const MAP_VIEW_FIELDS = ['layer', 'overlays', 'scale', 'tile', 'area'];
|
|
54
|
+
const MAP_LOOKUP_FIELDS = [
|
|
55
|
+
'provinceIds',
|
|
56
|
+
'stateIds',
|
|
57
|
+
'regionIds',
|
|
58
|
+
'query',
|
|
59
|
+
'coordinates',
|
|
60
|
+
'allocationRequests',
|
|
61
|
+
];
|
|
35
62
|
export const mapInspectRequestSchema = z
|
|
36
63
|
.object({
|
|
37
64
|
workspaceId: workspaceIdSchema,
|
|
@@ -66,33 +93,49 @@ export const mapInspectRequestSchema = z
|
|
|
66
93
|
lookupOnly: z.boolean().default(false),
|
|
67
94
|
includeOverview: z.boolean().default(true),
|
|
68
95
|
allocationRequests: z.array(mapAllocationRequestSchema).max(100).default([]),
|
|
96
|
+
references: z
|
|
97
|
+
.boolean()
|
|
98
|
+
.default(false)
|
|
99
|
+
.describe('Also check events, focuses, decisions, history and AI areas for states, provinces and regions the map lacks, and state tags without a country.'),
|
|
100
|
+
layer: mapLayerSchema
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('Render this base layer; combine with overlays, tile or area.'),
|
|
103
|
+
overlays: z.array(mapOverlaySchema).max(12).optional(),
|
|
104
|
+
scale: z.number().int().min(1).max(16).optional(),
|
|
105
|
+
tile: mapTileSchema.optional().describe('Render one top-left bitmap area.'),
|
|
106
|
+
area: mapAreaSchema
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Render the bounding area of these provinces, states or regions.'),
|
|
69
109
|
})
|
|
70
|
-
.strict()
|
|
110
|
+
.strict()
|
|
111
|
+
.superRefine((value, context) => {
|
|
112
|
+
if (value.tile !== undefined && value.area !== undefined)
|
|
113
|
+
context.addIssue({
|
|
114
|
+
code: 'custom',
|
|
115
|
+
path: ['area'],
|
|
116
|
+
message: 'Choose tile or area, not both',
|
|
117
|
+
});
|
|
118
|
+
if (!MAP_VIEW_FIELDS.some((field) => value[field] !== undefined))
|
|
119
|
+
return;
|
|
120
|
+
for (const field of MAP_LOOKUP_FIELDS) {
|
|
121
|
+
const fieldValue = value[field];
|
|
122
|
+
if (fieldValue !== undefined && !(Array.isArray(fieldValue) && fieldValue.length === 0))
|
|
123
|
+
context.addIssue({
|
|
124
|
+
code: 'custom',
|
|
125
|
+
path: [field],
|
|
126
|
+
message: `${field} is a lookup; request a rendered view (layer, overlays, tile, area, scale) in a separate call`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
/** Internal render request derived from a map inspection that names a view. */
|
|
71
131
|
export const mapRenderRequestSchema = z
|
|
72
132
|
.object({
|
|
73
133
|
workspaceId: workspaceIdSchema,
|
|
74
134
|
layer: mapLayerSchema.optional(),
|
|
75
135
|
overlays: z.array(mapOverlaySchema).max(12).optional(),
|
|
76
136
|
scale: z.number().int().min(1).max(16).optional(),
|
|
77
|
-
tile:
|
|
78
|
-
|
|
79
|
-
x: z.number().int().min(0),
|
|
80
|
-
y: z.number().int().min(0),
|
|
81
|
-
width: z.number().int().min(1).max(2_048),
|
|
82
|
-
height: z.number().int().min(1).max(2_048),
|
|
83
|
-
})
|
|
84
|
-
.strict()
|
|
85
|
-
.optional(),
|
|
86
|
-
area: z
|
|
87
|
-
.object({
|
|
88
|
-
provinceIds: z.array(z.number().int().nonnegative()).max(256).optional(),
|
|
89
|
-
stateIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
90
|
-
regionIds: z.array(z.number().int().nonnegative()).max(64).optional(),
|
|
91
|
-
padding: z.number().int().min(0).max(256).optional(),
|
|
92
|
-
})
|
|
93
|
-
.strict()
|
|
94
|
-
.refine(({ provinceIds, stateIds, regionIds }) => (provinceIds?.length ?? 0) + (stateIds?.length ?? 0) + (regionIds?.length ?? 0) > 0, 'Map area requires at least one province, state, or region ID')
|
|
95
|
-
.optional(),
|
|
137
|
+
tile: mapTileSchema.optional(),
|
|
138
|
+
area: mapAreaSchema.optional(),
|
|
96
139
|
})
|
|
97
140
|
.strict()
|
|
98
141
|
.refine(({ tile, area }) => tile === undefined || area === undefined, 'Choose tile or area, not both');
|
|
@@ -20,7 +20,7 @@ const operationBase = {
|
|
|
20
20
|
};
|
|
21
21
|
const moveStateDistributionSchema = z
|
|
22
22
|
.object({
|
|
23
|
-
stateValues: z.
|
|
23
|
+
stateValues: z.enum(['retain-in-current-states', 'proportional-by-land-pixels']),
|
|
24
24
|
ownership: z.literal('retain-in-current-states'),
|
|
25
25
|
provinceBuildings: z.literal('follow-province'),
|
|
26
26
|
victoryPoints: z.literal('follow-province'),
|
|
@@ -276,6 +276,7 @@ const splitStateOperation = (kind) => z
|
|
|
276
276
|
fileName: z.string().max(255).optional(),
|
|
277
277
|
localisation: stateLocalisationSchema.optional(),
|
|
278
278
|
distribution: splitStateDistributionSchema,
|
|
279
|
+
remapReferences: z.enum(['refuse', 'override']).optional(),
|
|
279
280
|
})
|
|
280
281
|
.strict();
|
|
281
282
|
const createStateOperation = z
|
|
@@ -290,6 +291,7 @@ const createStateOperation = z
|
|
|
290
291
|
fileName: z.string().max(255).optional(),
|
|
291
292
|
localisation: stateLocalisationSchema.optional(),
|
|
292
293
|
distribution: z.union([z.literal('proportional'), splitStateDistributionSchema]).optional(),
|
|
294
|
+
remapReferences: z.enum(['refuse', 'override']).optional(),
|
|
293
295
|
})
|
|
294
296
|
.strict()
|
|
295
297
|
.refine((value) => value.name !== undefined || value.displayName !== undefined, {
|
|
@@ -329,7 +331,7 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
|
|
|
329
331
|
sourceStateId: stateId,
|
|
330
332
|
targetStateId: stateId,
|
|
331
333
|
provinceIds: z.array(provinceId).min(1).max(100_000),
|
|
332
|
-
distribution: moveStateDistributionSchema,
|
|
334
|
+
distribution: z.union([z.literal('proportional'), moveStateDistributionSchema]).optional(),
|
|
333
335
|
})
|
|
334
336
|
.strict(),
|
|
335
337
|
z
|
|
@@ -408,7 +410,22 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
|
|
|
408
410
|
kind: z.literal('merge_states'),
|
|
409
411
|
sourceStateIds: z.array(stateId).min(1).max(100_000),
|
|
410
412
|
targetStateId: stateId,
|
|
411
|
-
|
|
413
|
+
// Every merge policy has one allowed value, so the whole object may be omitted.
|
|
414
|
+
distribution: mergeStateDistributionSchema.default({
|
|
415
|
+
stateValues: 'sum-into-target',
|
|
416
|
+
ownership: 'retain-target',
|
|
417
|
+
controller: 'retain-target',
|
|
418
|
+
cores: 'union',
|
|
419
|
+
claims: 'union',
|
|
420
|
+
victoryPoints: 'follow-province',
|
|
421
|
+
provinceBuildings: 'follow-province',
|
|
422
|
+
ports: 'follow-province',
|
|
423
|
+
supplyNodes: 'follow-province',
|
|
424
|
+
railways: 'follow-province',
|
|
425
|
+
positions: 'follow-province',
|
|
426
|
+
strategicRegion: 'require-same',
|
|
427
|
+
}),
|
|
428
|
+
remapReferences: z.enum(['refuse', 'override']).optional(),
|
|
412
429
|
})
|
|
413
430
|
.strict(),
|
|
414
431
|
splitProvinceOperation('split_province'),
|
|
@@ -452,6 +469,108 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
|
|
|
452
469
|
distribution: z.literal('move-membership'),
|
|
453
470
|
})
|
|
454
471
|
.strict(),
|
|
472
|
+
z
|
|
473
|
+
.object({
|
|
474
|
+
...operationBase,
|
|
475
|
+
kind: z.literal('create_world'),
|
|
476
|
+
width: z.number().int().min(256).max(8_192).multipleOf(256).default(1_024),
|
|
477
|
+
height: z.number().int().min(256).max(4_096).multipleOf(256).default(512),
|
|
478
|
+
seed: z.number().int().min(0).max(2_147_483_647).default(1),
|
|
479
|
+
landCoverage: z.number().min(0.05).max(0.9).default(0.35),
|
|
480
|
+
continents: z.number().int().min(1).max(12).default(3),
|
|
481
|
+
landPolygons: z
|
|
482
|
+
.array(z.array(pointSchema).min(3).max(10_000))
|
|
483
|
+
.max(64)
|
|
484
|
+
.optional()
|
|
485
|
+
.describe('Land outlines in top-left pixel coordinates; omit for generated continents.'),
|
|
486
|
+
landProvincePixels: z.number().int().min(60).max(50_000).default(400),
|
|
487
|
+
seaProvincePixels: z.number().int().min(200).max(200_000).default(2_500),
|
|
488
|
+
provincesPerState: z.number().int().min(1).max(64).default(5),
|
|
489
|
+
landProvincesPerRegion: z.number().int().min(2).max(400).default(25),
|
|
490
|
+
seaProvincesPerRegion: z.number().int().min(1).max(200).default(10),
|
|
491
|
+
countries: z
|
|
492
|
+
.array(z
|
|
493
|
+
.object({
|
|
494
|
+
tag: z.string().regex(/^[A-Z][A-Z0-9]{2}$/u),
|
|
495
|
+
name: z.string().min(1).max(64),
|
|
496
|
+
adjective: z.string().min(1).max(64).optional(),
|
|
497
|
+
color: rgbSchema.optional(),
|
|
498
|
+
share: z.number().min(0.05).max(100).optional(),
|
|
499
|
+
ideology: z.enum(['democratic', 'communism', 'fascism', 'neutrality']).optional(),
|
|
500
|
+
})
|
|
501
|
+
.strict())
|
|
502
|
+
.min(1)
|
|
503
|
+
.max(200),
|
|
504
|
+
continentNames: z.array(z.string().min(1).max(64)).max(12).optional(),
|
|
505
|
+
startDate: z
|
|
506
|
+
.string()
|
|
507
|
+
.regex(/^\d{3,4}\.\d{1,2}\.\d{1,2}$/u)
|
|
508
|
+
.default('1936.1.1'),
|
|
509
|
+
rivers: z.number().int().min(0).max(500).default(8),
|
|
510
|
+
replaceBaseGame: z
|
|
511
|
+
.boolean()
|
|
512
|
+
.default(true)
|
|
513
|
+
.describe('Add replace_path for the base game states, countries, units, strategic regions, supply areas and bookmarks to descriptor.mod.'),
|
|
514
|
+
})
|
|
515
|
+
.strict(),
|
|
516
|
+
z
|
|
517
|
+
.object({
|
|
518
|
+
...operationBase,
|
|
519
|
+
kind: z.literal('remap_map_references'),
|
|
520
|
+
entity: z.enum(['province', 'state', 'strategic-region']),
|
|
521
|
+
mapping: z
|
|
522
|
+
.array(z
|
|
523
|
+
.object({
|
|
524
|
+
from: z.number().int().min(0),
|
|
525
|
+
to: z
|
|
526
|
+
.union([
|
|
527
|
+
z.number().int().min(0),
|
|
528
|
+
z.array(z.number().int().min(0)).min(1).max(16),
|
|
529
|
+
z.null(),
|
|
530
|
+
])
|
|
531
|
+
.describe('One ID replaces; several IDs copy the referencing assignment once per ID; null removes it.'),
|
|
532
|
+
})
|
|
533
|
+
.strict())
|
|
534
|
+
.min(1)
|
|
535
|
+
.max(1_000),
|
|
536
|
+
readOnlySources: z
|
|
537
|
+
.enum(['refuse', 'override'])
|
|
538
|
+
.default('refuse')
|
|
539
|
+
.describe('override copies game or dependency files that need the change into the mod.'),
|
|
540
|
+
pathPrefixes: z.array(z.string().min(1).max(256)).max(32).optional(),
|
|
541
|
+
})
|
|
542
|
+
.strict(),
|
|
543
|
+
z
|
|
544
|
+
.object({
|
|
545
|
+
...operationBase,
|
|
546
|
+
kind: z.literal('regenerate_map_positions'),
|
|
547
|
+
positions: z
|
|
548
|
+
.array(z.enum(['buildings', 'units', 'weather']))
|
|
549
|
+
.min(1)
|
|
550
|
+
.max(3)
|
|
551
|
+
.default(['buildings', 'units', 'weather']),
|
|
552
|
+
stateIds: z.array(stateId).max(10_000).optional(),
|
|
553
|
+
provinceIds: z.array(provinceId).max(100_000).optional(),
|
|
554
|
+
regionIds: z.array(z.number().int().positive()).max(10_000).optional(),
|
|
555
|
+
mode: z
|
|
556
|
+
.enum(['missing', 'replace'])
|
|
557
|
+
.default('missing')
|
|
558
|
+
.describe('missing adds only absent positions; replace rewrites them for the selection.'),
|
|
559
|
+
})
|
|
560
|
+
.strict(),
|
|
561
|
+
z
|
|
562
|
+
.object({
|
|
563
|
+
...operationBase,
|
|
564
|
+
kind: z.literal('rebuild_supply'),
|
|
565
|
+
stateIds: z.array(stateId).max(10_000).optional(),
|
|
566
|
+
mode: z
|
|
567
|
+
.enum(['connect', 'replace'])
|
|
568
|
+
.default('connect')
|
|
569
|
+
.describe('connect adds a supply node to each selected state without one and links unconnected nodes by railway; replace rebuilds the whole network.'),
|
|
570
|
+
railwayLevel: z.number().int().min(1).max(5).default(1),
|
|
571
|
+
capitalRailwayLevel: z.number().int().min(1).max(5).default(3),
|
|
572
|
+
})
|
|
573
|
+
.strict(),
|
|
455
574
|
z
|
|
456
575
|
.object({
|
|
457
576
|
...operationBase,
|