@tiwater/office-mcp 0.21.34 → 0.21.35
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/_shared/effect-kind.mjs +43 -0
- package/office/contracts/tiwater-office-provider-contract-manifest-v1.json +1 -1
- package/office/index.mjs +55 -37
- package/package.json +1 -1
- package/pdf/contracts/tiwater-pdf-provider-contract-manifest-v1.json +1 -1
- package/text/contracts/tiwater-text-provider-contract-manifest-v1.json +1 -1
package/_shared/effect-kind.mjs
CHANGED
|
@@ -70,6 +70,17 @@ export function assertEffectKindToolContract(tool, expectedKind = undefined) {
|
|
|
70
70
|
return metadata.kind;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export function documentMutationFileArguments(tool, args) {
|
|
74
|
+
assertEffectKindToolContract(tool, 'document-mutation');
|
|
75
|
+
const bindings = boundFileArguments(tool?.inputSchema, args);
|
|
76
|
+
const current = bindings.filter(binding => binding.revisionRole === 'current');
|
|
77
|
+
const effectiveOutput = bindings.filter(binding => binding.role === 'write' && binding.effect);
|
|
78
|
+
if (current.length !== 1 || effectiveOutput.length !== 1) {
|
|
79
|
+
throw new Error(`document-mutation-file-arguments-invalid:${tool?.name || 'unnamed'}`);
|
|
80
|
+
}
|
|
81
|
+
return { current: current[0].value, effectiveOutput: effectiveOutput[0].value };
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
function fileBindings(schema) {
|
|
74
85
|
const bindings = [];
|
|
75
86
|
function visit(node) {
|
|
@@ -91,3 +102,35 @@ function fileBindings(schema) {
|
|
|
91
102
|
visit(schema);
|
|
92
103
|
return bindings;
|
|
93
104
|
}
|
|
105
|
+
|
|
106
|
+
function boundFileArguments(schema, value) {
|
|
107
|
+
const bindings = [];
|
|
108
|
+
function visit(node, currentValue) {
|
|
109
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return;
|
|
110
|
+
if (node['x-tiwater-file-role'] === 'read' || node['x-tiwater-file-role'] === 'write') {
|
|
111
|
+
if (typeof currentValue !== 'string' || currentValue.length === 0) {
|
|
112
|
+
throw new Error('provider-file-argument-invalid');
|
|
113
|
+
}
|
|
114
|
+
bindings.push({
|
|
115
|
+
role: node['x-tiwater-file-role'],
|
|
116
|
+
effect: node['x-tiwater-file-role'] === 'write'
|
|
117
|
+
&& node['x-tiwater-file-effect'] !== false,
|
|
118
|
+
revisionRole: node[documentRevisionRoleKey],
|
|
119
|
+
value: currentValue,
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(currentValue)) {
|
|
124
|
+
for (const entry of currentValue) visit(node.items, entry);
|
|
125
|
+
} else if (currentValue && typeof currentValue === 'object') {
|
|
126
|
+
for (const [name, child] of Object.entries(node.properties || {})) {
|
|
127
|
+
if (Object.hasOwn(currentValue, name)) visit(child, currentValue[name]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const keyword of ['allOf', 'anyOf', 'oneOf']) {
|
|
131
|
+
for (const child of node[keyword] || []) visit(child, currentValue);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
visit(schema, value);
|
|
135
|
+
return bindings;
|
|
136
|
+
}
|
package/office/index.mjs
CHANGED
|
@@ -24,7 +24,10 @@ import {
|
|
|
24
24
|
} from '../_shared/large-json-result.mjs';
|
|
25
25
|
import { withOutputWriteLock } from '../_shared/output-write-lock.mjs';
|
|
26
26
|
import { evidenceRoleMetadata } from '../_shared/evidence-role.mjs';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
documentMutationFileArguments,
|
|
29
|
+
effectKindMetadata,
|
|
30
|
+
} from '../_shared/effect-kind.mjs';
|
|
28
31
|
import { compactDocxObjectIdentity } from './docx-object-identity.mjs';
|
|
29
32
|
|
|
30
33
|
const packageMetadata = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
@@ -38,14 +41,20 @@ if (inputContractManifest.provider?.id !== packageMetadata.name
|
|
|
38
41
|
}
|
|
39
42
|
const inputContracts = new Map(await Promise.all(inputContractManifest.tools.map(async entry => {
|
|
40
43
|
const schema = JSON.parse(await readFile(new URL(`./contracts/${entry.name}.schema.json`, import.meta.url), 'utf8'));
|
|
41
|
-
return [entry.name, z.fromJSONSchema(schema)];
|
|
44
|
+
return [entry.name, { schema, validator: z.fromJSONSchema(schema) }];
|
|
42
45
|
})));
|
|
43
46
|
const invocationCwd = process.cwd();
|
|
44
47
|
|
|
45
48
|
function inputContract(toolName) {
|
|
46
49
|
const contract = inputContracts.get(toolName);
|
|
47
50
|
if (!contract) throw new Error(`Missing provider-owned MCP input contract: ${toolName}`);
|
|
48
|
-
return contract;
|
|
51
|
+
return contract.validator;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function inputContractSchema(toolName) {
|
|
55
|
+
const contract = inputContracts.get(toolName);
|
|
56
|
+
if (!contract) throw new Error(`Missing provider-owned MCP input contract: ${toolName}`);
|
|
57
|
+
return contract.schema;
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
const docxCandidates = [
|
|
@@ -151,14 +160,14 @@ const xlsxFixedTools = [
|
|
|
151
160
|
{"name":"xlsx_set_column_width","description":"Set current worksheet column widths."},
|
|
152
161
|
];
|
|
153
162
|
|
|
154
|
-
function fixedToolDefinitions(definitions) {
|
|
163
|
+
function fixedToolDefinitions(definitions, candidates) {
|
|
155
164
|
return definitions.map(definition => ({
|
|
156
165
|
name: definition.name,
|
|
157
166
|
effectKind: 'document-mutation',
|
|
158
167
|
description: definition.description,
|
|
159
168
|
inputSchema: inputContract(definition.name),
|
|
160
169
|
outputSchema: fixedEditOutput(definition.name),
|
|
161
|
-
handler: args => fixedEdit(
|
|
170
|
+
handler: (args, tool) => fixedEdit(tool, args, candidates),
|
|
162
171
|
}));
|
|
163
172
|
}
|
|
164
173
|
|
|
@@ -472,7 +481,7 @@ const tools = [
|
|
|
472
481
|
description: 'Replace existing target paragraph or table-cell content from explicitly selected native source cells, paragraphs, runs, text nodes, or exact text ranges while retaining target container formatting and table structure. For a source cell already returned by docx_read_table, select a Unicode-scalar range directly against its returned text; the provider retains every crossed native run, including superscript and subscript, without another descendant read. Consecutive run or text selections from one source paragraph form one target paragraph, and source paragraph boundaries remain paragraph boundaries. Use it after docx_fill_table_from_tables when the target needs selected source content instead of the whole source cell; pass returned addresses unchanged and never retype source-owned text. It does not copy source rows, cells, spans, or merges.',
|
|
473
482
|
inputSchema: inputContract('docx_replace_content_from_source'),
|
|
474
483
|
outputSchema: fixedEditOutput('docx_replace_content_from_source'),
|
|
475
|
-
handler: args => fixedEdit(
|
|
484
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
476
485
|
},
|
|
477
486
|
{
|
|
478
487
|
name: 'docx_set_text',
|
|
@@ -480,7 +489,7 @@ const tools = [
|
|
|
480
489
|
description: 'Replace the whole text content of paragraph or cell objects observed from this exact input DOCX while retaining target formatting, bookmarks, spans, and vertical merges. For a vertically merged logical cell, write its visible text to the restart cell rather than a continue cell. Tabs and line breaks remain native document text controls; targets containing non-text objects are rejected. Use this only for newly derived text. Content copied or selected from a source DOCX uses docx_replace_content_from_source so native runs such as superscript and subscript are retained. This does not insert objects, change table structure, copy source formatting, or decide business wording.',
|
|
481
490
|
inputSchema: inputContract('docx_set_text'),
|
|
482
491
|
outputSchema: fixedEditOutput('docx_set_text'),
|
|
483
|
-
handler: args => fixedEdit(
|
|
492
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
484
493
|
},
|
|
485
494
|
{
|
|
486
495
|
name: 'docx_set_paragraph_pagination',
|
|
@@ -488,7 +497,7 @@ const tools = [
|
|
|
488
497
|
description: 'Set native pagination properties on explicitly selected current DOCX paragraphs. Each change sets at least one pagination property. keepWithNext keeps a paragraph with the immediately following paragraph or table but does not guarantee that a table header remains with its first body row. keepLinesTogether keeps one paragraph on one page; pageBreakBefore starts it on a new page; preventWidowOrphanLines controls isolated first or last lines. Omitted properties remain unchanged. The caller chooses paragraphs from current native addresses; the provider does not decide document layout or business meaning.',
|
|
489
498
|
inputSchema: inputContract('docx_set_paragraph_pagination'),
|
|
490
499
|
outputSchema: fixedEditOutput('docx_set_paragraph_pagination'),
|
|
491
|
-
handler: args => fixedEdit(
|
|
500
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
492
501
|
},
|
|
493
502
|
{
|
|
494
503
|
name: 'docx_set_table_body',
|
|
@@ -496,7 +505,7 @@ const tools = [
|
|
|
496
505
|
description: 'Atomically replace one exact current target-table row range while retaining the table, target styles, grid widths, and all rows and surrounding content outside the range. When retaining leading rows reported with repeatHeader=true, existingRows starts after all of them and never at a verticalMerge=continue row. Name every target grid column in native order and choose one current row inside existingRows as the style prototype for each final row. Horizontal spans use contiguous column IDs. Set rowSpan on one logical cell to occupy multiple rows and omit those columns from the covered rows; the provider writes native vertical merge cells. Set cantSplit on a final physical row when it must remain whole across page boundaries; omit it to preserve the prototype row property. Every other grid column remains explicit. Every explicit cell includes an already-derived value; source-owned content is not retyped here. An empty final row array removes the range when another table row remains. The provider commits once and returns structural readback. It does not read source tables, map source to target, derive text, choose business columns, identify headers, or accept non-text cell content; use docx_fill_table_from_tables and docx_replace_content_from_source for source-owned table content.',
|
|
497
506
|
inputSchema: inputContract('docx_set_table_body'),
|
|
498
507
|
outputSchema: fixedEditOutput('docx_set_table_body'),
|
|
499
|
-
handler: args => fixedEdit(
|
|
508
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
500
509
|
},
|
|
501
510
|
{
|
|
502
511
|
name: 'docx_fill_table_from_tables',
|
|
@@ -504,7 +513,7 @@ const tools = [
|
|
|
504
513
|
description: 'Fill one current target-table body from one or more explicitly ordered current source-table row ranges. For each source, select an unmerged record column when every physical source row must remain an output row; mapped columns still retain their native vertical merges. Select a merged record column only when every mapping into one target cell has one equal scalar value throughout that merge group. Map source grid columns onto every target prototype cell. The provider concatenates source records in declared order, copies each mapped source cell in full, preserves horizontal spans, rebuilds vertical merges only within each source range, validates the complete target grid, commits once, and returns structural readback. It does not discover source tables, choose source or target business meaning, filter records, translate or rewrite text, or apply business-specific rules. A single source uses the same sources array with one item. If a target needs only selected source descendants, such as one language from a bilingual cell, immediately use the returned target-cell addresses with docx_replace_content_from_source before releasing that source or reading back the completed target.',
|
|
505
514
|
inputSchema: inputContract('docx_fill_table_from_tables'),
|
|
506
515
|
outputSchema: fixedEditOutput('docx_fill_table_from_tables'),
|
|
507
|
-
handler: args => fixedEdit(
|
|
516
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
508
517
|
},
|
|
509
518
|
{
|
|
510
519
|
name: 'docx_insert_objects',
|
|
@@ -512,7 +521,7 @@ const tools = [
|
|
|
512
521
|
description: 'Insert selected current DOCX objects under an existing parent. Table rows are objects: expand a target table by copying one contiguous observed row range and use repeat for count; sourceInput may equal input. A row range beginning with vertical-merge continuations may be inserted only inside a target boundary with the same active grid spans, which extends those merges. Individual table cells are not raw insertion targets because that would bypass the table grid.',
|
|
513
522
|
inputSchema: inputContract('docx_insert_objects'),
|
|
514
523
|
outputSchema: fixedEditOutput('docx_insert_objects'),
|
|
515
|
-
handler: args => fixedEdit(
|
|
524
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
516
525
|
},
|
|
517
526
|
{
|
|
518
527
|
name: 'docx_delete_object',
|
|
@@ -520,7 +529,7 @@ const tools = [
|
|
|
520
529
|
description: 'Delete selected current DOCX objects directly from the current target document. Selected table rows must close every vertical merge and cannot remove the whole table. Individual table cells are not raw deletion targets; use column or merge operations for table structure.',
|
|
521
530
|
inputSchema: inputContract('docx_delete_object'),
|
|
522
531
|
outputSchema: fixedEditOutput('docx_delete_object'),
|
|
523
|
-
handler: args => fixedEdit(
|
|
532
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
524
533
|
},
|
|
525
534
|
{
|
|
526
535
|
name: 'docx_merge_cells',
|
|
@@ -528,7 +537,7 @@ const tools = [
|
|
|
528
537
|
description: 'Merge selected current DOCX cells when they form one closed rectangle. A one-column, multi-row rectangle creates a vertical merge whose first cell is the restart owner and whose later cells are continuations. All selected cell content moves into the top-left owner, so the selected content must already be correct for that one logical cell.',
|
|
529
538
|
inputSchema: inputContract('docx_merge_cells'),
|
|
530
539
|
outputSchema: fixedEditOutput('docx_merge_cells'),
|
|
531
|
-
handler: args => fixedEdit(
|
|
540
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
532
541
|
},
|
|
533
542
|
{
|
|
534
543
|
name: 'docx_split_cells',
|
|
@@ -536,7 +545,7 @@ const tools = [
|
|
|
536
545
|
description: 'Split selected current DOCX merged cells.',
|
|
537
546
|
inputSchema: inputContract('docx_split_cells'),
|
|
538
547
|
outputSchema: fixedEditOutput('docx_split_cells'),
|
|
539
|
-
handler: args => fixedEdit(
|
|
548
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
540
549
|
},
|
|
541
550
|
{
|
|
542
551
|
name: 'docx_insert_table_columns',
|
|
@@ -544,7 +553,7 @@ const tools = [
|
|
|
544
553
|
description: 'Insert empty template-shaped grid columns into one current main-document table. Select an observed source grid column for width and per-row cell formatting, and optionally a before grid-column address; cells spanning the insertion boundary expand instead of being split. It does not copy business values or decide column meaning.',
|
|
545
554
|
inputSchema: inputContract('docx_insert_table_columns'),
|
|
546
555
|
outputSchema: fixedEditOutput('docx_insert_table_columns'),
|
|
547
|
-
handler: args => fixedEdit(
|
|
556
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
548
557
|
},
|
|
549
558
|
{
|
|
550
559
|
name: 'docx_delete_table_columns',
|
|
@@ -552,7 +561,7 @@ const tools = [
|
|
|
552
561
|
description: 'Delete selected observed grid columns from one current main-document table while shrinking spanning cells and preserving the remaining table grid. It cannot remove every column and does not decide whether a business column is unused.',
|
|
553
562
|
inputSchema: inputContract('docx_delete_table_columns'),
|
|
554
563
|
outputSchema: fixedEditOutput('docx_delete_table_columns'),
|
|
555
|
-
handler: args => fixedEdit(
|
|
564
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
556
565
|
},
|
|
557
566
|
{
|
|
558
567
|
name: 'docx_compare',
|
|
@@ -593,7 +602,7 @@ const tools = [
|
|
|
593
602
|
description: 'Apply one explicit font family and size policy to current main-document body and table text. It does not derive a policy or alter other run semantics.',
|
|
594
603
|
inputSchema: inputContract('docx_apply_font_policy'),
|
|
595
604
|
outputSchema: fixedEditOutput('docx_apply_font_policy'),
|
|
596
|
-
handler: args => fixedEdit(
|
|
605
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
597
606
|
},
|
|
598
607
|
{
|
|
599
608
|
name: 'docx_validate_toc_style_policy',
|
|
@@ -609,7 +618,7 @@ const tools = [
|
|
|
609
618
|
description: 'Apply explicit italic and per-level indentation values to current built-in table-of-contents paragraph styles. It does not change heading text or refresh fields.',
|
|
610
619
|
inputSchema: inputContract('docx_apply_toc_style_policy'),
|
|
611
620
|
outputSchema: fixedEditOutput('docx_apply_toc_style_policy'),
|
|
612
|
-
handler: args => fixedEdit(
|
|
621
|
+
handler: (args, tool) => fixedEdit(tool, args, docxCandidates),
|
|
613
622
|
},
|
|
614
623
|
{
|
|
615
624
|
name: 'docx_refresh_fields',
|
|
@@ -676,7 +685,7 @@ const tools = [
|
|
|
676
685
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
677
686
|
handler: xlsxReadRange,
|
|
678
687
|
},
|
|
679
|
-
...fixedToolDefinitions(xlsxFixedTools),
|
|
688
|
+
...fixedToolDefinitions(xlsxFixedTools, xlsxCandidates),
|
|
680
689
|
{
|
|
681
690
|
name: 'xlsx_validate',
|
|
682
691
|
description: 'Validate an XLSX workbook package and produce OpenXML validation evidence. Set returnContent true to return the complete result when it fits the response limit. Provide output to write the complete result to a new JSON file. The two choices are independent and may be used together; at least one is required.',
|
|
@@ -709,7 +718,7 @@ const tools = [
|
|
|
709
718
|
description: 'Apply one deterministic PPTX template-application plan to a current presentation. This tool executes the published plan; it does not select a template or derive business content, slide mappings, geometry, or formatting decisions.',
|
|
710
719
|
inputSchema: inputContract('pptx_apply_template'),
|
|
711
720
|
outputSchema: fixedEditOutput('pptx_apply_template'),
|
|
712
|
-
handler: args => fixedEdit(
|
|
721
|
+
handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
|
|
713
722
|
},
|
|
714
723
|
{
|
|
715
724
|
name: 'pptx_apply_format',
|
|
@@ -717,7 +726,7 @@ const tools = [
|
|
|
717
726
|
description: 'Apply one deterministic PPTX formatting plan to a current presentation. This tool executes published formatting operations; it does not derive values, coordinates, or business decisions.',
|
|
718
727
|
inputSchema: inputContract('pptx_apply_format'),
|
|
719
728
|
outputSchema: fixedEditOutput('pptx_apply_format'),
|
|
720
|
-
handler: args => fixedEdit(
|
|
729
|
+
handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
|
|
721
730
|
},
|
|
722
731
|
{
|
|
723
732
|
name: 'pptx_set_shape_geometry',
|
|
@@ -725,7 +734,7 @@ const tools = [
|
|
|
725
734
|
description: 'Set exact native EMU bounds for uniquely identified current-slide PPTX objects. One call batches only this fixed geometry action and does not infer repair coordinates.',
|
|
726
735
|
inputSchema: inputContract('pptx_set_shape_geometry'),
|
|
727
736
|
outputSchema: fixedEditOutput('pptx_set_shape_geometry'),
|
|
728
|
-
handler: args => fixedEdit(
|
|
737
|
+
handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
|
|
729
738
|
},
|
|
730
739
|
{
|
|
731
740
|
name: 'pptx_replace_picture_image',
|
|
@@ -733,7 +742,7 @@ const tools = [
|
|
|
733
742
|
description: 'Replace embedded PNG or JPEG media for uniquely identified current-slide PPTX pictures while preserving the picture object, geometry, crop, and unrelated media. One call batches only this fixed replacement action.',
|
|
734
743
|
inputSchema: inputContract('pptx_replace_picture_image'),
|
|
735
744
|
outputSchema: fixedEditOutput('pptx_replace_picture_image'),
|
|
736
|
-
handler: args => fixedEdit(
|
|
745
|
+
handler: (args, tool) => fixedEdit(tool, args, pptxCandidates),
|
|
737
746
|
},
|
|
738
747
|
{
|
|
739
748
|
name: 'pptx_validate',
|
|
@@ -769,8 +778,8 @@ function buildServer() {
|
|
|
769
778
|
},
|
|
770
779
|
async args => {
|
|
771
780
|
const payload = typeof args.output === 'string'
|
|
772
|
-
? await withOutputWriteLock(args.output, () => tool.handler(args))
|
|
773
|
-
: await tool.handler(args);
|
|
781
|
+
? await withOutputWriteLock(args.output, () => tool.handler(args, tool))
|
|
782
|
+
: await tool.handler(args, tool);
|
|
774
783
|
return createToolResult(payload, { isError: payload?.summary?.pass === false });
|
|
775
784
|
},
|
|
776
785
|
);
|
|
@@ -1165,28 +1174,37 @@ async function copyTransform(tool, candidates, command, args, suffix = []) {
|
|
|
1165
1174
|
}
|
|
1166
1175
|
}
|
|
1167
1176
|
|
|
1168
|
-
async function fixedEdit(tool, args) {
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1177
|
+
async function fixedEdit(tool, args, candidates) {
|
|
1178
|
+
const publishedContract = {
|
|
1179
|
+
name: tool.name,
|
|
1180
|
+
inputSchema: inputContractSchema(tool.name),
|
|
1181
|
+
annotations: tool.annotations,
|
|
1182
|
+
_meta: effectKindMetadata(tool.effectKind),
|
|
1183
|
+
};
|
|
1184
|
+
const bindings = documentMutationFileArguments(publishedContract, args);
|
|
1185
|
+
const input = path.resolve(bindings.current);
|
|
1186
|
+
const output = path.resolve(bindings.effectiveOutput);
|
|
1171
1187
|
const receiptOutput = path.resolve(requireString(args.receiptOutput, 'receiptOutput'));
|
|
1172
|
-
if (
|
|
1188
|
+
if (input !== output) await requireNewFile(output, 'output');
|
|
1173
1189
|
await requireNewFile(receiptOutput, 'receiptOutput');
|
|
1174
|
-
const candidates = tool.startsWith('docx_') ? docxCandidates
|
|
1175
|
-
: tool.startsWith('xlsx_') ? xlsxCandidates
|
|
1176
|
-
: pptxCandidates;
|
|
1177
1190
|
return withTempJsonFile(args, async requestPath => {
|
|
1178
|
-
const result = await runJsonCandidateChain(candidates, [tool, requestPath], { allowedExitCodes: [0, 1] });
|
|
1191
|
+
const result = await runJsonCandidateChain(candidates, [tool.name, requestPath], { allowedExitCodes: [0, 1] });
|
|
1179
1192
|
if (result.code !== 0) {
|
|
1180
|
-
const detail = result.stderr.trim() || result.stdout.trim()
|
|
1193
|
+
const detail = result.stderr.trim() || result.stdout.trim()
|
|
1194
|
+
|| `${tool.name} failed with exit code ${result.code}`;
|
|
1181
1195
|
throw new Error(detail);
|
|
1182
1196
|
}
|
|
1183
|
-
if (result.json?.tool !== tool) throw new Error(`${tool} returned a mismatched tool identity`);
|
|
1197
|
+
if (result.json?.tool !== tool.name) throw new Error(`${tool.name} returned a mismatched tool identity`);
|
|
1184
1198
|
await requireReturnedArtifact(result.json.receipt, receiptOutput, 'receipt');
|
|
1185
1199
|
if (result.json.output === null) {
|
|
1186
|
-
if (result.json.summary?.pass !== false)
|
|
1200
|
+
if (result.json.summary?.pass !== false) {
|
|
1201
|
+
throw new Error(`${tool.name} omitted output without reporting failure`);
|
|
1202
|
+
}
|
|
1187
1203
|
} else {
|
|
1188
1204
|
await requireReturnedArtifact(result.json.output, output, 'output');
|
|
1189
|
-
if (result.json.summary?.pass !== true)
|
|
1205
|
+
if (result.json.summary?.pass !== true) {
|
|
1206
|
+
throw new Error(`${tool.name} returned output without reporting success`);
|
|
1207
|
+
}
|
|
1190
1208
|
}
|
|
1191
1209
|
return { ...result.json, runtime: commandRuntime(result) };
|
|
1192
1210
|
});
|
package/package.json
CHANGED