hoi4-agent-tools 3.0.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/README.md +4 -0
  3. package/dist/bin/stdio.js +23 -12
  4. package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
  5. package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
  6. package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
  7. package/dist/hoi4_agent_tools/core/condition-evaluator.js +399 -0
  8. package/dist/hoi4_agent_tools/core/condition-model.js +1 -0
  9. package/dist/hoi4_agent_tools/core/condition-schema.js +42 -0
  10. package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
  11. package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
  12. package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
  13. package/dist/hoi4_agent_tools/core/engine.js +48 -5
  14. package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
  15. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  16. package/dist/hoi4_agent_tools/core/index.js +47 -4
  17. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  18. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  19. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  20. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  21. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  22. package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
  23. package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
  24. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  25. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  26. package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
  27. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  28. package/dist/hoi4_agent_tools/core/result.js +1 -0
  29. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  30. package/dist/hoi4_agent_tools/core/scenario-state.js +97 -0
  31. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  32. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +74 -25
  33. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  34. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  35. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  36. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  37. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  38. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  39. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  40. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  41. package/dist/hoi4_agent_tools/event/service.js +67 -15
  42. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  43. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  44. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  45. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  46. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  47. package/dist/hoi4_agent_tools/gui/scenario-conditions.js +119 -0
  48. package/dist/hoi4_agent_tools/gui/scenario-generator.js +59 -73
  49. package/dist/hoi4_agent_tools/gui/scenario.js +3 -0
  50. package/dist/hoi4_agent_tools/gui/studio.js +5 -2
  51. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  52. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  53. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  54. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  55. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  56. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  57. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  58. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  59. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  60. package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
  61. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  62. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  63. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  64. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  65. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  66. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
  67. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  68. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  69. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
  70. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  71. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  72. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  73. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  74. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  75. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  76. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  77. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  78. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  79. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  80. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  81. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  82. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  83. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  84. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  85. package/dist/hoi4_agent_tools/probability/scenario-state.js +2 -95
  86. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  87. package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +1 -287
  88. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  89. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  90. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  91. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  92. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  93. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  94. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  95. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  96. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  97. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  98. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  99. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  100. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  101. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  102. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  103. package/dist/hoi4_agent_tools/version.js +1 -1
  104. package/docs/README.md +2 -0
  105. package/docs/development.md +2 -0
  106. package/docs/events.md +7 -2
  107. package/docs/gui.md +3 -1
  108. package/docs/helper-expansion.md +78 -0
  109. package/docs/http.md +2 -0
  110. package/docs/jobs.md +85 -0
  111. package/docs/technology.md +6 -2
  112. package/package.json +11 -5
  113. package/schemas/configuration.schema.json +1 -1
  114. package/schemas/continuous-focus-palette.schema.json +1 -1
  115. package/schemas/custom-weighted-pool.schema.json +1 -1
  116. package/schemas/event-feature-manifest.schema.json +1 -1
  117. package/schemas/focus-plan.schema.json +1 -1
  118. package/schemas/focus-planning-sidecar.schema.json +1 -1
  119. package/schemas/gui-animation-source.schema.json +1 -1
  120. package/schemas/gui-helper.schema.json +1 -1
  121. package/schemas/gui-scenario.schema.json +189 -33
  122. package/schemas/helper-expansion-request.schema.json +44 -0
  123. package/schemas/helper-expansion-summary.schema.json +70 -0
  124. package/schemas/map-operation.schema.json +1 -1
  125. package/schemas/operation-result.schema.json +1 -1
  126. package/schemas/probability-analysis-result.schema.json +1 -1
  127. package/schemas/probability-compare-input.schema.json +1 -1
  128. package/schemas/probability-evaluate-input.schema.json +1 -1
  129. package/schemas/probability-inspect-input.schema.json +1 -1
  130. package/schemas/probability-render-input.schema.json +1 -1
  131. package/schemas/probability-scenario-set.schema.json +1 -1
  132. package/schemas/probability-sequence-input.schema.json +1 -1
  133. package/schemas/probability-simulate-input.schema.json +1 -1
  134. package/schemas/probability-sweep-input.schema.json +1 -1
  135. package/server.json +2 -2
@@ -1,287 +1 @@
1
- import { assignments, astPathFor, firstScalar, nodeLocation, } from '../core/source/index.js';
2
- import { resolveScopeContext, rootScopeContext, rootStateValue, scopeIdentity, scopeStateValue, } from './scenario-state.js';
3
- function combineAnd(values) {
4
- if (values.includes('false'))
5
- return 'false';
6
- return values.includes('unresolved') ? 'unresolved' : 'true';
7
- }
8
- function combineOr(values) {
9
- if (values.includes('true'))
10
- return 'true';
11
- return values.includes('unresolved') ? 'unresolved' : 'false';
12
- }
13
- function invert(value) {
14
- return value === 'true' ? 'false' : value === 'false' ? 'true' : 'unresolved';
15
- }
16
- function bool(value) {
17
- if (typeof value === 'boolean')
18
- return value;
19
- if (value === 'yes' || value === 'true')
20
- return true;
21
- if (value === 'no' || value === 'false')
22
- return false;
23
- return undefined;
24
- }
25
- function numeric(value) {
26
- if (typeof value === 'number' && Number.isFinite(value))
27
- return value;
28
- if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))
29
- return Number(value);
30
- return undefined;
31
- }
32
- function compare(left, right, operator) {
33
- if (typeof left === 'number' && typeof right === 'number') {
34
- if (operator === '>')
35
- return left > right;
36
- if (operator === '<')
37
- return left < right;
38
- }
39
- return operator === '!=' ? left !== right : left === right;
40
- }
41
- function unresolved(assignment, candidate, reason) {
42
- return {
43
- state: 'unresolved',
44
- unresolved: [
45
- {
46
- code: 'TRIGGER_UNRESOLVED',
47
- message: reason,
48
- path: assignment.key.value,
49
- candidateId: candidate.id,
50
- ...(candidate.provenance[0] === undefined ? {} : { provenance: candidate.provenance[0] }),
51
- },
52
- ],
53
- };
54
- }
55
- function evaluateCheckVariable(block, scenario, candidate, assignment, scopeContext) {
56
- const variable = firstScalar(block, 'var')?.value;
57
- const target = firstScalar(block, 'value')?.value;
58
- if (variable === undefined || target === undefined)
59
- return unresolved(assignment, candidate, 'check_variable requires declared var and value');
60
- const left = scopeStateValue(scenario, `variable.${variable}`, scopeContext) ??
61
- scopeStateValue(scenario, variable, scopeContext);
62
- const leftNumber = numeric(left);
63
- const rightNumber = numeric(target);
64
- if (leftNumber === undefined || rightNumber === undefined)
65
- return unresolved(assignment, candidate, `Scenario does not declare numeric variable ${variable}`);
66
- const compareName = firstScalar(block, 'compare')?.value ?? 'equals';
67
- const result = compareName === 'greater_than'
68
- ? leftNumber > rightNumber
69
- : compareName === 'greater_than_or_equals'
70
- ? leftNumber >= rightNumber
71
- : compareName === 'less_than'
72
- ? leftNumber < rightNumber
73
- : compareName === 'less_than_or_equals'
74
- ? leftNumber <= rightNumber
75
- : compareName === 'not_equals'
76
- ? leftNumber !== rightNumber
77
- : leftNumber === rightNumber;
78
- return { state: result ? 'true' : 'false', unresolved: [] };
79
- }
80
- function evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
81
- const key = assignment.key.value;
82
- if (assignment.value.type === 'block') {
83
- if (key === 'AND')
84
- return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, scopeContext);
85
- if (key === 'OR') {
86
- const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
87
- return {
88
- state: combineOr(children.map(({ state }) => state)),
89
- unresolved: children.flatMap(({ unresolved: items }) => items),
90
- helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
91
- };
92
- }
93
- if (key === 'NOT' || key === 'NOR' || key === 'NAND') {
94
- const children = assignments(assignment.value).map((child) => evaluateAssignment(child, scenario, candidate, definitions, helperStack, scopeContext));
95
- const base = key === 'NAND'
96
- ? combineAnd(children.map(({ state }) => state))
97
- : combineOr(children.map(({ state }) => state));
98
- return {
99
- state: invert(base),
100
- unresolved: children.flatMap(({ unresolved: items }) => items),
101
- helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
102
- };
103
- }
104
- if (key === 'check_variable')
105
- return evaluateCheckVariable(assignment.value, scenario, candidate, assignment, scopeContext);
106
- if (key === 'is_in_array' || key === 'is_variable_in_array') {
107
- const arrayId = firstScalar(assignment.value, 'array')?.value;
108
- const declaredValue = firstScalar(assignment.value, 'value')?.value;
109
- if (arrayId === undefined || declaredValue === undefined)
110
- return unresolved(assignment, candidate, `${key} requires array and value`);
111
- const array = scopeStateValue(scenario, `array.${arrayId}`, scopeContext) ??
112
- rootStateValue(scenario, `array.${arrayId}`);
113
- if (!Array.isArray(array))
114
- return unresolved(assignment, candidate, `Scenario does not declare array ${arrayId}`);
115
- const compared = ['THIS', 'ROOT', 'PREV'].includes(declaredValue.toUpperCase()) ||
116
- resolveScopeContext(scenario, declaredValue, scopeContext) !== undefined
117
- ? scopeIdentity(resolveScopeContext(scenario, declaredValue, scopeContext) ?? scopeContext)
118
- : (scopeStateValue(scenario, declaredValue, scopeContext) ?? declaredValue);
119
- return { state: array.includes(compared) ? 'true' : 'false', unresolved: [] };
120
- }
121
- const resolvedScope = resolveScopeContext(scenario, key, scopeContext);
122
- if (resolvedScope !== undefined)
123
- return evaluateTriggerBlock(assignment.value, scenario, candidate, definitions, helperStack, resolvedScope);
124
- const helper = definitions.scriptedTriggers.get(key);
125
- if (helper !== undefined) {
126
- if (helperStack.includes(key))
127
- return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
128
- if (assignments(assignment.value).length > 0)
129
- return unresolved(assignment, candidate, `Parameterized scripted trigger ${key} is not expanded without declared arguments`);
130
- const helperProvenance = {
131
- path: helper.file.displayPath,
132
- rootKind: helper.file.rootKind,
133
- loadOrder: helper.file.loadOrder,
134
- sourceHash: helper.file.sha256,
135
- location: nodeLocation(helper.document, helper.node, key),
136
- ...(astPathFor(helper.document, helper.node) === undefined
137
- ? {}
138
- : { astPath: astPathFor(helper.document, helper.node) }),
139
- symbol: key,
140
- helperChain: [...helperStack, key],
141
- };
142
- const evaluated = evaluateTriggerBlock(helper.value, scenario, {
143
- ...candidate,
144
- document: helper.document,
145
- provenance: [helperProvenance, ...candidate.provenance],
146
- }, definitions, [...helperStack, key], scopeContext);
147
- return {
148
- ...evaluated,
149
- helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
150
- };
151
- }
152
- const declaredScope = bool(rootStateValue(scenario, `scope.${key}`));
153
- if (declaredScope !== undefined)
154
- return { state: declaredScope ? 'true' : 'false', unresolved: [] };
155
- return unresolved(assignment, candidate, `Scoped or compound trigger ${key} is not declared by the scenario`);
156
- }
157
- const right = assignment.value.value;
158
- if (key === 'always') {
159
- const value = bool(right);
160
- return value === undefined
161
- ? unresolved(assignment, candidate, 'always trigger is not boolean')
162
- : { state: value ? 'true' : 'false', unresolved: [] };
163
- }
164
- if (key === 'has_country_flag' || key === 'has_global_flag' || key === 'has_state_flag') {
165
- const flags = scopeContext.expression === 'ROOT' ? scenario.flags : scopeContext.binding.flags;
166
- return { state: flags?.includes(right) === true ? 'true' : 'false', unresolved: [] };
167
- }
168
- if (key === 'has_event_target') {
169
- const eventTargets = scopeContext.binding.eventTargets ?? scenario.eventTargets;
170
- return {
171
- state: Object.hasOwn(eventTargets ?? {}, right) ? 'true' : 'false',
172
- unresolved: [],
173
- };
174
- }
175
- if (key === 'tag' || key === 'original_tag') {
176
- const actor = scopeContext.expression === 'ROOT'
177
- ? scenario.actor
178
- : (scopeContext.binding.actor ?? scopeContext.binding.id);
179
- if (actor === undefined)
180
- return unresolved(assignment, candidate, `Scenario does not declare actor for ${key}`);
181
- return {
182
- state: compare(actor, right, assignment.operator.text) ? 'true' : 'false',
183
- unresolved: [],
184
- };
185
- }
186
- if (key === 'date') {
187
- if (scenario.date === undefined)
188
- return unresolved(assignment, candidate, 'Scenario does not declare date');
189
- const leftDate = Number(scenario.date.replaceAll('.', ''));
190
- const rightDate = Number(right.replaceAll('.', ''));
191
- if (!Number.isFinite(leftDate) || !Number.isFinite(rightDate))
192
- return unresolved(assignment, candidate, 'Date comparison is not numeric');
193
- return {
194
- state: compare(leftDate, rightDate, assignment.operator.text) ? 'true' : 'false',
195
- unresolved: [],
196
- };
197
- }
198
- const helper = definitions.scriptedTriggers.get(key);
199
- if (helper !== undefined && bool(right) === true) {
200
- if (helperStack.includes(key))
201
- return unresolved(assignment, candidate, `Scripted trigger recursion detected at ${key}`);
202
- const helperProvenance = {
203
- path: helper.file.displayPath,
204
- rootKind: helper.file.rootKind,
205
- loadOrder: helper.file.loadOrder,
206
- sourceHash: helper.file.sha256,
207
- location: nodeLocation(helper.document, helper.node, key),
208
- ...(astPathFor(helper.document, helper.node) === undefined
209
- ? {}
210
- : { astPath: astPathFor(helper.document, helper.node) }),
211
- symbol: key,
212
- helperChain: [...helperStack, key],
213
- };
214
- const evaluated = evaluateTriggerBlock(helper.value, scenario, {
215
- ...candidate,
216
- document: helper.document,
217
- provenance: [helperProvenance, ...candidate.provenance],
218
- }, definitions, [...helperStack, key], scopeContext);
219
- return {
220
- ...evaluated,
221
- helperProvenance: [helperProvenance, ...(evaluated.helperProvenance ?? [])],
222
- };
223
- }
224
- const declared = scopeStateValue(scenario, key, scopeContext);
225
- if (declared === undefined)
226
- return unresolved(assignment, candidate, `Scenario does not declare trigger ${key}`);
227
- const expectedBoolean = bool(right);
228
- const actualBoolean = bool(declared);
229
- if (expectedBoolean !== undefined && actualBoolean !== undefined)
230
- return { state: expectedBoolean === actualBoolean ? 'true' : 'false', unresolved: [] };
231
- const leftNumber = numeric(declared);
232
- const rightNumber = numeric(right);
233
- if (leftNumber !== undefined && rightNumber !== undefined)
234
- return {
235
- state: compare(leftNumber, rightNumber, assignment.operator.text) ? 'true' : 'false',
236
- unresolved: [],
237
- };
238
- if (typeof declared === 'string')
239
- return {
240
- state: compare(declared, right, assignment.operator.text) ? 'true' : 'false',
241
- unresolved: [],
242
- };
243
- return unresolved(assignment, candidate, `Trigger ${key} cannot compare the declared scenario value`);
244
- }
245
- function evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext) {
246
- const evaluated = evaluateAssignmentCore(assignment, scenario, candidate, definitions, helperStack, scopeContext);
247
- const source = candidate.provenance[0];
248
- const provenance = candidate.document === undefined || source === undefined
249
- ? source
250
- : {
251
- ...source,
252
- location: nodeLocation(candidate.document, assignment, assignment.key.value),
253
- ...(astPathFor(candidate.document, assignment) === undefined
254
- ? {}
255
- : { astPath: astPathFor(candidate.document, assignment) }),
256
- symbol: assignment.key.value,
257
- };
258
- const expression = assignment.value.type === 'scalar'
259
- ? `${assignment.key.value} ${assignment.operator.text} ${assignment.value.value}`
260
- : assignment.key.value;
261
- return {
262
- ...evaluated,
263
- trace: [
264
- {
265
- operation: 'eligibility',
266
- expression,
267
- applied: evaluated.state,
268
- ...(provenance === undefined ? {} : { provenance }),
269
- },
270
- ...(evaluated.trace ?? []),
271
- ],
272
- };
273
- }
274
- export function evaluateTriggerBlock(block, scenario, candidate, definitions, helperStack = [], scopeContext = rootScopeContext(scenario)) {
275
- const override = scenario.candidateOverrides?.[candidate.id];
276
- if (override !== undefined)
277
- return { state: override ? 'true' : 'false', unresolved: [] };
278
- if (block === undefined)
279
- return { state: 'true', unresolved: [] };
280
- const children = assignments(block).map((assignment) => evaluateAssignment(assignment, scenario, candidate, definitions, helperStack, scopeContext));
281
- return {
282
- state: combineAnd(children.map(({ state }) => state)),
283
- unresolved: children.flatMap(({ unresolved: items }) => items),
284
- helperProvenance: children.flatMap(({ helperProvenance }) => helperProvenance ?? []),
285
- trace: children.flatMap(({ trace }) => trace ?? []),
286
- };
287
- }
1
+ export { evaluateTriggerBlock } from '../core/condition-evaluator.js';
@@ -4,6 +4,8 @@ export const workspaceIdSchema = z
4
4
  .string()
5
5
  .regex(/^[a-z][a-z0-9_-]{0,63}$/u)
6
6
  .default('current');
7
+ /** Caller-stable deduplication key for resumable background mutations. */
8
+ export const backgroundRequestKeySchema = z.string().min(1).max(256);
7
9
  const diagnosticCodeSchema = z.string().min(1).max(256);
8
10
  const diagnosticMessageSchema = z.string().max(4096);
9
11
  const sourceDisplayPathSchema = z.string().min(1).max(4096);
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod/v4';
2
2
  import { SOURCE_MAX_BYTES } from '../core/source/index.js';
3
- import { workspaceRelativePathSchema } from './common.js';
3
+ import { workspaceIdSchema, workspaceRelativePathSchema } from './common.js';
4
+ import { helperExpansionRequestSchema, validateHelperExpansionMode } from './helper-expansion.js';
4
5
  const eventIdSchema = z.string().min(1).max(256);
5
6
  const eventSourcePathSchema = z.string().min(1).max(1024);
6
7
  const eventNodeIdSchema = z.string().min(1).max(1024);
@@ -13,6 +14,7 @@ export const eventInspectModeSchema = z.enum([
13
14
  'state_flow',
14
15
  'lint',
15
16
  'impact',
17
+ 'helper_expansion',
16
18
  ]);
17
19
  export const eventDirectionSchema = z.enum(['upstream', 'downstream', 'both']);
18
20
  export const eventRenderViewSchema = z.enum([
@@ -93,3 +95,72 @@ export const eventProposedSourceSchema = z
93
95
  expectedSourceHash: eventSha256Schema.optional(),
94
96
  })
95
97
  .strict();
98
+ export function validateEventInspectRequest(value, context) {
99
+ validateHelperExpansionMode(value, context);
100
+ if (value.mode === 'trace' && value.selector === undefined)
101
+ context.addIssue({ code: 'custom', path: ['selector'], message: 'Trace requires selector' });
102
+ if (value.mode === 'explain_path' && (value.from === undefined || value.to === undefined))
103
+ context.addIssue({
104
+ code: 'custom',
105
+ path: value.from === undefined ? ['from'] : ['to'],
106
+ message: 'Path explanation requires from and to',
107
+ });
108
+ if (value.mode === 'impact' && value.impactSubject === undefined)
109
+ context.addIssue({
110
+ code: 'custom',
111
+ path: ['impactSubject'],
112
+ message: 'Impact analysis requires impactSubject',
113
+ });
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
+ export const eventRenderRequestSchema = z
135
+ .object({
136
+ workspaceId: workspaceIdSchema,
137
+ view: eventRenderViewSchema,
138
+ selector: eventSelectorSchema.optional(),
139
+ direction: eventDirectionSchema.optional(),
140
+ maxDepth: z.number().int().min(1).max(64).optional(),
141
+ maxNodes: z.number().int().min(1).max(240).optional(),
142
+ expandHelpers: z.boolean().optional(),
143
+ includeHtml: z.boolean().optional(),
144
+ refresh: z.boolean().optional(),
145
+ })
146
+ .strict();
147
+ export function validateEventCompareRequest(value, context) {
148
+ if (value.after !== undefined && value.proposedSources !== undefined)
149
+ context.addIssue({
150
+ code: 'custom',
151
+ path: ['after'],
152
+ message: 'after and proposedSources are mutually exclusive',
153
+ });
154
+ }
155
+ export const eventCompareRequestSchema = z
156
+ .object({
157
+ workspaceId: workspaceIdSchema,
158
+ before: eventGraphReferenceSchema.optional(),
159
+ after: eventGraphReferenceSchema.optional(),
160
+ proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
161
+ render: z.boolean().optional(),
162
+ maxRenderNodes: z.number().int().min(1).max(240).optional(),
163
+ refresh: z.boolean().optional(),
164
+ })
165
+ .strict()
166
+ .superRefine(validateEventCompareRequest);
@@ -0,0 +1,189 @@
1
+ import { z } from 'zod/v4';
2
+ import { FOCUS_RENDER_MAX_OUTPUT_SCALE, FOCUS_RENDER_MIN_OUTPUT_SCALE } from '../focus/index.js';
3
+ import { backgroundRequestKeySchema, workspaceIdSchema, workspaceRelativePathSchema, } from './common.js';
4
+ import { continuousFocusPaletteSchema, focusLayoutSchema, focusTreePlanSchema } from './focus.js';
5
+ function compact(schema, description) {
6
+ return z.unknown().describe(description).pipe(schema);
7
+ }
8
+ const compactFocusLayoutSchema = compact(focusLayoutSchema, 'Prior deterministic focus layout used to stabilize unchanged positions.');
9
+ const compactFocusPlanSchema = compact(z.union([focusTreePlanSchema, continuousFocusPaletteSchema]), 'Complete national or continuous focus plan.');
10
+ export const focusInspectRequestSchema = z
11
+ .object({
12
+ mode: z.enum(['national', 'continuous']).optional(),
13
+ workspaceId: workspaceIdSchema,
14
+ relativePath: workspaceRelativePathSchema.optional(),
15
+ treeId: z.string().min(1).max(256).optional(),
16
+ paletteId: z.string().min(1).max(256).optional(),
17
+ previous: compactFocusLayoutSchema.optional(),
18
+ laneSpacing: z.number().int().min(1).max(100).optional(),
19
+ nodeSpacing: z.number().int().min(1).max(100).optional(),
20
+ })
21
+ .strict()
22
+ .superRefine((value, context) => {
23
+ const mode = value.mode ?? 'national';
24
+ if (mode === 'national' && value.paletteId !== undefined)
25
+ context.addIssue({
26
+ code: 'custom',
27
+ path: ['paletteId'],
28
+ message: 'paletteId is only valid in continuous mode',
29
+ });
30
+ if (mode === 'continuous' && value.treeId !== undefined)
31
+ context.addIssue({
32
+ code: 'custom',
33
+ path: ['treeId'],
34
+ message: 'treeId is only valid in national mode',
35
+ });
36
+ if (mode === 'continuous') {
37
+ for (const field of ['previous', 'laneSpacing', 'nodeSpacing']) {
38
+ if (value[field] !== undefined)
39
+ context.addIssue({
40
+ code: 'custom',
41
+ path: [field],
42
+ message: `${field} is only valid in national mode`,
43
+ });
44
+ }
45
+ }
46
+ if (value.previous !== undefined && value.treeId === undefined)
47
+ context.addIssue({
48
+ code: 'custom',
49
+ path: ['previous'],
50
+ message: 'previous requires treeId so it applies to exactly one tree',
51
+ });
52
+ });
53
+ export const focusRenderRequestSchema = z
54
+ .object({
55
+ mode: z.enum(['national', 'continuous']).optional(),
56
+ workspaceId: workspaceIdSchema,
57
+ relativePath: workspaceRelativePathSchema,
58
+ treeId: z.string().min(1).max(256).optional(),
59
+ paletteId: z.string().min(1).max(256).optional(),
60
+ horizontalSpacing: z.number().int().min(80).max(1000).optional(),
61
+ verticalSpacing: z.number().int().min(60).max(1000).optional(),
62
+ reviewScale: z
63
+ .number()
64
+ .min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
65
+ .max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
66
+ .optional(),
67
+ columns: z.number().int().min(1).max(12).optional(),
68
+ padding: z.number().int().min(0).max(1000).optional(),
69
+ })
70
+ .strict()
71
+ .superRefine((value, context) => {
72
+ const mode = value.mode ?? 'national';
73
+ const invalid = mode === 'national'
74
+ ? [
75
+ ['paletteId', value.paletteId],
76
+ ['columns', value.columns],
77
+ ]
78
+ : [
79
+ ['treeId', value.treeId],
80
+ ['horizontalSpacing', value.horizontalSpacing],
81
+ ['verticalSpacing', value.verticalSpacing],
82
+ ['reviewScale', value.reviewScale],
83
+ ];
84
+ for (const [field, fieldValue] of invalid) {
85
+ if (fieldValue !== undefined)
86
+ context.addIssue({
87
+ code: 'custom',
88
+ path: [field],
89
+ message: `${field} is not valid in ${mode} mode`,
90
+ });
91
+ }
92
+ if (mode === 'continuous' && value.padding !== undefined && value.padding < 24)
93
+ context.addIssue({
94
+ code: 'custom',
95
+ path: ['padding'],
96
+ message: 'continuous focus padding must be at least 24 pixels',
97
+ });
98
+ });
99
+ export const focusRewriteRequestSchema = z
100
+ .object({
101
+ mode: z.enum(['national', 'continuous']).optional(),
102
+ workspaceId: workspaceIdSchema,
103
+ relativePath: workspaceRelativePathSchema,
104
+ treeId: z.string().min(1).max(256).optional(),
105
+ layoutMode: z.enum(['authored', 'compact']).default('authored'),
106
+ plan: compactFocusPlanSchema.optional(),
107
+ createIfMissing: z.boolean().default(false),
108
+ horizontalSpacing: z.number().int().min(80).max(1000).optional(),
109
+ verticalSpacing: z.number().int().min(60).max(1000).optional(),
110
+ padding: z.number().int().min(0).max(1000).optional(),
111
+ reviewScale: z
112
+ .number()
113
+ .min(FOCUS_RENDER_MIN_OUTPUT_SCALE)
114
+ .max(FOCUS_RENDER_MAX_OUTPUT_SCALE)
115
+ .optional(),
116
+ requestKey: backgroundRequestKeySchema.optional(),
117
+ })
118
+ .strict()
119
+ .superRefine((value, context) => {
120
+ const mode = value.mode ?? 'national';
121
+ const schema = mode === 'continuous' ? continuousFocusPaletteSchema : focusTreePlanSchema;
122
+ if (value.plan !== undefined && !schema.safeParse(value.plan).success)
123
+ context.addIssue({
124
+ code: 'custom',
125
+ path: ['plan'],
126
+ message: `plan must be a ${mode} focus plan`,
127
+ });
128
+ if (mode === 'continuous') {
129
+ if (value.plan === undefined)
130
+ context.addIssue({
131
+ code: 'custom',
132
+ path: ['plan'],
133
+ message: 'plan is required in continuous mode',
134
+ });
135
+ if (value.treeId !== undefined)
136
+ context.addIssue({
137
+ code: 'custom',
138
+ path: ['treeId'],
139
+ message: 'treeId is only valid in national mode',
140
+ });
141
+ if (value.layoutMode !== 'authored')
142
+ context.addIssue({
143
+ code: 'custom',
144
+ path: ['layoutMode'],
145
+ message: 'compact layout mode is only valid in national mode',
146
+ });
147
+ for (const [field, fieldValue] of [
148
+ ['horizontalSpacing', value.horizontalSpacing],
149
+ ['verticalSpacing', value.verticalSpacing],
150
+ ['padding', value.padding],
151
+ ['reviewScale', value.reviewScale],
152
+ ])
153
+ if (fieldValue !== undefined)
154
+ context.addIssue({
155
+ code: 'custom',
156
+ path: [field],
157
+ message: `${field} is only valid for national focus review renders`,
158
+ });
159
+ }
160
+ else {
161
+ if (value.plan === undefined && value.layoutMode !== 'compact')
162
+ context.addIssue({
163
+ code: 'custom',
164
+ path: ['plan'],
165
+ message: 'plan is required unless layoutMode is compact',
166
+ });
167
+ if (value.plan === undefined && value.treeId === undefined)
168
+ context.addIssue({
169
+ code: 'custom',
170
+ path: ['treeId'],
171
+ message: 'treeId is required for plan-free compact reflow',
172
+ });
173
+ if (value.plan === undefined && value.createIfMissing)
174
+ context.addIssue({
175
+ code: 'custom',
176
+ path: ['createIfMissing'],
177
+ message: 'plan-free compact reflow requires an existing focus source',
178
+ });
179
+ if (value.plan !== undefined &&
180
+ value.treeId !== undefined &&
181
+ 'id' in value.plan &&
182
+ value.plan.id !== value.treeId)
183
+ context.addIssue({
184
+ code: 'custom',
185
+ path: ['treeId'],
186
+ message: 'treeId must match the supplied national focus plan',
187
+ });
188
+ }
189
+ });