@superdoc/sdk 2.10.0-next.4 → 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/generated/contract.cjs +1 -0
- package/dist/generated/contract.js +2 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +12 -0
- package/dist/runtime/document-rpc.cjs +27 -0
- package/dist/runtime/document-rpc.d.ts +2 -0
- package/dist/runtime/document-rpc.js +25 -0
- package/dist/runtime/host.cjs +35 -2
- package/dist/runtime/host.d.ts +1 -0
- package/dist/runtime/host.js +36 -3
- package/dist/runtime/process.cjs +32 -0
- package/dist/runtime/process.d.ts +14 -0
- package/dist/runtime/process.js +32 -0
- 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 +8 -7
package/dist/agent/actions.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SuperDocCliError } from '../runtime/errors.js';
|
|
|
2
2
|
import { runSuperdocListTransformWorkflow } from '../action-primitives/tools/list-transform.js';
|
|
3
3
|
import { runSuperdocStructureInsertWorkflow } from '../action-primitives/tools/structure-insert.js';
|
|
4
4
|
import { runSuperdocTextTransformWorkflow } from '../action-primitives/tools/text-transform.js';
|
|
5
|
-
import { buildDocumentSnapshot, matchRunsForBlock, resolveSnapshotSelector, } from './doc-snapshot.js';
|
|
5
|
+
import { buildDocumentSnapshot, buildMutationSnapshot, matchRunsForBlock, MutationSnapshotError, resolveSnapshotSelector, } from './doc-snapshot.js';
|
|
6
6
|
const ACTION_NAMES = [
|
|
7
7
|
'insert_paragraphs',
|
|
8
8
|
'insert_heading',
|
|
@@ -511,6 +511,8 @@ function revisionVerification(preRevision, postRevision, expectChanged) {
|
|
|
511
511
|
}
|
|
512
512
|
function failedReceipt(intent, err, preSnapshot) {
|
|
513
513
|
const message = reasonOf(err);
|
|
514
|
+
const snapshotError = err instanceof MutationSnapshotError ? err : null;
|
|
515
|
+
const errorCode = snapshotError?.code ?? asString(asRecord(err)?.code) ?? 'ACTION_FAILED';
|
|
514
516
|
return {
|
|
515
517
|
status: 'failed',
|
|
516
518
|
intent,
|
|
@@ -520,7 +522,13 @@ function failedReceipt(intent, err, preSnapshot) {
|
|
|
520
522
|
selectedTargets: [],
|
|
521
523
|
executedOperations: [],
|
|
522
524
|
verification: [],
|
|
523
|
-
errors: [
|
|
525
|
+
errors: [
|
|
526
|
+
{
|
|
527
|
+
code: errorCode,
|
|
528
|
+
message,
|
|
529
|
+
...(snapshotError || errorCode === 'REVISION_CONFLICT' ? { recovery: { kind: 'reinspect' } } : {}),
|
|
530
|
+
},
|
|
531
|
+
],
|
|
524
532
|
};
|
|
525
533
|
}
|
|
526
534
|
async function receiptFromWorkflowResult(doc, intent, pre, workflowResult, selectedTargets = [], checks = [{ kind: 'revision-changed' }]) {
|
|
@@ -564,14 +572,16 @@ async function receiptFromWorkflowResult(doc, intent, pre, workflowResult, selec
|
|
|
564
572
|
}
|
|
565
573
|
function buildFullBlockTextTarget(snapshot, blockId) {
|
|
566
574
|
const block = snapshot.blocks.find((entry) => entry.nodeId === blockId);
|
|
567
|
-
|
|
575
|
+
const cell = block ? null : snapshot.tables.flatMap((table) => table.cells).find((entry) => entry.nodeId === blockId);
|
|
576
|
+
const text = block?.text ?? cell?.text;
|
|
577
|
+
if (text == null)
|
|
568
578
|
return null;
|
|
569
579
|
return {
|
|
570
580
|
kind: 'text',
|
|
571
581
|
blockId,
|
|
572
582
|
range: {
|
|
573
583
|
start: 0,
|
|
574
|
-
end:
|
|
584
|
+
end: text.length,
|
|
575
585
|
},
|
|
576
586
|
};
|
|
577
587
|
}
|
|
@@ -624,8 +634,8 @@ function evaluateChecks(pre, post, checks) {
|
|
|
624
634
|
else if (check.kind === 'comment-count-delta') {
|
|
625
635
|
results.push({
|
|
626
636
|
check,
|
|
627
|
-
passed: post.comments
|
|
628
|
-
detail: `pre=${pre.comments
|
|
637
|
+
passed: post.counts.comments - pre.counts.comments === check.delta,
|
|
638
|
+
detail: `pre=${pre.counts.comments} post=${post.counts.comments}`,
|
|
629
639
|
});
|
|
630
640
|
}
|
|
631
641
|
else if (check.kind === 'tracked-change-count-delta') {
|
|
@@ -1037,7 +1047,9 @@ async function styleTableCells(doc, cells, changeMode) {
|
|
|
1037
1047
|
return styled.length;
|
|
1038
1048
|
}
|
|
1039
1049
|
async function runInsertParagraphs(doc, args) {
|
|
1040
|
-
const pre =
|
|
1050
|
+
const pre = args.placement?.at === 'before' || args.placement?.at === 'after'
|
|
1051
|
+
? await buildMutationSnapshot(doc, { includeDomains: ['blocks'] })
|
|
1052
|
+
: await buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1041
1053
|
try {
|
|
1042
1054
|
// `texts` is the canonical input; a single `text` is normalized to one item
|
|
1043
1055
|
// upstream in the dispatcher, but tolerate it here too.
|
|
@@ -1064,7 +1076,7 @@ async function runInsertParagraphs(doc, args) {
|
|
|
1064
1076
|
if (created) {
|
|
1065
1077
|
return { kind: 'after', target: { kind: 'block', nodeType: created.nodeType, nodeId: created.nodeId } };
|
|
1066
1078
|
}
|
|
1067
|
-
const mid = await
|
|
1079
|
+
const mid = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1068
1080
|
const last = lastBlock(mid);
|
|
1069
1081
|
return last
|
|
1070
1082
|
? { kind: 'after', target: { kind: 'block', nodeType: last.nodeType, nodeId: last.nodeId } }
|
|
@@ -1126,7 +1138,9 @@ async function runInsertParagraphs(doc, args) {
|
|
|
1126
1138
|
}
|
|
1127
1139
|
}
|
|
1128
1140
|
async function runInsertHeading(doc, args) {
|
|
1129
|
-
const pre =
|
|
1141
|
+
const pre = args.placement?.at === 'before' || args.placement?.at === 'after'
|
|
1142
|
+
? await buildMutationSnapshot(doc, { includeDomains: ['blocks'] })
|
|
1143
|
+
: await buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1130
1144
|
try {
|
|
1131
1145
|
const placement = resolvePlacement(args.placement, pre);
|
|
1132
1146
|
const result = await executeCreateHeading(doc, args.text, args.level, placement, args.changeMode);
|
|
@@ -1151,7 +1165,7 @@ async function runReplaceText(doc, args) {
|
|
|
1151
1165
|
const requiresBlockSnapshot = args.selector != null;
|
|
1152
1166
|
const preIdentity = requiresBlockSnapshot ? null : await readDocumentIdentity(doc);
|
|
1153
1167
|
const pre = requiresBlockSnapshot
|
|
1154
|
-
? await
|
|
1168
|
+
? await buildMutationSnapshot(doc, { includeDomains: selectorDomains ?? ['blocks'] })
|
|
1155
1169
|
: snapshotFromIdentity(preIdentity);
|
|
1156
1170
|
try {
|
|
1157
1171
|
if (args.edits.length === 0) {
|
|
@@ -1203,6 +1217,7 @@ async function runReplaceText(doc, args) {
|
|
|
1203
1217
|
},
|
|
1204
1218
|
},
|
|
1205
1219
|
];
|
|
1220
|
+
applyExpectedRevision = pre.revision;
|
|
1206
1221
|
selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
|
|
1207
1222
|
preserveTargets.push({
|
|
1208
1223
|
nodeId: target.nodeId,
|
|
@@ -1275,7 +1290,7 @@ async function runReplaceText(doc, args) {
|
|
|
1275
1290
|
preserved.push({ nodeId: entry.nodeId, ...restored });
|
|
1276
1291
|
}
|
|
1277
1292
|
if (args.selector && selectedTargets[0]) {
|
|
1278
|
-
const post = await
|
|
1293
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: selectorDomains ?? ['blocks'] });
|
|
1279
1294
|
const blockId = selectedTargets[0].matched[0];
|
|
1280
1295
|
const postTarget = findSnapshotTextByNodeId(post, blockId);
|
|
1281
1296
|
const preTarget = findSnapshotTextByNodeId(pre, blockId);
|
|
@@ -1344,7 +1359,7 @@ async function runDeleteText(doc, args) {
|
|
|
1344
1359
|
// path). Rewriting the block minus the finds keeps the deletion local — without
|
|
1345
1360
|
// this a short/whitespace find matches document-wide and blows the target cap.
|
|
1346
1361
|
if (args.selector) {
|
|
1347
|
-
const scopedPre = await
|
|
1362
|
+
const scopedPre = await buildMutationSnapshot(doc, { includeDomains: snapshotDomainsForSelector(args.selector) });
|
|
1348
1363
|
try {
|
|
1349
1364
|
if (args.finds.length === 0) {
|
|
1350
1365
|
return failedReceipt('delete_text', new Error('finds must be non-empty'), scopedPre);
|
|
@@ -1387,7 +1402,7 @@ async function runDeleteText(doc, args) {
|
|
|
1387
1402
|
args: { replacement: { text: rewritten }, style: preserveRewriteStyle() },
|
|
1388
1403
|
},
|
|
1389
1404
|
];
|
|
1390
|
-
const scopedResult = await executeMutations(doc, scopedSteps, args.changeMode);
|
|
1405
|
+
const scopedResult = await executeMutations(doc, scopedSteps, args.changeMode, scopedPre.revision);
|
|
1391
1406
|
const scopedPost = await buildDocumentSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1392
1407
|
const verification = evaluateChecks(scopedPre, scopedPost, [{ kind: 'revision-changed' }]);
|
|
1393
1408
|
return {
|
|
@@ -1449,7 +1464,7 @@ async function runDeleteText(doc, args) {
|
|
|
1449
1464
|
},
|
|
1450
1465
|
args: {},
|
|
1451
1466
|
}));
|
|
1452
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
1467
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
1453
1468
|
const revision = asRecord(asRecord(result)?.revision);
|
|
1454
1469
|
const postIdentity = args.changeMode === 'tracked'
|
|
1455
1470
|
? await readDocumentIdentity(doc)
|
|
@@ -1492,7 +1507,7 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1492
1507
|
for (const domain of snapshotDomainsForSelector(selector))
|
|
1493
1508
|
domains.add(domain);
|
|
1494
1509
|
}
|
|
1495
|
-
const pre = await
|
|
1510
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
1496
1511
|
try {
|
|
1497
1512
|
if (args.selectors.length === 0) {
|
|
1498
1513
|
return failedReceipt('delete_blocks', new Error('selectors must be non-empty'), pre);
|
|
@@ -1585,7 +1600,7 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1585
1600
|
});
|
|
1586
1601
|
}
|
|
1587
1602
|
}
|
|
1588
|
-
const post = await
|
|
1603
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
1589
1604
|
// Tracked deletions leave the block in place until the revision is decided,
|
|
1590
1605
|
// so the block count cannot move — count the structural revisions instead.
|
|
1591
1606
|
// Direct deletions must show one fewer block of each deleted node type.
|
|
@@ -1703,7 +1718,7 @@ async function runInsertListItems(doc, args) {
|
|
|
1703
1718
|
}
|
|
1704
1719
|
}
|
|
1705
1720
|
async function runAddListItems(doc, args, opts) {
|
|
1706
|
-
const pre = await
|
|
1721
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
1707
1722
|
try {
|
|
1708
1723
|
const needle = args.anchorText?.trim();
|
|
1709
1724
|
const anchorNodeId = args.anchorNodeId?.trim();
|
|
@@ -2237,7 +2252,7 @@ async function runSplitList(doc, args) {
|
|
|
2237
2252
|
}
|
|
2238
2253
|
}
|
|
2239
2254
|
async function runCreateTable(doc, args) {
|
|
2240
|
-
const pre = await
|
|
2255
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
2241
2256
|
try {
|
|
2242
2257
|
if (!Number.isInteger(args.rows) || args.rows < 1 || !Number.isInteger(args.columns) || args.columns < 1) {
|
|
2243
2258
|
return failedReceipt('create_table', new Error('rows and columns must be positive integers'), pre);
|
|
@@ -2265,7 +2280,7 @@ async function runCreateTable(doc, args) {
|
|
|
2265
2280
|
});
|
|
2266
2281
|
}
|
|
2267
2282
|
}
|
|
2268
|
-
const post = await
|
|
2283
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
2269
2284
|
const verification = evaluateChecks(pre, post, [
|
|
2270
2285
|
{ kind: 'revision-changed' },
|
|
2271
2286
|
{ kind: 'block-count-delta', nodeType: 'table', delta: 1 },
|
|
@@ -2285,7 +2300,7 @@ async function runCreateTable(doc, args) {
|
|
|
2285
2300
|
}
|
|
2286
2301
|
}
|
|
2287
2302
|
async function runCommentParagraphs(doc, args) {
|
|
2288
|
-
const pre = await
|
|
2303
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2289
2304
|
try {
|
|
2290
2305
|
const blocks = pre.blocks.filter((b) => {
|
|
2291
2306
|
if (args.scope === 'all') {
|
|
@@ -2327,13 +2342,18 @@ async function runCommentParagraphs(doc, args) {
|
|
|
2327
2342
|
}
|
|
2328
2343
|
}
|
|
2329
2344
|
async function runAddComments(doc, args) {
|
|
2330
|
-
const
|
|
2345
|
+
const selectorList = args.selectors?.length ? args.selectors : args.selector ? [args.selector] : [];
|
|
2346
|
+
const domains = new Set(['blocks']);
|
|
2347
|
+
for (const selector of selectorList) {
|
|
2348
|
+
for (const domain of snapshotDomainsForSelector(selector))
|
|
2349
|
+
domains.add(domain);
|
|
2350
|
+
}
|
|
2351
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: [...domains] });
|
|
2331
2352
|
try {
|
|
2332
2353
|
// Batch form: `selectors` comments many blocks in ONE action call so the
|
|
2333
2354
|
// model never fans out N concurrent add_comments tool calls (which race the
|
|
2334
2355
|
// shared document and the comment-count verification). A single `selector`
|
|
2335
2356
|
// is the one-target shorthand. Comments are applied sequentially.
|
|
2336
|
-
const selectorList = args.selectors?.length ? args.selectors : args.selector ? [args.selector] : [];
|
|
2337
2357
|
if (!selectorList.length) {
|
|
2338
2358
|
return failedReceipt('add_comments', new Error('add_comments requires a "selector" or a non-empty "selectors" array'), pre);
|
|
2339
2359
|
}
|
|
@@ -2347,7 +2367,21 @@ async function runAddComments(doc, args) {
|
|
|
2347
2367
|
unresolved.push(sel);
|
|
2348
2368
|
}
|
|
2349
2369
|
if (!resolved.length) {
|
|
2350
|
-
return
|
|
2370
|
+
return {
|
|
2371
|
+
status: 'failed',
|
|
2372
|
+
intent: 'add_comments',
|
|
2373
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
2374
|
+
selectedTargets: unresolved.map((selector) => ({ selector, matched: [] })),
|
|
2375
|
+
executedOperations: [],
|
|
2376
|
+
verification: [],
|
|
2377
|
+
errors: [
|
|
2378
|
+
{
|
|
2379
|
+
code: 'TARGET_NOT_FOUND',
|
|
2380
|
+
message: 'none of the requested comment selectors resolved to a block',
|
|
2381
|
+
recovery: { kind: 'reinspect' },
|
|
2382
|
+
},
|
|
2383
|
+
],
|
|
2384
|
+
};
|
|
2351
2385
|
}
|
|
2352
2386
|
const executed = [];
|
|
2353
2387
|
for (const { commentText, nodeId } of resolved.map((r) => ({ commentText: args.commentText, nodeId: r.nodeId }))) {
|
|
@@ -2356,15 +2390,20 @@ async function runAddComments(doc, args) {
|
|
|
2356
2390
|
}
|
|
2357
2391
|
const post = await buildDocumentSnapshot(doc);
|
|
2358
2392
|
const verification = evaluateChecks(pre, post, [{ kind: 'comment-count-delta', delta: resolved.length }]);
|
|
2393
|
+
const verified = verification.every((v) => v.passed);
|
|
2394
|
+
const status = !verified ? 'failed' : unresolved.length > 0 ? 'partial' : 'ok';
|
|
2359
2395
|
return {
|
|
2360
|
-
status
|
|
2396
|
+
status,
|
|
2361
2397
|
intent: 'add_comments',
|
|
2362
2398
|
...(unresolved.length
|
|
2363
2399
|
? { note: `${unresolved.length} selector(s) did not resolve to a block and were skipped` }
|
|
2364
2400
|
: {}),
|
|
2365
2401
|
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
2366
2402
|
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
2367
|
-
selectedTargets:
|
|
2403
|
+
selectedTargets: [
|
|
2404
|
+
...resolved.map((r) => ({ selector: r.selector, matched: [r.nodeId] })),
|
|
2405
|
+
...unresolved.map((selector) => ({ selector, matched: [] })),
|
|
2406
|
+
],
|
|
2368
2407
|
executedOperations: executed,
|
|
2369
2408
|
verification,
|
|
2370
2409
|
};
|
|
@@ -2540,7 +2579,7 @@ async function runReplyToComment(doc, args) {
|
|
|
2540
2579
|
}
|
|
2541
2580
|
}
|
|
2542
2581
|
async function runRewriteBlock(doc, args) {
|
|
2543
|
-
const pre = await
|
|
2582
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2544
2583
|
try {
|
|
2545
2584
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
2546
2585
|
if (!target) {
|
|
@@ -2563,9 +2602,9 @@ async function runRewriteBlock(doc, args) {
|
|
|
2563
2602
|
},
|
|
2564
2603
|
},
|
|
2565
2604
|
];
|
|
2566
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
2605
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
2567
2606
|
const preservation = await preserveRunPatternAfterRewrite(doc, target.nodeId, target.text, preRuns, normalizedText);
|
|
2568
|
-
const post = await
|
|
2607
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2569
2608
|
const rewrittenBlock = findSnapshotTextByNodeId(post, target.nodeId);
|
|
2570
2609
|
const verification = [
|
|
2571
2610
|
revisionVerification(pre.revision, post.revision, true),
|
|
@@ -2974,7 +3013,7 @@ async function listAllTrackedChanges(listFn) {
|
|
|
2974
3013
|
}
|
|
2975
3014
|
}
|
|
2976
3015
|
async function runNormalizeBodyFontSize(doc, args) {
|
|
2977
|
-
const pre = await
|
|
3016
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
2978
3017
|
try {
|
|
2979
3018
|
if (!Number.isFinite(args.fontSize) || args.fontSize <= 0) {
|
|
2980
3019
|
return failedReceipt('normalize_body_font_size', new Error('fontSize must be a positive number'), pre);
|
|
@@ -2991,7 +3030,7 @@ async function runNormalizeBodyFontSize(doc, args) {
|
|
|
2991
3030
|
where: { by: 'block', nodeType: block.nodeType, nodeId: block.nodeId },
|
|
2992
3031
|
args: { inline: { fontSize: args.fontSize }, scope: 'block' },
|
|
2993
3032
|
}));
|
|
2994
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3033
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
2995
3034
|
const post = await buildDocumentSnapshot(doc);
|
|
2996
3035
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
2997
3036
|
return {
|
|
@@ -3022,7 +3061,7 @@ async function runNormalizeBodyFontSize(doc, args) {
|
|
|
3022
3061
|
* tracked-safe (changeMode).
|
|
3023
3062
|
*/
|
|
3024
3063
|
async function runSetFontFamily(doc, args) {
|
|
3025
|
-
const pre = await
|
|
3064
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3026
3065
|
try {
|
|
3027
3066
|
const fontFamily = args.fontFamily.trim();
|
|
3028
3067
|
if (fontFamily.length === 0) {
|
|
@@ -3149,7 +3188,7 @@ async function runSetFontFamily(doc, args) {
|
|
|
3149
3188
|
* the recreated block inside an adjacent table cell.
|
|
3150
3189
|
*/
|
|
3151
3190
|
async function runApplyStyle(doc, args) {
|
|
3152
|
-
const pre = await
|
|
3191
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3153
3192
|
try {
|
|
3154
3193
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3155
3194
|
if (!target) {
|
|
@@ -3251,7 +3290,7 @@ async function runApplyStyle(doc, args) {
|
|
|
3251
3290
|
* escape to superdoc_execute_code and apply the alignment untracked (no pPrChange).
|
|
3252
3291
|
*/
|
|
3253
3292
|
async function runFormatParagraph(doc, args) {
|
|
3254
|
-
const pre = await
|
|
3293
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3255
3294
|
try {
|
|
3256
3295
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3257
3296
|
if (!target) {
|
|
@@ -3270,7 +3309,7 @@ async function runFormatParagraph(doc, args) {
|
|
|
3270
3309
|
args: { alignment: normalizedAlignment, scope: 'block' },
|
|
3271
3310
|
},
|
|
3272
3311
|
];
|
|
3273
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3312
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
3274
3313
|
const post = await buildDocumentSnapshot(doc);
|
|
3275
3314
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3276
3315
|
return {
|
|
@@ -3305,7 +3344,7 @@ async function runFormatParagraph(doc, args) {
|
|
|
3305
3344
|
* still anchor on the (now tracked-deleted) source span and land right after it.
|
|
3306
3345
|
*/
|
|
3307
3346
|
async function runMoveText(doc, args) {
|
|
3308
|
-
const pre = await
|
|
3347
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3309
3348
|
try {
|
|
3310
3349
|
if (!args.text || args.text.length === 0) {
|
|
3311
3350
|
return failedReceipt('move_text', new Error('text (the exact source span to move) is required'), pre);
|
|
@@ -3422,7 +3461,7 @@ async function runMoveText(doc, args) {
|
|
|
3422
3461
|
* instead of inserting blank paragraphs.
|
|
3423
3462
|
*/
|
|
3424
3463
|
async function runSetParagraphSpacing(doc, args) {
|
|
3425
|
-
const pre = await
|
|
3464
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3426
3465
|
try {
|
|
3427
3466
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3428
3467
|
if (!target) {
|
|
@@ -3479,7 +3518,7 @@ async function runSetParagraphSpacing(doc, args) {
|
|
|
3479
3518
|
* THE way to "start X on a new page" instead of padding with empty paragraphs.
|
|
3480
3519
|
*/
|
|
3481
3520
|
async function runInsertPageBreak(doc, args) {
|
|
3482
|
-
const pre = await
|
|
3521
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3483
3522
|
try {
|
|
3484
3523
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
3485
3524
|
if (!target) {
|
|
@@ -3516,7 +3555,7 @@ async function runInsertPageBreak(doc, args) {
|
|
|
3516
3555
|
* Finds the text in the body and applies a link over its range.
|
|
3517
3556
|
*/
|
|
3518
3557
|
async function runAddHyperlink(doc, args) {
|
|
3519
|
-
const pre = await
|
|
3558
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3520
3559
|
try {
|
|
3521
3560
|
const needle = args.text;
|
|
3522
3561
|
const url = args.url;
|
|
@@ -3574,7 +3613,7 @@ async function runAddHyperlink(doc, args) {
|
|
|
3574
3613
|
* dialect, and silently apply the formatting untracked.
|
|
3575
3614
|
*/
|
|
3576
3615
|
async function runFormatText(doc, args) {
|
|
3577
|
-
const pre = await
|
|
3616
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
3578
3617
|
try {
|
|
3579
3618
|
const inline = {};
|
|
3580
3619
|
if (args.bold === true)
|
|
@@ -3735,7 +3774,7 @@ function findRanges(haystack, needle, caseSensitive) {
|
|
|
3735
3774
|
return ranges;
|
|
3736
3775
|
}
|
|
3737
3776
|
async function runApplyLetterSpacing(doc, args) {
|
|
3738
|
-
const pre = await
|
|
3777
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
3739
3778
|
try {
|
|
3740
3779
|
if (!Number.isFinite(args.letterSpacing)) {
|
|
3741
3780
|
return failedReceipt('apply_letter_spacing', new Error('letterSpacing must be a finite number'), pre);
|
|
@@ -3755,7 +3794,7 @@ async function runApplyLetterSpacing(doc, args) {
|
|
|
3755
3794
|
args: { inline: { letterSpacing: args.letterSpacing }, scope: 'block' },
|
|
3756
3795
|
},
|
|
3757
3796
|
];
|
|
3758
|
-
const result = await executeMutations(doc, steps, args.changeMode);
|
|
3797
|
+
const result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
3759
3798
|
const post = await buildDocumentSnapshot(doc);
|
|
3760
3799
|
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3761
3800
|
return {
|
|
@@ -3913,7 +3952,10 @@ async function runMoveRange(doc, args) {
|
|
|
3913
3952
|
}
|
|
3914
3953
|
}
|
|
3915
3954
|
async function runInsertToc(doc, args) {
|
|
3916
|
-
const
|
|
3955
|
+
const placement = args.placement ?? { at: 'document_start' };
|
|
3956
|
+
const pre = placement.at === 'before' || placement.at === 'after'
|
|
3957
|
+
? await buildMutationSnapshot(doc)
|
|
3958
|
+
: await buildDocumentSnapshot(doc);
|
|
3917
3959
|
try {
|
|
3918
3960
|
const tocFn = maybeMethod(doc, ['create', 'tableOfContents']);
|
|
3919
3961
|
if (!tocFn) {
|
|
@@ -3921,11 +3963,11 @@ async function runInsertToc(doc, args) {
|
|
|
3921
3963
|
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
3922
3964
|
});
|
|
3923
3965
|
}
|
|
3924
|
-
const
|
|
3966
|
+
const resolvedPlacement = resolvePlacement(placement, pre);
|
|
3925
3967
|
const executed = [];
|
|
3926
|
-
let tocPlacement =
|
|
3968
|
+
let tocPlacement = resolvedPlacement;
|
|
3927
3969
|
if (args.title) {
|
|
3928
|
-
const headingResult = await executeCreateHeading(doc, args.title, 1,
|
|
3970
|
+
const headingResult = await executeCreateHeading(doc, args.title, 1, resolvedPlacement, args.changeMode);
|
|
3929
3971
|
executed.push({ operationId: 'doc.create.heading', result: headingResult });
|
|
3930
3972
|
const headingNodeId = asString(asRecord(asRecord(headingResult)?.heading)?.nodeId);
|
|
3931
3973
|
if (headingNodeId) {
|
|
@@ -4052,7 +4094,7 @@ async function runStyleTable(doc, args) {
|
|
|
4052
4094
|
* should reach for instead of delete-and-recreate or insert/undo churn.
|
|
4053
4095
|
*/
|
|
4054
4096
|
async function runMoveTable(doc, args) {
|
|
4055
|
-
const pre = await
|
|
4097
|
+
const pre = await buildMutationSnapshot(doc);
|
|
4056
4098
|
try {
|
|
4057
4099
|
const tableOrdinal = args.tableOrdinal ?? 1;
|
|
4058
4100
|
const table = await resolveTableContextQuick(doc, tableOrdinal);
|
|
@@ -4578,8 +4620,8 @@ function parseScopedReplaceArgs(args) {
|
|
|
4578
4620
|
changeMode: args.changeMode === 'tracked' ? 'tracked' : 'direct',
|
|
4579
4621
|
};
|
|
4580
4622
|
}
|
|
4581
|
-
async function
|
|
4582
|
-
const pre = await
|
|
4623
|
+
async function runScopedReplaceUnchecked(doc, args) {
|
|
4624
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
4583
4625
|
const matched = resolveSnapshotSelector(pre, args.selector);
|
|
4584
4626
|
if (matched.length !== 1) {
|
|
4585
4627
|
const failure = describeSelectorFailure(args.selector, matched, pre, args.edits.map((e) => e.find));
|
|
@@ -4676,7 +4718,7 @@ async function runScopedReplace(doc, args) {
|
|
|
4676
4718
|
// applies directly, so restoring marks adds no second reviewable change.
|
|
4677
4719
|
const trackedPreRuns = await captureBlockRuns(doc, nodeId, block.text);
|
|
4678
4720
|
try {
|
|
4679
|
-
result = await executeMutations(doc, spanSteps, args.changeMode);
|
|
4721
|
+
result = await executeMutations(doc, spanSteps, args.changeMode, pre.revision);
|
|
4680
4722
|
if (result != null)
|
|
4681
4723
|
await preserveScopedRunPattern(doc, nodeId, block.text, trackedPreRuns, planned);
|
|
4682
4724
|
}
|
|
@@ -4698,10 +4740,10 @@ async function runScopedReplace(doc, args) {
|
|
|
4698
4740
|
args: { replacement: { text: expected }, style: preserveRewriteStyle() },
|
|
4699
4741
|
},
|
|
4700
4742
|
];
|
|
4701
|
-
result = await executeMutations(doc, steps, args.changeMode);
|
|
4743
|
+
result = await executeMutations(doc, steps, args.changeMode, pre.revision);
|
|
4702
4744
|
await preserveScopedRunPattern(doc, nodeId, block.text, preRuns, planned);
|
|
4703
4745
|
}
|
|
4704
|
-
const post = await
|
|
4746
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'tables'] });
|
|
4705
4747
|
const postBlock = findTextTarget(post, nodeId);
|
|
4706
4748
|
// In tracked mode the deleted text is still present in the block, so only
|
|
4707
4749
|
// require the inserted text to be visible.
|
|
@@ -4740,6 +4782,14 @@ async function runScopedReplace(doc, args) {
|
|
|
4740
4782
|
],
|
|
4741
4783
|
};
|
|
4742
4784
|
}
|
|
4785
|
+
async function runScopedReplace(doc, args) {
|
|
4786
|
+
try {
|
|
4787
|
+
return await runScopedReplaceUnchecked(doc, args);
|
|
4788
|
+
}
|
|
4789
|
+
catch (error) {
|
|
4790
|
+
return failedReceipt('replace_text', error);
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4743
4793
|
/**
|
|
4744
4794
|
* Build a `doc.format.apply` `inline` payload from a block row's sampled look.
|
|
4745
4795
|
* Shared by apply_style (likeText copy) and add_list_items (anchor auto-match)
|
|
@@ -5277,8 +5327,18 @@ async function listBlockRows(doc) {
|
|
|
5277
5327
|
const fn = maybeMethod(doc, ['blocks', 'list']);
|
|
5278
5328
|
if (!fn)
|
|
5279
5329
|
return [];
|
|
5280
|
-
const
|
|
5281
|
-
|
|
5330
|
+
const rows = [];
|
|
5331
|
+
const pageSize = 250;
|
|
5332
|
+
let offset = 0;
|
|
5333
|
+
while (true) {
|
|
5334
|
+
const raw = (await fn({ offset, limit: pageSize }));
|
|
5335
|
+
const page = Array.isArray(raw?.blocks) ? raw.blocks : [];
|
|
5336
|
+
rows.push(...page);
|
|
5337
|
+
offset += page.length;
|
|
5338
|
+
const total = typeof raw?.total === 'number' && Number.isFinite(raw.total) ? raw.total : rows.length;
|
|
5339
|
+
if (page.length === 0 || offset >= total)
|
|
5340
|
+
return rows;
|
|
5341
|
+
}
|
|
5282
5342
|
}
|
|
5283
5343
|
function blockNumbering(row) {
|
|
5284
5344
|
const numbering = row?.numbering;
|
|
@@ -5444,7 +5504,7 @@ async function convertNumberedRange(doc, fromMarker, toMarker, kind, tracked) {
|
|
|
5444
5504
|
};
|
|
5445
5505
|
}
|
|
5446
5506
|
async function convertParagraphRange(doc, fromText, toText, kind, tracked) {
|
|
5447
|
-
const pre = await
|
|
5507
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
5448
5508
|
const blocks = pre.blocks ?? [];
|
|
5449
5509
|
const findBlock = (needle) => blocks.findIndex((b) => b.nodeType === 'paragraph' && (b.text ?? '').toLowerCase().includes(needle.toLowerCase()));
|
|
5450
5510
|
const fromIdx = findBlock(fromText);
|
|
@@ -5488,7 +5548,7 @@ async function convertParagraphRange(doc, fromText, toText, kind, tracked) {
|
|
|
5488
5548
|
};
|
|
5489
5549
|
}
|
|
5490
5550
|
await createListFromParagraphRange(createFn, kind, range[0].nodeId, range[range.length - 1].nodeId, tracked ? 'tracked' : undefined);
|
|
5491
|
-
const post = await
|
|
5551
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'lists'] });
|
|
5492
5552
|
const postList = (post.lists ?? []).find((l) => l.items.some((it) => it.nodeId === range[0].nodeId));
|
|
5493
5553
|
const allInOneList = postList != null && range.every((b) => postList.items.some((it) => it.nodeId === b.nodeId));
|
|
5494
5554
|
return {
|
|
@@ -5757,7 +5817,7 @@ async function appendListAtPlacement(doc, args) {
|
|
|
5757
5817
|
const selector = placement && isRecord(placement.selector) ? placement.selector : null;
|
|
5758
5818
|
if (items.length === 0 || !placement || (at !== 'after' && at !== 'before') || !selector)
|
|
5759
5819
|
return null;
|
|
5760
|
-
const pre = await
|
|
5820
|
+
const pre = await buildMutationSnapshot(doc, { includeDomains: ['blocks'] });
|
|
5761
5821
|
const blocks = pre.blocks ?? [];
|
|
5762
5822
|
// Resolve through the SHARED selector resolver so the full selector
|
|
5763
5823
|
// vocabulary works (nodeId / textSearch / ordinal / relative / ref) and
|
|
@@ -5818,7 +5878,7 @@ async function appendListAtPlacement(doc, args) {
|
|
|
5818
5878
|
const listIds = typeof args.headingText === 'string' && args.headingText.length > 0 ? createdIds.slice(1) : createdIds;
|
|
5819
5879
|
await createListFromParagraphRange(listsCreateFn, kind, listIds[0], listIds[listIds.length - 1], parseChangeMode(args.changeMode));
|
|
5820
5880
|
// Verify both promises: the items form one list AND it sits at the anchor.
|
|
5821
|
-
const post = await
|
|
5881
|
+
const post = await buildMutationSnapshot(doc, { includeDomains: ['blocks', 'lists'] });
|
|
5822
5882
|
const postBlocks = post.blocks ?? [];
|
|
5823
5883
|
const postList = (post.lists ?? []).find((l) => l.items.some((it) => it.nodeId === listIds[0]));
|
|
5824
5884
|
const listOk = postList != null && listIds.every((id) => postList.items.some((it) => it.nodeId === id));
|
|
@@ -6381,7 +6441,7 @@ async function runAttachNumbering(doc, args) {
|
|
|
6381
6441
|
verification: [{ check: { kind: 'marker-rendered' }, passed: !!newMarker }],
|
|
6382
6442
|
};
|
|
6383
6443
|
}
|
|
6384
|
-
|
|
6444
|
+
async function dispatchSuperdocPerformAction(doc, args) {
|
|
6385
6445
|
if (!isRecord(args)) {
|
|
6386
6446
|
throw new SuperDocCliError('superdoc_perform_action arguments must be an object', {
|
|
6387
6447
|
code: 'INVALID_ARGUMENT',
|
|
@@ -6995,6 +7055,24 @@ export async function superdocPerformAction(doc, args) {
|
|
|
6995
7055
|
}
|
|
6996
7056
|
}
|
|
6997
7057
|
}
|
|
7058
|
+
export async function superdocPerformAction(doc, args) {
|
|
7059
|
+
try {
|
|
7060
|
+
return await dispatchSuperdocPerformAction(doc, args);
|
|
7061
|
+
}
|
|
7062
|
+
catch (error) {
|
|
7063
|
+
if (!(error instanceof MutationSnapshotError))
|
|
7064
|
+
throw error;
|
|
7065
|
+
const action = isRecord(args) && isActionName(args.action) ? args.action : 'superdoc_perform_action';
|
|
7066
|
+
return {
|
|
7067
|
+
status: 'failed',
|
|
7068
|
+
intent: action,
|
|
7069
|
+
selectedTargets: [],
|
|
7070
|
+
executedOperations: [],
|
|
7071
|
+
verification: [],
|
|
7072
|
+
errors: [{ code: error.code, message: error.message, recovery: { kind: 'reinspect' } }],
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
6998
7076
|
function parseChangeMode(value) {
|
|
6999
7077
|
if (value === 'direct' || value === 'tracked')
|
|
7000
7078
|
return value;
|