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
|
@@ -8,6 +8,9 @@ import { ReferenceService } from './service.js';
|
|
|
8
8
|
import { sourceLookup } from './source-lookup.js';
|
|
9
9
|
import { scriptValidateDataSchema, scriptValidateRequestSchema, } from '../schemas/script-validation.js';
|
|
10
10
|
import { validateScript } from './script-validation.js';
|
|
11
|
+
import { readErrorLog } from './error-log.js';
|
|
12
|
+
import { modIndex } from './mod-index.js';
|
|
13
|
+
import { errorLogDataSchema, errorLogRequestSchema, modIndexDataSchema, modIndexRequestSchema, } from '../schemas/mod-tools.js';
|
|
11
14
|
import { eventPopupDefinitions } from './event-popups.js';
|
|
12
15
|
import { locatePatterns, selectFile } from './source-locate.js';
|
|
13
16
|
/**
|
|
@@ -43,6 +46,8 @@ const schemas = {
|
|
|
43
46
|
'hoi4.reference_context': referenceContextRequestSchema,
|
|
44
47
|
'hoi4.source_lookup': sourceLookupRequestSchema,
|
|
45
48
|
'hoi4.script_validate': scriptValidateRequestSchema,
|
|
49
|
+
'hoi4.error_log': errorLogRequestSchema,
|
|
50
|
+
'hoi4.mod_index': modIndexRequestSchema,
|
|
46
51
|
};
|
|
47
52
|
export function isReferenceTool(name) {
|
|
48
53
|
return Object.hasOwn(schemas, name);
|
|
@@ -69,10 +74,34 @@ export class ReferenceToolService {
|
|
|
69
74
|
? await this.references.read(workspace, referenceReadRequestSchema.parse(parsed), signal)
|
|
70
75
|
: name === 'hoi4.reference_context'
|
|
71
76
|
? await this.references.context(workspace, referenceContextRequestSchema.parse(parsed), signal)
|
|
72
|
-
: name === 'hoi4.
|
|
73
|
-
? await
|
|
74
|
-
:
|
|
77
|
+
: name === 'hoi4.error_log'
|
|
78
|
+
? await readErrorLog(workspace, errorLogRequestSchema.parse(parsed), this.engine.resolver.config().gameUserRoot, signal)
|
|
79
|
+
: name === 'hoi4.mod_index'
|
|
80
|
+
? await modIndex(this.engine, workspaceId, modIndexRequestSchema.parse(parsed), context.principal, signal)
|
|
81
|
+
: name === 'hoi4.script_validate'
|
|
82
|
+
? await this.validateScript(workspaceId, workspace, scriptValidateRequestSchema.parse(parsed), context.principal, signal)
|
|
83
|
+
: await sourceLookup(this.engine, workspaceId, sourceLookupRequestSchema.parse(parsed), context.principal, signal);
|
|
75
84
|
const result = emptyServiceResult(workspaceId, data);
|
|
85
|
+
if (name === 'hoi4.error_log') {
|
|
86
|
+
const log = errorLogDataSchema.parse(data);
|
|
87
|
+
result.code = 'ERROR_LOG_READ';
|
|
88
|
+
result.validation = {
|
|
89
|
+
passed: log.modEntries === 0,
|
|
90
|
+
checks: [
|
|
91
|
+
{
|
|
92
|
+
id: 'error-log-mod-entries',
|
|
93
|
+
passed: log.modEntries === 0,
|
|
94
|
+
message: log.modEntries === 0
|
|
95
|
+
? 'The last game launch logged no errors located in this mod'
|
|
96
|
+
: `The last game launch logged ${log.modEntries} distinct errors located in this mod`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (name === 'hoi4.mod_index') {
|
|
102
|
+
const index = modIndexDataSchema.parse(data);
|
|
103
|
+
result.code = index.complete ? 'MOD_INDEXED' : 'MOD_INDEXED_PARTIAL';
|
|
104
|
+
}
|
|
76
105
|
if (name === 'hoi4.script_validate') {
|
|
77
106
|
const checked = scriptValidateDataSchema.parse(data);
|
|
78
107
|
result.code = checked.valid === null ? 'SCRIPT_CHECK_PARTIAL' : 'SCRIPT_CHECKED';
|
|
@@ -117,20 +146,32 @@ export class ReferenceToolService {
|
|
|
117
146
|
#playerText = new Map();
|
|
118
147
|
/** File mode reads the scanned file and the mod's scripted helper names from one scan. */
|
|
119
148
|
async validateScript(workspaceId, workspace, request, principal, signal) {
|
|
120
|
-
if (request.path === undefined)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
149
|
+
if (request.path === undefined) {
|
|
150
|
+
if (request.kind !== undefined)
|
|
151
|
+
return validateScript(this.references, workspace, request, signal);
|
|
152
|
+
// A snippet without a kind is checked as an effect and as a trigger; the reading with
|
|
153
|
+
// fewer errors and unresolved commands is the one the author meant.
|
|
154
|
+
const effect = await validateScript(this.references, workspace, { ...request, kind: 'effect' }, signal);
|
|
155
|
+
const trigger = await validateScript(this.references, workspace, { ...request, kind: 'trigger' }, signal);
|
|
156
|
+
const misses = (checked) => checked.findings.filter(({ status }) => status === 'error').length * 100_000 +
|
|
157
|
+
checked.unresolvedCount;
|
|
158
|
+
return misses(trigger) < misses(effect) ? trigger : effect;
|
|
159
|
+
}
|
|
160
|
+
// Every file check of the supported families shares one scan, so checking files across
|
|
161
|
+
// folders reuses one snapshot and its incremental index; other folders add their own.
|
|
162
|
+
const scan = (extra) => this.engine.scan(workspaceId, { patterns: [...fileCheckPatterns, ...extra] }, principal, signal);
|
|
163
|
+
let snapshot = await scan([]);
|
|
164
|
+
let file;
|
|
165
|
+
try {
|
|
166
|
+
file = selectFile(snapshot, request.path);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const extra = locatePatterns(this.engine, workspaceId, request.path, principal);
|
|
170
|
+
if (extra === undefined)
|
|
171
|
+
throw error;
|
|
172
|
+
snapshot = await scan(extra);
|
|
173
|
+
file = selectFile(snapshot, request.path);
|
|
174
|
+
}
|
|
134
175
|
const playerText = playerTextFamily(file.relativePath)
|
|
135
176
|
? this.playerTextContext(workspaceId, snapshot)
|
|
136
177
|
: undefined;
|
|
@@ -143,8 +184,26 @@ export class ReferenceToolService {
|
|
|
143
184
|
implicitTriggers: implicitTriggerNames(snapshot),
|
|
144
185
|
...(playerText === undefined ? {} : { playerText }),
|
|
145
186
|
eventPopups: this.eventPopupContext(workspaceId, snapshot),
|
|
187
|
+
knownMistakes: { eventNamespaces: this.eventNamespaces(workspaceId, snapshot) },
|
|
146
188
|
});
|
|
147
189
|
}
|
|
190
|
+
#eventNamespaces = new Map();
|
|
191
|
+
/** Namespaces declared with `add_namespace` in every active events file, cached by hash. */
|
|
192
|
+
eventNamespaces(workspaceId, snapshot) {
|
|
193
|
+
const sources = [...snapshot.index.files.values()].filter(({ relativePath, shadowedBy }) => shadowedBy === undefined && relativePath.startsWith('events/'));
|
|
194
|
+
const cacheKey = hashCanonical(sources.map(({ displayPath, sha256 }) => [displayPath, sha256]));
|
|
195
|
+
const cached = this.#eventNamespaces.get(workspaceId);
|
|
196
|
+
if (cached?.key === cacheKey)
|
|
197
|
+
return cached.namespaces;
|
|
198
|
+
const namespaces = new Set();
|
|
199
|
+
for (const file of sources)
|
|
200
|
+
for (const match of file.bytes
|
|
201
|
+
.toString('utf8')
|
|
202
|
+
.matchAll(/^\s*add_namespace\s*=\s*"?([\w.]+)"?/gmu))
|
|
203
|
+
namespaces.add(match[1]);
|
|
204
|
+
this.#eventNamespaces.set(workspaceId, { key: cacheKey, namespaces });
|
|
205
|
+
return namespaces;
|
|
206
|
+
}
|
|
148
207
|
/** Every active event definition, cached by the hashes of the events files. */
|
|
149
208
|
eventPopupContext(workspaceId, snapshot) {
|
|
150
209
|
const sources = [...snapshot.index.files.values()]
|
|
@@ -170,7 +229,9 @@ export class ReferenceToolService {
|
|
|
170
229
|
return cached.context;
|
|
171
230
|
const localisation = new Map();
|
|
172
231
|
for (const symbol of snapshot.index.symbols) {
|
|
173
|
-
if (symbol.kind !== 'localisation' ||
|
|
232
|
+
if (symbol.kind !== 'localisation' ||
|
|
233
|
+
symbol.sourceShadowed ||
|
|
234
|
+
!symbol.id.startsWith('l_english:'))
|
|
174
235
|
continue;
|
|
175
236
|
const key = symbol.id.slice('l_english:'.length);
|
|
176
237
|
const previous = localisation.get(key);
|
|
@@ -216,7 +277,15 @@ export class ReferenceToolService {
|
|
|
216
277
|
return context;
|
|
217
278
|
}
|
|
218
279
|
}
|
|
219
|
-
|
|
280
|
+
/** The families file mode checks, the helpers and databases it resolves, and localisation. */
|
|
281
|
+
const fileCheckPatterns = [
|
|
282
|
+
'events/**/*.txt',
|
|
283
|
+
'common/decisions/**/*.txt',
|
|
284
|
+
'common/national_focus/**/*.txt',
|
|
285
|
+
'common/on_actions/**/*.txt',
|
|
286
|
+
'common/scripted_effects/**/*.txt',
|
|
287
|
+
'common/scripted_triggers/**/*.txt',
|
|
288
|
+
...Object.keys(implicitTriggerSources).map((folder) => `common/${folder}/*.txt`),
|
|
220
289
|
'localisation/**/*_l_english.yml',
|
|
221
290
|
'common/scripted_localisation/**/*.txt',
|
|
222
291
|
];
|
|
@@ -7,7 +7,8 @@ const tooltipTriggerBlocks = {
|
|
|
7
7
|
};
|
|
8
8
|
/** Words that describe the mod's development rather than the game world. */
|
|
9
9
|
const implementationWording = /\b(?:TODO|FIXME|TBD|hard-?coded|newly added|(?:has been|was|been) reworked|placeholder)\b/iu;
|
|
10
|
-
|
|
10
|
+
/** Colour codes; text icons (£) are legible on parchment and stay allowed. */
|
|
11
|
+
const colourCode = '§';
|
|
11
12
|
function blockOf(entry) {
|
|
12
13
|
return entry.value.type === 'block' ? entry.value : undefined;
|
|
13
14
|
}
|
|
@@ -61,14 +62,14 @@ export function playerTextFindings(family, document, context) {
|
|
|
61
62
|
add(node, 'SCRIPT_TEXT_IMPLEMENTATION_WORDING', `${key} (${where(text)}) describes development rather than the game: "${implementationWording.exec(text.value)[0]}"`);
|
|
62
63
|
if (!options.colourFree)
|
|
63
64
|
return;
|
|
64
|
-
if (
|
|
65
|
+
if (text.value.includes(colourCode)) {
|
|
65
66
|
add(node, 'SCRIPT_NEWS_TEXT_COLOUR_CODE', `${key} (${where(text)}) uses a colour code in news or report event text`);
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
for (const name of scriptedTokens(text.value, context))
|
|
69
70
|
for (const branchKey of context.scriptedLocalisation.get(name) ?? []) {
|
|
70
71
|
const branch = context.localisation.get(branchKey);
|
|
71
|
-
if (branch
|
|
72
|
+
if (branch?.value.includes(colourCode) === true) {
|
|
72
73
|
add(node, 'SCRIPT_NEWS_TEXT_COLOUR_CODE', `${key} (${where(text)}) shows [${name}], whose branch ${branchKey} (${where(branch)}) uses a colour code in news or report event text`);
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
@@ -4,6 +4,7 @@ import { ServiceError } from '../core/result.js';
|
|
|
4
4
|
import { locationFor, parseClausewitz, } from '../core/source/index.js';
|
|
5
5
|
import { repeatedPopupFindings } from './event-popups.js';
|
|
6
6
|
import { playerTextFindings, playerTextPosition } from './player-text.js';
|
|
7
|
+
import { knownMistakeFindings } from './known-mistakes.js';
|
|
7
8
|
import { editDistance } from './suggestions.js';
|
|
8
9
|
import { publicReferenceSection } from './service.js';
|
|
9
10
|
const MAX_COMMANDS = 256;
|
|
@@ -487,10 +488,26 @@ export async function validateScript(references, workspace, input, signal, file)
|
|
|
487
488
|
scope: UNKNOWN_SCOPE,
|
|
488
489
|
line: position.line,
|
|
489
490
|
column: position.column,
|
|
490
|
-
message: finding.message,
|
|
491
|
+
message: finding.message.slice(0, 500),
|
|
491
492
|
suggestions: [],
|
|
492
493
|
});
|
|
493
494
|
}
|
|
495
|
+
const knownMistakes = plan.family === 'snippet' || plan.family === 'unsupported' ? undefined : file?.knownMistakes;
|
|
496
|
+
if (knownMistakes !== undefined)
|
|
497
|
+
for (const finding of knownMistakeFindings(plan.family, document, knownMistakes)) {
|
|
498
|
+
const position = playerTextPosition(documentPath, document.lineIndex, finding);
|
|
499
|
+
findings.push({
|
|
500
|
+
code: finding.code,
|
|
501
|
+
status: finding.rule.severity,
|
|
502
|
+
command: finding.command.slice(0, 256),
|
|
503
|
+
kind: 'definition',
|
|
504
|
+
scope: UNKNOWN_SCOPE,
|
|
505
|
+
line: position.line,
|
|
506
|
+
column: position.column,
|
|
507
|
+
message: finding.message.slice(0, 500),
|
|
508
|
+
suggestions: [],
|
|
509
|
+
});
|
|
510
|
+
}
|
|
494
511
|
const unresolvedCount = findings.filter(({ status }) => status === 'unresolved').length;
|
|
495
512
|
const invalid = findings.some(({ status }) => status === 'error');
|
|
496
513
|
const truncated = visited > commandLimit;
|
|
@@ -513,6 +530,7 @@ export async function validateScript(references, workspace, input, signal, file)
|
|
|
513
530
|
'declared_scope',
|
|
514
531
|
...(playerTextChecked ? ['player_text'] : []),
|
|
515
532
|
...(eventPopups === undefined ? [] : ['event_popups']),
|
|
533
|
+
...(knownMistakes === undefined ? [] : ['known_mistakes']),
|
|
516
534
|
],
|
|
517
535
|
parametersChecked: false,
|
|
518
536
|
documentation: {
|
|
@@ -6,6 +6,7 @@ import { ServiceError } from '../core/result.js';
|
|
|
6
6
|
import { isWithin } from '../core/workspace.js';
|
|
7
7
|
import { prepareSource, verifySource, readBoundedFile, } from '../core/scanner.js';
|
|
8
8
|
import { decodeSource } from '../core/source/encoding.js';
|
|
9
|
+
import { activeKnownMistakes } from './known-mistakes.js';
|
|
9
10
|
import { readBoundedJsonLines } from './lines.js';
|
|
10
11
|
import { RankingIndex, sectionTerms } from './ranking.js';
|
|
11
12
|
const MAX_FILES = 512;
|
|
@@ -544,6 +545,11 @@ export class ReferenceService {
|
|
|
544
545
|
missing,
|
|
545
546
|
coverage: inventory.coverage,
|
|
546
547
|
skipped: inventory.skipped,
|
|
548
|
+
knownMistakes: activeKnownMistakes(input.surface).map(({ id, title, fix }) => ({
|
|
549
|
+
id,
|
|
550
|
+
title,
|
|
551
|
+
fix,
|
|
552
|
+
})),
|
|
547
553
|
};
|
|
548
554
|
}
|
|
549
555
|
publicSection(section) {
|
|
@@ -112,25 +112,6 @@ export function validateEventInspectRequest(value, context) {
|
|
|
112
112
|
message: 'Impact analysis requires impactSubject',
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
export const eventInspectRequestSchema = z
|
|
116
|
-
.object({
|
|
117
|
-
workspaceId: workspaceIdSchema,
|
|
118
|
-
mode: eventInspectModeSchema,
|
|
119
|
-
selector: eventSelectorSchema.optional(),
|
|
120
|
-
from: eventSelectorSchema.optional(),
|
|
121
|
-
to: eventSelectorSchema.optional(),
|
|
122
|
-
direction: eventDirectionSchema.optional(),
|
|
123
|
-
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
124
|
-
maxNodes: z.number().int().min(1).max(5_000).optional(),
|
|
125
|
-
maxEdges: z.number().int().min(1).max(20_000).optional(),
|
|
126
|
-
expandHelpers: z.boolean().optional(),
|
|
127
|
-
stateSubject: eventStateSubjectSchema.optional(),
|
|
128
|
-
impactSubject: eventImpactSubjectSchema.optional(),
|
|
129
|
-
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
130
|
-
refresh: z.boolean().optional(),
|
|
131
|
-
})
|
|
132
|
-
.strict()
|
|
133
|
-
.superRefine(validateEventInspectRequest);
|
|
134
115
|
export const eventRenderRequestSchema = z
|
|
135
116
|
.object({
|
|
136
117
|
workspaceId: workspaceIdSchema,
|
|
@@ -172,3 +153,81 @@ export const eventCompareRequestSchema = z
|
|
|
172
153
|
})
|
|
173
154
|
.strict()
|
|
174
155
|
.superRefine(validateEventCompareRequest);
|
|
156
|
+
export const eventInspectToolModeSchema = z.enum([...eventInspectModeSchema.options, 'compare']);
|
|
157
|
+
const compareOnlyFields = [
|
|
158
|
+
'before',
|
|
159
|
+
'after',
|
|
160
|
+
'proposedSources',
|
|
161
|
+
'maxChainNodes',
|
|
162
|
+
'render',
|
|
163
|
+
'maxRenderNodes',
|
|
164
|
+
];
|
|
165
|
+
const inspectOnlyFields = [
|
|
166
|
+
'from',
|
|
167
|
+
'to',
|
|
168
|
+
'direction',
|
|
169
|
+
'maxDepth',
|
|
170
|
+
'maxNodes',
|
|
171
|
+
'maxEdges',
|
|
172
|
+
'expandHelpers',
|
|
173
|
+
'stateSubject',
|
|
174
|
+
'impactSubject',
|
|
175
|
+
'helperExpansion',
|
|
176
|
+
];
|
|
177
|
+
/**
|
|
178
|
+
* Inspection and comparison share one tool: mode compare diffs a cached, artifact or proposed
|
|
179
|
+
* event graph against another, every other mode analyzes the current graph.
|
|
180
|
+
*/
|
|
181
|
+
export const eventInspectRequestSchema = z
|
|
182
|
+
.object({
|
|
183
|
+
workspaceId: workspaceIdSchema,
|
|
184
|
+
mode: eventInspectToolModeSchema,
|
|
185
|
+
selector: eventSelectorSchema.optional(),
|
|
186
|
+
from: eventSelectorSchema.optional(),
|
|
187
|
+
to: eventSelectorSchema.optional(),
|
|
188
|
+
direction: eventDirectionSchema.optional(),
|
|
189
|
+
maxDepth: z.number().int().min(1).max(64).optional(),
|
|
190
|
+
maxNodes: z.number().int().min(1).max(5_000).optional(),
|
|
191
|
+
maxEdges: z.number().int().min(1).max(20_000).optional(),
|
|
192
|
+
expandHelpers: z.boolean().optional(),
|
|
193
|
+
stateSubject: eventStateSubjectSchema.optional(),
|
|
194
|
+
impactSubject: eventImpactSubjectSchema.optional(),
|
|
195
|
+
helperExpansion: helperExpansionRequestSchema.optional(),
|
|
196
|
+
before: eventGraphReferenceSchema.optional(),
|
|
197
|
+
after: eventGraphReferenceSchema.optional(),
|
|
198
|
+
proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
|
|
199
|
+
maxChainNodes: z.number().int().min(1).max(5_000).optional(),
|
|
200
|
+
render: z.boolean().optional(),
|
|
201
|
+
maxRenderNodes: z.number().int().min(1).max(240).optional(),
|
|
202
|
+
refresh: z.boolean().optional(),
|
|
203
|
+
})
|
|
204
|
+
.strict()
|
|
205
|
+
.superRefine(validateEventInspectToolRequest);
|
|
206
|
+
export function validateEventInspectToolRequest(value, context) {
|
|
207
|
+
const compare = value.mode === 'compare';
|
|
208
|
+
for (const field of compare ? inspectOnlyFields : compareOnlyFields)
|
|
209
|
+
if (value[field] !== undefined)
|
|
210
|
+
context.addIssue({
|
|
211
|
+
code: 'custom',
|
|
212
|
+
path: [field],
|
|
213
|
+
message: compare
|
|
214
|
+
? `${field} is not used by mode compare`
|
|
215
|
+
: `${field} is used only by mode compare`,
|
|
216
|
+
});
|
|
217
|
+
if (compare)
|
|
218
|
+
validateEventCompareRequest(value, context);
|
|
219
|
+
else
|
|
220
|
+
validateEventInspectRequest(value, context);
|
|
221
|
+
}
|
|
222
|
+
/** Split an inspect request into a comparison or an analysis of the current graph. */
|
|
223
|
+
export function splitEventInspectRequest(input) {
|
|
224
|
+
const { mode, ...rest } = input;
|
|
225
|
+
if (mode === 'compare') {
|
|
226
|
+
const request = { workspaceId: rest.workspaceId };
|
|
227
|
+
for (const field of [...compareOnlyFields, 'selector', 'refresh'])
|
|
228
|
+
if (rest[field] !== undefined)
|
|
229
|
+
request[field] = rest[field];
|
|
230
|
+
return { compare: true, request: eventCompareRequestSchema.parse(request) };
|
|
231
|
+
}
|
|
232
|
+
return { compare: false, request: { ...rest, mode } };
|
|
233
|
+
}
|
|
@@ -77,10 +77,20 @@ export const focusRenderRequestSchema = z
|
|
|
77
77
|
cropFocusIds: z.array(z.string().min(1).max(256)).max(16).optional(),
|
|
78
78
|
columns: z.number().int().min(1).max(12).optional(),
|
|
79
79
|
padding: z.number().int().min(0).max(1000).optional(),
|
|
80
|
+
png: z
|
|
81
|
+
.boolean()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Default true: add a PNG with decoded source icons. False returns only the faster HTML, SVG and JSON.'),
|
|
80
84
|
})
|
|
81
85
|
.strict()
|
|
82
86
|
.superRefine((value, context) => {
|
|
83
87
|
const mode = value.mode ?? 'national';
|
|
88
|
+
if (value.png === false && (value.cropFocusIds?.length ?? 0) > 0)
|
|
89
|
+
context.addIssue({
|
|
90
|
+
code: 'custom',
|
|
91
|
+
path: ['cropFocusIds'],
|
|
92
|
+
message: 'cropFocusIds produce PNG crops; omit png or set it to true',
|
|
93
|
+
});
|
|
84
94
|
const invalid = mode === 'national'
|
|
85
95
|
? [
|
|
86
96
|
['paletteId', value.paletteId],
|
|
@@ -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
|
+
}
|