@superdoc/sdk 2.10.0-next.7 → 2.10.0-next.9

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 (53) hide show
  1. package/README.md +45 -0
  2. package/dist/action-primitives/doc-index.cjs +6 -4
  3. package/dist/action-primitives/doc-index.js +6 -4
  4. package/dist/action-primitives/engine.cjs +6 -2
  5. package/dist/action-primitives/engine.js +6 -2
  6. package/dist/action-primitives/receipt.d.ts +4 -0
  7. package/dist/action-primitives/tools/structure-insert.d.ts +1 -1
  8. package/dist/agent/actions.cjs +203 -223
  9. package/dist/agent/actions.d.ts +5 -0
  10. package/dist/agent/actions.js +203 -223
  11. package/dist/agent/catalog.cjs +15 -0
  12. package/dist/agent/catalog.js +15 -0
  13. package/dist/agent/doc-snapshot.cjs +22 -46
  14. package/dist/agent/doc-snapshot.js +22 -46
  15. package/dist/agent/execution-context.cjs +385 -0
  16. package/dist/agent/execution-context.d.ts +97 -0
  17. package/dist/agent/execution-context.js +376 -0
  18. package/dist/agent/runtime.cjs +116 -158
  19. package/dist/agent/runtime.d.ts +3 -0
  20. package/dist/agent/runtime.js +117 -159
  21. package/dist/agent/v2-preset-compat.cjs +5 -1
  22. package/dist/agent/v2-preset-compat.js +4 -1
  23. package/dist/embedded-tools.generated.cjs +5 -5
  24. package/dist/embedded-tools.generated.js +5 -5
  25. package/dist/generated/client.cjs +2 -0
  26. package/dist/generated/client.d.ts +47 -0
  27. package/dist/generated/client.js +2 -0
  28. package/dist/generated/contract.cjs +1605 -1297
  29. package/dist/generated/contract.js +1605 -1297
  30. package/dist/index.cjs +11 -0
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.js +11 -0
  33. package/dist/presets/core.cjs +1 -1
  34. package/dist/presets/core.js +1 -1
  35. package/dist/runtime/document-evidence.cjs +40 -0
  36. package/dist/runtime/document-evidence.d.ts +13 -0
  37. package/dist/runtime/document-evidence.js +30 -0
  38. package/dist/runtime/host.cjs +30 -2
  39. package/dist/runtime/host.d.ts +2 -0
  40. package/dist/runtime/host.js +30 -2
  41. package/dist/runtime/process.cjs +6 -0
  42. package/dist/runtime/process.d.ts +2 -0
  43. package/dist/runtime/process.js +6 -0
  44. package/dist/runtime/sdk-version.generated.cjs +1 -1
  45. package/dist/runtime/sdk-version.generated.d.ts +1 -1
  46. package/dist/runtime/sdk-version.generated.js +1 -1
  47. package/package.json +10 -9
  48. package/tools/catalog.json +35 -0
  49. package/tools/tools-policy.json +1 -1
  50. package/tools/tools.anthropic.json +35 -0
  51. package/tools/tools.generic.json +35 -0
  52. package/tools/tools.openai.json +35 -0
  53. package/tools/tools.vercel.json +35 -0
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var executionContext = require('./execution-context.cjs');
3
4
  var errors = require('../runtime/errors.cjs');
4
5
  var listTransform = require('../action-primitives/tools/list-transform.cjs');
5
6
  var structureInsert = require('../action-primitives/tools/structure-insert.cjs');
@@ -170,18 +171,18 @@ const ACTION_GROUPS = [
170
171
  * action at compile time.
171
172
  */
172
173
  const ACTION_ARGS = {
173
- insert_paragraphs: ['text', 'texts', 'headingLevel', 'placement', 'changeMode'],
174
+ insert_paragraphs: ['text', 'texts', 'headingLevel', 'placement', 'changeMode', 'evidence'],
174
175
  insert_heading: ['text', 'level', 'placement', 'changeMode'],
175
176
  replace_text: ['edits', 'selector', 'caseSensitive', 'changeMode'],
176
177
  delete_text: ['finds', 'selector', 'caseSensitive', 'changeMode'],
177
178
  delete_blocks: ['selectors', 'selector', 'changeMode'],
178
179
  append_list: ['items', 'kind', 'headingText', 'headingLevel', 'placement', 'changeMode'],
179
- create_table: ['rows', 'columns', 'cellTexts', 'placement', 'changeMode'],
180
+ create_table: ['rows', 'columns', 'cellTexts', 'placement', 'changeMode', 'evidence'],
180
181
  comment_paragraphs: ['commentText', 'scope', 'excludeBlockQuotes'],
181
182
  add_comments: ['commentText', 'selector', 'selectors'],
182
183
  resolve_comments: ['anchorText', 'reopen'],
183
184
  reply_to_comment: ['commentText', 'anchorText', 'commentId'],
184
- rewrite_block: ['text', 'selector', 'changeMode'],
185
+ rewrite_block: ['text', 'selector', 'changeMode', 'evidence'],
185
186
  accept_tracked_changes: ['author', 'changeType'],
186
187
  reject_tracked_changes: ['author', 'changeType'],
187
188
  normalize_body_font_size: ['fontSize', 'changeMode'],
@@ -216,7 +217,7 @@ const ACTION_ARGS = {
216
217
  'changeMode',
217
218
  ],
218
219
  apply_style: ['selector', 'styleId', 'headingLevel', 'likeText'],
219
- format_paragraph: ['selector', 'alignment', 'changeMode'],
220
+ format_paragraph: ['selector', 'alignment', 'changeMode', 'evidence'],
220
221
  move_text: ['text', 'afterText', 'changeMode'],
221
222
  style_table: ['tableOrdinal', 'accentColor'],
222
223
  move_table: ['tableOrdinal', 'placement'],
@@ -319,10 +320,6 @@ function findSnapshotTextByNodeId(snapshot, nodeId) {
319
320
  }
320
321
  return null;
321
322
  }
322
- function lastBlock(snapshot) {
323
- const block = snapshot.blocks[snapshot.blocks.length - 1];
324
- return block ? { nodeId: block.nodeId, nodeType: block.nodeType } : null;
325
- }
326
323
  function createdBlockTarget(result) {
327
324
  const rec = asRecord(result);
328
325
  const paragraph = asRecord(rec?.paragraph);
@@ -860,7 +857,7 @@ forceDirectFill = false) {
860
857
  });
861
858
  }
862
859
  const extracted = asRecord(await extractFn({}));
863
- const blocks = (Array.isArray(extracted?.blocks) ? extracted?.blocks : [])
860
+ const blocks = (Array.isArray(extracted?.blocks) ? extracted.blocks : [])
864
861
  .map((block) => asRecord(block))
865
862
  .filter((block) => block != null)
866
863
  .flatMap((block) => {
@@ -1049,95 +1046,68 @@ async function styleTableCells(doc, cells, changeMode) {
1049
1046
  await applyItemStyling(doc, nodeIds, specs, changeMode);
1050
1047
  return styled.length;
1051
1048
  }
1052
- async function runInsertParagraphs(doc, args) {
1053
- const pre = args.placement?.at === 'before' || args.placement?.at === 'after'
1054
- ? await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] })
1055
- : await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
1049
+ async function runInsertParagraphs(doc, args, formatArgs) {
1050
+ const context = new executionContext.ExecutionContext(doc, { evidence: args.evidence, selectors: placementSelectors(args.placement) });
1056
1051
  try {
1057
- // `texts` is the canonical input; a single `text` is normalized to one item
1058
- // upstream in the dispatcher, but tolerate it here too.
1059
- const texts = args.texts ?? (args.text ? [args.text] : []);
1060
- const placement = resolvePlacement(args.placement, pre);
1061
- const executedOperations = [];
1062
- // Blank-line spacing between drafted paragraphs is only desirable when
1063
- // creating a NEW document (a blank doc has ~one empty block). Inserting into
1064
- // an existing doc must NOT scatter blank paragraphs through it.
1065
- const isNewDocument = (pre.counts?.blocks ?? 0) <= 1;
1066
- // For the first item: respect the requested placement and headingLevel.
1067
- // For subsequent items: append after the previously inserted block by
1068
- // using `documentEnd` (the SDK keeps blocks contiguous) so order is
1069
- // preserved.
1052
+ const pre = await context.start();
1053
+ const placement = await resolveFactPlacement(args.placement, pre);
1054
+ const isNewDocument = (await pre.count()) <= 1;
1055
+ const anchor = placement.kind === 'before' || placement.kind === 'after'
1056
+ ? await pre.block(placement.target.nodeId)
1057
+ : await pre.target({
1058
+ kind: 'placement',
1059
+ at: placement.kind === 'documentStart' ? 'document_start' : 'document_end',
1060
+ });
1061
+ const preRows = pre.complete ? await listBlockRows(doc) : anchor ? [anchor] : [];
1070
1062
  let currentPlacement = placement;
1071
- let headingFirst = false;
1072
- if (typeof args.headingLevel === 'number' && args.headingLevel >= 1 && args.headingLevel <= 6) {
1073
- headingFirst = true;
1074
- }
1075
- // Advance the insertion point to sit right after the block just created
1076
- // (prefer the receipt's created id; fall back to the document's last block).
1077
- const advanceAfter = async (result) => {
1078
- const created = createdBlockTarget(result);
1079
- if (created) {
1080
- return { kind: 'after', target: { kind: 'block', nodeType: created.nodeType, nodeId: created.nodeId } };
1063
+ const nodeIds = [];
1064
+ const specs = [];
1065
+ const advance = async (result) => {
1066
+ const target = createdBlockTarget(result);
1067
+ if (target) {
1068
+ currentPlacement = {
1069
+ kind: 'after',
1070
+ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId },
1071
+ };
1072
+ return target.nodeId;
1081
1073
  }
1082
- const mid = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
1083
- const last = lastBlock(mid);
1084
- return last
1085
- ? { kind: 'after', target: { kind: 'block', nodeType: last.nodeType, nodeId: last.nodeId } }
1086
- : { kind: 'documentEnd' };
1074
+ const current = await context.finishRevision();
1075
+ const last = await current.target({ kind: 'placement', at: 'document_end' });
1076
+ if (!last)
1077
+ throw new Error('Created paragraph identity is unavailable.');
1078
+ currentPlacement = { kind: 'after', target: { kind: 'block', nodeType: last.nodeType, nodeId: last.nodeId } };
1079
+ return last.nodeId;
1087
1080
  };
1088
- const styledNodeIds = [];
1089
- const styledSpecs = [];
1090
- for (let i = 0; i < texts.length; i += 1) {
1091
- const text = texts[i];
1092
- const isFirst = i === 0;
1093
- const result = isFirst && headingFirst
1094
- ? await executeCreateHeading(doc, text, args.headingLevel, currentPlacement, args.changeMode)
1095
- : await executeCreateParagraph(doc, text, currentPlacement, args.changeMode);
1096
- executedOperations.push({
1097
- operationId: isFirst && headingFirst ? 'doc.create.heading' : 'doc.create.paragraph',
1098
- result,
1099
- });
1100
- // Remember the created block for the post-insert styling pass when this
1101
- // paragraph carries a runs/marks spec.
1102
- const spec = args.textSpecs?.[i];
1103
- if (spec && ((spec.runs && spec.runs.length > 0) || spec.marks != null)) {
1104
- const created = createdBlockTarget(result);
1105
- if (created?.nodeId) {
1106
- styledNodeIds.push(created.nodeId);
1107
- styledSpecs.push({ ...spec, text });
1108
- }
1109
- }
1110
- currentPlacement = await advanceAfter(result);
1111
- // New-document drafts get a blank paragraph after each item for spacing;
1112
- // inserts into an existing document do not (would scatter blank lines).
1113
- if (isNewDocument) {
1114
- const spacer = await executeCreateParagraph(doc, '', currentPlacement, args.changeMode);
1115
- executedOperations.push({ operationId: 'doc.create.paragraph', result: spacer, rationale: 'spacing' });
1116
- currentPlacement = await advanceAfter(spacer);
1081
+ const texts = args.texts ?? (args.text ? [args.text] : []);
1082
+ for (let i = 0; i < texts.length; i++) {
1083
+ const result = i === 0 && args.headingLevel
1084
+ ? await executeCreateHeading(context.document, texts[i], args.headingLevel, currentPlacement, args.changeMode)
1085
+ : await executeCreateParagraph(context.document, texts[i], currentPlacement, args.changeMode);
1086
+ const id = await advance(result);
1087
+ nodeIds.push(id);
1088
+ specs.push({ ...args.textSpecs?.[i], text: texts[i] });
1089
+ if (isNewDocument)
1090
+ await advance(await executeCreateParagraph(context.document, '', currentPlacement, args.changeMode));
1091
+ }
1092
+ if (specs.some((spec) => spec.runs?.length || spec.marks))
1093
+ await applyItemStyling(context.document, nodeIds, specs, args.changeMode);
1094
+ let post = await context.finishRevision();
1095
+ let formatted;
1096
+ if (formatArgs) {
1097
+ let localRows;
1098
+ if (!pre.complete) {
1099
+ const created = await post.page({ nodeIds, limit: Math.max(nodeIds.length, 1) });
1100
+ localRows = [...(anchor ? [anchor] : []), ...created].sort((a, b) => a.ordinal - b.ordinal);
1117
1101
  }
1102
+ const count = context.executedOperations.length;
1103
+ formatted = await matchInsertedBlockFormatting(context.document, preRows, context.receipt('insert_paragraphs', []), formatArgs, localRows);
1104
+ if (context.executedOperations.length !== count)
1105
+ post = await context.finishRevision();
1118
1106
  }
1119
- if (styledNodeIds.length > 0) {
1120
- const styling = await applyItemStyling(doc, styledNodeIds, styledSpecs, args.changeMode);
1121
- executedOperations.push({
1122
- operationId: 'doc.format.apply',
1123
- result: styling,
1124
- rationale: `Styled ${styledNodeIds.length} inserted paragraph(s)`,
1125
- });
1126
- }
1127
- const postIdentity = await readDocumentIdentity(doc);
1128
- const verification = [revisionVerification(pre.revision, postIdentity.revision, true)];
1129
- return {
1130
- status: verification.every((v) => v.passed) ? 'ok' : 'failed',
1131
- intent: 'insert_paragraphs',
1132
- preSnapshot: { revision: pre.revision, counts: pre.counts },
1133
- postSnapshot: postIdentity,
1134
- selectedTargets: [],
1135
- executedOperations,
1136
- verification,
1137
- };
1107
+ return context.receipt('insert_paragraphs', await executionContext.evaluateFactChecks(pre, post, [{ kind: 'revision-changed' }]), formatted?.contextualFormatting ? { contextualFormatting: formatted.contextualFormatting } : {});
1138
1108
  }
1139
- catch (err) {
1140
- return failedReceipt('insert_paragraphs', err, pre);
1109
+ catch (error) {
1110
+ return context.failure('insert_paragraphs', error);
1141
1111
  }
1142
1112
  }
1143
1113
  async function runInsertHeading(doc, args) {
@@ -2254,52 +2224,75 @@ async function runSplitList(doc, args) {
2254
2224
  return failedReceipt('split_list', err, pre);
2255
2225
  }
2256
2226
  }
2227
+ async function resolveFactPlacement(placement, facts) {
2228
+ if (!placement || placement.at === 'document_end')
2229
+ return { kind: 'documentEnd' };
2230
+ if (placement.at === 'document_start')
2231
+ return { kind: 'documentStart' };
2232
+ const target = await facts.target(placement.selector);
2233
+ if (!target)
2234
+ throw new errors.SuperDocCliError('placement selector did not resolve to a unique body block', {
2235
+ code: 'INVALID_ARGUMENT',
2236
+ });
2237
+ return { kind: placement.at, target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } };
2238
+ }
2239
+ function placementSelectors(placement) {
2240
+ return placement?.at === 'before' || placement?.at === 'after' ? [placement.selector] : [];
2241
+ }
2257
2242
  async function runCreateTable(doc, args) {
2258
- const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
2243
+ const checks = [
2244
+ { kind: 'revision-changed' },
2245
+ { kind: 'block-count-delta', nodeType: 'table', delta: 1 },
2246
+ ];
2247
+ const context = new executionContext.ExecutionContext(doc, {
2248
+ evidence: args.evidence,
2249
+ selectors: placementSelectors(args.placement),
2250
+ checks,
2251
+ });
2259
2252
  try {
2260
- if (!Number.isInteger(args.rows) || args.rows < 1 || !Number.isInteger(args.columns) || args.columns < 1) {
2261
- return failedReceipt('create_table', new Error('rows and columns must be positive integers'), pre);
2262
- }
2263
- const placement = resolvePlacement(args.placement, pre);
2264
- const insertedTableOrdinal = estimateInsertedTableOrdinal(pre, placement);
2265
- const result = await executeCreateTable(doc, args, placement);
2266
- const executedOperations = [
2267
- { operationId: 'doc.create.table', result },
2268
- ];
2269
- const createdTableNodeId = asString(asRecord(asRecord(result)?.table)?.nodeId);
2270
- if (createdTableNodeId && args.cellTexts) {
2271
- const appliedCells = await applyTableCellTexts(doc, createdTableNodeId, insertedTableOrdinal, flattenCellTexts(args.cellTexts), args.changeMode);
2272
- if (appliedCells.length > 0) {
2273
- executedOperations.push({
2274
- operationId: 'doc.mutations.apply',
2275
- rationale: `Populated ${appliedCells.length} table cells.`,
2276
- });
2253
+ if (!Number.isInteger(args.rows) || args.rows < 1 || !Number.isInteger(args.columns) || args.columns < 1)
2254
+ throw new Error('rows and columns must be positive integers');
2255
+ const pre = await context.start();
2256
+ const placement = await resolveFactPlacement(args.placement, pre);
2257
+ const result = await executeCreateTable(context.document, args, placement);
2258
+ const tableNodeId = asString(asRecord(asRecord(result)?.table)?.nodeId);
2259
+ if (args.cellTexts && tableNodeId) {
2260
+ const cells = flattenCellTexts(args.cellTexts).filter((cell) => cell.text.trim());
2261
+ if (pre.complete) {
2262
+ const applied = await applyTableCellTexts(context.document, tableNodeId, estimateInsertedTableOrdinal(pre.complete, placement), cells, args.changeMode);
2263
+ await styleTableCells(context.document, applied, args.changeMode);
2277
2264
  }
2278
- const styledCount = await styleTableCells(doc, appliedCells, args.changeMode);
2279
- if (styledCount > 0) {
2280
- executedOperations.push({
2281
- operationId: 'doc.format.apply',
2282
- rationale: `Styled ${styledCount} table cell(s).`,
2283
- });
2265
+ else {
2266
+ for (const cell of cells)
2267
+ await context.document.tables.setCellText({
2268
+ target: { kind: 'block', nodeType: 'table', nodeId: tableNodeId },
2269
+ rowIndex: cell.rowIndex,
2270
+ columnIndex: cell.columnIndex,
2271
+ text: cell.text,
2272
+ changeMode: args.changeMode,
2273
+ });
2274
+ const styled = cells.filter((cell) => cell.runs?.length || cell.marks);
2275
+ if (styled.length) {
2276
+ const identities = await doc.tables.getCells({ nodeId: tableNodeId });
2277
+ const resolved = styled.map((cell) => {
2278
+ const identity = identities.cells.find((item) => item.rowIndex === cell.rowIndex &&
2279
+ item.columnIndex <= cell.columnIndex &&
2280
+ item.columnIndex + item.colspan > cell.columnIndex);
2281
+ if (!identity?.firstParagraphNodeId)
2282
+ throw new Error('Required cell paragraph identity is unavailable.');
2283
+ return { ...cell, nodeId: identity.firstParagraphNodeId };
2284
+ });
2285
+ const styling = await applyItemStyling(context.document, resolved.map((cell) => cell.nodeId), resolved, args.changeMode);
2286
+ if (styling.skipped)
2287
+ throw new Error(`Requested cell styling failed: ${styling.skipped}`);
2288
+ }
2284
2289
  }
2285
2290
  }
2286
- const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
2287
- const verification = evaluateChecks(pre, post, [
2288
- { kind: 'revision-changed' },
2289
- { kind: 'block-count-delta', nodeType: 'table', delta: 1 },
2290
- ]);
2291
- return {
2292
- status: verification.every((v) => v.passed) ? 'ok' : 'failed',
2293
- intent: 'create_table',
2294
- preSnapshot: { revision: pre.revision, counts: pre.counts },
2295
- postSnapshot: { revision: post.revision, counts: post.counts },
2296
- selectedTargets: [],
2297
- executedOperations,
2298
- verification,
2299
- };
2291
+ const post = await context.finish();
2292
+ return context.receipt('create_table', await executionContext.evaluateFactChecks(pre, post, checks));
2300
2293
  }
2301
- catch (err) {
2302
- return failedReceipt('create_table', err, pre);
2294
+ catch (error) {
2295
+ return context.failure('create_table', error);
2303
2296
  }
2304
2297
  }
2305
2298
  async function runCommentParagraphs(doc, args) {
@@ -2582,67 +2575,39 @@ async function runReplyToComment(doc, args) {
2582
2575
  }
2583
2576
  }
2584
2577
  async function runRewriteBlock(doc, args) {
2585
- const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
2578
+ const context = new executionContext.ExecutionContext(doc, { evidence: args.evidence, selectors: [args.selector] });
2586
2579
  try {
2587
- const target = selectorToBlockTarget(args.selector, pre);
2588
- if (!target) {
2589
- return failedReceipt('rewrite_block', new Error('selector did not resolve to a body block'), pre);
2590
- }
2591
- const normalizedText = normalizeTitleLikeRewriteText(target.text, args.text);
2592
- // The engine rewrites the whole block as ONE uniform run — losing an
2593
- // intra-paragraph pattern (bold "(n)" lead-in), or smearing the first
2594
- // run's marks across the entire text. Capture the current run layout so
2595
- // it can be re-applied over the unchanged prefix/suffix afterward.
2596
- const preRuns = await captureBlockRuns(doc, target.nodeId, target.text);
2597
- const steps = [
2580
+ const pre = await context.start();
2581
+ const target = await pre.target(args.selector);
2582
+ if (!target)
2583
+ throw new Error('selector did not resolve to a body block');
2584
+ context.selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
2585
+ const text = normalizeTitleLikeRewriteText(target.text, args.text);
2586
+ const runs = await captureBlockRuns(doc, target.nodeId, target.text);
2587
+ await executeMutations(context.document, [
2598
2588
  {
2599
2589
  id: 'rewrite-block-1',
2600
2590
  op: 'text.rewrite',
2601
2591
  where: { by: 'block', nodeType: target.nodeType, nodeId: target.nodeId },
2602
- args: {
2603
- replacement: { text: normalizedText },
2604
- style: preserveRewriteStyle(),
2605
- },
2592
+ args: { replacement: { text }, style: preserveRewriteStyle() },
2606
2593
  },
2607
- ];
2608
- const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
2609
- const preservation = await preserveRunPatternAfterRewrite(doc, target.nodeId, target.text, preRuns, normalizedText);
2610
- const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
2611
- const rewrittenBlock = findSnapshotTextByNodeId(post, target.nodeId);
2594
+ ], args.changeMode, pre.revision);
2595
+ await preserveRunPatternAfterRewrite(context.document, target.nodeId, target.text, runs, text);
2596
+ const post = await context.finish();
2597
+ const rewritten = await post.block(target.nodeId);
2612
2598
  const verification = [
2613
2599
  revisionVerification(pre.revision, post.revision, true),
2614
2600
  {
2615
- check: { kind: 'block-text-contains', nodeId: target.nodeId, text: normalizedText },
2616
- passed: !!rewrittenBlock && verifyRewrittenBlockText(rewrittenBlock.text, normalizedText, args.changeMode),
2601
+ check: { kind: 'block-text-contains', nodeId: target.nodeId, text },
2602
+ passed: !!rewritten && verifyRewrittenBlockText(rewritten.text, text, args.changeMode),
2617
2603
  },
2618
2604
  ];
2619
- const changed = pre.revision !== post.revision;
2620
- const rewritten = !!rewrittenBlock && verifyRewrittenBlockText(rewrittenBlock.text, args.text, args.changeMode);
2621
- return {
2622
- status: changed && rewritten ? 'ok' : 'failed',
2623
- intent: 'rewrite_block',
2624
- preSnapshot: { revision: pre.revision, counts: pre.counts },
2625
- postSnapshot: { revision: post.revision, counts: post.counts },
2626
- selectedTargets: [{ selector: args.selector, matched: [target.nodeId] }],
2627
- executedOperations: [
2628
- { operationId: 'doc.mutations.apply', result },
2629
- ...(preservation ? [{ operationId: 'doc.format.apply', result: preservation }] : []),
2630
- ],
2631
- verification,
2632
- errors: changed && rewritten
2633
- ? undefined
2634
- : [
2635
- {
2636
- code: 'ACTION_FAILED',
2637
- message: changed
2638
- ? 'rewrite_block did not produce the requested rewritten text for the selected block'
2639
- : 'rewrite_block produced no change for the selected block; keep the same target and provide a changed rewrite',
2640
- },
2641
- ],
2642
- };
2605
+ return context.receipt('rewrite_block', verification, verification.every((check) => check.passed)
2606
+ ? {}
2607
+ : { errors: [{ code: 'ACTION_FAILED', message: 'rewrite_block did not produce the requested changed text' }] });
2643
2608
  }
2644
- catch (err) {
2645
- return failedReceipt('rewrite_block', err, pre);
2609
+ catch (error) {
2610
+ return context.failure('rewrite_block', error);
2646
2611
  }
2647
2612
  }
2648
2613
  const RUN_BOOL_MARKS = ['bold', 'italic', 'underline', 'strike'];
@@ -3293,43 +3258,30 @@ async function runApplyStyle(doc, args) {
3293
3258
  * escape to superdoc_execute_code and apply the alignment untracked (no pPrChange).
3294
3259
  */
3295
3260
  async function runFormatParagraph(doc, args) {
3296
- const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
3261
+ const context = new executionContext.ExecutionContext(doc, { evidence: args.evidence, selectors: [args.selector] });
3297
3262
  try {
3298
- const target = selectorToBlockTarget(args.selector, pre);
3299
- if (!target) {
3300
- return failedReceipt('format_paragraph', new Error('selector did not resolve to a unique body block'), pre);
3301
- }
3302
3263
  const alignment = typeof args.alignment === 'string' ? args.alignment.trim().toLowerCase() : '';
3303
- const normalizedAlignment = alignment === 'both' ? 'justify' : alignment;
3304
- if (!['left', 'center', 'right', 'justify'].includes(normalizedAlignment)) {
3305
- return failedReceipt('format_paragraph', new Error('format_paragraph requires alignment: left | center | right | justify'), pre);
3306
- }
3307
- const steps = [
3264
+ const normalized = alignment === 'both' ? 'justify' : alignment;
3265
+ if (!['left', 'center', 'right', 'justify'].includes(normalized))
3266
+ throw new Error('format_paragraph requires alignment: left | center | right | justify');
3267
+ const pre = await context.start();
3268
+ const target = await pre.target(args.selector);
3269
+ if (!target)
3270
+ throw new Error('selector did not resolve to a unique body block');
3271
+ context.selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
3272
+ await executeMutations(context.document, [
3308
3273
  {
3309
3274
  id: 'format-paragraph-1',
3310
3275
  op: 'format.apply',
3311
3276
  where: { by: 'block', nodeType: target.nodeType, nodeId: target.nodeId },
3312
- args: { alignment: normalizedAlignment, scope: 'block' },
3277
+ args: { alignment: normalized, scope: 'block' },
3313
3278
  },
3314
- ];
3315
- const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
3316
- const post = await docSnapshot.buildDocumentSnapshot(doc);
3317
- const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
3318
- return {
3319
- status: verification.every((v) => v.passed) ? 'ok' : 'failed',
3320
- intent: `format_paragraph: ${normalizedAlignment}`,
3321
- preSnapshot: { revision: pre.revision, counts: pre.counts },
3322
- postSnapshot: { revision: post.revision, counts: post.counts },
3323
- selectedTargets: [{ selector: args.selector, matched: [target.nodeId] }],
3324
- applied: { alignment: normalizedAlignment },
3325
- executedOperations: [
3326
- { operationId: 'doc.mutations.apply', result: compactOpResult(result), rationale: `align ${target.nodeId}` },
3327
- ],
3328
- verification,
3329
- };
3279
+ ], args.changeMode, pre.revision);
3280
+ const post = await context.finish();
3281
+ return context.receipt(`format_paragraph: ${normalized}`, await executionContext.evaluateFactChecks(pre, post, [{ kind: 'revision-changed' }]), { applied: { alignment: normalized } });
3330
3282
  }
3331
- catch (err) {
3332
- return failedReceipt('format_paragraph', err, pre);
3283
+ catch (error) {
3284
+ return context.failure('format_paragraph', error);
3333
3285
  }
3334
3286
  }
3335
3287
  /**
@@ -5985,8 +5937,9 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
5985
5937
  const formatApplyFn = maybeMethod(doc, ['format', 'apply']);
5986
5938
  if (formatApplyFn) {
5987
5939
  const textLength = (created.textPreview ?? '').length;
5988
- const post = await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
5989
- const full = post.blocks.find((b) => b.nodeId === created.nodeId)?.text ?? '';
5940
+ const full = createdText ||
5941
+ (await doc.blocks.list({ nodeIds: [created.nodeId], limit: 1, includeText: true })).blocks.find((block) => block.nodeId === created.nodeId)?.text ||
5942
+ '';
5990
5943
  await formatApplyFn({
5991
5944
  blockId: created.nodeId,
5992
5945
  start: 0,
@@ -6183,11 +6136,11 @@ async function matchBlockRunPattern(doc, reference, created, createdText) {
6183
6136
  }
6184
6137
  return Object.keys(applied).length > 0 ? { matchedSibling: reference.nodeId, ...applied } : null;
6185
6138
  }
6186
- async function matchInsertedBlockFormatting(doc, preBlocks, receipt, args) {
6139
+ async function matchInsertedBlockFormatting(doc, preBlocks, receipt, args, availableRows) {
6187
6140
  if (receipt.status !== 'ok')
6188
6141
  return receipt;
6189
6142
  try {
6190
- const postBlocks = await listBlockRows(doc);
6143
+ const postBlocks = availableRows ?? (await listBlockRows(doc));
6191
6144
  const preIds = new Set(preBlocks.map((b) => b.nodeId));
6192
6145
  const createdRows = postBlocks
6193
6146
  .map((b, i) => ({ row: b, index: i }))
@@ -6478,9 +6431,9 @@ async function dispatchSuperdocPerformAction(doc, args) {
6478
6431
  });
6479
6432
  }
6480
6433
  const headingLevel = asNumber(args.headingLevel);
6481
- const preRows = await listBlockRows(doc);
6482
- const receipt = await runInsertParagraphs(doc, {
6434
+ return runInsertParagraphs(doc, {
6483
6435
  action,
6436
+ evidence: args.evidence,
6484
6437
  texts,
6485
6438
  textSpecs,
6486
6439
  placement: parsePlacement(args.placement),
@@ -6488,8 +6441,7 @@ async function dispatchSuperdocPerformAction(doc, args) {
6488
6441
  headingLevel: headingLevel != null && Number.isInteger(headingLevel) && headingLevel >= 1 && headingLevel <= 6
6489
6442
  ? headingLevel
6490
6443
  : undefined,
6491
- });
6492
- return matchInsertedBlockFormatting(doc, preRows, receipt, args);
6444
+ }, args);
6493
6445
  }
6494
6446
  case 'insert_heading': {
6495
6447
  const text = asString(args.text);
@@ -6613,6 +6565,7 @@ async function dispatchSuperdocPerformAction(doc, args) {
6613
6565
  }
6614
6566
  return runCreateTable(doc, {
6615
6567
  action,
6568
+ evidence: args.evidence,
6616
6569
  rows,
6617
6570
  columns,
6618
6571
  cellTexts: parseCellTexts(args.cellTexts),
@@ -6636,16 +6589,41 @@ async function dispatchSuperdocPerformAction(doc, args) {
6636
6589
  }
6637
6590
  case 'add_comments': {
6638
6591
  const commentText = asString(args.commentText);
6639
- const selector = parseSelector(args.selector);
6640
- const selectors = Array.isArray(args.selectors)
6641
- ? args.selectors.map((s) => parseSelector(s)).filter((s) => Boolean(s))
6642
- : undefined;
6643
- if (!commentText || (!selector && !(selectors && selectors.length))) {
6592
+ if (!commentText) {
6644
6593
  throw new errors.SuperDocCliError('add_comments requires "commentText" and a "selector" or non-empty "selectors" array', {
6645
6594
  code: 'INVALID_ARGUMENT',
6646
6595
  });
6647
6596
  }
6648
- return runAddComments(doc, { action, commentText, selector: selector ?? undefined, selectors });
6597
+ const hasSelector = args.selector != null;
6598
+ const hasSelectors = args.selectors != null;
6599
+ if (hasSelector && hasSelectors) {
6600
+ throw new errors.SuperDocCliError('add_comments requires either "selector" or "selectors", not both', {
6601
+ code: 'INVALID_ARGUMENT',
6602
+ });
6603
+ }
6604
+ if (hasSelectors) {
6605
+ if (!Array.isArray(args.selectors) || args.selectors.length === 0) {
6606
+ throw new errors.SuperDocCliError('add_comments requires "selectors" to be a non-empty array', {
6607
+ code: 'INVALID_ARGUMENT',
6608
+ });
6609
+ }
6610
+ // Parse the complete batch before mutation. Filtering an invalid entry
6611
+ // would make the receipt report success for only part of the request.
6612
+ const selectors = [];
6613
+ for (const [index, raw] of args.selectors.entries()) {
6614
+ const parsed = parseSelector(raw);
6615
+ if (!parsed) {
6616
+ throw new errors.SuperDocCliError(`add_comments selectors[${index}] is not a valid selector; no comments were added`, { code: 'INVALID_ARGUMENT' });
6617
+ }
6618
+ selectors.push(parsed);
6619
+ }
6620
+ return runAddComments(doc, { action, commentText, selectors });
6621
+ }
6622
+ const selector = parseSelector(args.selector);
6623
+ if (!selector) {
6624
+ throw new errors.SuperDocCliError('add_comments requires "selector" to be valid or "selectors" to be a non-empty array', { code: 'INVALID_ARGUMENT' });
6625
+ }
6626
+ return runAddComments(doc, { action, commentText, selector });
6649
6627
  }
6650
6628
  case 'resolve_comments':
6651
6629
  return runResolveComments(doc, {
@@ -6674,6 +6652,7 @@ async function dispatchSuperdocPerformAction(doc, args) {
6674
6652
  }
6675
6653
  return runRewriteBlock(doc, {
6676
6654
  action,
6655
+ evidence: args.evidence,
6677
6656
  text,
6678
6657
  selector,
6679
6658
  changeMode: parseChangeMode(args.changeMode),
@@ -6925,6 +6904,7 @@ async function dispatchSuperdocPerformAction(doc, args) {
6925
6904
  }
6926
6905
  return runFormatParagraph(doc, {
6927
6906
  action,
6907
+ evidence: args.evidence,
6928
6908
  selector,
6929
6909
  alignment: asString(args.alignment) ?? '',
6930
6910
  changeMode: parseChangeMode(args.changeMode),
@@ -1,3 +1,4 @@
1
+ import { type EvidencePolicy } from './execution-context.js';
1
2
  /**
2
3
  * Clean agent actions.
3
4
  *
@@ -34,6 +35,7 @@ export type ActionPlacement = {
34
35
  };
35
36
  export type ActionArgs = InsertParagraphsArgs | InsertHeadingArgs | ReplaceTextArgs | DeleteTextArgs | DeleteBlocksArgs | AppendListArgs | AddListItemsArgs | ConvertListArgs | AttachNumberingArgs | SplitListArgs | CreateTableArgs | CommentParagraphsArgs | AddCommentsArgs | ResolveCommentsArgs | ReplyToCommentArgs | RewriteBlockArgs | FormatTextArgs | FormatParagraphArgs | ApplyStyleArgs | MoveTextArgs | UndoChangesArgs | RedoChangesArgs | AcceptTrackedChangesArgs | RejectTrackedChangesArgs | NormalizeBodyFontSizeArgs | SetFontFamilyArgs | ApplyLetterSpacingArgs | FillPlaceholdersArgs | MoveRangeArgs | InsertTocArgs | StyleTableArgs | MoveTableArgs | DeleteTableArgs | SetParagraphSpacingArgs | InsertPageBreakArgs | AddHyperlinkArgs | InsertTableRowArgs | InsertTableColumnArgs | DeleteTableRowArgs | DeleteTableColumnArgs | SplitTableArgs;
36
37
  export type InsertParagraphsArgs = {
38
+ evidence?: EvidencePolicy;
37
39
  action: 'insert_paragraphs';
38
40
  texts?: readonly string[];
39
41
  text?: string;
@@ -171,6 +173,7 @@ export type AddListItemsArgs = {
171
173
  changeMode?: AgentChangeMode;
172
174
  };
173
175
  export type CreateTableArgs = {
176
+ evidence?: EvidencePolicy;
174
177
  action: 'create_table';
175
178
  rows: number;
176
179
  columns: number;
@@ -214,6 +217,7 @@ export type SetFontFamilyArgs = {
214
217
  changeMode?: AgentChangeMode;
215
218
  };
216
219
  export type RewriteBlockArgs = {
220
+ evidence?: EvidencePolicy;
217
221
  action: 'rewrite_block';
218
222
  selector: AgentSelector;
219
223
  text: string;
@@ -432,6 +436,7 @@ export type ResolveCommentsArgs = {
432
436
  reopen?: boolean;
433
437
  };
434
438
  export type FormatParagraphArgs = {
439
+ evidence?: EvidencePolicy;
435
440
  action: 'format_paragraph';
436
441
  /** Block whose paragraph properties to change. */
437
442
  selector: AgentSelector;