@superdoc/sdk 2.10.0-next.5 → 2.10.0-next.6
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/dist/agent/actions.cjs +135 -57
- package/dist/agent/actions.js +136 -58
- package/dist/agent/doc-snapshot.cjs +183 -40
- package/dist/agent/doc-snapshot.d.ts +11 -0
- package/dist/agent/doc-snapshot.js +181 -40
- package/dist/agent/runtime.cjs +54 -6
- package/dist/agent/runtime.js +55 -7
- package/dist/runtime/sdk-version.generated.cjs +1 -1
- package/dist/runtime/sdk-version.generated.d.ts +1 -1
- package/dist/runtime/sdk-version.generated.js +1 -1
- package/package.json +7 -6
package/dist/agent/actions.cjs
CHANGED
|
@@ -514,6 +514,8 @@ function revisionVerification(preRevision, postRevision, expectChanged) {
|
|
|
514
514
|
}
|
|
515
515
|
function failedReceipt(intent, err, preSnapshot) {
|
|
516
516
|
const message = reasonOf(err);
|
|
517
|
+
const snapshotError = err instanceof docSnapshot.MutationSnapshotError ? err : null;
|
|
518
|
+
const errorCode = snapshotError?.code ?? asString(asRecord(err)?.code) ?? 'ACTION_FAILED';
|
|
517
519
|
return {
|
|
518
520
|
status: 'failed',
|
|
519
521
|
intent,
|
|
@@ -523,7 +525,13 @@ function failedReceipt(intent, err, preSnapshot) {
|
|
|
523
525
|
selectedTargets: [],
|
|
524
526
|
executedOperations: [],
|
|
525
527
|
verification: [],
|
|
526
|
-
errors: [
|
|
528
|
+
errors: [
|
|
529
|
+
{
|
|
530
|
+
code: errorCode,
|
|
531
|
+
message,
|
|
532
|
+
...(snapshotError || errorCode === 'REVISION_CONFLICT' ? { recovery: { kind: 'reinspect' } } : {}),
|
|
533
|
+
},
|
|
534
|
+
],
|
|
527
535
|
};
|
|
528
536
|
}
|
|
529
537
|
async function receiptFromWorkflowResult(doc, intent, pre, workflowResult, selectedTargets = [], checks = [{ kind: 'revision-changed' }]) {
|
|
@@ -567,14 +575,16 @@ async function receiptFromWorkflowResult(doc, intent, pre, workflowResult, selec
|
|
|
567
575
|
}
|
|
568
576
|
function buildFullBlockTextTarget(snapshot, blockId) {
|
|
569
577
|
const block = snapshot.blocks.find((entry) => entry.nodeId === blockId);
|
|
570
|
-
|
|
578
|
+
const cell = block ? null : snapshot.tables.flatMap((table) => table.cells).find((entry) => entry.nodeId === blockId);
|
|
579
|
+
const text = block?.text ?? cell?.text;
|
|
580
|
+
if (text == null)
|
|
571
581
|
return null;
|
|
572
582
|
return {
|
|
573
583
|
kind: 'text',
|
|
574
584
|
blockId,
|
|
575
585
|
range: {
|
|
576
586
|
start: 0,
|
|
577
|
-
end:
|
|
587
|
+
end: text.length,
|
|
578
588
|
},
|
|
579
589
|
};
|
|
580
590
|
}
|
|
@@ -627,8 +637,8 @@ function evaluateChecks(pre, post, checks) {
|
|
|
627
637
|
else if (check.kind === 'comment-count-delta') {
|
|
628
638
|
results.push({
|
|
629
639
|
check,
|
|
630
|
-
passed: post.comments
|
|
631
|
-
detail: `pre=${pre.comments
|
|
640
|
+
passed: post.counts.comments - pre.counts.comments === check.delta,
|
|
641
|
+
detail: `pre=${pre.counts.comments} post=${post.counts.comments}`,
|
|
632
642
|
});
|
|
633
643
|
}
|
|
634
644
|
else if (check.kind === 'tracked-change-count-delta') {
|
|
@@ -1040,7 +1050,9 @@ async function styleTableCells(doc, cells, changeMode) {
|
|
|
1040
1050
|
return styled.length;
|
|
1041
1051
|
}
|
|
1042
1052
|
async function runInsertParagraphs(doc, args) {
|
|
1043
|
-
const pre =
|
|
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'] });
|
|
1044
1056
|
try {
|
|
1045
1057
|
// `texts` is the canonical input; a single `text` is normalized to one item
|
|
1046
1058
|
// upstream in the dispatcher, but tolerate it here too.
|
|
@@ -1067,7 +1079,7 @@ async function runInsertParagraphs(doc, args) {
|
|
|
1067
1079
|
if (created) {
|
|
1068
1080
|
return { kind: 'after', target: { kind: 'block', nodeType: created.nodeType, nodeId: created.nodeId } };
|
|
1069
1081
|
}
|
|
1070
|
-
const mid = await docSnapshot.
|
|
1082
|
+
const mid = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1071
1083
|
const last = lastBlock(mid);
|
|
1072
1084
|
return last
|
|
1073
1085
|
? { kind: 'after', target: { kind: 'block', nodeType: last.nodeType, nodeId: last.nodeId } }
|
|
@@ -1129,7 +1141,9 @@ async function runInsertParagraphs(doc, args) {
|
|
|
1129
1141
|
}
|
|
1130
1142
|
}
|
|
1131
1143
|
async function runInsertHeading(doc, args) {
|
|
1132
|
-
const pre =
|
|
1144
|
+
const pre = args.placement?.at === 'before' || args.placement?.at === 'after'
|
|
1145
|
+
? await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] })
|
|
1146
|
+
: await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1133
1147
|
try {
|
|
1134
1148
|
const placement = resolvePlacement(args.placement, pre);
|
|
1135
1149
|
const result = await executeCreateHeading(doc, args.text, args.level, placement, args.changeMode);
|
|
@@ -1154,7 +1168,7 @@ async function runReplaceText(doc, args) {
|
|
|
1154
1168
|
const requiresBlockSnapshot = args.selector != null;
|
|
1155
1169
|
const preIdentity = requiresBlockSnapshot ? null : await readDocumentIdentity(doc);
|
|
1156
1170
|
const pre = requiresBlockSnapshot
|
|
1157
|
-
? await docSnapshot.
|
|
1171
|
+
? await docSnapshot.buildMutationSnapshot(doc, { includeDomains: selectorDomains ?? ['blocks'] })
|
|
1158
1172
|
: snapshotFromIdentity(preIdentity);
|
|
1159
1173
|
try {
|
|
1160
1174
|
if (args.edits.length === 0) {
|
|
@@ -1206,6 +1220,7 @@ async function runReplaceText(doc, args) {
|
|
|
1206
1220
|
},
|
|
1207
1221
|
},
|
|
1208
1222
|
];
|
|
1223
|
+
applyExpectedRevision = pre.revision;
|
|
1209
1224
|
selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
|
|
1210
1225
|
preserveTargets.push({
|
|
1211
1226
|
nodeId: target.nodeId,
|
|
@@ -1278,7 +1293,7 @@ async function runReplaceText(doc, args) {
|
|
|
1278
1293
|
preserved.push({ nodeId: entry.nodeId, ...restored });
|
|
1279
1294
|
}
|
|
1280
1295
|
if (args.selector && selectedTargets[0]) {
|
|
1281
|
-
const post = await docSnapshot.
|
|
1296
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: selectorDomains ?? ['blocks'] });
|
|
1282
1297
|
const blockId = selectedTargets[0].matched[0];
|
|
1283
1298
|
const postTarget = findSnapshotTextByNodeId(post, blockId);
|
|
1284
1299
|
const preTarget = findSnapshotTextByNodeId(pre, blockId);
|
|
@@ -1347,7 +1362,7 @@ async function runDeleteText(doc, args) {
|
|
|
1347
1362
|
// path). Rewriting the block minus the finds keeps the deletion local — without
|
|
1348
1363
|
// this a short/whitespace find matches document-wide and blows the target cap.
|
|
1349
1364
|
if (args.selector) {
|
|
1350
|
-
const scopedPre = await docSnapshot.
|
|
1365
|
+
const scopedPre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: snapshotDomainsForSelector(args.selector) });
|
|
1351
1366
|
try {
|
|
1352
1367
|
if (args.finds.length === 0) {
|
|
1353
1368
|
return failedReceipt('delete_text', new Error('finds must be non-empty'), scopedPre);
|
|
@@ -1390,7 +1405,7 @@ async function runDeleteText(doc, args) {
|
|
|
1390
1405
|
args: { replacement: { text: rewritten }, style: preserveRewriteStyle() },
|
|
1391
1406
|
},
|
|
1392
1407
|
];
|
|
1393
|
-
const scopedResult = await executeMutations(doc, scopedSteps, args.changeMode);
|
|
1408
|
+
const scopedResult = await executeMutations(doc, scopedSteps, args.changeMode, scopedPre.revision);
|
|
1394
1409
|
const scopedPost = await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1395
1410
|
const verification = evaluateChecks(scopedPre, scopedPost, [{ kind: 'revision-changed' }]);
|
|
1396
1411
|
return {
|
|
@@ -1452,7 +1467,7 @@ async function runDeleteText(doc, args) {
|
|
|
1452
1467
|
},
|
|
1453
1468
|
args: {},
|
|
1454
1469
|
}));
|
|
1455
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
1470
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
1456
1471
|
const revision = asRecord(asRecord(result)?.revision);
|
|
1457
1472
|
const postIdentity = args.changeMode === 'tracked'
|
|
1458
1473
|
? await readDocumentIdentity(doc)
|
|
@@ -1495,7 +1510,7 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1495
1510
|
for (const domain of snapshotDomainsForSelector(selector))
|
|
1496
1511
|
domains.add(domain);
|
|
1497
1512
|
}
|
|
1498
|
-
const pre = await docSnapshot.
|
|
1513
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
1499
1514
|
try {
|
|
1500
1515
|
if (args.selectors.length === 0) {
|
|
1501
1516
|
return failedReceipt('delete_blocks', new Error('selectors must be non-empty'), pre);
|
|
@@ -1588,7 +1603,7 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1588
1603
|
});
|
|
1589
1604
|
}
|
|
1590
1605
|
}
|
|
1591
|
-
const post = await docSnapshot.
|
|
1606
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
1592
1607
|
// Tracked deletions leave the block in place until the revision is decided,
|
|
1593
1608
|
// so the block count cannot move — count the structural revisions instead.
|
|
1594
1609
|
// Direct deletions must show one fewer block of each deleted node type.
|
|
@@ -1706,7 +1721,7 @@ async function runInsertListItems(doc, args) {
|
|
|
1706
1721
|
}
|
|
1707
1722
|
}
|
|
1708
1723
|
async function runAddListItems(doc, args, opts) {
|
|
1709
|
-
const pre = await docSnapshot.
|
|
1724
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1710
1725
|
try {
|
|
1711
1726
|
const needle = args.anchorText?.trim();
|
|
1712
1727
|
const anchorNodeId = args.anchorNodeId?.trim();
|
|
@@ -2240,7 +2255,7 @@ async function runSplitList(doc, args) {
|
|
|
2240
2255
|
}
|
|
2241
2256
|
}
|
|
2242
2257
|
async function runCreateTable(doc, args) {
|
|
2243
|
-
const pre = await docSnapshot.
|
|
2258
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
2244
2259
|
try {
|
|
2245
2260
|
if (!Number.isInteger(args.rows) || args.rows < 1 || !Number.isInteger(args.columns) || args.columns < 1) {
|
|
2246
2261
|
return failedReceipt('create_table', new Error('rows and columns must be positive integers'), pre);
|
|
@@ -2268,7 +2283,7 @@ async function runCreateTable(doc, args) {
|
|
|
2268
2283
|
});
|
|
2269
2284
|
}
|
|
2270
2285
|
}
|
|
2271
|
-
const post = await docSnapshot.
|
|
2286
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
2272
2287
|
const verification = evaluateChecks(pre, post, [
|
|
2273
2288
|
{ kind: 'revision-changed' },
|
|
2274
2289
|
{ kind: 'block-count-delta', nodeType: 'table', delta: 1 },
|
|
@@ -2288,7 +2303,7 @@ async function runCreateTable(doc, args) {
|
|
|
2288
2303
|
}
|
|
2289
2304
|
}
|
|
2290
2305
|
async function runCommentParagraphs(doc, args) {
|
|
2291
|
-
const pre = await docSnapshot.
|
|
2306
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2292
2307
|
try {
|
|
2293
2308
|
const blocks = pre.blocks.filter((b) => {
|
|
2294
2309
|
if (args.scope === 'all') {
|
|
@@ -2330,13 +2345,18 @@ async function runCommentParagraphs(doc, args) {
|
|
|
2330
2345
|
}
|
|
2331
2346
|
}
|
|
2332
2347
|
async function runAddComments(doc, args) {
|
|
2333
|
-
const
|
|
2348
|
+
const selectorList = args.selectors?.length ? args.selectors : args.selector ? [args.selector] : [];
|
|
2349
|
+
const domains = new Set(['blocks']);
|
|
2350
|
+
for (const selector of selectorList) {
|
|
2351
|
+
for (const domain of snapshotDomainsForSelector(selector))
|
|
2352
|
+
domains.add(domain);
|
|
2353
|
+
}
|
|
2354
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
2334
2355
|
try {
|
|
2335
2356
|
// Batch form: `selectors` comments many blocks in ONE action call so the
|
|
2336
2357
|
// model never fans out N concurrent add_comments tool calls (which race the
|
|
2337
2358
|
// shared document and the comment-count verification). A single `selector`
|
|
2338
2359
|
// is the one-target shorthand. Comments are applied sequentially.
|
|
2339
|
-
const selectorList = args.selectors?.length ? args.selectors : args.selector ? [args.selector] : [];
|
|
2340
2360
|
if (!selectorList.length) {
|
|
2341
2361
|
return failedReceipt('add_comments', new Error('add_comments requires a "selector" or a non-empty "selectors" array'), pre);
|
|
2342
2362
|
}
|
|
@@ -2350,7 +2370,21 @@ async function runAddComments(doc, args) {
|
|
|
2350
2370
|
unresolved.push(sel);
|
|
2351
2371
|
}
|
|
2352
2372
|
if (!resolved.length) {
|
|
2353
|
-
return
|
|
2373
|
+
return {
|
|
2374
|
+
status: 'failed',
|
|
2375
|
+
intent: 'add_comments',
|
|
2376
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
2377
|
+
selectedTargets: unresolved.map((selector) => ({ selector, matched: [] })),
|
|
2378
|
+
executedOperations: [],
|
|
2379
|
+
verification: [],
|
|
2380
|
+
errors: [
|
|
2381
|
+
{
|
|
2382
|
+
code: 'TARGET_NOT_FOUND',
|
|
2383
|
+
message: 'none of the requested comment selectors resolved to a block',
|
|
2384
|
+
recovery: { kind: 'reinspect' },
|
|
2385
|
+
},
|
|
2386
|
+
],
|
|
2387
|
+
};
|
|
2354
2388
|
}
|
|
2355
2389
|
const executed = [];
|
|
2356
2390
|
for (const { commentText, nodeId } of resolved.map((r) => ({ commentText: args.commentText, nodeId: r.nodeId }))) {
|
|
@@ -2359,15 +2393,20 @@ async function runAddComments(doc, args) {
|
|
|
2359
2393
|
}
|
|
2360
2394
|
const post = await docSnapshot.buildDocumentSnapshot(doc);
|
|
2361
2395
|
const verification = evaluateChecks(pre, post, [{ kind: 'comment-count-delta', delta: resolved.length }]);
|
|
2396
|
+
const verified = verification.every((v) => v.passed);
|
|
2397
|
+
const status = !verified ? 'failed' : unresolved.length > 0 ? 'partial' : 'ok';
|
|
2362
2398
|
return {
|
|
2363
|
-
status
|
|
2399
|
+
status,
|
|
2364
2400
|
intent: 'add_comments',
|
|
2365
2401
|
...(unresolved.length
|
|
2366
2402
|
? { note: `${unresolved.length} selector(s) did not resolve to a block and were skipped` }
|
|
2367
2403
|
: {}),
|
|
2368
2404
|
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
2369
2405
|
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
2370
|
-
selectedTargets:
|
|
2406
|
+
selectedTargets: [
|
|
2407
|
+
...resolved.map((r) => ({ selector: r.selector, matched: [r.nodeId] })),
|
|
2408
|
+
...unresolved.map((selector) => ({ selector, matched: [] })),
|
|
2409
|
+
],
|
|
2371
2410
|
executedOperations: executed,
|
|
2372
2411
|
verification,
|
|
2373
2412
|
};
|
|
@@ -2543,7 +2582,7 @@ async function runReplyToComment(doc, args) {
|
|
|
2543
2582
|
}
|
|
2544
2583
|
}
|
|
2545
2584
|
async function runRewriteBlock(doc, args) {
|
|
2546
|
-
const pre = await docSnapshot.
|
|
2585
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2547
2586
|
try {
|
|
2548
2587
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
2549
2588
|
if (!target) {
|
|
@@ -2566,9 +2605,9 @@ async function runRewriteBlock(doc, args) {
|
|
|
2566
2605
|
},
|
|
2567
2606
|
},
|
|
2568
2607
|
];
|
|
2569
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
2608
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
2570
2609
|
const preservation = await preserveRunPatternAfterRewrite(doc, target.nodeId, target.text, preRuns, normalizedText);
|
|
2571
|
-
const post = await docSnapshot.
|
|
2610
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2572
2611
|
const rewrittenBlock = findSnapshotTextByNodeId(post, target.nodeId);
|
|
2573
2612
|
const verification = [
|
|
2574
2613
|
revisionVerification(pre.revision, post.revision, true),
|
|
@@ -2977,7 +3016,7 @@ async function listAllTrackedChanges(listFn) {
|
|
|
2977
3016
|
}
|
|
2978
3017
|
}
|
|
2979
3018
|
async function runNormalizeBodyFontSize(doc, args) {
|
|
2980
|
-
const pre = await docSnapshot.
|
|
3019
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2981
3020
|
try {
|
|
2982
3021
|
if (!Number.isFinite(args.fontSize) || args.fontSize <= 0) {
|
|
2983
3022
|
return failedReceipt('normalize_body_font_size', new Error('fontSize must be a positive number'), pre);
|
|
@@ -2994,7 +3033,7 @@ async function runNormalizeBodyFontSize(doc, args) {
|
|
|
2994
3033
|
where: { by: 'block', nodeType: block.nodeType, nodeId: block.nodeId },
|
|
2995
3034
|
args: { inline: { fontSize: args.fontSize }, scope: 'block' },
|
|
2996
3035
|
}));
|
|
2997
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3036
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
2998
3037
|
const post = await docSnapshot.buildDocumentSnapshot(doc);
|
|
2999
3038
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3000
3039
|
return {
|
|
@@ -3025,7 +3064,7 @@ async function runNormalizeBodyFontSize(doc, args) {
|
|
|
3025
3064
|
* tracked-safe (changeMode).
|
|
3026
3065
|
*/
|
|
3027
3066
|
async function runSetFontFamily(doc, args) {
|
|
3028
|
-
const pre = await docSnapshot.
|
|
3067
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3029
3068
|
try {
|
|
3030
3069
|
const fontFamily = args.fontFamily.trim();
|
|
3031
3070
|
if (fontFamily.length === 0) {
|
|
@@ -3152,7 +3191,7 @@ async function runSetFontFamily(doc, args) {
|
|
|
3152
3191
|
* the recreated block inside an adjacent table cell.
|
|
3153
3192
|
*/
|
|
3154
3193
|
async function runApplyStyle(doc, args) {
|
|
3155
|
-
const pre = await docSnapshot.
|
|
3194
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3156
3195
|
try {
|
|
3157
3196
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3158
3197
|
if (!target) {
|
|
@@ -3254,7 +3293,7 @@ async function runApplyStyle(doc, args) {
|
|
|
3254
3293
|
* escape to superdoc_execute_code and apply the alignment untracked (no pPrChange).
|
|
3255
3294
|
*/
|
|
3256
3295
|
async function runFormatParagraph(doc, args) {
|
|
3257
|
-
const pre = await docSnapshot.
|
|
3296
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3258
3297
|
try {
|
|
3259
3298
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3260
3299
|
if (!target) {
|
|
@@ -3273,7 +3312,7 @@ async function runFormatParagraph(doc, args) {
|
|
|
3273
3312
|
args: { alignment: normalizedAlignment, scope: 'block' },
|
|
3274
3313
|
},
|
|
3275
3314
|
];
|
|
3276
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3315
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
3277
3316
|
const post = await docSnapshot.buildDocumentSnapshot(doc);
|
|
3278
3317
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3279
3318
|
return {
|
|
@@ -3308,7 +3347,7 @@ async function runFormatParagraph(doc, args) {
|
|
|
3308
3347
|
* still anchor on the (now tracked-deleted) source span and land right after it.
|
|
3309
3348
|
*/
|
|
3310
3349
|
async function runMoveText(doc, args) {
|
|
3311
|
-
const pre = await docSnapshot.
|
|
3350
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3312
3351
|
try {
|
|
3313
3352
|
if (!args.text || args.text.length === 0) {
|
|
3314
3353
|
return failedReceipt('move_text', new Error('text (the exact source span to move) is required'), pre);
|
|
@@ -3425,7 +3464,7 @@ async function runMoveText(doc, args) {
|
|
|
3425
3464
|
* instead of inserting blank paragraphs.
|
|
3426
3465
|
*/
|
|
3427
3466
|
async function runSetParagraphSpacing(doc, args) {
|
|
3428
|
-
const pre = await docSnapshot.
|
|
3467
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3429
3468
|
try {
|
|
3430
3469
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3431
3470
|
if (!target) {
|
|
@@ -3482,7 +3521,7 @@ async function runSetParagraphSpacing(doc, args) {
|
|
|
3482
3521
|
* THE way to "start X on a new page" instead of padding with empty paragraphs.
|
|
3483
3522
|
*/
|
|
3484
3523
|
async function runInsertPageBreak(doc, args) {
|
|
3485
|
-
const pre = await docSnapshot.
|
|
3524
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3486
3525
|
try {
|
|
3487
3526
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3488
3527
|
if (!target) {
|
|
@@ -3519,7 +3558,7 @@ async function runInsertPageBreak(doc, args) {
|
|
|
3519
3558
|
* Finds the text in the body and applies a link over its range.
|
|
3520
3559
|
*/
|
|
3521
3560
|
async function runAddHyperlink(doc, args) {
|
|
3522
|
-
const pre = await docSnapshot.
|
|
3561
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3523
3562
|
try {
|
|
3524
3563
|
const needle = args.text;
|
|
3525
3564
|
const url = args.url;
|
|
@@ -3577,7 +3616,7 @@ async function runAddHyperlink(doc, args) {
|
|
|
3577
3616
|
* dialect, and silently apply the formatting untracked.
|
|
3578
3617
|
*/
|
|
3579
3618
|
async function runFormatText(doc, args) {
|
|
3580
|
-
const pre = await docSnapshot.
|
|
3619
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3581
3620
|
try {
|
|
3582
3621
|
const inline = {};
|
|
3583
3622
|
if (args.bold === true)
|
|
@@ -3738,7 +3777,7 @@ function findRanges(haystack, needle, caseSensitive) {
|
|
|
3738
3777
|
return ranges;
|
|
3739
3778
|
}
|
|
3740
3779
|
async function runApplyLetterSpacing(doc, args) {
|
|
3741
|
-
const pre = await docSnapshot.
|
|
3780
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3742
3781
|
try {
|
|
3743
3782
|
if (!Number.isFinite(args.letterSpacing)) {
|
|
3744
3783
|
return failedReceipt('apply_letter_spacing', new Error('letterSpacing must be a finite number'), pre);
|
|
@@ -3758,7 +3797,7 @@ async function runApplyLetterSpacing(doc, args) {
|
|
|
3758
3797
|
args: { inline: { letterSpacing: args.letterSpacing }, scope: 'block' },
|
|
3759
3798
|
},
|
|
3760
3799
|
];
|
|
3761
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3800
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
3762
3801
|
const post = await docSnapshot.buildDocumentSnapshot(doc);
|
|
3763
3802
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3764
3803
|
return {
|
|
@@ -3916,7 +3955,10 @@ async function runMoveRange(doc, args) {
|
|
|
3916
3955
|
}
|
|
3917
3956
|
}
|
|
3918
3957
|
async function runInsertToc(doc, args) {
|
|
3919
|
-
const
|
|
3958
|
+
const placement = args.placement ?? { at: 'document_start' };
|
|
3959
|
+
const pre = placement.at === 'before' || placement.at === 'after'
|
|
3960
|
+
? await docSnapshot.buildMutationSnapshot(doc)
|
|
3961
|
+
: await docSnapshot.buildDocumentSnapshot(doc);
|
|
3920
3962
|
try {
|
|
3921
3963
|
const tocFn = maybeMethod(doc, ['create', 'tableOfContents']);
|
|
3922
3964
|
if (!tocFn) {
|
|
@@ -3924,11 +3966,11 @@ async function runInsertToc(doc, args) {
|
|
|
3924
3966
|
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
3925
3967
|
});
|
|
3926
3968
|
}
|
|
3927
|
-
const
|
|
3969
|
+
const resolvedPlacement = resolvePlacement(placement, pre);
|
|
3928
3970
|
const executed = [];
|
|
3929
|
-
let tocPlacement =
|
|
3971
|
+
let tocPlacement = resolvedPlacement;
|
|
3930
3972
|
if (args.title) {
|
|
3931
|
-
const headingResult = await executeCreateHeading(doc, args.title, 1,
|
|
3973
|
+
const headingResult = await executeCreateHeading(doc, args.title, 1, resolvedPlacement, args.changeMode);
|
|
3932
3974
|
executed.push({ operationId: 'doc.create.heading', result: headingResult });
|
|
3933
3975
|
const headingNodeId = asString(asRecord(asRecord(headingResult)?.heading)?.nodeId);
|
|
3934
3976
|
if (headingNodeId) {
|
|
@@ -4055,7 +4097,7 @@ async function runStyleTable(doc, args) {
|
|
|
4055
4097
|
* should reach for instead of delete-and-recreate or insert/undo churn.
|
|
4056
4098
|
*/
|
|
4057
4099
|
async function runMoveTable(doc, args) {
|
|
4058
|
-
const pre = await docSnapshot.
|
|
4100
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc);
|
|
4059
4101
|
try {
|
|
4060
4102
|
const tableOrdinal = args.tableOrdinal ?? 1;
|
|
4061
4103
|
const table = await resolveTableContextQuick(doc, tableOrdinal);
|
|
@@ -4581,8 +4623,8 @@ function parseScopedReplaceArgs(args) {
|
|
|
4581
4623
|
changeMode: args.changeMode === 'tracked' ? 'tracked' : 'direct',
|
|
4582
4624
|
};
|
|
4583
4625
|
}
|
|
4584
|
-
async function
|
|
4585
|
-
const pre = await docSnapshot.
|
|
4626
|
+
async function runScopedReplaceUnchecked(doc, args) {
|
|
4627
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
4586
4628
|
const matched = docSnapshot.resolveSnapshotSelector(pre, args.selector);
|
|
4587
4629
|
if (matched.length !== 1) {
|
|
4588
4630
|
const failure = describeSelectorFailure(args.selector, matched, pre, args.edits.map((e) => e.find));
|
|
@@ -4679,7 +4721,7 @@ async function runScopedReplace(doc, args) {
|
|
|
4679
4721
|
// applies directly, so restoring marks adds no second reviewable change.
|
|
4680
4722
|
const trackedPreRuns = await captureBlockRuns(doc, nodeId, block.text);
|
|
4681
4723
|
try {
|
|
4682
|
-
result = await executeMutations(doc, spanSteps, args.changeMode);
|
|
4724
|
+
result = await executeMutations(doc, spanSteps, args.changeMode, pre.revision);
|
|
4683
4725
|
if (result != null)
|
|
4684
4726
|
await preserveScopedRunPattern(doc, nodeId, block.text, trackedPreRuns, planned);
|
|
4685
4727
|
}
|
|
@@ -4701,10 +4743,10 @@ async function runScopedReplace(doc, args) {
|
|
|
4701
4743
|
args: { replacement: { text: expected }, style: preserveRewriteStyle() },
|
|
4702
4744
|
},
|
|
4703
4745
|
];
|
|
4704
|
-
result = await executeMutations(doc, steps, args.changeMode);
|
|
4746
|
+
result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
4705
4747
|
await preserveScopedRunPattern(doc, nodeId, block.text, preRuns, planned);
|
|
4706
4748
|
}
|
|
4707
|
-
const post = await docSnapshot.
|
|
4749
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
4708
4750
|
const postBlock = findTextTarget(post, nodeId);
|
|
4709
4751
|
// In tracked mode the deleted text is still present in the block, so only
|
|
4710
4752
|
// require the inserted text to be visible.
|
|
@@ -4743,6 +4785,14 @@ async function runScopedReplace(doc, args) {
|
|
|
4743
4785
|
],
|
|
4744
4786
|
};
|
|
4745
4787
|
}
|
|
4788
|
+
async function runScopedReplace(doc, args) {
|
|
4789
|
+
try {
|
|
4790
|
+
return await runScopedReplaceUnchecked(doc, args);
|
|
4791
|
+
}
|
|
4792
|
+
catch (error) {
|
|
4793
|
+
return failedReceipt('replace_text', error);
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4746
4796
|
/**
|
|
4747
4797
|
* Build a `doc.format.apply` `inline` payload from a block row's sampled look.
|
|
4748
4798
|
* Shared by apply_style (likeText copy) and add_list_items (anchor auto-match)
|
|
@@ -5280,8 +5330,18 @@ async function listBlockRows(doc) {
|
|
|
5280
5330
|
const fn = maybeMethod(doc, ['blocks', 'list']);
|
|
5281
5331
|
if (!fn)
|
|
5282
5332
|
return [];
|
|
5283
|
-
const
|
|
5284
|
-
|
|
5333
|
+
const rows = [];
|
|
5334
|
+
const pageSize = 250;
|
|
5335
|
+
let offset = 0;
|
|
5336
|
+
while (true) {
|
|
5337
|
+
const raw = (await fn({ offset, limit: pageSize }));
|
|
5338
|
+
const page = Array.isArray(raw?.blocks) ? raw.blocks : [];
|
|
5339
|
+
rows.push(...page);
|
|
5340
|
+
offset += page.length;
|
|
5341
|
+
const total = typeof raw?.total === 'number' && Number.isFinite(raw.total) ? raw.total : rows.length;
|
|
5342
|
+
if (page.length === 0 || offset >= total)
|
|
5343
|
+
return rows;
|
|
5344
|
+
}
|
|
5285
5345
|
}
|
|
5286
5346
|
function blockNumbering(row) {
|
|
5287
5347
|
const numbering = row?.numbering;
|
|
@@ -5447,7 +5507,7 @@ async function convertNumberedRange(doc, fromMarker, toMarker, kind, tracked) {
|
|
|
5447
5507
|
};
|
|
5448
5508
|
}
|
|
5449
5509
|
async function convertParagraphRange(doc, fromText, toText, kind, tracked) {
|
|
5450
|
-
const pre = await docSnapshot.
|
|
5510
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
5451
5511
|
const blocks = pre.blocks ?? [];
|
|
5452
5512
|
const findBlock = (needle) => blocks.findIndex((b) => b.nodeType === 'paragraph' && (b.text ?? '').toLowerCase().includes(needle.toLowerCase()));
|
|
5453
5513
|
const fromIdx = findBlock(fromText);
|
|
@@ -5491,7 +5551,7 @@ async function convertParagraphRange(doc, fromText, toText, kind, tracked) {
|
|
|
5491
5551
|
};
|
|
5492
5552
|
}
|
|
5493
5553
|
await createListFromParagraphRange(createFn, kind, range[0].nodeId, range[range.length - 1].nodeId, tracked ? 'tracked' : undefined);
|
|
5494
|
-
const post = await docSnapshot.
|
|
5554
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'lists'] });
|
|
5495
5555
|
const postList = (post.lists ?? []).find((l) => l.items.some((it) => it.nodeId === range[0].nodeId));
|
|
5496
5556
|
const allInOneList = postList != null && range.every((b) => postList.items.some((it) => it.nodeId === b.nodeId));
|
|
5497
5557
|
return {
|
|
@@ -5760,7 +5820,7 @@ async function appendListAtPlacement(doc, args) {
|
|
|
5760
5820
|
const selector = placement && isRecord(placement.selector) ? placement.selector : null;
|
|
5761
5821
|
if (items.length === 0 || !placement || (at !== 'after' && at !== 'before') || !selector)
|
|
5762
5822
|
return null;
|
|
5763
|
-
const pre = await docSnapshot.
|
|
5823
|
+
const pre = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
5764
5824
|
const blocks = pre.blocks ?? [];
|
|
5765
5825
|
// Resolve through the SHARED selector resolver so the full selector
|
|
5766
5826
|
// vocabulary works (nodeId / textSearch / ordinal / relative / ref) and
|
|
@@ -5821,7 +5881,7 @@ async function appendListAtPlacement(doc, args) {
|
|
|
5821
5881
|
const listIds = typeof args.headingText === 'string' && args.headingText.length > 0 ? createdIds.slice(1) : createdIds;
|
|
5822
5882
|
await createListFromParagraphRange(listsCreateFn, kind, listIds[0], listIds[listIds.length - 1], parseChangeMode(args.changeMode));
|
|
5823
5883
|
// Verify both promises: the items form one list AND it sits at the anchor.
|
|
5824
|
-
const post = await docSnapshot.
|
|
5884
|
+
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: ['blocks', 'lists'] });
|
|
5825
5885
|
const postBlocks = post.blocks ?? [];
|
|
5826
5886
|
const postList = (post.lists ?? []).find((l) => l.items.some((it) => it.nodeId === listIds[0]));
|
|
5827
5887
|
const listOk = postList != null && listIds.every((id) => postList.items.some((it) => it.nodeId === id));
|
|
@@ -6384,7 +6444,7 @@ async function runAttachNumbering(doc, args) {
|
|
|
6384
6444
|
verification: [{ check: { kind: 'marker-rendered' }, passed: !!newMarker }],
|
|
6385
6445
|
};
|
|
6386
6446
|
}
|
|
6387
|
-
async function
|
|
6447
|
+
async function dispatchSuperdocPerformAction(doc, args) {
|
|
6388
6448
|
if (!isRecord(args)) {
|
|
6389
6449
|
throw new errors.SuperDocCliError('superdoc_perform_action arguments must be an object', {
|
|
6390
6450
|
code: 'INVALID_ARGUMENT',
|
|
@@ -6996,6 +7056,24 @@ async function superdocPerformAction(doc, args) {
|
|
|
6996
7056
|
}
|
|
6997
7057
|
}
|
|
6998
7058
|
}
|
|
7059
|
+
async function superdocPerformAction(doc, args) {
|
|
7060
|
+
try {
|
|
7061
|
+
return await dispatchSuperdocPerformAction(doc, args);
|
|
7062
|
+
}
|
|
7063
|
+
catch (error) {
|
|
7064
|
+
if (!(error instanceof docSnapshot.MutationSnapshotError))
|
|
7065
|
+
throw error;
|
|
7066
|
+
const action = isRecord(args) && isActionName(args.action) ? args.action : 'superdoc_perform_action';
|
|
7067
|
+
return {
|
|
7068
|
+
status: 'failed',
|
|
7069
|
+
intent: action,
|
|
7070
|
+
selectedTargets: [],
|
|
7071
|
+
executedOperations: [],
|
|
7072
|
+
verification: [],
|
|
7073
|
+
errors: [{ code: error.code, message: error.message, recovery: { kind: 'reinspect' } }],
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
6999
7077
|
function parseChangeMode(value) {
|
|
7000
7078
|
if (value === 'direct' || value === 'tracked')
|
|
7001
7079
|
return value;
|