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.
- package/CHANGELOG.md +32 -1
- package/README.md +10 -2
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +16 -0
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/impact-comparison.js +49 -0
- package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
- package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +202 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +64 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +387 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/decision/ai.js +104 -0
- package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
- package/dist/hoi4_agent_tools/decision/cost.js +177 -0
- package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
- package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
- package/dist/hoi4_agent_tools/decision/service.js +172 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
- package/dist/hoi4_agent_tools/impact/service.js +127 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +18 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +45 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +16 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +65 -0
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +3 -0
- package/docs/analysis.md +100 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +86 -0
- package/docs/technology.md +6 -2
- package/package.json +15 -6
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +290 -0
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/impact-inspect-input.schema.json +203 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { canonicalJson } from './canonical.js';
|
|
3
|
+
import { ServiceError, MAX_INLINE_ARTIFACT_LINKS } from './result.js';
|
|
4
|
+
export { MAX_INLINE_ARTIFACT_LINKS } from './result.js';
|
|
5
|
+
import { diagnosticSchema } from '../schemas/common.js';
|
|
6
|
+
import { artifactLinkSchema, validationSummarySchema } from '../schemas/transaction.js';
|
|
7
|
+
export const MAX_INLINE_FILES_SCANNED = 64;
|
|
8
|
+
export const MAX_INLINE_PROPOSED_FILES = 64;
|
|
9
|
+
export const MAX_INLINE_CHANGED_FILES = 64;
|
|
10
|
+
export const MAX_INLINE_DIAGNOSTICS = 20;
|
|
11
|
+
export const MAX_INLINE_BLOCKERS = 20;
|
|
12
|
+
export const MAX_INLINE_VALIDATION_CHECKS = 5;
|
|
13
|
+
// Leave headroom for the enclosing JSON-RPC response and transport metadata.
|
|
14
|
+
export const MAX_TOOL_RESULT_BYTES = 32_768;
|
|
15
|
+
const deferredFilesDiagnostic = Symbol('deferredFilesDiagnostic');
|
|
16
|
+
const operationResultBaseSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
status: z.enum(['ok', 'blocked', 'error']),
|
|
19
|
+
code: z.string().max(256),
|
|
20
|
+
workspaceId: z.string().max(64),
|
|
21
|
+
filesScanned: z.array(z.string().max(4096)).max(MAX_INLINE_FILES_SCANNED),
|
|
22
|
+
proposedFiles: z.array(z.string().max(1024)).max(MAX_INLINE_PROPOSED_FILES),
|
|
23
|
+
changedFiles: z.array(z.string().max(1024)).max(MAX_INLINE_CHANGED_FILES),
|
|
24
|
+
diagnostics: z.array(diagnosticSchema).max(MAX_INLINE_DIAGNOSTICS),
|
|
25
|
+
artifacts: z.array(artifactLinkSchema).max(MAX_INLINE_ARTIFACT_LINKS),
|
|
26
|
+
validation: validationSummarySchema,
|
|
27
|
+
blockers: z
|
|
28
|
+
.array(z
|
|
29
|
+
.object({
|
|
30
|
+
code: z.string().max(256),
|
|
31
|
+
message: z.string().max(4096),
|
|
32
|
+
details: z.record(z.string().max(256), z.unknown()).optional(),
|
|
33
|
+
})
|
|
34
|
+
.strict())
|
|
35
|
+
.max(MAX_INLINE_BLOCKERS),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
export const operationResultSchema = operationResultBaseSchema
|
|
39
|
+
.extend({ data: z.record(z.string().max(256), z.unknown()) })
|
|
40
|
+
.strict();
|
|
41
|
+
const emptyToolDataSchema = z.object({}).strict();
|
|
42
|
+
const compactPublicOperationResultSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
status: z.enum(['ok', 'blocked', 'error']),
|
|
45
|
+
code: z.string(),
|
|
46
|
+
workspaceId: z.string(),
|
|
47
|
+
filesScanned: z.array(z.unknown()),
|
|
48
|
+
proposedFiles: z.array(z.unknown()),
|
|
49
|
+
changedFiles: z.array(z.unknown()),
|
|
50
|
+
diagnostics: z.array(z.unknown()).max(MAX_INLINE_DIAGNOSTICS),
|
|
51
|
+
artifacts: z.array(z.unknown()).max(MAX_INLINE_ARTIFACT_LINKS),
|
|
52
|
+
validation: z.unknown(),
|
|
53
|
+
blockers: z.array(z.unknown()),
|
|
54
|
+
data: z.object({}).catchall(z.unknown()),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
/**
|
|
58
|
+
* Advertise one compact operation envelope while retaining exact per-tool runtime validation.
|
|
59
|
+
* The generic operation-result schema remains available for stored envelopes.
|
|
60
|
+
*/
|
|
61
|
+
export function strictOperationResultSchema(successDataSchema) {
|
|
62
|
+
const exactSchema = operationResultBaseSchema
|
|
63
|
+
.extend({ data: z.union([emptyToolDataSchema, successDataSchema]) })
|
|
64
|
+
.strict();
|
|
65
|
+
return compactPublicOperationResultSchema.superRefine((value, context) => {
|
|
66
|
+
const parsed = exactSchema.safeParse(value);
|
|
67
|
+
if (parsed.success)
|
|
68
|
+
return;
|
|
69
|
+
for (const issue of parsed.error.issues) {
|
|
70
|
+
context.addIssue({ code: 'custom', path: issue.path, message: issue.message });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Keep source inventories compact; complete inventories belong in linked resources. */
|
|
75
|
+
export function setInlineFilesScanned(result, files) {
|
|
76
|
+
result.filesScanned = files.slice(0, MAX_INLINE_FILES_SCANNED);
|
|
77
|
+
if (files.length <= MAX_INLINE_FILES_SCANNED)
|
|
78
|
+
return;
|
|
79
|
+
const truncation = {
|
|
80
|
+
code: 'MCP_INLINE_FILES_TRUNCATED',
|
|
81
|
+
severity: 'info',
|
|
82
|
+
category: 'configuration',
|
|
83
|
+
message: `Inline source inventory is limited to ${MAX_INLINE_FILES_SCANNED} paths; use the linked resource for the complete inventory`,
|
|
84
|
+
details: {
|
|
85
|
+
total: files.length,
|
|
86
|
+
returned: MAX_INLINE_FILES_SCANNED,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
Object.defineProperty(result, deferredFilesDiagnostic, {
|
|
90
|
+
value: truncation,
|
|
91
|
+
enumerable: false,
|
|
92
|
+
configurable: true,
|
|
93
|
+
});
|
|
94
|
+
result.diagnostics = [truncation, ...result.diagnostics].slice(0, MAX_INLINE_DIAGNOSTICS);
|
|
95
|
+
}
|
|
96
|
+
function preserveDeferredFilesDiagnostic(result) {
|
|
97
|
+
const deferred = result[deferredFilesDiagnostic];
|
|
98
|
+
if (deferred === undefined || result.diagnostics.some(({ code }) => code === deferred.code)) {
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...result,
|
|
103
|
+
diagnostics: [deferred, ...result.diagnostics].slice(0, MAX_INLINE_DIAGNOSTICS),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function boundedResult(result, actualBytes) {
|
|
107
|
+
const hasLinkedEvidence = result.artifacts.length > 0;
|
|
108
|
+
const truncationDiagnostic = {
|
|
109
|
+
code: 'MCP_RESPONSE_TRUNCATED',
|
|
110
|
+
severity: 'warning',
|
|
111
|
+
category: 'configuration',
|
|
112
|
+
message: hasLinkedEvidence
|
|
113
|
+
? 'Tool output exceeded the wire budget; inspect the linked evidence for complete details'
|
|
114
|
+
: 'Tool output exceeded the wire budget; nonessential inline details were omitted',
|
|
115
|
+
details: { actualBytes, maxBytes: MAX_TOOL_RESULT_BYTES },
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
status: result.status,
|
|
119
|
+
code: result.code.slice(0, 256),
|
|
120
|
+
workspaceId: result.workspaceId.slice(0, 64),
|
|
121
|
+
filesScanned: [],
|
|
122
|
+
proposedFiles: [],
|
|
123
|
+
changedFiles: [],
|
|
124
|
+
diagnostics: [truncationDiagnostic],
|
|
125
|
+
artifacts: result.artifacts
|
|
126
|
+
.slice(0, 1)
|
|
127
|
+
.map(({ description: _description, ...artifact }) => artifact),
|
|
128
|
+
validation: { passed: result.validation.passed, checks: [] },
|
|
129
|
+
blockers: result.status === 'ok'
|
|
130
|
+
? []
|
|
131
|
+
: [
|
|
132
|
+
{
|
|
133
|
+
code: 'MCP_RESPONSE_TRUNCATED',
|
|
134
|
+
message: hasLinkedEvidence
|
|
135
|
+
? 'Tool output exceeded the wire budget; inspect the linked evidence for complete details'
|
|
136
|
+
: 'Tool output exceeded the wire budget; nonessential inline details were omitted',
|
|
137
|
+
details: {
|
|
138
|
+
actualBytes,
|
|
139
|
+
maxBytes: MAX_TOOL_RESULT_BYTES,
|
|
140
|
+
artifactLinksTotal: result.artifacts.length,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
data: {},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function jsonStringBytes(value) {
|
|
148
|
+
let bytes = 2 + Buffer.byteLength(value, 'utf8');
|
|
149
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
150
|
+
const code = value.charCodeAt(index);
|
|
151
|
+
if (code === 0x22 || code === 0x5c)
|
|
152
|
+
bytes += 1;
|
|
153
|
+
else if (code <= 0x1f)
|
|
154
|
+
bytes += [0x08, 0x09, 0x0a, 0x0c, 0x0d].includes(code) ? 1 : 5;
|
|
155
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
156
|
+
const next = value.charCodeAt(index + 1);
|
|
157
|
+
if (next >= 0xdc00 && next <= 0xdfff)
|
|
158
|
+
index += 1;
|
|
159
|
+
else
|
|
160
|
+
bytes += 3;
|
|
161
|
+
}
|
|
162
|
+
else if (code >= 0xdc00 && code <= 0xdfff)
|
|
163
|
+
bytes += 3;
|
|
164
|
+
}
|
|
165
|
+
return bytes;
|
|
166
|
+
}
|
|
167
|
+
function jsonBytesWithin(value, budget) {
|
|
168
|
+
let bytes = 0;
|
|
169
|
+
const seen = new WeakSet();
|
|
170
|
+
const maximumDepth = 128;
|
|
171
|
+
const add = (count) => {
|
|
172
|
+
bytes += count;
|
|
173
|
+
return bytes <= budget;
|
|
174
|
+
};
|
|
175
|
+
const visit = (candidate, depth = 0) => {
|
|
176
|
+
if (depth > maximumDepth)
|
|
177
|
+
return false;
|
|
178
|
+
if (candidate === null)
|
|
179
|
+
return add(4);
|
|
180
|
+
if (typeof candidate === 'string')
|
|
181
|
+
return add(jsonStringBytes(candidate));
|
|
182
|
+
if (typeof candidate === 'number') {
|
|
183
|
+
if (!Number.isFinite(candidate))
|
|
184
|
+
return false;
|
|
185
|
+
return add(String(Object.is(candidate, -0) ? 0 : candidate).length);
|
|
186
|
+
}
|
|
187
|
+
if (typeof candidate === 'boolean')
|
|
188
|
+
return add(candidate ? 4 : 5);
|
|
189
|
+
if (Array.isArray(candidate)) {
|
|
190
|
+
if (seen.has(candidate))
|
|
191
|
+
return false;
|
|
192
|
+
seen.add(candidate);
|
|
193
|
+
try {
|
|
194
|
+
if (!add(2 + Math.max(0, candidate.length - 1)))
|
|
195
|
+
return false;
|
|
196
|
+
return candidate.every((entry) => visit(entry, depth + 1));
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
seen.delete(candidate);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (typeof candidate === 'object') {
|
|
203
|
+
const object = candidate;
|
|
204
|
+
if (seen.has(object))
|
|
205
|
+
return false;
|
|
206
|
+
seen.add(object);
|
|
207
|
+
try {
|
|
208
|
+
const entries = Object.entries(object).filter(([, entry]) => entry !== undefined);
|
|
209
|
+
if (!add(2 + Math.max(0, entries.length - 1)))
|
|
210
|
+
return false;
|
|
211
|
+
for (const [key, entry] of entries) {
|
|
212
|
+
if (!add(jsonStringBytes(key) + 1) || !visit(entry, depth + 1))
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
seen.delete(object);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
};
|
|
223
|
+
return visit(value);
|
|
224
|
+
}
|
|
225
|
+
function compactDetails(details) {
|
|
226
|
+
return jsonBytesWithin(details, 2_048) ? details : { truncated: true };
|
|
227
|
+
}
|
|
228
|
+
function compactDescriptions(result) {
|
|
229
|
+
const compactLocation = (location) => location === undefined
|
|
230
|
+
? undefined
|
|
231
|
+
: {
|
|
232
|
+
...location,
|
|
233
|
+
path: location.path.slice(0, 1_024),
|
|
234
|
+
...(location.symbol === undefined ? {} : { symbol: location.symbol.slice(0, 256) }),
|
|
235
|
+
};
|
|
236
|
+
const collectionTotals = {
|
|
237
|
+
filesScanned: result.filesScanned.length,
|
|
238
|
+
proposedFiles: result.proposedFiles.length,
|
|
239
|
+
changedFiles: result.changedFiles.length,
|
|
240
|
+
diagnostics: result.diagnostics.length,
|
|
241
|
+
artifacts: result.artifacts.length,
|
|
242
|
+
validationChecks: result.validation.checks.length,
|
|
243
|
+
blockers: result.blockers.length,
|
|
244
|
+
};
|
|
245
|
+
const collectionLimits = {
|
|
246
|
+
filesScanned: MAX_INLINE_FILES_SCANNED,
|
|
247
|
+
proposedFiles: MAX_INLINE_PROPOSED_FILES,
|
|
248
|
+
changedFiles: MAX_INLINE_CHANGED_FILES,
|
|
249
|
+
diagnostics: MAX_INLINE_DIAGNOSTICS,
|
|
250
|
+
artifacts: MAX_INLINE_ARTIFACT_LINKS,
|
|
251
|
+
validationChecks: MAX_INLINE_VALIDATION_CHECKS,
|
|
252
|
+
blockers: MAX_INLINE_BLOCKERS,
|
|
253
|
+
};
|
|
254
|
+
const truncatedCollections = Object.fromEntries(Object.entries(collectionTotals).flatMap(([name, total]) => {
|
|
255
|
+
const limit = collectionLimits[name];
|
|
256
|
+
return total > limit ? [[name, { total, limit }]] : [];
|
|
257
|
+
}));
|
|
258
|
+
if (Object.keys(truncatedCollections).length > 0 &&
|
|
259
|
+
result.diagnostics.length > MAX_INLINE_DIAGNOSTICS - 1) {
|
|
260
|
+
truncatedCollections.diagnostics = {
|
|
261
|
+
total: result.diagnostics.length,
|
|
262
|
+
limit: MAX_INLINE_DIAGNOSTICS - 1,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const needsTruncationDiagnostic = Object.keys(truncatedCollections).length > 0;
|
|
266
|
+
const diagnosticSlots = Math.max(0, MAX_INLINE_DIAGNOSTICS - (needsTruncationDiagnostic ? 1 : 0));
|
|
267
|
+
const hardDiagnostics = result.diagnostics.filter(({ severity }) => severity === 'error' || severity === 'blocker');
|
|
268
|
+
const priorityDiagnostics = result.diagnostics.filter(({ code, severity }) => severity === 'error' || severity === 'blocker' || code === 'MCP_INLINE_FILES_TRUNCATED');
|
|
269
|
+
const otherDiagnostics = result.diagnostics.filter((diagnostic) => !priorityDiagnostics.includes(diagnostic));
|
|
270
|
+
const retainedPriorityDiagnostics = priorityDiagnostics.slice(0, diagnosticSlots);
|
|
271
|
+
const retainedDiagnostics = [
|
|
272
|
+
...retainedPriorityDiagnostics,
|
|
273
|
+
...otherDiagnostics.slice(0, diagnosticSlots - retainedPriorityDiagnostics.length),
|
|
274
|
+
];
|
|
275
|
+
const omittedHardDiagnostics = hardDiagnostics.filter((diagnostic) => !retainedDiagnostics.includes(diagnostic)).length;
|
|
276
|
+
const compactDiagnostics = retainedDiagnostics.map((diagnostic) => ({
|
|
277
|
+
...diagnostic,
|
|
278
|
+
code: diagnostic.code.slice(0, 256),
|
|
279
|
+
message: diagnostic.message.slice(0, 1_024),
|
|
280
|
+
...(diagnostic.operationId === undefined
|
|
281
|
+
? {}
|
|
282
|
+
: { operationId: diagnostic.operationId.slice(0, 256) }),
|
|
283
|
+
...(diagnostic.location === undefined
|
|
284
|
+
? {}
|
|
285
|
+
: { location: compactLocation(diagnostic.location) }),
|
|
286
|
+
...(diagnostic.related === undefined
|
|
287
|
+
? {}
|
|
288
|
+
: {
|
|
289
|
+
related: diagnostic.related.slice(0, 3).map((location) => compactLocation(location)),
|
|
290
|
+
}),
|
|
291
|
+
...(diagnostic.details === undefined ? {} : { details: compactDetails(diagnostic.details) }),
|
|
292
|
+
}));
|
|
293
|
+
const truncationDiagnostic = needsTruncationDiagnostic
|
|
294
|
+
? {
|
|
295
|
+
code: 'MCP_INLINE_COLLECTIONS_TRUNCATED',
|
|
296
|
+
severity: omittedHardDiagnostics > 0 ? 'blocker' : 'info',
|
|
297
|
+
category: 'configuration',
|
|
298
|
+
message: result.artifacts.length > 0
|
|
299
|
+
? 'Large collections were bounded inline; linked resources retain bulk evidence'
|
|
300
|
+
: 'Large collections were bounded inline',
|
|
301
|
+
details: {
|
|
302
|
+
collections: truncatedCollections,
|
|
303
|
+
diagnosticsReturned: retainedDiagnostics.length,
|
|
304
|
+
omittedHardDiagnostics,
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: undefined;
|
|
308
|
+
return {
|
|
309
|
+
...result,
|
|
310
|
+
filesScanned: result.filesScanned.slice(0, MAX_INLINE_FILES_SCANNED),
|
|
311
|
+
proposedFiles: result.proposedFiles.slice(0, MAX_INLINE_PROPOSED_FILES),
|
|
312
|
+
changedFiles: result.changedFiles.slice(0, MAX_INLINE_CHANGED_FILES),
|
|
313
|
+
diagnostics: truncationDiagnostic === undefined
|
|
314
|
+
? compactDiagnostics
|
|
315
|
+
: [truncationDiagnostic, ...compactDiagnostics],
|
|
316
|
+
artifacts: result.artifacts.slice(0, MAX_INLINE_ARTIFACT_LINKS).map((artifact) => ({
|
|
317
|
+
...artifact,
|
|
318
|
+
...(artifact.description === undefined
|
|
319
|
+
? {}
|
|
320
|
+
: { description: artifact.description.slice(0, 256) }),
|
|
321
|
+
})),
|
|
322
|
+
validation: {
|
|
323
|
+
...result.validation,
|
|
324
|
+
checks: result.validation.checks.slice(0, MAX_INLINE_VALIDATION_CHECKS).map((check) => ({
|
|
325
|
+
...check,
|
|
326
|
+
id: check.id.slice(0, 256),
|
|
327
|
+
message: check.message.slice(0, 1_024),
|
|
328
|
+
})),
|
|
329
|
+
},
|
|
330
|
+
blockers: result.blockers.slice(0, MAX_INLINE_BLOCKERS).map((blocker) => ({
|
|
331
|
+
...blocker,
|
|
332
|
+
code: blocker.code.slice(0, 256),
|
|
333
|
+
message: blocker.message.slice(0, 1_024),
|
|
334
|
+
...(blocker.details === undefined ? {} : { details: compactDetails(blocker.details) }),
|
|
335
|
+
})),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function buildToolResult(selected) {
|
|
339
|
+
const structuredContent = operationResultSchema.parse(selected);
|
|
340
|
+
const links = selected.artifacts.map((artifact) => ({
|
|
341
|
+
type: 'resource_link',
|
|
342
|
+
uri: artifact.uri,
|
|
343
|
+
name: artifact.name,
|
|
344
|
+
mimeType: artifact.mimeType,
|
|
345
|
+
...(artifact.description === undefined ? {} : { description: artifact.description }),
|
|
346
|
+
...(artifact.size === undefined ? {} : { size: artifact.size }),
|
|
347
|
+
}));
|
|
348
|
+
const summary = canonicalJson({
|
|
349
|
+
status: selected.status,
|
|
350
|
+
code: selected.code,
|
|
351
|
+
workspaceId: selected.workspaceId,
|
|
352
|
+
artifactCount: selected.artifacts.length,
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: 'text', text: summary }, ...links],
|
|
356
|
+
structuredContent,
|
|
357
|
+
...(selected.status === 'error' ? { isError: true } : {}),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
export function toolResult(result) {
|
|
361
|
+
const completeResult = preserveDeferredFilesDiagnostic(result);
|
|
362
|
+
const compact = compactDescriptions(completeResult);
|
|
363
|
+
const estimatedBytes = jsonBytesWithin(compact, MAX_TOOL_RESULT_BYTES)
|
|
364
|
+
? Buffer.byteLength(canonicalJson(compact), 'utf8')
|
|
365
|
+
: MAX_TOOL_RESULT_BYTES + 1;
|
|
366
|
+
const selected = estimatedBytes > MAX_TOOL_RESULT_BYTES ? boundedResult(compact, estimatedBytes) : compact;
|
|
367
|
+
let output = buildToolResult(selected);
|
|
368
|
+
const wireBytes = Buffer.byteLength(canonicalJson(output), 'utf8');
|
|
369
|
+
if (wireBytes > MAX_TOOL_RESULT_BYTES) {
|
|
370
|
+
output = buildToolResult(boundedResult(compact, wireBytes));
|
|
371
|
+
}
|
|
372
|
+
const finalBytes = Buffer.byteLength(canonicalJson(output), 'utf8');
|
|
373
|
+
if (finalBytes > MAX_TOOL_RESULT_BYTES) {
|
|
374
|
+
throw new Error('Constant MCP response-limit result exceeded its wire budget');
|
|
375
|
+
}
|
|
376
|
+
return output;
|
|
377
|
+
}
|
|
378
|
+
export function errorResult(error, workspaceId = '', context = {}) {
|
|
379
|
+
const serviceError = error instanceof ServiceError
|
|
380
|
+
? error
|
|
381
|
+
: new ServiceError('INTERNAL_ERROR', 'Unexpected internal error');
|
|
382
|
+
const internalWriteError = serviceError.code.startsWith('TRANSACTION_');
|
|
383
|
+
const publicCode = internalWriteError
|
|
384
|
+
? `REWRITE_${serviceError.code.slice('TRANSACTION_'.length)}`
|
|
385
|
+
: serviceError.code;
|
|
386
|
+
const publicMessage = internalWriteError
|
|
387
|
+
? 'Rewrite could not be completed'
|
|
388
|
+
: serviceError.message;
|
|
389
|
+
const publicDetails = internalWriteError
|
|
390
|
+
? {}
|
|
391
|
+
: Object.fromEntries(Object.entries(serviceError.details).filter(([key]) => ![
|
|
392
|
+
'cause',
|
|
393
|
+
'stack',
|
|
394
|
+
'absolutePath',
|
|
395
|
+
'transactionId',
|
|
396
|
+
'planHash',
|
|
397
|
+
'rollbackStatus',
|
|
398
|
+
].includes(key)));
|
|
399
|
+
return toolResult({
|
|
400
|
+
status: publicCode.includes('BLOCKED') ? 'blocked' : 'error',
|
|
401
|
+
code: publicCode,
|
|
402
|
+
workspaceId,
|
|
403
|
+
filesScanned: [],
|
|
404
|
+
proposedFiles: context.proposedFiles ?? [],
|
|
405
|
+
changedFiles: context.changedFiles ?? [],
|
|
406
|
+
diagnostics: [],
|
|
407
|
+
artifacts: context.artifacts ?? [],
|
|
408
|
+
validation: context.validation ?? { passed: false, checks: [] },
|
|
409
|
+
blockers: [{ code: publicCode, message: publicMessage, details: publicDetails }],
|
|
410
|
+
data: context.data ?? {},
|
|
411
|
+
});
|
|
412
|
+
}
|