@superdoc/sdk 2.13.1-next.2 → 2.13.1-next.4
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/action-primitives/tools/structure-insert.cjs +24 -19
- package/dist/action-primitives/tools/structure-insert.js +24 -19
- package/dist/agent/action-change-mode.cjs +35 -0
- package/dist/agent/action-change-mode.d.ts +3 -0
- package/dist/agent/action-change-mode.js +30 -0
- package/dist/agent/actions.cjs +59 -59
- package/dist/agent/actions.d.ts +10 -7
- package/dist/agent/actions.js +59 -59
- package/dist/embedded-prompts.generated.cjs +1 -1
- package/dist/embedded-prompts.generated.js +1 -1
- package/dist/generated/client.d.ts +2 -0
- package/dist/generated/contract.cjs +27 -10
- package/dist/generated/contract.js +27 -10
- package/dist/presets/core.cjs +9 -1
- package/dist/presets/core.js +10 -2
- package/dist/prompts/system-prompt.md +8 -8
- 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/runtime/transport-common.cjs +7 -1
- package/dist/runtime/transport-common.js +7 -1
- package/package.json +6 -6
- package/tools/tools-policy.json +1 -1
|
@@ -713,15 +713,6 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
713
713
|
details: { startNodeId: plan.startNodeId, endNodeId: resolvedEndNodeId },
|
|
714
714
|
});
|
|
715
715
|
}
|
|
716
|
-
if (plan.changeMode === 'tracked') {
|
|
717
|
-
return engine.workflowStepFailure({
|
|
718
|
-
status: 'failed',
|
|
719
|
-
phase: 'execute',
|
|
720
|
-
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_TRACKED_UNSUPPORTED',
|
|
721
|
-
message: 'move_range is direct-only today. Tracked mode would require a tracked block-range move. Nothing was changed. Use move_text for tracked text-span moves or run move_range without changeMode.',
|
|
722
|
-
details: { startNodeId: plan.startNodeId, endNodeId: resolvedEndNodeId },
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
716
|
const destinationIndex = findBlockIndexByNodeId(blocks, plan.destinationNodeId);
|
|
726
717
|
if (destinationIndex < 0) {
|
|
727
718
|
return engine.workflowStepFailure({
|
|
@@ -771,6 +762,30 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
771
762
|
});
|
|
772
763
|
}
|
|
773
764
|
const anchorBlock = blocks[anchorIndex] ?? destinationBlock;
|
|
765
|
+
const sourceStart = blocks[startIndex];
|
|
766
|
+
const sourceEnd = blocks[endIndex];
|
|
767
|
+
if (sourceStart == null || sourceEnd == null) {
|
|
768
|
+
return engine.workflowStepFailure({
|
|
769
|
+
status: 'failed',
|
|
770
|
+
phase: 'execute',
|
|
771
|
+
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_NOT_FOUND',
|
|
772
|
+
message: 'move_range lost the source range while preparing deletion.',
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
if (plan.changeMode === 'tracked') {
|
|
776
|
+
try {
|
|
777
|
+
await context.documentHandle.blocks.deleteRange({ start: toListedBlockTarget(sourceStart), end: toListedBlockTarget(sourceEnd), force: true }, {
|
|
778
|
+
...withChangeModeOption(context.invokeOptions, plan.changeMode),
|
|
779
|
+
dryRun: true,
|
|
780
|
+
expectedRevision: beforeRevision,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
if (!(error instanceof Error) || !('code' in error) || typeof error.code !== 'string')
|
|
785
|
+
throw error;
|
|
786
|
+
return engine.workflowStepFailure({ status: 'failed', phase: 'execute', code: error.code, message: error.message });
|
|
787
|
+
}
|
|
788
|
+
}
|
|
774
789
|
const insertedBlockNodeIds = [];
|
|
775
790
|
// First copy lands relative to the anchor with the requested position; every
|
|
776
791
|
// subsequent copy lands right after the previous one, preserving range order.
|
|
@@ -790,16 +805,6 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
790
805
|
previousInserted = await createCopiedSectionBlock({ context, block, at: insertionAt, changeMode: plan.changeMode });
|
|
791
806
|
insertedBlockNodeIds.push(previousInserted.nodeId);
|
|
792
807
|
}
|
|
793
|
-
const sourceStart = blocks[startIndex];
|
|
794
|
-
const sourceEnd = blocks[endIndex];
|
|
795
|
-
if (sourceStart == null || sourceEnd == null) {
|
|
796
|
-
return engine.workflowStepFailure({
|
|
797
|
-
status: 'failed',
|
|
798
|
-
phase: 'execute',
|
|
799
|
-
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_NOT_FOUND',
|
|
800
|
-
message: 'move_range lost the source range while preparing deletion.',
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
808
|
// Delete the ORIGINAL source range by nodeId. The freshly-created copies have
|
|
804
809
|
// new nodeIds, so the delete cannot touch them (the copy is preserved).
|
|
805
810
|
const deleteResult = await context.documentHandle.blocks.deleteRange({
|
|
@@ -710,15 +710,6 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
710
710
|
details: { startNodeId: plan.startNodeId, endNodeId: resolvedEndNodeId },
|
|
711
711
|
});
|
|
712
712
|
}
|
|
713
|
-
if (plan.changeMode === 'tracked') {
|
|
714
|
-
return workflowStepFailure({
|
|
715
|
-
status: 'failed',
|
|
716
|
-
phase: 'execute',
|
|
717
|
-
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_TRACKED_UNSUPPORTED',
|
|
718
|
-
message: 'move_range is direct-only today. Tracked mode would require a tracked block-range move. Nothing was changed. Use move_text for tracked text-span moves or run move_range without changeMode.',
|
|
719
|
-
details: { startNodeId: plan.startNodeId, endNodeId: resolvedEndNodeId },
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
713
|
const destinationIndex = findBlockIndexByNodeId(blocks, plan.destinationNodeId);
|
|
723
714
|
if (destinationIndex < 0) {
|
|
724
715
|
return workflowStepFailure({
|
|
@@ -768,6 +759,30 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
768
759
|
});
|
|
769
760
|
}
|
|
770
761
|
const anchorBlock = blocks[anchorIndex] ?? destinationBlock;
|
|
762
|
+
const sourceStart = blocks[startIndex];
|
|
763
|
+
const sourceEnd = blocks[endIndex];
|
|
764
|
+
if (sourceStart == null || sourceEnd == null) {
|
|
765
|
+
return workflowStepFailure({
|
|
766
|
+
status: 'failed',
|
|
767
|
+
phase: 'execute',
|
|
768
|
+
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_NOT_FOUND',
|
|
769
|
+
message: 'move_range lost the source range while preparing deletion.',
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (plan.changeMode === 'tracked') {
|
|
773
|
+
try {
|
|
774
|
+
await context.documentHandle.blocks.deleteRange({ start: toListedBlockTarget(sourceStart), end: toListedBlockTarget(sourceEnd), force: true }, {
|
|
775
|
+
...withChangeModeOption(context.invokeOptions, plan.changeMode),
|
|
776
|
+
dryRun: true,
|
|
777
|
+
expectedRevision: beforeRevision,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
if (!(error instanceof Error) || !('code' in error) || typeof error.code !== 'string')
|
|
782
|
+
throw error;
|
|
783
|
+
return workflowStepFailure({ status: 'failed', phase: 'execute', code: error.code, message: error.message });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
771
786
|
const insertedBlockNodeIds = [];
|
|
772
787
|
// First copy lands relative to the anchor with the requested position; every
|
|
773
788
|
// subsequent copy lands right after the previous one, preserving range order.
|
|
@@ -787,16 +802,6 @@ async function executeMoveRangeStep(context, plan) {
|
|
|
787
802
|
previousInserted = await createCopiedSectionBlock({ context, block, at: insertionAt, changeMode: plan.changeMode });
|
|
788
803
|
insertedBlockNodeIds.push(previousInserted.nodeId);
|
|
789
804
|
}
|
|
790
|
-
const sourceStart = blocks[startIndex];
|
|
791
|
-
const sourceEnd = blocks[endIndex];
|
|
792
|
-
if (sourceStart == null || sourceEnd == null) {
|
|
793
|
-
return workflowStepFailure({
|
|
794
|
-
status: 'failed',
|
|
795
|
-
phase: 'execute',
|
|
796
|
-
code: 'WORKFLOW_STRUCTURE_INSERT_RANGE_NOT_FOUND',
|
|
797
|
-
message: 'move_range lost the source range while preparing deletion.',
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
805
|
// Delete the ORIGINAL source range by nodeId. The freshly-created copies have
|
|
801
806
|
// new nodeIds, so the delete cannot touch them (the copy is preserved).
|
|
802
807
|
const deleteResult = await context.documentHandle.blocks.deleteRange({
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var documentEvidence = require('../runtime/document-evidence.cjs');
|
|
4
|
+
var operationCatalog = require('./operation-catalog.cjs');
|
|
5
|
+
|
|
6
|
+
function bindActionChangeMode(document, changeMode) {
|
|
7
|
+
const wrap = (value, path, owner) => new Proxy(value, {
|
|
8
|
+
get(target, property, receiver) {
|
|
9
|
+
const member = Reflect.get(target, property, receiver);
|
|
10
|
+
if (typeof property !== 'string')
|
|
11
|
+
return member;
|
|
12
|
+
const memberPath = [...path, property];
|
|
13
|
+
if (typeof member === 'function' && operationCatalog.getOperationCatalogEntry(`doc.${memberPath.join('.')}`)?.isMutating) {
|
|
14
|
+
return (input = {}, options) => Reflect.apply(member, target, [
|
|
15
|
+
// CLI inputs can already contain a mode. Keep it consistent with
|
|
16
|
+
// the options used by in-process Document API hosts.
|
|
17
|
+
input.changeMode === undefined ? input : { ...input, changeMode },
|
|
18
|
+
{ ...options, changeMode },
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
if (member && (typeof member === 'object' || typeof member === 'function')) {
|
|
22
|
+
return wrap(member, memberPath, target);
|
|
23
|
+
}
|
|
24
|
+
return member;
|
|
25
|
+
},
|
|
26
|
+
apply(target, receiver, args) {
|
|
27
|
+
return Reflect.apply(target, owner ?? receiver, args);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const wrapped = wrap(document, []);
|
|
31
|
+
documentEvidence.forwardDocumentEvidence(document, wrapped);
|
|
32
|
+
return wrapped;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.bindActionChangeMode = bindActionChangeMode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { forwardDocumentEvidence } from '../runtime/document-evidence.js';
|
|
2
|
+
import { getOperationCatalogEntry } from './operation-catalog.js';
|
|
3
|
+
export function bindActionChangeMode(document, changeMode) {
|
|
4
|
+
const wrap = (value, path, owner) => new Proxy(value, {
|
|
5
|
+
get(target, property, receiver) {
|
|
6
|
+
const member = Reflect.get(target, property, receiver);
|
|
7
|
+
if (typeof property !== 'string')
|
|
8
|
+
return member;
|
|
9
|
+
const memberPath = [...path, property];
|
|
10
|
+
if (typeof member === 'function' && getOperationCatalogEntry(`doc.${memberPath.join('.')}`)?.isMutating) {
|
|
11
|
+
return (input = {}, options) => Reflect.apply(member, target, [
|
|
12
|
+
// CLI inputs can already contain a mode. Keep it consistent with
|
|
13
|
+
// the options used by in-process Document API hosts.
|
|
14
|
+
input.changeMode === undefined ? input : { ...input, changeMode },
|
|
15
|
+
{ ...options, changeMode },
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
if (member && (typeof member === 'object' || typeof member === 'function')) {
|
|
19
|
+
return wrap(member, memberPath, target);
|
|
20
|
+
}
|
|
21
|
+
return member;
|
|
22
|
+
},
|
|
23
|
+
apply(target, receiver, args) {
|
|
24
|
+
return Reflect.apply(target, owner ?? receiver, args);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const wrapped = wrap(document, []);
|
|
28
|
+
forwardDocumentEvidence(document, wrapped);
|
|
29
|
+
return wrapped;
|
|
30
|
+
}
|
package/dist/agent/actions.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var executionContext = require('./execution-context.cjs');
|
|
4
|
+
var actionChangeMode = require('./action-change-mode.cjs');
|
|
4
5
|
var errors = require('../runtime/errors.cjs');
|
|
5
6
|
var listTransform = require('../action-primitives/tools/list-transform.cjs');
|
|
6
7
|
var structureInsert = require('../action-primitives/tools/structure-insert.cjs');
|
|
@@ -67,7 +68,7 @@ const ACTION_HINTS = {
|
|
|
67
68
|
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
69
|
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
70
|
fill_placeholders: 'values[] and/or fields[{label?,value}], changeMode?',
|
|
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).
|
|
71
|
+
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). changeMode:"tracked" records both the copies and source deletion for review. 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
72
|
comment_paragraphs: 'commentText, scope?: all|body, excludeBlockQuotes?',
|
|
72
73
|
add_comments: 'commentText, selector (one block) OR selectors[] to comment MANY blocks in ONE call (same text) — use selectors[] for "comment every heading/section/clause"; NEVER emit multiple add_comments calls, batch the targets into selectors[]',
|
|
73
74
|
resolve_comments: 'anchorText? (resolve only comments anchored on text containing this; omit to resolve ALL open comments), reopen? (true = reopen resolved comments instead) — THE way to resolve (or reopen) comments. Use for "resolve the comment(s)" / "mark comments resolved".',
|
|
@@ -86,7 +87,7 @@ const ACTION_HINTS = {
|
|
|
86
87
|
delete_table_column: 'tableOrdinal?, columnIndex, changeMode?',
|
|
87
88
|
split_table: 'tableOrdinal?, rowIndex, separatorText?, changeMode?',
|
|
88
89
|
convert_list: 'kind: ordered|bullet, listOrdinal? 1-based or anchorText? text inside an item of the target list, OR fromMarker+toMarker rendered clause numbers like "2.1."/"2.3." to convert a numbered-clause range, OR fromText+toText exact text inside the first/last of consecutive plain paragraphs to convert them into a list IN PLACE, changeMode? — converts lists, clause ranges, or plain paragraphs to a list without touching text; never recreate-and-delete to make existing paragraphs a list',
|
|
89
|
-
split_list: 'anchorText (text inside the item that should START the second list), restartNumbering? (default true — the new list restarts at 1; false keeps continuous numbering) — THE way to SPLIT one list into two at an item ("split the list starting at item 7 into a new list with reset numbering"). Nested sub-items stay with their parent.
|
|
90
|
+
split_list: 'anchorText (text inside the item that should START the second list), restartNumbering? (default true — the new list restarts at 1; false keeps continuous numbering) — THE way to SPLIT one list into two at an item ("split the list starting at item 7 into a new list with reset numbering"). Nested sub-items stay with their parent. changeMode? records the change for review.',
|
|
90
91
|
undo_changes: 'untilMarker? rendered marker to restore e.g. "2.1.", steps? 1-25 — deterministic revert that steps document history back until the marker reappears',
|
|
91
92
|
redo_changes: 'steps? 1-25 (default 1) — the inverse of undo_changes: steps history FORWARD to re-apply edits a prior undo removed. THE recovery when an undo overshot. Only works right after an undo, before any new edit.',
|
|
92
93
|
attach_numbering: 'anchorText|nodeId, likeMarker e.g. "10.", changeMode? — make an existing block a numbered clause at the same scheme/level as the sibling rendering likeMarker; changeMode:"tracked" records the former unnumbered state as a w:pPrChange',
|
|
@@ -100,9 +101,9 @@ const ACTION_HINTS = {
|
|
|
100
101
|
style_table: 'tableOrdinal? (1-based, default 1), accentColor? (header fill hex, default dark navy) — THE way to make a table look professional: filled accent header row with white bold text, bold first-column labels, banded rows, clean borders. Use after create_table or on any existing table.',
|
|
101
102
|
move_table: 'tableOrdinal? (1-based, default 1), placement {at:"document_end"|"document_start"|"after"|"before", selector?} — THE way to MOVE a whole table (with all its content) to a new position IN ONE call. Use for "move the table to the end / under section X". NEVER delete-and-recreate a table to relocate it, and never chain inserts/undos to fake a move.',
|
|
102
103
|
delete_table: 'tableOrdinal? (1-based, default 1), changeMode? — THE way to DELETE an entire table in one call. Use for "remove/delete the table". Do NOT delete rows one by one or hijack another table.',
|
|
103
|
-
set_paragraph_spacing: 'selector (block/blocks to space), lineSpacing? (multiplier e.g. 1.5 or 2), spaceBefore? / spaceAfter? (points) — THE way to set line spacing or space before/after paragraphs. Use this for "add spacing" / "1.5 line spacing" / "more room between paragraphs" — NEVER insert blank paragraphs for spacing.
|
|
104
|
-
insert_page_break: 'selector (block that should start on a new page) — THE way to "start X on a new page" / add a page break before a block (sets pageBreakBefore). Use instead of inserting empty paragraphs to push content down.
|
|
105
|
-
add_hyperlink: 'text (exact text to turn into a link), url, tooltip? — THE way to make existing text a clickable hyperlink to a URL.
|
|
104
|
+
set_paragraph_spacing: 'selector (block/blocks to space), lineSpacing? (multiplier e.g. 1.5 or 2), spaceBefore? / spaceAfter? (points) — THE way to set line spacing or space before/after paragraphs. Use this for "add spacing" / "1.5 line spacing" / "more room between paragraphs" — NEVER insert blank paragraphs for spacing. changeMode? records the change for review.',
|
|
105
|
+
insert_page_break: 'selector (block that should start on a new page) — THE way to "start X on a new page" / add a page break before a block (sets pageBreakBefore). Use instead of inserting empty paragraphs to push content down. changeMode? records the change for review.',
|
|
106
|
+
add_hyperlink: 'text (exact text to turn into a link), url, tooltip? — THE way to make existing text a clickable hyperlink to a URL. changeMode? records the change for review.',
|
|
106
107
|
};
|
|
107
108
|
/** Category grouping for the rendered tool description. Must cover ACTION_NAMES exactly (unit-tested). */
|
|
108
109
|
const ACTION_GROUPS = [
|
|
@@ -197,7 +198,7 @@ const ACTION_ARGS = {
|
|
|
197
198
|
delete_table_column: ['tableOrdinal', 'columnIndex', 'changeMode'],
|
|
198
199
|
split_table: ['tableOrdinal', 'rowIndex', 'separatorText', 'changeMode'],
|
|
199
200
|
convert_list: ['kind', 'fromMarker', 'toMarker', 'fromText', 'toText', 'listOrdinal', 'anchorText', 'changeMode'],
|
|
200
|
-
split_list: ['anchorText', 'restartNumbering'],
|
|
201
|
+
split_list: ['anchorText', 'restartNumbering', 'changeMode'],
|
|
201
202
|
undo_changes: ['untilMarker', 'steps'],
|
|
202
203
|
redo_changes: ['steps'],
|
|
203
204
|
attach_numbering: ['likeMarker', 'nodeId', 'anchorText', 'changeMode'],
|
|
@@ -216,15 +217,15 @@ const ACTION_ARGS = {
|
|
|
216
217
|
'fontSize',
|
|
217
218
|
'changeMode',
|
|
218
219
|
],
|
|
219
|
-
apply_style: ['selector', 'styleId', 'headingLevel', 'likeText'],
|
|
220
|
+
apply_style: ['selector', 'styleId', 'headingLevel', 'likeText', 'changeMode'],
|
|
220
221
|
format_paragraph: ['selector', 'alignment', 'changeMode', 'evidence'],
|
|
221
222
|
move_text: ['text', 'afterText', 'changeMode'],
|
|
222
|
-
style_table: ['tableOrdinal', 'accentColor'],
|
|
223
|
-
move_table: ['tableOrdinal', 'placement'],
|
|
223
|
+
style_table: ['tableOrdinal', 'accentColor', 'changeMode'],
|
|
224
|
+
move_table: ['tableOrdinal', 'placement', 'changeMode'],
|
|
224
225
|
delete_table: ['tableOrdinal', 'changeMode'],
|
|
225
|
-
set_paragraph_spacing: ['selector', 'lineSpacing', 'spaceBefore', 'spaceAfter'],
|
|
226
|
-
insert_page_break: ['selector'],
|
|
227
|
-
add_hyperlink: ['text', 'url', 'tooltip'],
|
|
226
|
+
set_paragraph_spacing: ['selector', 'lineSpacing', 'spaceBefore', 'spaceAfter', 'changeMode'],
|
|
227
|
+
insert_page_break: ['selector', 'changeMode'],
|
|
228
|
+
add_hyperlink: ['text', 'url', 'tooltip', 'changeMode'],
|
|
228
229
|
};
|
|
229
230
|
const ACTION_DISPOSITIONS = {
|
|
230
231
|
insert_paragraphs: 'tracked',
|
|
@@ -245,7 +246,7 @@ const ACTION_DISPOSITIONS = {
|
|
|
245
246
|
set_font_family: 'tracked',
|
|
246
247
|
apply_letter_spacing: 'tracked',
|
|
247
248
|
fill_placeholders: 'tracked',
|
|
248
|
-
move_range: '
|
|
249
|
+
move_range: 'tracked',
|
|
249
250
|
insert_toc: 'tracked',
|
|
250
251
|
insert_table_row: 'tracked',
|
|
251
252
|
insert_table_column: 'tracked',
|
|
@@ -253,21 +254,21 @@ const ACTION_DISPOSITIONS = {
|
|
|
253
254
|
delete_table_column: 'tracked',
|
|
254
255
|
split_table: 'tracked',
|
|
255
256
|
convert_list: 'tracked',
|
|
256
|
-
split_list: '
|
|
257
|
+
split_list: 'tracked',
|
|
257
258
|
undo_changes: 'direct',
|
|
258
259
|
redo_changes: 'direct',
|
|
259
260
|
attach_numbering: 'tracked',
|
|
260
261
|
add_list_items: 'tracked',
|
|
261
262
|
format_text: 'tracked',
|
|
262
|
-
apply_style: '
|
|
263
|
+
apply_style: 'tracked',
|
|
263
264
|
format_paragraph: 'tracked',
|
|
264
265
|
move_text: 'tracked',
|
|
265
|
-
style_table: '
|
|
266
|
-
move_table: '
|
|
266
|
+
style_table: 'tracked',
|
|
267
|
+
move_table: 'tracked',
|
|
267
268
|
delete_table: 'tracked',
|
|
268
|
-
set_paragraph_spacing: '
|
|
269
|
-
insert_page_break: '
|
|
270
|
-
add_hyperlink: '
|
|
269
|
+
set_paragraph_spacing: 'tracked',
|
|
270
|
+
insert_page_break: 'tracked',
|
|
271
|
+
add_hyperlink: 'tracked',
|
|
271
272
|
};
|
|
272
273
|
/** Fresh copy of {@link ACTION_DISPOSITIONS} for integrators (safe to mutate). */
|
|
273
274
|
function getActionDispositions() {
|
|
@@ -886,14 +887,7 @@ async function executeCommentCreate(doc, snapshot, commentText, blockId) {
|
|
|
886
887
|
target,
|
|
887
888
|
});
|
|
888
889
|
}
|
|
889
|
-
async function applyTableCellTexts(doc, tableNodeId, tableOrdinal, cellTexts, changeMode
|
|
890
|
-
/**
|
|
891
|
-
* Force the cell fill to run as a direct edit even under `changeMode:'tracked'`.
|
|
892
|
-
* Set only by the column-insert caller, whose new cells land in pre-existing
|
|
893
|
-
* rows that carry no tracked-insertion marker for the adapter to absorb them
|
|
894
|
-
* into. See the tracked-mode note below.
|
|
895
|
-
*/
|
|
896
|
-
forceDirectFill = false) {
|
|
890
|
+
async function applyTableCellTexts(doc, tableNodeId, tableOrdinal, cellTexts, changeMode) {
|
|
897
891
|
const nonEmptyCells = cellTexts.filter((cell) => cell.text.trim().length > 0);
|
|
898
892
|
if (nonEmptyCells.length === 0)
|
|
899
893
|
return [];
|
|
@@ -933,19 +927,6 @@ forceDirectFill = false) {
|
|
|
933
927
|
// block rewrite the v2 collab backend silently drops for freshly-inserted
|
|
934
928
|
// cells (rows/columns land empty), so it is only a fallback for non-collab or
|
|
935
929
|
// older hosts without the op.
|
|
936
|
-
//
|
|
937
|
-
// Tracked mode: the adapter accepts a tracked cell fill only where the target
|
|
938
|
-
// cell's row carries a same-author `<w:trPr><w:ins/>` marker, which already
|
|
939
|
-
// owns the accept/reject fate of everything inside it. A tracked row insert
|
|
940
|
-
// and a tracked new table both produce such rows, so their fills go through
|
|
941
|
-
// tracked and the transport now lets them reach the adapter
|
|
942
|
-
// (supportsConditionalTrackedMode, SD-4035).
|
|
943
|
-
//
|
|
944
|
-
// A tracked COLUMN insert is the exception: its new cells are added to
|
|
945
|
-
// PRE-EXISTING rows, which carry no marker, so the adapter fails closed with
|
|
946
|
-
// CAPABILITY_UNAVAILABLE and the column would land empty. Those fills stay
|
|
947
|
-
// direct — the table-level insertion still owns the redline.
|
|
948
|
-
const cellFillChangeMode = forceDirectFill && changeMode === 'tracked' ? undefined : changeMode;
|
|
949
930
|
const setCellFn = maybeMethod(doc, ['tables', 'setCellText']);
|
|
950
931
|
const legacySteps = [];
|
|
951
932
|
const resolved = [];
|
|
@@ -964,7 +945,7 @@ forceDirectFill = false) {
|
|
|
964
945
|
rowIndex: cell.rowIndex,
|
|
965
946
|
columnIndex: cell.columnIndex,
|
|
966
947
|
text: cell.text,
|
|
967
|
-
...(
|
|
948
|
+
...(changeMode ? { changeMode: changeMode } : {}),
|
|
968
949
|
});
|
|
969
950
|
}
|
|
970
951
|
else {
|
|
@@ -977,7 +958,7 @@ forceDirectFill = false) {
|
|
|
977
958
|
}
|
|
978
959
|
}
|
|
979
960
|
if (legacySteps.length > 0)
|
|
980
|
-
await executeMutations(doc, legacySteps,
|
|
961
|
+
await executeMutations(doc, legacySteps, changeMode);
|
|
981
962
|
return resolved;
|
|
982
963
|
}
|
|
983
964
|
/**
|
|
@@ -2242,7 +2223,7 @@ async function runAddListItems(doc, args, opts) {
|
|
|
2242
2223
|
* split_list — split one list into two at a target item, restarting the new
|
|
2243
2224
|
* half's numbering (default). Thin wrapper over doc.lists.split (separate +
|
|
2244
2225
|
* setValue). The split point is the item that should START the second list,
|
|
2245
|
-
* located by anchorText.
|
|
2226
|
+
* located by anchorText.
|
|
2246
2227
|
*/
|
|
2247
2228
|
async function runSplitList(doc, args) {
|
|
2248
2229
|
const pre = await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: ['blocks', 'lists'] });
|
|
@@ -4317,9 +4298,7 @@ async function runInsertTableColumn(doc, args) {
|
|
|
4317
4298
|
columnCells = [{ rowIndex: 0, columnIndex: newColumnIndex, text: args.headerText }];
|
|
4318
4299
|
}
|
|
4319
4300
|
if (columnCells.length > 0) {
|
|
4320
|
-
|
|
4321
|
-
// carry no tracked-insertion marker, so a tracked fill fails closed.
|
|
4322
|
-
const applied = await applyTableCellTexts(doc, table.nodeId, table.ordinal, columnCells, args.changeMode, true);
|
|
4301
|
+
const applied = await applyTableCellTexts(doc, table.nodeId, table.ordinal, columnCells, args.changeMode);
|
|
4323
4302
|
if (applied.length > 0) {
|
|
4324
4303
|
executed.push({
|
|
4325
4304
|
operationId: 'doc.mutations.apply',
|
|
@@ -5933,13 +5912,14 @@ function indentsEqual(a, b) {
|
|
|
5933
5912
|
}
|
|
5934
5913
|
async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anchorRow, allowNumberingAttach, isTitle = false, allowLeadPattern = true, createdText = '') {
|
|
5935
5914
|
try {
|
|
5936
|
-
//
|
|
5937
|
-
//
|
|
5938
|
-
|
|
5915
|
+
// A requested heading level constrains style matching, including explicit
|
|
5916
|
+
// placement anchors: copying another level would change document structure.
|
|
5917
|
+
const compatibleHeading = (row) => created.headingLevel == null || row.headingLevel === created.headingLevel;
|
|
5918
|
+
let reference = anchorRow && !anchorRow.isEmpty && compatibleHeading(anchorRow) ? anchorRow : null;
|
|
5939
5919
|
if (!reference) {
|
|
5940
5920
|
for (let i = createdIndex - 1; i >= 0; i--) {
|
|
5941
5921
|
const b = postBlocks[i];
|
|
5942
|
-
if (preIds.has(b.nodeId) && b.nodeType === created.nodeType && !b.isEmpty) {
|
|
5922
|
+
if (preIds.has(b.nodeId) && b.nodeType === created.nodeType && !b.isEmpty && compatibleHeading(b)) {
|
|
5943
5923
|
reference = b;
|
|
5944
5924
|
break;
|
|
5945
5925
|
}
|
|
@@ -5948,7 +5928,7 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
|
|
|
5948
5928
|
if (!reference) {
|
|
5949
5929
|
for (let i = createdIndex + 1; i < postBlocks.length; i++) {
|
|
5950
5930
|
const b = postBlocks[i];
|
|
5951
|
-
if (preIds.has(b.nodeId) && b.nodeType === created.nodeType && !b.isEmpty) {
|
|
5931
|
+
if (preIds.has(b.nodeId) && b.nodeType === created.nodeType && !b.isEmpty && compatibleHeading(b)) {
|
|
5952
5932
|
reference = b;
|
|
5953
5933
|
break;
|
|
5954
5934
|
}
|
|
@@ -5983,11 +5963,28 @@ async function matchOneBlock(doc, postBlocks, preIds, created, createdIndex, anc
|
|
|
5983
5963
|
if (reference.styleId && reference.styleId !== created.styleId) {
|
|
5984
5964
|
const setStyleFn = maybeMethod(doc, ['styles', 'paragraph', 'setStyle']);
|
|
5985
5965
|
if (setStyleFn) {
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5966
|
+
const target = { kind: 'block', nodeType: created.nodeType, nodeId: created.nodeId };
|
|
5967
|
+
let canCopyStyle = true;
|
|
5968
|
+
if (created.headingLevel != null) {
|
|
5969
|
+
// The reference's effective level can come from a direct outline
|
|
5970
|
+
// override that its style does not contain. Preserve it before copying.
|
|
5971
|
+
const setOutlineFn = maybeMethod(doc, ['format', 'paragraph', 'setOutlineLevel']);
|
|
5972
|
+
if (!setOutlineFn) {
|
|
5973
|
+
canCopyStyle = false;
|
|
5974
|
+
applied.styleSkipped = 'Heading outline preservation is unavailable.';
|
|
5975
|
+
}
|
|
5976
|
+
else {
|
|
5977
|
+
const result = asRecord(await setOutlineFn({ target, outlineLevel: created.headingLevel - 1 }));
|
|
5978
|
+
if (result?.success === false) {
|
|
5979
|
+
canCopyStyle = false;
|
|
5980
|
+
applied.styleSkipped = 'Heading outline preservation failed.';
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
if (canCopyStyle) {
|
|
5985
|
+
await setStyleFn({ target, styleId: reference.styleId });
|
|
5986
|
+
applied.styleId = reference.styleId;
|
|
5987
|
+
}
|
|
5991
5988
|
}
|
|
5992
5989
|
}
|
|
5993
5990
|
// Same "inline look" the style-copy paths use, minus properties the
|
|
@@ -7161,7 +7158,8 @@ function parseExactTrackedChangeIdList(value) {
|
|
|
7161
7158
|
}
|
|
7162
7159
|
async function superdocPerformAction(doc, args) {
|
|
7163
7160
|
try {
|
|
7164
|
-
|
|
7161
|
+
const changeMode = isRecord(args) ? parseChangeMode(args.changeMode) : undefined;
|
|
7162
|
+
return await dispatchSuperdocPerformAction(changeMode ? actionChangeMode.bindActionChangeMode(doc, changeMode) : doc, args);
|
|
7165
7163
|
}
|
|
7166
7164
|
catch (error) {
|
|
7167
7165
|
if (!(error instanceof docSnapshot.MutationSnapshotError))
|
|
@@ -7180,7 +7178,9 @@ async function superdocPerformAction(doc, args) {
|
|
|
7180
7178
|
function parseChangeMode(value) {
|
|
7181
7179
|
if (value === 'direct' || value === 'tracked')
|
|
7182
7180
|
return value;
|
|
7183
|
-
|
|
7181
|
+
if (value === undefined)
|
|
7182
|
+
return undefined;
|
|
7183
|
+
throw new errors.SuperDocCliError('changeMode must be direct or tracked.', { code: 'INVALID_ARGUMENT' });
|
|
7184
7184
|
}
|
|
7185
7185
|
function parseReplaceTextChangeMode(value) {
|
|
7186
7186
|
if (value === undefined)
|
package/dist/agent/actions.d.ts
CHANGED
|
@@ -284,6 +284,7 @@ export type InsertTocArgs = {
|
|
|
284
284
|
changeMode?: AgentChangeMode;
|
|
285
285
|
};
|
|
286
286
|
export type StyleTableArgs = {
|
|
287
|
+
changeMode?: AgentChangeMode;
|
|
287
288
|
action: 'style_table';
|
|
288
289
|
/** 1-based table ordinal (default 1). */
|
|
289
290
|
tableOrdinal: number;
|
|
@@ -291,6 +292,7 @@ export type StyleTableArgs = {
|
|
|
291
292
|
accentColor?: string;
|
|
292
293
|
};
|
|
293
294
|
export type MoveTableArgs = {
|
|
295
|
+
changeMode?: AgentChangeMode;
|
|
294
296
|
action: 'move_table';
|
|
295
297
|
/** 1-based table ordinal to move (default 1). */
|
|
296
298
|
tableOrdinal?: number;
|
|
@@ -362,6 +364,7 @@ export type RedoChangesArgs = {
|
|
|
362
364
|
steps?: number;
|
|
363
365
|
};
|
|
364
366
|
export type SplitListArgs = {
|
|
367
|
+
changeMode?: AgentChangeMode;
|
|
365
368
|
action: 'split_list';
|
|
366
369
|
/** Text inside the list item that should START the new (second) list. */
|
|
367
370
|
anchorText: string;
|
|
@@ -375,6 +378,7 @@ export type AttachNumberingArgs = {
|
|
|
375
378
|
likeMarker: string;
|
|
376
379
|
};
|
|
377
380
|
export type ApplyStyleArgs = {
|
|
381
|
+
changeMode?: AgentChangeMode;
|
|
378
382
|
action: 'apply_style';
|
|
379
383
|
/** Block to restyle. */
|
|
380
384
|
selector: AgentSelector;
|
|
@@ -437,13 +441,9 @@ export declare const ACTION_ARGS: Record<ActionName, readonly string[]>;
|
|
|
437
441
|
* What an action does with `changeMode`.
|
|
438
442
|
*
|
|
439
443
|
* - `tracked`: honors `changeMode: "tracked"` and records a reviewable tracked
|
|
440
|
-
* change
|
|
441
|
-
*
|
|
442
|
-
*
|
|
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.
|
|
444
|
+
* change.
|
|
445
|
+
* - `direct`: performs a review, comment, or history operation that does not
|
|
446
|
+
* create a tracked document edit.
|
|
447
447
|
*
|
|
448
448
|
* Source of truth for the tracked-only agent policy (`enforceChangeMode`):
|
|
449
449
|
* `direct` actions are hidden and refused unless a host allowlists them.
|
|
@@ -482,6 +482,7 @@ export type MoveTextArgs = {
|
|
|
482
482
|
changeMode?: AgentChangeMode;
|
|
483
483
|
};
|
|
484
484
|
export type SetParagraphSpacingArgs = {
|
|
485
|
+
changeMode?: AgentChangeMode;
|
|
485
486
|
action: 'set_paragraph_spacing';
|
|
486
487
|
selector: AgentSelector;
|
|
487
488
|
/** Line-spacing multiplier (1, 1.5, 2). */
|
|
@@ -492,10 +493,12 @@ export type SetParagraphSpacingArgs = {
|
|
|
492
493
|
spaceAfter?: number;
|
|
493
494
|
};
|
|
494
495
|
export type InsertPageBreakArgs = {
|
|
496
|
+
changeMode?: AgentChangeMode;
|
|
495
497
|
action: 'insert_page_break';
|
|
496
498
|
selector: AgentSelector;
|
|
497
499
|
};
|
|
498
500
|
export type AddHyperlinkArgs = {
|
|
501
|
+
changeMode?: AgentChangeMode;
|
|
499
502
|
action: 'add_hyperlink';
|
|
500
503
|
/** Exact text to turn into a hyperlink. */
|
|
501
504
|
text: string;
|