@superdoc/sdk 2.13.0-next.8 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/actions.cjs +118 -117
- package/dist/agent/actions.d.ts +19 -0
- package/dist/agent/actions.js +116 -117
- package/dist/agent/catalog.cjs +52 -8
- package/dist/agent/catalog.d.ts +54 -1
- package/dist/agent/catalog.js +55 -8
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.d.ts +3762 -969
- package/dist/generated/contract.cjs +1665 -2
- package/dist/generated/contract.js +1666 -2
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/presets/core.cjs +77 -15
- package/dist/presets/core.js +79 -17
- package/dist/presets.d.ts +23 -0
- package/dist/runtime/host.cjs +24 -15
- package/dist/runtime/host.d.ts +3 -1
- package/dist/runtime/host.js +24 -15
- 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/dist/tools.cjs +34 -8
- package/dist/tools.d.ts +28 -3
- package/dist/tools.js +34 -8
- package/package.json +7 -7
- package/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- package/tools/__pycache__/intent_dispatch_generated.cpython-311.pyc +0 -0
- package/tools/catalog.json +3517 -427
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +3371 -281
- package/tools/tools.generic.json +4089 -999
- package/tools/tools.openai.json +4072 -982
- package/tools/tools.vercel.json +4072 -982
package/dist/agent/actions.cjs
CHANGED
|
@@ -65,7 +65,7 @@ const ACTION_HINTS = {
|
|
|
65
65
|
delete_blocks: 'selectors[] (each resolving to ONE inspected block: list item, paragraph or heading), changeMode? — THE way to DELETE a whole LIST ITEM, paragraph or heading, bullet/number included. Use for "remove the first item under Article II" / "delete that clause". delete_text only strikes the text and leaves an empty numbered item behind. Deletes several blocks in ONE call; use delete_table for a whole table and delete_text to clear a table cell (a cell must keep a paragraph)',
|
|
66
66
|
append_list: 'items[], kind?: ordered|bullet, headingText?, headingLevel?, placement? {at:"after"|"before",selector} builds the list at that block instead of document end',
|
|
67
67
|
create_table: 'rows, columns, cellTexts? (2D: rows of cells; each cell is a plain string OR a STYLED object {runs:[{text,marks}], marks?} to format the cell — marks: bold/italic/underline/strikethrough/fontFamily/fontSize(points)/color(hex)/highlight), placement?, changeMode? — style cells HERE, do not format_text afterward; changeMode:"tracked" makes the table insertion itself a tracked change',
|
|
68
|
-
rewrite_block: "selector, text, changeMode? — REPLACES the whole block text
|
|
68
|
+
rewrite_block: "selector, text, changeMode? — REPLACES the whole block text using the document API's formatting preservation. For appending a NEW paragraph/clause use insert_paragraphs, and for small wording edits prefer replace_text with a minimal find span",
|
|
69
69
|
fill_placeholders: 'values[] and/or fields[{label?,value}], changeMode?',
|
|
70
70
|
move_range: 'fromText (text in the FIRST block of the range to move), toText? (text in the LAST block — omit to auto-extend across the whole VISUAL SECTION: from fromText up to the next heading-like/ALL-CAPS/bold title), then exactly ONE destination: afterText OR beforeText (text in the block to land after/before). Direct-only today: changeMode:"tracked" fails with no mutation because block-range deletion cannot be tracked. Moves plain paragraph/heading text only; a range containing a table, list, or image is refused with nothing changed. THE way to MOVE a contiguous block range or a visual "section" identified BY TEXT (works on styled-paragraph sections like PREAMBLE / SCHEDULE A that are NOT Word heading nodes). afterText on a heading-like block lands the range after that block\'s WHOLE section.',
|
|
71
71
|
comment_paragraphs: 'commentText, scope?: all|body, excludeBlockQuotes?',
|
|
@@ -226,6 +226,53 @@ const ACTION_ARGS = {
|
|
|
226
226
|
insert_page_break: ['selector'],
|
|
227
227
|
add_hyperlink: ['text', 'url', 'tooltip'],
|
|
228
228
|
};
|
|
229
|
+
const ACTION_DISPOSITIONS = {
|
|
230
|
+
insert_paragraphs: 'tracked',
|
|
231
|
+
insert_heading: 'tracked',
|
|
232
|
+
replace_text: 'tracked',
|
|
233
|
+
delete_text: 'tracked',
|
|
234
|
+
delete_blocks: 'tracked',
|
|
235
|
+
append_list: 'tracked',
|
|
236
|
+
create_table: 'tracked',
|
|
237
|
+
comment_paragraphs: 'direct',
|
|
238
|
+
add_comments: 'direct',
|
|
239
|
+
resolve_comments: 'direct',
|
|
240
|
+
reply_to_comment: 'direct',
|
|
241
|
+
rewrite_block: 'tracked',
|
|
242
|
+
accept_tracked_changes: 'direct',
|
|
243
|
+
reject_tracked_changes: 'direct',
|
|
244
|
+
normalize_body_font_size: 'tracked',
|
|
245
|
+
set_font_family: 'tracked',
|
|
246
|
+
apply_letter_spacing: 'tracked',
|
|
247
|
+
fill_placeholders: 'tracked',
|
|
248
|
+
move_range: 'direct',
|
|
249
|
+
insert_toc: 'tracked',
|
|
250
|
+
insert_table_row: 'tracked',
|
|
251
|
+
insert_table_column: 'tracked',
|
|
252
|
+
delete_table_row: 'tracked',
|
|
253
|
+
delete_table_column: 'tracked',
|
|
254
|
+
split_table: 'tracked',
|
|
255
|
+
convert_list: 'tracked',
|
|
256
|
+
split_list: 'direct',
|
|
257
|
+
undo_changes: 'direct',
|
|
258
|
+
redo_changes: 'direct',
|
|
259
|
+
attach_numbering: 'tracked',
|
|
260
|
+
add_list_items: 'tracked',
|
|
261
|
+
format_text: 'tracked',
|
|
262
|
+
apply_style: 'direct',
|
|
263
|
+
format_paragraph: 'tracked',
|
|
264
|
+
move_text: 'tracked',
|
|
265
|
+
style_table: 'direct',
|
|
266
|
+
move_table: 'direct',
|
|
267
|
+
delete_table: 'tracked',
|
|
268
|
+
set_paragraph_spacing: 'direct',
|
|
269
|
+
insert_page_break: 'direct',
|
|
270
|
+
add_hyperlink: 'direct',
|
|
271
|
+
};
|
|
272
|
+
/** Fresh copy of {@link ACTION_DISPOSITIONS} for integrators (safe to mutate). */
|
|
273
|
+
function getActionDispositions() {
|
|
274
|
+
return { ...ACTION_DISPOSITIONS };
|
|
275
|
+
}
|
|
229
276
|
function isActionName(value) {
|
|
230
277
|
return typeof value === 'string' && ACTION_NAMES.includes(value);
|
|
231
278
|
}
|
|
@@ -641,8 +688,8 @@ function evaluateChecks(pre, post, checks) {
|
|
|
641
688
|
else if (check.kind === 'tracked-change-count-delta') {
|
|
642
689
|
results.push({
|
|
643
690
|
check,
|
|
644
|
-
passed: post.trackedChanges
|
|
645
|
-
detail: `pre=${pre.trackedChanges
|
|
691
|
+
passed: post.counts.trackedChanges - pre.counts.trackedChanges === check.delta,
|
|
692
|
+
detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}`,
|
|
646
693
|
});
|
|
647
694
|
}
|
|
648
695
|
else if (check.kind === 'revision-changed') {
|
|
@@ -1149,11 +1196,6 @@ async function runReplaceTextUnchecked(doc, args) {
|
|
|
1149
1196
|
let skippedEdits = [];
|
|
1150
1197
|
let steps;
|
|
1151
1198
|
let applyExpectedRevision;
|
|
1152
|
-
// Blocks whose text this call rewrites, with their pre-mutation run layout:
|
|
1153
|
-
// the engine replace lane renders the replaced range as uniform runs, so a
|
|
1154
|
-
// whole-paragraph find/replace flattens intra-paragraph formatting (bold
|
|
1155
|
-
// lead-ins). Restore it positionally after the mutation.
|
|
1156
|
-
const preserveTargets = [];
|
|
1157
1199
|
if (args.selector) {
|
|
1158
1200
|
const target = selectorToBlockTarget(args.selector, pre);
|
|
1159
1201
|
if (!target) {
|
|
@@ -1192,13 +1234,6 @@ async function runReplaceTextUnchecked(doc, args) {
|
|
|
1192
1234
|
];
|
|
1193
1235
|
applyExpectedRevision = pre.revision;
|
|
1194
1236
|
selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
|
|
1195
|
-
preserveTargets.push({
|
|
1196
|
-
nodeId: target.nodeId,
|
|
1197
|
-
nodeType: target.nodeType ?? 'paragraph',
|
|
1198
|
-
oldText: target.text,
|
|
1199
|
-
newText: rewrittenText,
|
|
1200
|
-
runs: await captureBlockRuns(doc, target.nodeId, target.text),
|
|
1201
|
-
});
|
|
1202
1237
|
}
|
|
1203
1238
|
else {
|
|
1204
1239
|
let candidates = args.edits.map((edit, editIndex) => ({
|
|
@@ -1256,12 +1291,6 @@ async function runReplaceTextUnchecked(doc, args) {
|
|
|
1256
1291
|
steps = candidates.map((candidate) => candidate.step);
|
|
1257
1292
|
}
|
|
1258
1293
|
const result = await executeMutations(doc, steps, args.changeMode, applyExpectedRevision);
|
|
1259
|
-
const preserved = [];
|
|
1260
|
-
for (const entry of preserveTargets) {
|
|
1261
|
-
const restored = await preserveRunPatternAfterRewrite(doc, entry.nodeId, entry.oldText, entry.runs, entry.newText);
|
|
1262
|
-
if (restored)
|
|
1263
|
-
preserved.push({ nodeId: entry.nodeId, ...restored });
|
|
1264
|
-
}
|
|
1265
1294
|
if (args.selector && selectedTargets[0]) {
|
|
1266
1295
|
const post = await docSnapshot.buildMutationSnapshot(doc, { includeDomains: selectorDomains ?? ['blocks'] });
|
|
1267
1296
|
const blockId = selectedTargets[0].matched[0];
|
|
@@ -1290,10 +1319,7 @@ async function runReplaceTextUnchecked(doc, args) {
|
|
|
1290
1319
|
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
1291
1320
|
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
1292
1321
|
selectedTargets,
|
|
1293
|
-
executedOperations: [
|
|
1294
|
-
{ operationId: 'doc.mutations.apply', result },
|
|
1295
|
-
...(preserved.length > 0 ? [{ operationId: 'doc.format.apply', result: { preserved } }] : []),
|
|
1296
|
-
],
|
|
1322
|
+
executedOperations: [{ operationId: 'doc.mutations.apply', result }],
|
|
1297
1323
|
verification,
|
|
1298
1324
|
};
|
|
1299
1325
|
}
|
|
@@ -1317,7 +1343,6 @@ async function runReplaceTextUnchecked(doc, args) {
|
|
|
1317
1343
|
...(skippedEditRationale ? { rationale: skippedEditRationale } : {}),
|
|
1318
1344
|
result,
|
|
1319
1345
|
},
|
|
1320
|
-
...(preserved.length > 0 ? [{ operationId: 'doc.format.apply', result: { preserved } }] : []),
|
|
1321
1346
|
],
|
|
1322
1347
|
verification,
|
|
1323
1348
|
};
|
|
@@ -1566,11 +1591,25 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1566
1591
|
const changeMode = parseChangeMode(args.changeMode);
|
|
1567
1592
|
const executedOperations = [];
|
|
1568
1593
|
const deleted = [];
|
|
1594
|
+
const addressedTrackedChangeIds = new Set();
|
|
1595
|
+
let allTrackedEffectsAddressed = true;
|
|
1569
1596
|
for (const target of targets) {
|
|
1570
1597
|
try {
|
|
1571
1598
|
const result = await deleteFn({ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } }, changeMode ? { changeMode } : undefined);
|
|
1572
1599
|
executedOperations.push({ operationId: 'doc.blocks.delete', result: compactOpResult(result) });
|
|
1573
1600
|
deleted.push({ nodeId: target.nodeId, nodeType: target.nodeType });
|
|
1601
|
+
if (changeMode === 'tracked') {
|
|
1602
|
+
// One block transaction can create multiple logical review entities
|
|
1603
|
+
// (for example, text plus its section break). Exact receipt refs let
|
|
1604
|
+
// us fail closed on missing entities. Older handles omit refs, and
|
|
1605
|
+
// adjacent deletions can coalesce into one review entity, so their
|
|
1606
|
+
// truthful fallback is the positive observed inventory delta rather
|
|
1607
|
+
// than one assumed entity per operation.
|
|
1608
|
+
const refIds = trackedChangeRefIds(result);
|
|
1609
|
+
allTrackedEffectsAddressed &&= refIds.size > 0;
|
|
1610
|
+
for (const entityId of refIds)
|
|
1611
|
+
addressedTrackedChangeIds.add(entityId);
|
|
1612
|
+
}
|
|
1574
1613
|
}
|
|
1575
1614
|
catch (err) {
|
|
1576
1615
|
errors$1.push({
|
|
@@ -1584,14 +1623,37 @@ async function runDeleteBlocks(doc, args) {
|
|
|
1584
1623
|
// Tracked deletions leave the block in place until the revision is decided,
|
|
1585
1624
|
// so the block count cannot move — count the structural revisions instead.
|
|
1586
1625
|
// Direct deletions must show one fewer block of each deleted node type.
|
|
1626
|
+
const observedTrackedChangeDelta = post.counts.trackedChanges - pre.counts.trackedChanges;
|
|
1627
|
+
// A later deletion can coalesce the batch and replace entity identities
|
|
1628
|
+
// returned by earlier operation receipts. For a multi-operation batch the
|
|
1629
|
+
// final inventory is therefore the authoritative count. A single operation
|
|
1630
|
+
// keeps using its addressed refs so fan-out remains verifiable even when
|
|
1631
|
+
// the inventory listing is capped.
|
|
1632
|
+
const expectedTrackedChangeDelta = deleted.length > 1
|
|
1633
|
+
? observedTrackedChangeDelta
|
|
1634
|
+
: allTrackedEffectsAddressed
|
|
1635
|
+
? addressedTrackedChangeIds.size
|
|
1636
|
+
: Math.max(observedTrackedChangeDelta, addressedTrackedChangeIds.size);
|
|
1587
1637
|
const checks = changeMode === 'tracked'
|
|
1588
|
-
? [
|
|
1638
|
+
? [
|
|
1639
|
+
{
|
|
1640
|
+
kind: 'tracked-change-count-delta',
|
|
1641
|
+
delta: expectedTrackedChangeDelta,
|
|
1642
|
+
},
|
|
1643
|
+
]
|
|
1589
1644
|
: [...new Set(deleted.map((entry) => entry.nodeType))].map((nodeType) => ({
|
|
1590
1645
|
kind: 'block-count-delta',
|
|
1591
1646
|
nodeType,
|
|
1592
1647
|
delta: -deleted.filter((entry) => entry.nodeType === nodeType).length,
|
|
1593
1648
|
}));
|
|
1594
1649
|
const verification = evaluateChecks(pre, post, checks);
|
|
1650
|
+
if (changeMode === 'tracked' && observedTrackedChangeDelta <= 0) {
|
|
1651
|
+
verification[0] = {
|
|
1652
|
+
...verification[0],
|
|
1653
|
+
passed: false,
|
|
1654
|
+
detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}; no tracked review entity was created`,
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1595
1657
|
// Compare against the DEDUPED target list: two selectors naming the same
|
|
1596
1658
|
// block are one deletion, and counting the raw selectors would report a
|
|
1597
1659
|
// fully applied call as `partial` with no error to explain it. Selectors
|
|
@@ -2590,7 +2652,6 @@ async function runRewriteBlock(doc, args) {
|
|
|
2590
2652
|
throw new Error('selector did not resolve to a body block');
|
|
2591
2653
|
context.selectedTargets.push({ selector: args.selector, matched: [target.nodeId] });
|
|
2592
2654
|
const text = normalizeTitleLikeRewriteText(target.text, args.text);
|
|
2593
|
-
const runs = await captureBlockRuns(doc, target.nodeId, target.text);
|
|
2594
2655
|
await executeMutations(context.document, [
|
|
2595
2656
|
{
|
|
2596
2657
|
id: 'rewrite-block-1',
|
|
@@ -2599,7 +2660,6 @@ async function runRewriteBlock(doc, args) {
|
|
|
2599
2660
|
args: { replacement: { text }, style: preserveRewriteStyle() },
|
|
2600
2661
|
},
|
|
2601
2662
|
], args.changeMode, pre.revision);
|
|
2602
|
-
await preserveRunPatternAfterRewrite(context.document, target.nodeId, target.text, runs, text);
|
|
2603
2663
|
const post = await context.finish();
|
|
2604
2664
|
const rewritten = await post.block(target.nodeId);
|
|
2605
2665
|
const verification = [
|
|
@@ -2672,23 +2732,8 @@ async function captureBlockRuns(doc, nodeId, text) {
|
|
|
2672
2732
|
}
|
|
2673
2733
|
}
|
|
2674
2734
|
/**
|
|
2675
|
-
*
|
|
2676
|
-
*
|
|
2677
|
-
* drops intra-paragraph formatting AND can smear the first run's marks over
|
|
2678
|
-
* everything. Marks are restored positionally over the parts of the text the
|
|
2679
|
-
* rewrite did NOT change (the common prefix and suffix of old vs new text),
|
|
2680
|
-
* and the changed middle takes the marks of the run it continues from —
|
|
2681
|
-
* Word's "typing continues formatting" rule. Best-effort: never fails the
|
|
2682
|
-
* rewrite.
|
|
2683
|
-
*/
|
|
2684
|
-
/**
|
|
2685
|
-
* Span-aware run-pattern restore for scoped replaces. The generic
|
|
2686
|
-
* preserveRunPatternAfterRewrite diffs prefix/suffix and treats everything
|
|
2687
|
-
* between the FIRST and LAST change as one modified middle — with two
|
|
2688
|
-
* separated replacements that flattens independently-styled runs between
|
|
2689
|
-
* them. Here the exact spans are known, so every untouched interval keeps its
|
|
2690
|
-
* original run formatting at the offset-mapped position, and each replacement
|
|
2691
|
-
* inherits the marks of the run it starts in.
|
|
2735
|
+
* Restore scoped replacements from their known spans: each untouched interval
|
|
2736
|
+
* keeps its run formatting, and replacements inherit the marks at their start.
|
|
2692
2737
|
* `spans` are FULL-TEXT coordinates, ascending, non-overlapping.
|
|
2693
2738
|
*/
|
|
2694
2739
|
async function preserveScopedRunPattern(doc, nodeId, oldText, oldRuns, spans) {
|
|
@@ -2781,74 +2826,6 @@ async function preserveScopedRunPattern(doc, nodeId, oldText, oldRuns, spans) {
|
|
|
2781
2826
|
return { skipped: reasonOf(err) };
|
|
2782
2827
|
}
|
|
2783
2828
|
}
|
|
2784
|
-
async function preserveRunPatternAfterRewrite(doc, nodeId, oldText, oldRuns, newText) {
|
|
2785
|
-
// Nothing to restore when the block was uniformly plain; a single STYLED run
|
|
2786
|
-
// still needs restoring (whole-block replaces drop even uniform marks).
|
|
2787
|
-
if (!oldRuns || oldRuns.length === 0)
|
|
2788
|
-
return null;
|
|
2789
|
-
if (!oldRuns.some((run) => Object.keys(inlineFromSnapshotRun(run, new Set())).length > 0))
|
|
2790
|
-
return null;
|
|
2791
|
-
const formatFn = maybeMethod(doc, ['format', 'apply']);
|
|
2792
|
-
if (!formatFn)
|
|
2793
|
-
return null;
|
|
2794
|
-
// Boolean marks any old run carried: plain ranges must explicitly clear
|
|
2795
|
-
// these, because the rewritten single run may have smeared them everywhere.
|
|
2796
|
-
const normalize = new Set(RUN_BOOL_MARKS.filter((key) => oldRuns.some((run) => run[key])));
|
|
2797
|
-
// query.match run offsets are relative to the trimmed text used to match.
|
|
2798
|
-
const oldTrim = oldText.trim();
|
|
2799
|
-
const newTrimStart = newText.length - newText.trimStart().length;
|
|
2800
|
-
const newTrim = newText.trim();
|
|
2801
|
-
let prefix = 0;
|
|
2802
|
-
const maxCommon = Math.min(oldTrim.length, newTrim.length);
|
|
2803
|
-
while (prefix < maxCommon && oldTrim[prefix] === newTrim[prefix])
|
|
2804
|
-
prefix += 1;
|
|
2805
|
-
let suffix = 0;
|
|
2806
|
-
while (suffix < maxCommon - prefix && oldTrim[oldTrim.length - 1 - suffix] === newTrim[newTrim.length - 1 - suffix])
|
|
2807
|
-
suffix += 1;
|
|
2808
|
-
const delta = newTrim.length - oldTrim.length;
|
|
2809
|
-
let applied = 0;
|
|
2810
|
-
const applyRange = async (start, end, inline) => {
|
|
2811
|
-
if (end <= start || Object.keys(inline).length === 0)
|
|
2812
|
-
return;
|
|
2813
|
-
// Always direct, even under tracked mode. This pass RESTORES the marks the
|
|
2814
|
-
// rewrite flattened; it is not new authorial formatting, and the text
|
|
2815
|
-
// revision already owns the span's accept/reject fate. Applying it tracked
|
|
2816
|
-
// adds a second reviewable change per rewrite (SD-4309's conformance matrix
|
|
2817
|
-
// counts one per action), and rejecting that change alone would strand the
|
|
2818
|
-
// text in the flattened look the rewrite produced.
|
|
2819
|
-
const changed = await applyInlineQuiet(formatFn, {
|
|
2820
|
-
blockId: nodeId,
|
|
2821
|
-
start: newTrimStart + start,
|
|
2822
|
-
end: newTrimStart + end,
|
|
2823
|
-
inline,
|
|
2824
|
-
});
|
|
2825
|
-
if (changed)
|
|
2826
|
-
applied += 1;
|
|
2827
|
-
};
|
|
2828
|
-
try {
|
|
2829
|
-
for (const run of oldRuns) {
|
|
2830
|
-
const inline = inlineFromSnapshotRun(run, normalize);
|
|
2831
|
-
// Portion of the run inside the unchanged prefix.
|
|
2832
|
-
await applyRange(run.start, Math.min(run.end, prefix), inline);
|
|
2833
|
-
// Portion inside the unchanged suffix, shifted by the length delta.
|
|
2834
|
-
const sufStart = Math.max(run.start, oldTrim.length - suffix);
|
|
2835
|
-
if (run.end > sufStart)
|
|
2836
|
-
await applyRange(sufStart + delta, run.end + delta, inline);
|
|
2837
|
-
}
|
|
2838
|
-
// The CHANGED middle keeps whatever the engine smeared; give it the marks
|
|
2839
|
-
// of the run the change continues from (the one containing the last
|
|
2840
|
-
// unchanged prefix character).
|
|
2841
|
-
const middleEnd = newTrim.length - suffix;
|
|
2842
|
-
if (middleEnd > prefix) {
|
|
2843
|
-
const anchorRun = oldRuns.find((run) => prefix > run.start && prefix <= run.end) ?? oldRuns[oldRuns.length - 1];
|
|
2844
|
-
await applyRange(prefix, middleEnd, inlineFromSnapshotRun(anchorRun, normalize));
|
|
2845
|
-
}
|
|
2846
|
-
return applied > 0 ? { restoredRunRanges: applied } : null;
|
|
2847
|
-
}
|
|
2848
|
-
catch (err) {
|
|
2849
|
-
return { skipped: reasonOf(err) };
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
2829
|
const NAMED_COLORS = {
|
|
2853
2830
|
black: '000000',
|
|
2854
2831
|
blue: '0000FF',
|
|
@@ -4620,6 +4597,15 @@ function compactOpResult(result) {
|
|
|
4620
4597
|
compact.stepCount = result.steps.length;
|
|
4621
4598
|
return compact;
|
|
4622
4599
|
}
|
|
4600
|
+
function trackedChangeRefIds(result) {
|
|
4601
|
+
const refs = asRecord(result)?.trackedChangeRefs;
|
|
4602
|
+
if (!Array.isArray(refs))
|
|
4603
|
+
return new Set();
|
|
4604
|
+
return new Set(refs.flatMap((entry) => {
|
|
4605
|
+
const entityId = asString(asRecord(entry)?.entityId);
|
|
4606
|
+
return entityId ? [entityId] : [];
|
|
4607
|
+
}));
|
|
4608
|
+
}
|
|
4623
4609
|
function parseScopedReplaceArgs(args, changeMode) {
|
|
4624
4610
|
if (args.action !== 'replace_text')
|
|
4625
4611
|
return null;
|
|
@@ -4733,9 +4719,8 @@ async function runScopedReplaceUnchecked(doc, args) {
|
|
|
4733
4719
|
},
|
|
4734
4720
|
args: { replacement: { text: span.replace } },
|
|
4735
4721
|
}));
|
|
4736
|
-
//
|
|
4737
|
-
//
|
|
4738
|
-
// applies directly, so restoring marks adds no second reviewable change.
|
|
4722
|
+
// Restore the known edit spans directly so the formatting pass adds no
|
|
4723
|
+
// separate tracked change.
|
|
4739
4724
|
const trackedPreRuns = await captureBlockRuns(doc, nodeId, block.text);
|
|
4740
4725
|
try {
|
|
4741
4726
|
result = await executeMutations(doc, spanSteps, args.changeMode, pre.revision);
|
|
@@ -4925,10 +4910,24 @@ function leadInShapeOf(blockText, labelText, quote) {
|
|
|
4925
4910
|
if (occurrence > 0)
|
|
4926
4911
|
return { kind: 'inside', delimiter: inside, occurrence };
|
|
4927
4912
|
}
|
|
4913
|
+
// A single emphasised word followed by a space and plain prose: the token
|
|
4914
|
+
// itself is the label ("WHEREAS the Party…"). Multi-word runs stay
|
|
4915
|
+
// unrecognised — an emphasised opening clause is prose, not a label.
|
|
4916
|
+
if (/^\S+\s$/.test(labelText) && blockText.length > labelText.length && blockText.startsWith(labelText)) {
|
|
4917
|
+
return { kind: 'word', label: labelText.trimEnd() };
|
|
4918
|
+
}
|
|
4928
4919
|
return null;
|
|
4929
4920
|
}
|
|
4930
4921
|
/** The span of `text` that carries the label look, or null when it has none. */
|
|
4931
4922
|
function leadInSpanOf(text, shape) {
|
|
4923
|
+
if (shape.kind === 'word') {
|
|
4924
|
+
// Only the SAME token inherits the look — "WHEREAS the Parties…" does,
|
|
4925
|
+
// "The Parties further agree…" does not (no guessing a label there).
|
|
4926
|
+
const next = text.charAt(shape.label.length);
|
|
4927
|
+
return text.startsWith(shape.label) && (next === '' || /\s/.test(next))
|
|
4928
|
+
? { start: 0, end: shape.label.length }
|
|
4929
|
+
: null;
|
|
4930
|
+
}
|
|
4932
4931
|
if (shape.kind === 'quoted') {
|
|
4933
4932
|
const opener = text.slice(0, 2).indexOf(shape.quote);
|
|
4934
4933
|
if (opener < 0)
|
|
@@ -7411,8 +7410,10 @@ function parseSelector(value) {
|
|
|
7411
7410
|
const ACTION_NAMES_LIST = ACTION_NAMES;
|
|
7412
7411
|
|
|
7413
7412
|
exports.ACTION_ARGS = ACTION_ARGS;
|
|
7413
|
+
exports.ACTION_DISPOSITIONS = ACTION_DISPOSITIONS;
|
|
7414
7414
|
exports.ACTION_GROUPS = ACTION_GROUPS;
|
|
7415
7415
|
exports.ACTION_HINTS = ACTION_HINTS;
|
|
7416
7416
|
exports.ACTION_NAMES_LIST = ACTION_NAMES_LIST;
|
|
7417
|
+
exports.getActionDispositions = getActionDispositions;
|
|
7417
7418
|
exports.isActionName = isActionName;
|
|
7418
7419
|
exports.superdocPerformAction = superdocPerformAction;
|
package/dist/agent/actions.d.ts
CHANGED
|
@@ -433,6 +433,25 @@ export declare const ACTION_GROUPS: ReadonlyArray<{
|
|
|
433
433
|
* action at compile time.
|
|
434
434
|
*/
|
|
435
435
|
export declare const ACTION_ARGS: Record<ActionName, readonly string[]>;
|
|
436
|
+
/**
|
|
437
|
+
* What an action does with `changeMode`.
|
|
438
|
+
*
|
|
439
|
+
* - `tracked`: honors `changeMode: "tracked"` and records a reviewable tracked
|
|
440
|
+
* change (a multi-step action may run a bookkeeping step directly on purpose —
|
|
441
|
+
* insert_table_column fills the new cells directly so a column shows up as ONE
|
|
442
|
+
* Word-style tracked change, not one per cell).
|
|
443
|
+
* - `direct`: edits the document directly whatever `changeMode` says. Either the
|
|
444
|
+
* action never reads the argument (apply_style, insert_page_break, …), the
|
|
445
|
+
* engine refuses the tracked form (move_range), or the action is a review /
|
|
446
|
+
* comment operation that has no tracked form by nature.
|
|
447
|
+
*
|
|
448
|
+
* Source of truth for the tracked-only agent policy (`enforceChangeMode`):
|
|
449
|
+
* `direct` actions are hidden and refused unless a host allowlists them.
|
|
450
|
+
*/
|
|
451
|
+
export type ActionDisposition = 'tracked' | 'direct';
|
|
452
|
+
export declare const ACTION_DISPOSITIONS: Record<ActionName, ActionDisposition>;
|
|
453
|
+
/** Fresh copy of {@link ACTION_DISPOSITIONS} for integrators (safe to mutate). */
|
|
454
|
+
export declare function getActionDispositions(): Record<ActionName, ActionDisposition>;
|
|
436
455
|
export declare function isActionName(value: unknown): value is ActionName;
|
|
437
456
|
export type ResolveCommentsArgs = {
|
|
438
457
|
action: 'resolve_comments';
|