hoi4-agent-tools 3.0.9 → 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 (124) hide show
  1. package/CHANGELOG.md +23 -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/dependency-pages.js +216 -0
  8. package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
  9. package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
  10. package/dist/hoi4_agent_tools/core/engine.js +48 -5
  11. package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
  12. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  13. package/dist/hoi4_agent_tools/core/index.js +47 -4
  14. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  15. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  16. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  17. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  18. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  19. package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
  20. package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
  21. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  22. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  23. package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
  24. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  25. package/dist/hoi4_agent_tools/core/result.js +1 -0
  26. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  27. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  28. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
  29. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  30. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  31. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  32. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  33. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  34. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  35. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  36. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  37. package/dist/hoi4_agent_tools/event/service.js +67 -15
  38. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  39. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  40. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  41. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  42. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  43. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  44. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  45. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  46. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  47. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  48. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  49. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  50. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  51. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  52. package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
  53. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  54. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  55. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  56. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  57. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  58. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
  59. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  60. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  61. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
  62. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  63. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  64. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  65. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  66. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  67. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  68. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  69. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  70. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  71. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  72. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  73. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  74. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  75. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  76. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  77. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  78. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  79. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  80. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  81. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  82. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  83. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  84. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  85. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  86. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  87. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  88. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  89. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  90. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  91. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  92. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  93. package/dist/hoi4_agent_tools/version.js +1 -1
  94. package/docs/README.md +2 -0
  95. package/docs/development.md +2 -0
  96. package/docs/events.md +7 -2
  97. package/docs/helper-expansion.md +78 -0
  98. package/docs/http.md +2 -0
  99. package/docs/jobs.md +85 -0
  100. package/docs/technology.md +6 -2
  101. package/package.json +11 -5
  102. package/schemas/configuration.schema.json +1 -1
  103. package/schemas/continuous-focus-palette.schema.json +1 -1
  104. package/schemas/custom-weighted-pool.schema.json +1 -1
  105. package/schemas/event-feature-manifest.schema.json +1 -1
  106. package/schemas/focus-plan.schema.json +1 -1
  107. package/schemas/focus-planning-sidecar.schema.json +1 -1
  108. package/schemas/gui-animation-source.schema.json +1 -1
  109. package/schemas/gui-helper.schema.json +1 -1
  110. package/schemas/gui-scenario.schema.json +1 -1
  111. package/schemas/helper-expansion-request.schema.json +44 -0
  112. package/schemas/helper-expansion-summary.schema.json +70 -0
  113. package/schemas/map-operation.schema.json +1 -1
  114. package/schemas/operation-result.schema.json +1 -1
  115. package/schemas/probability-analysis-result.schema.json +1 -1
  116. package/schemas/probability-compare-input.schema.json +1 -1
  117. package/schemas/probability-evaluate-input.schema.json +1 -1
  118. package/schemas/probability-inspect-input.schema.json +1 -1
  119. package/schemas/probability-render-input.schema.json +1 -1
  120. package/schemas/probability-scenario-set.schema.json +1 -1
  121. package/schemas/probability-sequence-input.schema.json +1 -1
  122. package/schemas/probability-simulate-input.schema.json +1 -1
  123. package/schemas/probability-sweep-input.schema.json +1 -1
  124. package/server.json +2 -2
@@ -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
+ }