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
@@ -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
+ }
@@ -1,98 +1,5 @@
1
- function caseInsensitiveEntry(record, key) {
2
- if (record === undefined)
3
- return undefined;
4
- if (Object.hasOwn(record, key))
5
- return record[key];
6
- const normalized = key.toLowerCase();
7
- const found = Object.entries(record).find(([candidate]) => candidate.toLowerCase() === normalized);
8
- return found?.[1];
9
- }
10
- export function rootScopeContext(scenario) {
11
- return {
12
- expression: 'ROOT',
13
- binding: {
14
- id: scenario.actor ?? 'ROOT',
15
- ...(scenario.actor === undefined ? {} : { actor: scenario.actor }),
16
- state: scenario.state,
17
- ...(scenario.flags === undefined ? {} : { flags: scenario.flags }),
18
- ...(scenario.eventTargets === undefined ? {} : { eventTargets: scenario.eventTargets }),
19
- },
20
- };
21
- }
22
- function explicitScope(scenario, expression, parent) {
23
- const binding = caseInsensitiveEntry(scenario.scopes, expression);
24
- return binding === undefined ? undefined : { expression, binding, parent };
25
- }
26
- export function resolveScopeContext(scenario, expression, current) {
27
- const upper = expression.toUpperCase();
28
- if (upper === 'ROOT')
29
- return rootScopeContext(scenario);
30
- if (upper === 'THIS')
31
- return current;
32
- if (upper === 'PREV')
33
- return current.parent ?? rootScopeContext(scenario);
34
- const chained = current.expression === 'ROOT' || current.expression === 'THIS'
35
- ? expression
36
- : `${current.expression}.${expression}`;
37
- const chainedBinding = explicitScope(scenario, chained, current);
38
- if (chainedBinding !== undefined)
39
- return chainedBinding;
40
- const direct = explicitScope(scenario, expression, current);
41
- if (direct !== undefined)
42
- return direct;
43
- const targetMatch = /^(?:event_target|scope):(.+)$/iu.exec(expression);
44
- const targetId = targetMatch === null
45
- ? undefined
46
- : (caseInsensitiveEntry(current.binding.eventTargets, targetMatch[1]) ??
47
- caseInsensitiveEntry(scenario.eventTargets, targetMatch[1]));
48
- if (targetId !== undefined) {
49
- const declaredBinding = Object.values(scenario.scopes ?? {}).find((binding) => binding.id === targetId || binding.actor === targetId);
50
- return {
51
- expression,
52
- binding: declaredBinding ?? { id: targetId, actor: targetId, state: {} },
53
- parent: current,
54
- };
55
- }
56
- return undefined;
57
- }
58
- export function scopeStateValue(scenario, key, current) {
59
- if (Object.hasOwn(current.binding.state, key))
60
- return current.binding.state[key];
61
- if (Object.hasOwn(current.binding.state, `trigger.${key}`))
62
- return current.binding.state[`trigger.${key}`];
63
- if (current.expression !== 'ROOT')
64
- return undefined;
65
- if (Object.hasOwn(scenario.state, key))
66
- return scenario.state[key];
67
- if (Object.hasOwn(scenario.state, `trigger.${key}`))
68
- return scenario.state[`trigger.${key}`];
69
- return undefined;
70
- }
71
- export function rootStateValue(scenario, key) {
72
- if (Object.hasOwn(scenario.state, key))
73
- return scenario.state[key];
74
- if (Object.hasOwn(scenario.state, `trigger.${key}`))
75
- return scenario.state[`trigger.${key}`];
76
- return undefined;
77
- }
78
- export function scopeIdentity(current) {
79
- return current.binding.actor ?? current.binding.id;
80
- }
81
- export function scopedExpressionValue(scenario, expression) {
82
- const scopes = Object.entries(scenario.scopes ?? {}).sort(([left], [right]) => right.length - left.length);
83
- for (const [scope, binding] of scopes) {
84
- for (const prefix of [`${scope}.`, `scope.${scope}.`, `scopes.${scope}.`]) {
85
- if (!expression.toLowerCase().startsWith(prefix.toLowerCase()))
86
- continue;
87
- const key = expression.slice(prefix.length).replace(/^state\./u, '');
88
- if (Object.hasOwn(binding.state, key))
89
- return binding.state[key];
90
- if (Object.hasOwn(binding.state, `variable.${key}`))
91
- return binding.state[`variable.${key}`];
92
- }
93
- }
94
- return undefined;
95
- }
1
+ import { rootStateValue, scopedExpressionValue } from '../core/scenario-state.js';
2
+ export { rootScopeContext, resolveScopeContext, scopeStateValue, rootStateValue, scopeIdentity, scopedExpressionValue, } from '../core/scenario-state.js';
96
3
  function updateScopeState(scenario, path, value) {
97
4
  const scopes = Object.entries(scenario.scopes ?? {}).sort(([left], [right]) => right.length - left.length);
98
5
  for (const [scope, binding] of scopes) {
@@ -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 &&