hoi4-agent-tools 3.0.9 → 3.2.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 (145) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/README.md +10 -2
  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/analysis-evidence.js +9 -0
  6. package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -0
  7. package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
  8. package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
  9. package/dist/hoi4_agent_tools/core/condition-evaluator.js +16 -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/impact-comparison.js +49 -0
  16. package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
  17. package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
  18. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  19. package/dist/hoi4_agent_tools/core/index.js +47 -4
  20. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  21. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  22. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  23. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  24. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  25. package/dist/hoi4_agent_tools/core/job-worker-host.js +202 -0
  26. package/dist/hoi4_agent_tools/core/job-worker.js +64 -0
  27. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  28. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  29. package/dist/hoi4_agent_tools/core/operation-tasks.js +387 -0
  30. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  31. package/dist/hoi4_agent_tools/core/result.js +1 -0
  32. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  33. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  34. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
  35. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  36. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  37. package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
  38. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  39. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  40. package/dist/hoi4_agent_tools/decision/ai.js +104 -0
  41. package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
  42. package/dist/hoi4_agent_tools/decision/cost.js +177 -0
  43. package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
  44. package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
  45. package/dist/hoi4_agent_tools/decision/service.js +172 -0
  46. package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
  47. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  48. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  49. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  50. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  51. package/dist/hoi4_agent_tools/event/service.js +67 -15
  52. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  53. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  54. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  55. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  56. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  57. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  58. package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
  59. package/dist/hoi4_agent_tools/impact/service.js +127 -0
  60. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  61. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  62. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  63. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  64. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  65. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  66. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  67. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  68. package/dist/hoi4_agent_tools/mcp/server/create.js +18 -6
  69. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  70. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  71. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  72. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  73. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  74. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +45 -0
  75. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  76. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  77. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +16 -0
  78. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  79. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  80. package/dist/hoi4_agent_tools/mcp/tools/analysis.js +65 -0
  81. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  82. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  83. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  84. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  85. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  86. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  87. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  88. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  89. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  90. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  91. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  92. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  93. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  94. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  95. package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
  96. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  97. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  98. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  99. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  100. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  101. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  102. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  103. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  104. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  105. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  106. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  107. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  108. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  109. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  110. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  111. package/dist/hoi4_agent_tools/version.js +1 -1
  112. package/docs/README.md +3 -0
  113. package/docs/analysis.md +100 -0
  114. package/docs/development.md +2 -0
  115. package/docs/events.md +7 -2
  116. package/docs/helper-expansion.md +78 -0
  117. package/docs/http.md +2 -0
  118. package/docs/jobs.md +86 -0
  119. package/docs/technology.md +6 -2
  120. package/package.json +15 -6
  121. package/schemas/configuration.schema.json +1 -1
  122. package/schemas/continuous-focus-palette.schema.json +1 -1
  123. package/schemas/custom-weighted-pool.schema.json +1 -1
  124. package/schemas/decision-inspect-input.schema.json +290 -0
  125. package/schemas/event-feature-manifest.schema.json +1 -1
  126. package/schemas/focus-plan.schema.json +1 -1
  127. package/schemas/focus-planning-sidecar.schema.json +1 -1
  128. package/schemas/gui-animation-source.schema.json +1 -1
  129. package/schemas/gui-helper.schema.json +1 -1
  130. package/schemas/gui-scenario.schema.json +1 -1
  131. package/schemas/helper-expansion-request.schema.json +44 -0
  132. package/schemas/helper-expansion-summary.schema.json +70 -0
  133. package/schemas/impact-inspect-input.schema.json +203 -0
  134. package/schemas/map-operation.schema.json +1 -1
  135. package/schemas/operation-result.schema.json +1 -1
  136. package/schemas/probability-analysis-result.schema.json +1 -1
  137. package/schemas/probability-compare-input.schema.json +1 -1
  138. package/schemas/probability-evaluate-input.schema.json +1 -1
  139. package/schemas/probability-inspect-input.schema.json +1 -1
  140. package/schemas/probability-render-input.schema.json +1 -1
  141. package/schemas/probability-scenario-set.schema.json +1 -1
  142. package/schemas/probability-sequence-input.schema.json +1 -1
  143. package/schemas/probability-simulate-input.schema.json +1 -1
  144. package/schemas/probability-sweep-input.schema.json +1 -1
  145. package/server.json +3 -3
@@ -0,0 +1,121 @@
1
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
2
+ import { createMcpServer } from '../server/create.js';
3
+ import { createModernOperationServer } from '../server/create-modern-operations.js';
4
+ import { BoundedStdioServerTransport, STDIO_MAX_FRAME_BYTES } from './bounded-stdio.js';
5
+ import { FinalProtocolTransport } from './protocol-gate.js';
6
+ const MAX_QUEUED_MESSAGES = 128;
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ /** Modern discovery or metadata selects the modern connection; initialize selects legacy. */
11
+ export function isModernStdioOpening(message) {
12
+ if (!('method' in message))
13
+ return false;
14
+ if (message.method === 'server/discover')
15
+ return true;
16
+ if (message.method === 'initialize' || !('params' in message))
17
+ return false;
18
+ const params = message.params;
19
+ const meta = isRecord(params) ? params._meta : undefined;
20
+ return (isRecord(meta) && Object.keys(meta).some((key) => key.startsWith('io.modelcontextprotocol/')));
21
+ }
22
+ /** One logical SDK connection over the already-started bounded physical stream. */
23
+ class PinnedStdioTransport {
24
+ physical;
25
+ reportError;
26
+ onclose = () => undefined;
27
+ onerror = () => undefined;
28
+ onmessage = () => undefined;
29
+ started = false;
30
+ queuedBytes = 0;
31
+ queue = [];
32
+ constructor(physical, reportError) {
33
+ this.physical = physical;
34
+ this.reportError = reportError;
35
+ }
36
+ receive(message) {
37
+ if (this.started) {
38
+ this.onmessage(message);
39
+ return;
40
+ }
41
+ const bytes = Buffer.byteLength(JSON.stringify(message)) + 1;
42
+ if (this.queue.length >= MAX_QUEUED_MESSAGES ||
43
+ bytes > STDIO_MAX_FRAME_BYTES - this.queuedBytes) {
44
+ const error = new Error('Stdio negotiation queue exceeded its bounded limit');
45
+ this.reportError(error);
46
+ this.onerror(error);
47
+ void this.physical.close();
48
+ return;
49
+ }
50
+ this.queue.push(message);
51
+ this.queuedBytes += bytes;
52
+ }
53
+ start() {
54
+ if (this.started)
55
+ throw new Error('Negotiated stdio transport already started');
56
+ this.started = true;
57
+ for (const message of this.queue.splice(0))
58
+ this.onmessage(message);
59
+ this.queuedBytes = 0;
60
+ return Promise.resolve();
61
+ }
62
+ send(message) {
63
+ return this.physical.send(message);
64
+ }
65
+ close() {
66
+ return this.physical.close();
67
+ }
68
+ }
69
+ /** Pin the protocol generation on the first message while retaining the old bounded wire. */
70
+ export async function serveNegotiatedStdio(engine, options) {
71
+ const physical = new BoundedStdioServerTransport({
72
+ ...(options.stdin === undefined ? {} : { stdin: options.stdin }),
73
+ ...(options.stdout === undefined ? {} : { stdout: options.stdout }),
74
+ });
75
+ let pinned;
76
+ const reportedErrors = new WeakSet();
77
+ const reportError = (error) => {
78
+ if (reportedErrors.has(error))
79
+ return;
80
+ reportedErrors.add(error);
81
+ options.onerror(error);
82
+ };
83
+ physical.onclose = () => pinned?.onclose();
84
+ physical.onerror = (error) => {
85
+ reportError(error);
86
+ pinned?.onerror(error);
87
+ };
88
+ physical.onmessage = (message) => {
89
+ if (pinned === undefined) {
90
+ pinned = new PinnedStdioTransport(physical, reportError);
91
+ try {
92
+ if (isModernStdioOpening(message)) {
93
+ serveStdio(() => createModernOperationServer(engine, {}, undefined, options.createModernPrivateTools === undefined
94
+ ? {}
95
+ : { createPrivateTools: options.createModernPrivateTools }), {
96
+ transport: pinned,
97
+ legacy: 'reject',
98
+ onerror: reportError,
99
+ });
100
+ }
101
+ else {
102
+ const server = createMcpServer(engine, {});
103
+ options.registerLegacy?.(server);
104
+ server.server.onerror = reportError;
105
+ void server.connect(new FinalProtocolTransport(pinned)).catch((error) => {
106
+ options.onFailure(error);
107
+ void physical.close();
108
+ });
109
+ }
110
+ }
111
+ catch (error) {
112
+ options.onFailure(error);
113
+ void physical.close();
114
+ return;
115
+ }
116
+ }
117
+ pinned.receive(message);
118
+ };
119
+ await physical.start();
120
+ return physical;
121
+ }
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod/v4';
2
+ import { IdleCacheLifetime } from '../core/idle-cache-lifetime.js';
3
+ import { errorResult, toolResult } from '../core/operation-result.js';
4
+ import { ServiceError } from '../core/result.js';
5
+ import { probabilityCompareRequestSchema, probabilityEvaluateRequestSchema, probabilityInspectRequestSchema, probabilityRenderRequestSchema, probabilitySequenceRequestSchema, probabilitySimulateRequestSchema, probabilitySweepRequestSchema, } from '../schemas/probability-requests.js';
6
+ import { compareProbabilities, evaluateProbabilities, inspectProbabilities, normalizeProbabilityCompareRequest, normalizeProbabilityEvaluateRequest, normalizeProbabilityInspectRequest, normalizeProbabilityRenderRequest, normalizeProbabilitySequenceRequest, normalizeProbabilitySimulateRequest, normalizeProbabilitySweepRequest, renderProbabilities, sequenceProbabilities, simulateProbabilities, sweepProbabilities, } from './operations.js';
7
+ import { ProbabilityAnalyzer } from './service.js';
8
+ const wireResult = z.record(z.string(), z.json());
9
+ /** Registers probability operations without invoking transport handlers. */
10
+ export function registerProbabilityJobs(operations, engine) {
11
+ const analyzer = new ProbabilityAnalyzer(engine);
12
+ const lifetime = new IdleCacheLifetime(() => analyzer.clearCaches());
13
+ function register(name, schema, normalize, execute, message) {
14
+ operations.registerRead(name, schema, async (input, context) => {
15
+ const release = lifetime.begin();
16
+ try {
17
+ if (input.workspaceId !== 'current' && input.workspaceId !== context.workspaceId)
18
+ throw new ServiceError('JOB_ARGUMENT_SCOPE_MISMATCH', 'The request workspace does not match the authorized job workspace');
19
+ await context.progress({ completed: 0, total: 3, message });
20
+ const result = await execute(analyzer, normalize(input), {
21
+ workspaceId: context.workspaceId,
22
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
23
+ signal: context.signal,
24
+ });
25
+ await context.progress({
26
+ completed: 3,
27
+ total: 3,
28
+ message: 'Probability operation complete',
29
+ });
30
+ const wire = wireResult.parse(JSON.parse(JSON.stringify(toolResult(result))));
31
+ const revision = result.data !== null &&
32
+ typeof result.data === 'object' &&
33
+ 'sourceRevision' in result.data &&
34
+ typeof result.data.sourceRevision === 'string'
35
+ ? result.data.sourceRevision
36
+ : undefined;
37
+ if (revision !== undefined)
38
+ await context.stageResult(wire, { sourceRevision: revision });
39
+ return wire;
40
+ }
41
+ catch (error) {
42
+ if (context.signal.aborted)
43
+ throw error;
44
+ return wireResult.parse(JSON.parse(JSON.stringify(errorResult(error, context.workspaceId))));
45
+ }
46
+ finally {
47
+ release();
48
+ }
49
+ });
50
+ }
51
+ register('hoi4.probability_inspect', probabilityInspectRequestSchema, normalizeProbabilityInspectRequest, inspectProbabilities, 'Inspecting weighted source');
52
+ register('hoi4.probability_evaluate', probabilityEvaluateRequestSchema, normalizeProbabilityEvaluateRequest, evaluateProbabilities, 'Evaluating weighted scenarios');
53
+ register('hoi4.probability_sweep', probabilitySweepRequestSchema, normalizeProbabilitySweepRequest, sweepProbabilities, 'Evaluating parameter sweep');
54
+ register('hoi4.probability_simulate', probabilitySimulateRequestSchema, normalizeProbabilitySimulateRequest, simulateProbabilities, 'Sampling uncertain scenarios');
55
+ register('hoi4.probability_sequence', probabilitySequenceRequestSchema, normalizeProbabilitySequenceRequest, sequenceProbabilities, 'Analyzing declared sequence');
56
+ register('hoi4.probability_compare', probabilityCompareRequestSchema, normalizeProbabilityCompareRequest, compareProbabilities, 'Comparing weighted source');
57
+ register('hoi4.probability_render', probabilityRenderRequestSchema, normalizeProbabilityRenderRequest, renderProbabilities, 'Rendering analysis resources');
58
+ }
@@ -0,0 +1,183 @@
1
+ import { emptyServiceResult } from '../core/result.js';
2
+ import { setInlineFilesScanned } from '../core/operation-result.js';
3
+ import { probabilityCompareRequestSchema, probabilityEvaluateRequestSchema, probabilityInspectRequestSchema, probabilityRenderRequestSchema, probabilitySequenceRequestSchema, probabilitySimulateRequestSchema, probabilitySweepRequestSchema, } from '../schemas/probability-requests.js';
4
+ function runtime(context, refresh) {
5
+ return {
6
+ workspaceId: context.workspaceId,
7
+ ...(context.principal === undefined ? {} : { principal: context.principal }),
8
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
9
+ ...(refresh === undefined ? {} : { refresh }),
10
+ };
11
+ }
12
+ export function probabilityAnalysisData(result) {
13
+ return {
14
+ operation: result.operation,
15
+ analysisId: result.analysisId,
16
+ analysisStatus: result.status,
17
+ adapterId: result.adapter.id,
18
+ sourceRevision: result.metadata.sourceRevision,
19
+ sourceHash: result.metadata.sourceHash,
20
+ scenarioHash: result.metadata.scenarioHash,
21
+ cacheKey: result.metadata.cacheKey,
22
+ scenarios: result.scenarios.length,
23
+ candidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.length, 0),
24
+ ...(result.scenarios.some(({ scopePools }) => (scopePools?.length ?? 0) > 0)
25
+ ? {
26
+ scopePools: result.scenarios.reduce((sum, scenario) => sum + (scenario.scopePools?.length ?? 0), 0),
27
+ scopePoolCandidates: result.scenarios.reduce((sum, scenario) => sum +
28
+ (scenario.scopePools?.reduce((poolSum, pool) => poolSum + pool.candidates.length, 0) ?? 0), 0),
29
+ }
30
+ : {}),
31
+ unresolved: result.unresolved.length,
32
+ diagnostics: result.diagnostics.length,
33
+ ...(result.sweep === undefined ? {} : { sweepPoints: result.sweep.points.length }),
34
+ ...(result.metadata.samples === undefined ? {} : { samples: result.metadata.samples }),
35
+ ...(result.sequence === undefined ? {} : { sequenceMethod: result.sequence.method }),
36
+ ...(result.comparison === undefined
37
+ ? {}
38
+ : { comparisonChanges: result.comparison.scenarioChanges.length }),
39
+ visualResources: result.resources.filter(({ mimeType }) => mimeType.startsWith('image/'))
40
+ .length,
41
+ };
42
+ }
43
+ export function probabilityAnalysisServiceResult(workspaceId, result) {
44
+ const output = emptyServiceResult(workspaceId, probabilityAnalysisData(result));
45
+ output.code =
46
+ result.status === 'complete'
47
+ ? 'PROBABILITY_ANALYZED'
48
+ : result.status === 'stale'
49
+ ? result.diagnostics.some(({ code }) => code === 'PROBABILITY_SCENARIO_STALE')
50
+ ? 'PROBABILITY_SCENARIO_STALE'
51
+ : 'PROBABILITY_ANALYSIS_STALE'
52
+ : 'PROBABILITY_ANALYZED_PARTIAL';
53
+ output.artifacts = result.resources;
54
+ output.diagnostics = result.diagnostics;
55
+ output.validation = {
56
+ passed: result.status !== 'blocked' && result.status !== 'cancelled',
57
+ checks: [
58
+ {
59
+ id: 'uncertainty-visible',
60
+ passed: true,
61
+ message: `${result.unresolved.length} unresolved or bounded analysis item(s) are explicit`,
62
+ },
63
+ ],
64
+ };
65
+ return output;
66
+ }
67
+ export function normalizeProbabilityInspectRequest(input) {
68
+ return probabilityInspectRequestSchema.parse(input);
69
+ }
70
+ export function normalizeProbabilityEvaluateRequest(input) {
71
+ return probabilityEvaluateRequestSchema.parse(input);
72
+ }
73
+ export function normalizeProbabilitySweepRequest(input) {
74
+ return probabilitySweepRequestSchema.parse(input);
75
+ }
76
+ export function normalizeProbabilitySimulateRequest(input) {
77
+ return probabilitySimulateRequestSchema.parse(input);
78
+ }
79
+ export function normalizeProbabilitySequenceRequest(input) {
80
+ return probabilitySequenceRequestSchema.parse(input);
81
+ }
82
+ export function normalizeProbabilityCompareRequest(input) {
83
+ return probabilityCompareRequestSchema.parse(input);
84
+ }
85
+ export function normalizeProbabilityRenderRequest(input) {
86
+ return probabilityRenderRequestSchema.parse(input);
87
+ }
88
+ export async function inspectProbabilities(analyzer, input, context) {
89
+ const inspected = await analyzer.inspect(runtime(context, input.refresh), input.adapter, input.source, input.candidatePool, input.customPoolManifest);
90
+ const result = emptyServiceResult(context.workspaceId, {
91
+ adapters: inspected.adapters.length,
92
+ ...(inspected.surface === undefined
93
+ ? {}
94
+ : {
95
+ adapterId: inspected.surface.adapter.id,
96
+ sourceRevision: inspected.surface.sourceRevision,
97
+ sourceHash: inspected.surface.sourceHash,
98
+ poolComplete: inspected.surface.poolComplete,
99
+ }),
100
+ ...(inspected.discovery === undefined
101
+ ? {}
102
+ : {
103
+ ...(inspected.discovery.requestedAdapter === undefined
104
+ ? {}
105
+ : { requestedAdapter: inspected.discovery.requestedAdapter }),
106
+ ...(inspected.discovery.suggestedAdapter === undefined
107
+ ? {}
108
+ : { suggestedAdapter: inspected.discovery.suggestedAdapter }),
109
+ discoveryReason: inspected.discovery.reason,
110
+ sourceRevision: inspected.discovery.sourceRevision,
111
+ sourceHash: inspected.discovery.sourceHash,
112
+ }),
113
+ candidates: inspected.surface?.candidateCount ?? 0,
114
+ availableCandidates: inspected.discovery?.availableAdapters.reduce((sum, { candidateCount }) => sum + candidateCount, 0) ?? 0,
115
+ availableAdapters: inspected.discovery?.availableAdapters.map((available) => ({
116
+ adapterId: available.adapterId,
117
+ candidates: available.candidateCount,
118
+ identifierMatches: available.identifierMatchCount,
119
+ candidatePoolMatches: available.candidatePoolMatchCount,
120
+ })) ?? [],
121
+ candidateExamples: inspected.discovery?.exampleCandidateIds ?? [],
122
+ requiredInputs: inspected.surface?.requiredInputs.length ?? 0,
123
+ unresolved: inspected.surface?.unsupported.length ?? 0,
124
+ });
125
+ result.code =
126
+ inspected.surface !== undefined
127
+ ? 'PROBABILITY_SOURCE_INSPECTED'
128
+ : inspected.discovery !== undefined
129
+ ? 'PROBABILITY_SOURCE_DISCOVERED'
130
+ : 'PROBABILITY_ADAPTERS_LISTED';
131
+ result.artifacts = inspected.artifacts;
132
+ setInlineFilesScanned(result, inspected.filesScanned);
133
+ return result;
134
+ }
135
+ export async function evaluateProbabilities(analyzer, input, context) {
136
+ const analyzed = await analyzer.evaluate({
137
+ ...runtime(context, input.refresh),
138
+ ...input,
139
+ workspaceId: context.workspaceId,
140
+ });
141
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
142
+ }
143
+ export async function sweepProbabilities(analyzer, input, context) {
144
+ const analyzed = await analyzer.sweep({
145
+ ...runtime(context, input.refresh),
146
+ ...input,
147
+ workspaceId: context.workspaceId,
148
+ });
149
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
150
+ }
151
+ export async function simulateProbabilities(analyzer, input, context) {
152
+ const analyzed = await analyzer.simulate({
153
+ ...runtime(context, input.refresh),
154
+ ...input,
155
+ workspaceId: context.workspaceId,
156
+ });
157
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
158
+ }
159
+ export async function sequenceProbabilities(analyzer, input, context) {
160
+ const analyzed = await analyzer.sequence({
161
+ ...runtime(context),
162
+ ...input,
163
+ workspaceId: context.workspaceId,
164
+ });
165
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
166
+ }
167
+ export async function compareProbabilities(analyzer, input, context) {
168
+ const analyzed = await analyzer.compare({
169
+ ...runtime(context, input.refresh),
170
+ ...input,
171
+ workspaceId: context.workspaceId,
172
+ });
173
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
174
+ }
175
+ export async function renderProbabilities(analyzer, input, context) {
176
+ const analyzed = await analyzer.render({
177
+ ...runtime(context),
178
+ ...input,
179
+ workspaceId: context.workspaceId,
180
+ outputs: input.outputs.filter((output) => output !== 'json'),
181
+ });
182
+ return probabilityAnalysisServiceResult(context.workspaceId, analyzed);
183
+ }
@@ -6,6 +6,7 @@ import { probabilityDomainScanPatterns, probabilitySharedDefinitionPatterns, } f
6
6
  import { publicArtifactLink } from '../core/artifacts.js';
7
7
  import { ServiceError } from '../core/result.js';
8
8
  import { PACKAGE_VERSION } from '../version.js';
9
+ import { probabilityAnalysisResultSchema } from '../schemas/probability.js';
9
10
  import { probabilityAdapter, probabilityAdapters } from './adapters.js';
10
11
  import { compareProbabilityResults } from './compare.js';
11
12
  import { evaluateExactCandidates, evaluateSurfaceScenarios } from './evaluation.js';
@@ -710,6 +711,28 @@ export class ProbabilityAnalyzer {
710
711
  this.state.byCacheKey.clear();
711
712
  this.engine.releaseScanCaches();
712
713
  }
714
+ async retainedAnalysis(request) {
715
+ const workspace = this.engine.resolver.get(request.workspaceId, request.principal);
716
+ const candidates = (await this.engine.artifacts.list(workspace, request.signal)).filter(({ name }) => name === `${request.analysisId}.json`);
717
+ for (const artifact of candidates) {
718
+ request.signal?.throwIfAborted();
719
+ try {
720
+ const logical = await this.engine.artifacts.readLogical(workspace, artifact.uri, { mimeType: 'application/json', maxBytes: 134_217_728, maxChunks: 2_048 }, request.signal);
721
+ const result = probabilityAnalysisResultSchema.parse(JSON.parse(logical.bytes.toString('utf8')));
722
+ if (result.analysisId !== request.analysisId ||
723
+ result.metadata.workspaceId !== request.workspaceId ||
724
+ result.operation === 'render')
725
+ continue;
726
+ rememberAnalysis(this.state, result);
727
+ return result;
728
+ }
729
+ catch (error) {
730
+ if (error instanceof ServiceError && error.code === 'ARTIFACT_WORKSPACE_MISMATCH')
731
+ throw error;
732
+ }
733
+ }
734
+ throw new ServiceError('PROBABILITY_ANALYSIS_NOT_CACHED', 'Render requires a retained analysis ID from this authorized workspace', { analysisId: request.analysisId });
735
+ }
713
736
  async scan(context, sourcePaths = [], emptyScope = 'domain') {
714
737
  if (context.refresh === true)
715
738
  this.engine.invalidate(context.workspaceId);
@@ -1394,9 +1417,7 @@ export class ProbabilityAnalyzer {
1394
1417
  return this.store(result, request.outputs ?? ['json', 'comparison'], false, request.signal, request.principal);
1395
1418
  }
1396
1419
  async render(request) {
1397
- const result = this.state.byId.get(request.analysisId);
1398
- if (result === undefined)
1399
- throw new ServiceError('PROBABILITY_ANALYSIS_NOT_CACHED', 'Render requires an analysis ID produced by this server process', { analysisId: request.analysisId });
1420
+ const result = this.state.byId.get(request.analysisId) ?? (await this.retainedAnalysis(request));
1400
1421
  if (result.metadata.workspaceId !== request.workspaceId)
1401
1422
  throw new ServiceError('PROBABILITY_ANALYSIS_WORKSPACE_MISMATCH', 'Analysis belongs to a different workspace');
1402
1423
  if (request.expectedScenarioHash !== undefined &&
@@ -0,0 +1,228 @@
1
+ import { z } from 'zod/v4';
2
+ import { workspaceIdSchema, workspaceRelativePathSchema } from './common.js';
3
+ const symbolKindSchema = z.enum([
4
+ 'focus_tree',
5
+ 'focus',
6
+ 'continuous_focus_palette',
7
+ 'continuous_focus',
8
+ 'decision',
9
+ 'decision_category',
10
+ 'event',
11
+ 'idea',
12
+ 'leader',
13
+ 'formable',
14
+ 'scripted_effect',
15
+ 'scripted_trigger',
16
+ 'script_constant',
17
+ 'mtth_variable',
18
+ 'ai_strategy',
19
+ 'ai_strategy_plan',
20
+ 'sprite',
21
+ 'texture',
22
+ 'gui_element',
23
+ 'scripted_gui',
24
+ 'technology',
25
+ 'technology_folder',
26
+ 'technology_category',
27
+ 'technology_tag',
28
+ 'equipment',
29
+ 'equipment_module',
30
+ 'sub_unit',
31
+ 'unit_category',
32
+ 'building',
33
+ 'ability',
34
+ 'combat_tactic',
35
+ 'doctrine_folder',
36
+ 'grand_doctrine',
37
+ 'doctrine_track',
38
+ 'subdoctrine',
39
+ 'localisation',
40
+ 'variable',
41
+ 'flag',
42
+ 'event_target',
43
+ 'state',
44
+ 'province',
45
+ 'province_color',
46
+ 'strategic_region',
47
+ 'adjacency',
48
+ 'supply_node',
49
+ 'railway',
50
+ ]);
51
+ export const impactSymbolSelectorSchema = z
52
+ .object({
53
+ kind: symbolKindSchema,
54
+ id: z.string().min(1).max(512),
55
+ })
56
+ .strict();
57
+ const sourceDisplayPathSchema = z
58
+ .string()
59
+ .min(1)
60
+ .max(4096)
61
+ .refine((value) => {
62
+ const separator = value.indexOf(':');
63
+ if (separator < 1)
64
+ return false;
65
+ const root = value.slice(0, separator);
66
+ if (!/^(?:mod|game|fixture|dependency-[0-9]+)$/u.test(root))
67
+ return false;
68
+ return workspaceRelativePathSchema.safeParse(value.slice(separator + 1)).success;
69
+ }, 'Use a scanned source display path under an authorized workspace root');
70
+ export const proposedAnalysisSourceSchema = z
71
+ .object({
72
+ relativePath: workspaceRelativePathSchema,
73
+ content: z
74
+ .string()
75
+ .max(8 * 1024 * 1024)
76
+ .nullable(),
77
+ })
78
+ .strict();
79
+ const scenarioScalarSchema = z.union([z.string().max(4096), z.number(), z.boolean(), z.null()]);
80
+ const scenarioValueSchema = z.union([
81
+ scenarioScalarSchema,
82
+ z.array(scenarioScalarSchema).max(10_000),
83
+ ]);
84
+ const scenarioStateSchema = z
85
+ .record(z.string().min(1).max(1024), scenarioValueSchema)
86
+ .refine((state) => Object.keys(state).length <= 10_000, 'Scenario state has too many keys');
87
+ const scopeBindingSchema = z
88
+ .object({
89
+ id: z.string().min(1).max(512),
90
+ type: z
91
+ .enum([
92
+ 'country',
93
+ 'state',
94
+ 'character',
95
+ 'unit_leader',
96
+ 'operative',
97
+ 'strategic_region',
98
+ 'province',
99
+ 'unknown',
100
+ ])
101
+ .optional(),
102
+ actor: z.string().max(256).optional(),
103
+ state: scenarioStateSchema,
104
+ flags: z.array(z.string().min(1).max(512)).max(10_000).optional(),
105
+ eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
106
+ weight: z.union([z.number(), z.string().max(1024)]).optional(),
107
+ })
108
+ .strict();
109
+ export const declaredConditionScenarioSchema = z
110
+ .object({
111
+ id: z.string().min(1).max(512),
112
+ actor: z.string().max(256).optional(),
113
+ date: z.string().max(64).optional(),
114
+ state: scenarioStateSchema,
115
+ flags: z.array(z.string().min(1).max(512)).max(10_000).optional(),
116
+ eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
117
+ scopes: z
118
+ .record(z.string().min(1).max(128), scopeBindingSchema)
119
+ .refine((scopes) => Object.keys(scopes).length <= 64, 'Too many declared scopes')
120
+ .optional(),
121
+ candidateOverrides: z.record(z.string().max(512), z.boolean()).optional(),
122
+ closedFlags: z.boolean().optional(),
123
+ })
124
+ .strict();
125
+ export const impactInspectRequestSchema = z
126
+ .object({
127
+ workspaceId: workspaceIdSchema,
128
+ symbols: z.array(impactSymbolSelectorSchema).max(64).default([]),
129
+ changedFiles: z.array(sourceDisplayPathSchema).max(64).default([]),
130
+ scenarioSuites: z
131
+ .array(workspaceRelativePathSchema.refine((value) => value.toLowerCase().endsWith('.json'), 'Scenario suite path must name a JSON file'))
132
+ .max(32)
133
+ .default([]),
134
+ proposedSources: z.array(proposedAnalysisSourceSchema).min(1).max(64).optional(),
135
+ maxNodes: z.number().int().min(1).max(50_000).optional(),
136
+ maxEdges: z.number().int().min(1).max(100_000).optional(),
137
+ maxDepth: z.number().int().min(1).max(64).optional(),
138
+ expectedRevision: z
139
+ .string()
140
+ .regex(/^[a-f0-9]{64}$/u)
141
+ .optional(),
142
+ refresh: z.boolean().default(false),
143
+ })
144
+ .strict()
145
+ .superRefine((input, context) => {
146
+ if (input.symbols.length === 0 &&
147
+ input.changedFiles.length === 0 &&
148
+ input.proposedSources === undefined)
149
+ context.addIssue({
150
+ code: 'custom',
151
+ message: 'Impact inspection requires a symbol, changed file, or proposed source',
152
+ });
153
+ });
154
+ export const decisionInspectRequestSchema = z
155
+ .object({
156
+ workspaceId: workspaceIdSchema,
157
+ mode: z.enum(['inventory', 'inspect', 'compare']).default('inventory'),
158
+ id: z.string().min(1).max(512).optional(),
159
+ scenarios: z.array(declaredConditionScenarioSchema).max(64).default([]),
160
+ proposedSources: z.array(proposedAnalysisSourceSchema).min(1).max(64).optional(),
161
+ expectedRevision: z
162
+ .string()
163
+ .regex(/^[a-f0-9]{64}$/u)
164
+ .optional(),
165
+ refresh: z.boolean().default(false),
166
+ })
167
+ .strict()
168
+ .superRefine((input, context) => {
169
+ const ids = new Set();
170
+ for (const [index, scenario] of input.scenarios.entries()) {
171
+ if (ids.has(scenario.id))
172
+ context.addIssue({
173
+ code: 'custom',
174
+ path: ['scenarios', index, 'id'],
175
+ message: 'Scenario IDs must be unique',
176
+ });
177
+ ids.add(scenario.id);
178
+ }
179
+ if (input.mode === 'inventory') {
180
+ if (input.proposedSources !== undefined)
181
+ context.addIssue({
182
+ code: 'custom',
183
+ path: ['proposedSources'],
184
+ message: 'Inventory mode does not accept proposed sources',
185
+ });
186
+ return;
187
+ }
188
+ if (input.id === undefined)
189
+ context.addIssue({
190
+ code: 'custom',
191
+ path: ['id'],
192
+ message: 'Decision inspection requires an identifier',
193
+ });
194
+ if (input.scenarios.length === 0)
195
+ context.addIssue({
196
+ code: 'custom',
197
+ path: ['scenarios'],
198
+ message: 'Decision inspection requires declared scenarios',
199
+ });
200
+ if (input.mode === 'compare' && input.proposedSources === undefined)
201
+ context.addIssue({
202
+ code: 'custom',
203
+ path: ['proposedSources'],
204
+ message: 'Decision comparison requires proposed sources',
205
+ });
206
+ if (input.mode === 'inspect' && input.proposedSources !== undefined)
207
+ context.addIssue({
208
+ code: 'custom',
209
+ path: ['proposedSources'],
210
+ message: 'Use compare mode for proposed sources',
211
+ });
212
+ });
213
+ /** A reference projection of future scenario suites; Stage 3 reads but never executes it. */
214
+ export const impactScenarioSuiteReferenceSchema = z
215
+ .object({
216
+ schemaVersion: z.literal('1.0'),
217
+ id: z.string().min(1).max(512),
218
+ cases: z
219
+ .array(z
220
+ .object({
221
+ id: z.string().min(1).max(512),
222
+ sourceSelectors: z.array(impactSymbolSelectorSchema).max(64).default([]),
223
+ sourceFiles: z.array(sourceDisplayPathSchema).max(64).default([]),
224
+ })
225
+ .loose())
226
+ .max(1_000),
227
+ })
228
+ .loose();
@@ -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);