@superdoc/sdk 2.12.0-next.1 → 2.12.0-next.11
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/README.md +15 -0
- package/dist/actions/define.cjs +6 -1
- package/dist/actions/define.js +6 -1
- package/dist/agent/actions.cjs +125 -13
- package/dist/agent/actions.d.ts +12 -5
- package/dist/agent/actions.js +125 -13
- package/dist/agent/catalog.cjs +7 -1
- package/dist/agent/catalog.js +7 -1
- package/dist/agent/doc-snapshot.cjs +30 -24
- package/dist/agent/doc-snapshot.d.ts +1 -0
- package/dist/agent/doc-snapshot.js +29 -24
- package/dist/agent/runtime.cjs +6 -1
- package/dist/agent/runtime.d.ts +5 -1
- package/dist/agent/runtime.js +6 -1
- package/dist/agent/textbox-snapshot.cjs +65 -0
- package/dist/agent/textbox-snapshot.d.ts +22 -0
- package/dist/agent/textbox-snapshot.js +60 -0
- package/dist/embedded-prompts.generated.cjs +2 -2
- package/dist/embedded-prompts.generated.js +2 -2
- package/dist/generated/client.d.ts +54 -0
- package/dist/generated/contract.cjs +397 -8
- package/dist/generated/contract.js +397 -8
- package/dist/index.d.ts +1 -1
- package/dist/prompts/mcp-prompt.md +1 -1
- package/dist/prompts/system-prompt.md +1 -1
- 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 -8
- package/tools/tools-policy.json +1 -1
package/README.md
CHANGED
|
@@ -96,3 +96,18 @@ automatically replay the write.
|
|
|
96
96
|
## License
|
|
97
97
|
|
|
98
98
|
AGPL-3.0. Commercial licenses are available from [SuperDoc](https://www.superdoc.dev).
|
|
99
|
+
|
|
100
|
+
## Inspecting textbox sections
|
|
101
|
+
|
|
102
|
+
`superdoc_inspect` includes discovered text-bearing textbox stories in `textboxes` when
|
|
103
|
+
block inspection is enabled. Each entry contains its explicit `story` locator,
|
|
104
|
+
its story-local `total`, and normalized `blocks` with text, identities, and list
|
|
105
|
+
markers. The top-level `blocks` array and its ordinals continue to refer to the
|
|
106
|
+
body; textbox block ordinals belong to their containing story.
|
|
107
|
+
|
|
108
|
+
The block offset, limit, text cap, and block filters also apply within each
|
|
109
|
+
textbox. Count-only inspections and inspections excluding the blocks domain do
|
|
110
|
+
not collect textbox text. Check `diagnostics` for incomplete story discovery or
|
|
111
|
+
reads. To edit a discovered heading, use `query.match` with its story locator
|
|
112
|
+
and pass the returned reference to `replace`; a textbox ordinal is not a body
|
|
113
|
+
action selector.
|
package/dist/actions/define.cjs
CHANGED
|
@@ -556,7 +556,12 @@ function mergeIntoAgentAction(tools, actions) {
|
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
558
|
const nextActionProp = { ...actionProp, enum: enumValues };
|
|
559
|
-
const
|
|
559
|
+
const hasOpenCustomSchema = actions.some((action) => action.inputSchema.additionalProperties !== false);
|
|
560
|
+
const nextSchema = {
|
|
561
|
+
...schema,
|
|
562
|
+
additionalProperties: hasOpenCustomSchema ? true : schema.additionalProperties,
|
|
563
|
+
properties: { ...properties, action: nextActionProp },
|
|
564
|
+
};
|
|
560
565
|
const baseDescription = typeof schemaContainer.description === 'string' ? schemaContainer.description : '';
|
|
561
566
|
const nextDescription = baseDescription + customActionsDescription(actions);
|
|
562
567
|
const nextContainer = { ...schemaContainer, description: nextDescription, [schemaKey]: nextSchema };
|
package/dist/actions/define.js
CHANGED
|
@@ -553,7 +553,12 @@ function mergeIntoAgentAction(tools, actions) {
|
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
555
|
const nextActionProp = { ...actionProp, enum: enumValues };
|
|
556
|
-
const
|
|
556
|
+
const hasOpenCustomSchema = actions.some((action) => action.inputSchema.additionalProperties !== false);
|
|
557
|
+
const nextSchema = {
|
|
558
|
+
...schema,
|
|
559
|
+
additionalProperties: hasOpenCustomSchema ? true : schema.additionalProperties,
|
|
560
|
+
properties: { ...properties, action: nextActionProp },
|
|
561
|
+
};
|
|
557
562
|
const baseDescription = typeof schemaContainer.description === 'string' ? schemaContainer.description : '';
|
|
558
563
|
const nextDescription = baseDescription + customActionsDescription(actions);
|
|
559
564
|
const nextContainer = { ...schemaContainer, description: nextDescription, [schemaKey]: nextSchema };
|
package/dist/agent/actions.cjs
CHANGED
|
@@ -72,8 +72,8 @@ const ACTION_HINTS = {
|
|
|
72
72
|
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
73
|
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".',
|
|
74
74
|
reply_to_comment: 'commentText (the reply body), anchorText (text the target comment is anchored on / mentions) OR commentId — THE way to REPLY to an existing comment thread. Use for "reply to the comment about X" / "respond to Reviewer\'s comment". Adds a threaded reply, not a new top-level comment.',
|
|
75
|
-
accept_tracked_changes: 'author
|
|
76
|
-
reject_tracked_changes: 'author
|
|
75
|
+
accept_tracked_changes: 'id? (string or string[]) OR author?/changeType?: insert|delete|replacement|format — a string[] decides that exact set atomically; e.g. changeType:"format" accepts ONLY formatting revisions (bold/italic/color), leaving text edits pending',
|
|
76
|
+
reject_tracked_changes: 'id? (string or string[]) OR author?/changeType?: insert|delete|replacement|format — a string[] decides that exact set atomically',
|
|
77
77
|
normalize_body_font_size: 'fontSize, changeMode?',
|
|
78
78
|
set_font_family: 'fontFamily (e.g. "Arial"), selector? (one block) OR targetText/targetTexts[] (occurrences) — omit both to set the WHOLE body font, caseSensitive?, changeMode? — THE way to change the typeface. Use for "change the font to X" / "set the heading font to Y".',
|
|
79
79
|
format_text: "bold?/italic?/underline?/strike? booleans, highlight? color name, color?, fontSize?, applied to EVERY occurrence of targetText (or targetTexts[] for several phrases in one call, or a selector'd block), caseSensitive?, changeMode? — THE way to bold/italicize/underline/highlight text, tracked-safe",
|
|
@@ -183,8 +183,8 @@ const ACTION_ARGS = {
|
|
|
183
183
|
resolve_comments: ['anchorText', 'reopen'],
|
|
184
184
|
reply_to_comment: ['commentText', 'anchorText', 'commentId'],
|
|
185
185
|
rewrite_block: ['text', 'selector', 'changeMode', 'evidence'],
|
|
186
|
-
accept_tracked_changes: ['author', 'changeType'],
|
|
187
|
-
reject_tracked_changes: ['author', 'changeType'],
|
|
186
|
+
accept_tracked_changes: ['id', 'author', 'changeType'],
|
|
187
|
+
reject_tracked_changes: ['id', 'author', 'changeType'],
|
|
188
188
|
normalize_body_font_size: ['fontSize', 'changeMode'],
|
|
189
189
|
set_font_family: ['fontFamily', 'selector', 'targetText', 'targetTexts', 'caseSensitive', 'changeMode'],
|
|
190
190
|
apply_letter_spacing: ['selector', 'letterSpacing', 'changeMode'],
|
|
@@ -2872,17 +2872,28 @@ function normalizeTableColor(raw) {
|
|
|
2872
2872
|
return normalized ? `#${normalized}` : null;
|
|
2873
2873
|
}
|
|
2874
2874
|
async function runAcceptTrackedChanges(doc, args) {
|
|
2875
|
-
return runTrackedChangeDecision(doc, 'accept_tracked_changes', 'accept', args.author, args.changeType);
|
|
2875
|
+
return runTrackedChangeDecision(doc, 'accept_tracked_changes', 'accept', Array.isArray(args.id) ? args.id : typeof args.id === 'string' ? [args.id] : undefined, args.author, args.changeType);
|
|
2876
2876
|
}
|
|
2877
2877
|
async function runRejectTrackedChanges(doc, args) {
|
|
2878
|
-
return runTrackedChangeDecision(doc, 'reject_tracked_changes', 'reject', args.author, args.changeType);
|
|
2878
|
+
return runTrackedChangeDecision(doc, 'reject_tracked_changes', 'reject', Array.isArray(args.id) ? args.id : typeof args.id === 'string' ? [args.id] : undefined, args.author, args.changeType);
|
|
2879
2879
|
}
|
|
2880
|
-
async function runTrackedChangeDecision(doc, intentLabel, decision, author, changeType) {
|
|
2880
|
+
async function runTrackedChangeDecision(doc, intentLabel, decision, exactIds, author, changeType) {
|
|
2881
2881
|
const pre = await docSnapshot.buildDocumentSnapshot(doc);
|
|
2882
2882
|
try {
|
|
2883
|
-
const listFn = maybeMethod(doc, ['trackChanges', 'list']);
|
|
2884
2883
|
const decideFn = maybeMethod(doc, ['trackChanges', 'decide']);
|
|
2885
|
-
if (!
|
|
2884
|
+
if (!decideFn) {
|
|
2885
|
+
throw new errors.SuperDocCliError('doc.trackChanges.list / decide are not available on the document handle.', {
|
|
2886
|
+
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
// AIDEV-NOTE: Exact `id` must go to decide({ kind: 'id'|'ids' }) once.
|
|
2890
|
+
// Listing then filtering treats a stale id as an ok no-op, which looks
|
|
2891
|
+
// like success and invites a follow-up with no selector (accept/reject all).
|
|
2892
|
+
if (exactIds && exactIds.length > 0) {
|
|
2893
|
+
return decideExactTrackedChanges(doc, intentLabel, decision, decideFn, exactIds, pre);
|
|
2894
|
+
}
|
|
2895
|
+
const listFn = maybeMethod(doc, ['trackChanges', 'list']);
|
|
2896
|
+
if (!listFn) {
|
|
2886
2897
|
throw new errors.SuperDocCliError('doc.trackChanges.list / decide are not available on the document handle.', {
|
|
2887
2898
|
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
2888
2899
|
});
|
|
@@ -2963,6 +2974,53 @@ async function runTrackedChangeDecision(doc, intentLabel, decision, author, chan
|
|
|
2963
2974
|
return failedReceipt(intentLabel, err, pre);
|
|
2964
2975
|
}
|
|
2965
2976
|
}
|
|
2977
|
+
function trackedChangeDecideFailure(result) {
|
|
2978
|
+
const rec = asRecord(result);
|
|
2979
|
+
if (!rec || rec.success !== false)
|
|
2980
|
+
return null;
|
|
2981
|
+
const failure = asRecord(rec.failure);
|
|
2982
|
+
return {
|
|
2983
|
+
code: asString(failure?.code, 'ACTION_FAILED') || 'ACTION_FAILED',
|
|
2984
|
+
message: asString(failure?.message, 'tracked-change decision failed') || 'tracked-change decision failed',
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
async function decideExactTrackedChanges(doc, intentLabel, decision, decideFn, exactIds, pre) {
|
|
2988
|
+
const target = exactIds.length === 1 ? { kind: 'id', id: exactIds[0] } : { kind: 'ids', ids: [...exactIds] };
|
|
2989
|
+
const result = await decideFn({ decision, target });
|
|
2990
|
+
const failure = trackedChangeDecideFailure(result);
|
|
2991
|
+
const selectedTargets = exactIds.map((id) => ({
|
|
2992
|
+
selector: { kind: 'entity', entityType: 'trackedChange', entityId: id },
|
|
2993
|
+
matched: failure ? [] : [id],
|
|
2994
|
+
}));
|
|
2995
|
+
if (failure) {
|
|
2996
|
+
return {
|
|
2997
|
+
status: 'failed',
|
|
2998
|
+
intent: intentLabel,
|
|
2999
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
3000
|
+
selectedTargets,
|
|
3001
|
+
executedOperations: [{ operationId: 'doc.trackChanges.decide', result }],
|
|
3002
|
+
verification: [],
|
|
3003
|
+
errors: [
|
|
3004
|
+
{
|
|
3005
|
+
code: failure.code,
|
|
3006
|
+
message: failure.message,
|
|
3007
|
+
recovery: { kind: 'reinspect' },
|
|
3008
|
+
},
|
|
3009
|
+
],
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
const post = await docSnapshot.buildDocumentSnapshot(doc);
|
|
3013
|
+
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3014
|
+
return {
|
|
3015
|
+
status: verification.every((v) => v.passed) ? 'ok' : 'failed',
|
|
3016
|
+
intent: intentLabel,
|
|
3017
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
3018
|
+
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
3019
|
+
selectedTargets,
|
|
3020
|
+
executedOperations: [{ operationId: 'doc.trackChanges.decide', result }],
|
|
3021
|
+
verification,
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
2966
3024
|
async function listAllTrackedChanges(listFn) {
|
|
2967
3025
|
const PAGE = 250;
|
|
2968
3026
|
let offset = 0;
|
|
@@ -6660,13 +6718,11 @@ async function dispatchSuperdocPerformAction(doc, args) {
|
|
|
6660
6718
|
}
|
|
6661
6719
|
case 'accept_tracked_changes':
|
|
6662
6720
|
return runAcceptTrackedChanges(doc, {
|
|
6663
|
-
|
|
6664
|
-
changeType: parseTrackedChangeKind(args.changeType),
|
|
6721
|
+
...parseTrackedChangeDecisionArgs(action, args),
|
|
6665
6722
|
});
|
|
6666
6723
|
case 'reject_tracked_changes':
|
|
6667
6724
|
return runRejectTrackedChanges(doc, {
|
|
6668
|
-
|
|
6669
|
-
changeType: parseTrackedChangeKind(args.changeType),
|
|
6725
|
+
...parseTrackedChangeDecisionArgs(action, args),
|
|
6670
6726
|
});
|
|
6671
6727
|
case 'normalize_body_font_size': {
|
|
6672
6728
|
const fontSize = asNumber(args.fontSize);
|
|
@@ -7036,6 +7092,62 @@ async function dispatchSuperdocPerformAction(doc, args) {
|
|
|
7036
7092
|
}
|
|
7037
7093
|
}
|
|
7038
7094
|
}
|
|
7095
|
+
function parseTrackedChangeDecisionArgs(action, args) {
|
|
7096
|
+
const hasId = args.id !== undefined;
|
|
7097
|
+
const hasAuthor = args.author !== undefined;
|
|
7098
|
+
const hasChangeType = args.changeType !== undefined;
|
|
7099
|
+
const id = hasId ? parseExactTrackedChangeIdList(args.id) : undefined;
|
|
7100
|
+
const author = asString(args.author)?.trim();
|
|
7101
|
+
const changeType = parseTrackedChangeKind(args.changeType);
|
|
7102
|
+
if (hasId && !id) {
|
|
7103
|
+
throw new errors.SuperDocCliError(`${action} requires "id" to be a non-empty string or array of non-empty strings`, {
|
|
7104
|
+
code: 'INVALID_ARGUMENT',
|
|
7105
|
+
});
|
|
7106
|
+
}
|
|
7107
|
+
if (hasAuthor && !author) {
|
|
7108
|
+
throw new errors.SuperDocCliError(`${action} requires "author" to be a non-empty string when provided`, {
|
|
7109
|
+
code: 'INVALID_ARGUMENT',
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
7112
|
+
if (hasChangeType && !changeType) {
|
|
7113
|
+
throw new errors.SuperDocCliError(`${action} requires "changeType" to be insert, delete, replacement, or format when provided`, {
|
|
7114
|
+
code: 'INVALID_ARGUMENT',
|
|
7115
|
+
});
|
|
7116
|
+
}
|
|
7117
|
+
if (id && (hasAuthor || hasChangeType)) {
|
|
7118
|
+
throw new errors.SuperDocCliError(`${action} cannot combine "id" with "author" or "changeType"`, {
|
|
7119
|
+
code: 'INVALID_ARGUMENT',
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
if (id)
|
|
7123
|
+
return { id };
|
|
7124
|
+
return {
|
|
7125
|
+
...(author ? { author } : {}),
|
|
7126
|
+
...(changeType ? { changeType } : {}),
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
function parseExactTrackedChangeIdList(value) {
|
|
7130
|
+
if (typeof value === 'string') {
|
|
7131
|
+
const trimmed = value.trim();
|
|
7132
|
+
return trimmed.length > 0 ? [trimmed] : undefined;
|
|
7133
|
+
}
|
|
7134
|
+
if (!Array.isArray(value))
|
|
7135
|
+
return undefined;
|
|
7136
|
+
const ids = [];
|
|
7137
|
+
const seen = new Set();
|
|
7138
|
+
for (const entry of value) {
|
|
7139
|
+
if (typeof entry !== 'string')
|
|
7140
|
+
return undefined;
|
|
7141
|
+
const trimmed = entry.trim();
|
|
7142
|
+
if (trimmed.length === 0)
|
|
7143
|
+
return undefined;
|
|
7144
|
+
if (seen.has(trimmed))
|
|
7145
|
+
continue;
|
|
7146
|
+
seen.add(trimmed);
|
|
7147
|
+
ids.push(trimmed);
|
|
7148
|
+
}
|
|
7149
|
+
return ids.length > 0 ? ids : undefined;
|
|
7150
|
+
}
|
|
7039
7151
|
async function superdocPerformAction(doc, args) {
|
|
7040
7152
|
try {
|
|
7041
7153
|
return await dispatchSuperdocPerformAction(doc, args);
|
package/dist/agent/actions.d.ts
CHANGED
|
@@ -224,17 +224,23 @@ export type RewriteBlockArgs = {
|
|
|
224
224
|
changeMode?: AgentChangeMode;
|
|
225
225
|
};
|
|
226
226
|
export type TrackedChangeKind = 'insert' | 'delete' | 'replacement' | 'format';
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
type TrackedChangeDecisionSelector = {
|
|
228
|
+
/** One change, or an atomic set. */
|
|
229
|
+
id: string | readonly string[];
|
|
230
|
+
author?: never;
|
|
231
|
+
changeType?: never;
|
|
232
|
+
} | {
|
|
233
|
+
id?: never;
|
|
229
234
|
author?: string;
|
|
230
235
|
/** Restrict the decision to one kind of change, e.g. 'format' = formatting-only revisions. */
|
|
231
236
|
changeType?: TrackedChangeKind;
|
|
232
237
|
};
|
|
238
|
+
export type AcceptTrackedChangesArgs = {
|
|
239
|
+
action: 'accept_tracked_changes';
|
|
240
|
+
} & TrackedChangeDecisionSelector;
|
|
233
241
|
export type RejectTrackedChangesArgs = {
|
|
234
242
|
action: 'reject_tracked_changes';
|
|
235
|
-
|
|
236
|
-
changeType?: TrackedChangeKind;
|
|
237
|
-
};
|
|
243
|
+
} & TrackedChangeDecisionSelector;
|
|
238
244
|
export type NormalizeBodyFontSizeArgs = {
|
|
239
245
|
action: 'normalize_body_font_size';
|
|
240
246
|
fontSize: number;
|
|
@@ -479,3 +485,4 @@ export type AddHyperlinkArgs = {
|
|
|
479
485
|
};
|
|
480
486
|
export declare function superdocPerformAction(doc: BoundDocApi, args: unknown): Promise<AgentReceipt>;
|
|
481
487
|
export declare const ACTION_NAMES_LIST: readonly ActionName[];
|
|
488
|
+
export {};
|
package/dist/agent/actions.js
CHANGED
|
@@ -69,8 +69,8 @@ export const ACTION_HINTS = {
|
|
|
69
69
|
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[]',
|
|
70
70
|
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".',
|
|
71
71
|
reply_to_comment: 'commentText (the reply body), anchorText (text the target comment is anchored on / mentions) OR commentId — THE way to REPLY to an existing comment thread. Use for "reply to the comment about X" / "respond to Reviewer\'s comment". Adds a threaded reply, not a new top-level comment.',
|
|
72
|
-
accept_tracked_changes: 'author
|
|
73
|
-
reject_tracked_changes: 'author
|
|
72
|
+
accept_tracked_changes: 'id? (string or string[]) OR author?/changeType?: insert|delete|replacement|format — a string[] decides that exact set atomically; e.g. changeType:"format" accepts ONLY formatting revisions (bold/italic/color), leaving text edits pending',
|
|
73
|
+
reject_tracked_changes: 'id? (string or string[]) OR author?/changeType?: insert|delete|replacement|format — a string[] decides that exact set atomically',
|
|
74
74
|
normalize_body_font_size: 'fontSize, changeMode?',
|
|
75
75
|
set_font_family: 'fontFamily (e.g. "Arial"), selector? (one block) OR targetText/targetTexts[] (occurrences) — omit both to set the WHOLE body font, caseSensitive?, changeMode? — THE way to change the typeface. Use for "change the font to X" / "set the heading font to Y".',
|
|
76
76
|
format_text: "bold?/italic?/underline?/strike? booleans, highlight? color name, color?, fontSize?, applied to EVERY occurrence of targetText (or targetTexts[] for several phrases in one call, or a selector'd block), caseSensitive?, changeMode? — THE way to bold/italicize/underline/highlight text, tracked-safe",
|
|
@@ -180,8 +180,8 @@ export const ACTION_ARGS = {
|
|
|
180
180
|
resolve_comments: ['anchorText', 'reopen'],
|
|
181
181
|
reply_to_comment: ['commentText', 'anchorText', 'commentId'],
|
|
182
182
|
rewrite_block: ['text', 'selector', 'changeMode', 'evidence'],
|
|
183
|
-
accept_tracked_changes: ['author', 'changeType'],
|
|
184
|
-
reject_tracked_changes: ['author', 'changeType'],
|
|
183
|
+
accept_tracked_changes: ['id', 'author', 'changeType'],
|
|
184
|
+
reject_tracked_changes: ['id', 'author', 'changeType'],
|
|
185
185
|
normalize_body_font_size: ['fontSize', 'changeMode'],
|
|
186
186
|
set_font_family: ['fontFamily', 'selector', 'targetText', 'targetTexts', 'caseSensitive', 'changeMode'],
|
|
187
187
|
apply_letter_spacing: ['selector', 'letterSpacing', 'changeMode'],
|
|
@@ -2869,17 +2869,28 @@ function normalizeTableColor(raw) {
|
|
|
2869
2869
|
return normalized ? `#${normalized}` : null;
|
|
2870
2870
|
}
|
|
2871
2871
|
async function runAcceptTrackedChanges(doc, args) {
|
|
2872
|
-
return runTrackedChangeDecision(doc, 'accept_tracked_changes', 'accept', args.author, args.changeType);
|
|
2872
|
+
return runTrackedChangeDecision(doc, 'accept_tracked_changes', 'accept', Array.isArray(args.id) ? args.id : typeof args.id === 'string' ? [args.id] : undefined, args.author, args.changeType);
|
|
2873
2873
|
}
|
|
2874
2874
|
async function runRejectTrackedChanges(doc, args) {
|
|
2875
|
-
return runTrackedChangeDecision(doc, 'reject_tracked_changes', 'reject', args.author, args.changeType);
|
|
2875
|
+
return runTrackedChangeDecision(doc, 'reject_tracked_changes', 'reject', Array.isArray(args.id) ? args.id : typeof args.id === 'string' ? [args.id] : undefined, args.author, args.changeType);
|
|
2876
2876
|
}
|
|
2877
|
-
async function runTrackedChangeDecision(doc, intentLabel, decision, author, changeType) {
|
|
2877
|
+
async function runTrackedChangeDecision(doc, intentLabel, decision, exactIds, author, changeType) {
|
|
2878
2878
|
const pre = await buildDocumentSnapshot(doc);
|
|
2879
2879
|
try {
|
|
2880
|
-
const listFn = maybeMethod(doc, ['trackChanges', 'list']);
|
|
2881
2880
|
const decideFn = maybeMethod(doc, ['trackChanges', 'decide']);
|
|
2882
|
-
if (!
|
|
2881
|
+
if (!decideFn) {
|
|
2882
|
+
throw new SuperDocCliError('doc.trackChanges.list / decide are not available on the document handle.', {
|
|
2883
|
+
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
// AIDEV-NOTE: Exact `id` must go to decide({ kind: 'id'|'ids' }) once.
|
|
2887
|
+
// Listing then filtering treats a stale id as an ok no-op, which looks
|
|
2888
|
+
// like success and invites a follow-up with no selector (accept/reject all).
|
|
2889
|
+
if (exactIds && exactIds.length > 0) {
|
|
2890
|
+
return decideExactTrackedChanges(doc, intentLabel, decision, decideFn, exactIds, pre);
|
|
2891
|
+
}
|
|
2892
|
+
const listFn = maybeMethod(doc, ['trackChanges', 'list']);
|
|
2893
|
+
if (!listFn) {
|
|
2883
2894
|
throw new SuperDocCliError('doc.trackChanges.list / decide are not available on the document handle.', {
|
|
2884
2895
|
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
2885
2896
|
});
|
|
@@ -2960,6 +2971,53 @@ async function runTrackedChangeDecision(doc, intentLabel, decision, author, chan
|
|
|
2960
2971
|
return failedReceipt(intentLabel, err, pre);
|
|
2961
2972
|
}
|
|
2962
2973
|
}
|
|
2974
|
+
function trackedChangeDecideFailure(result) {
|
|
2975
|
+
const rec = asRecord(result);
|
|
2976
|
+
if (!rec || rec.success !== false)
|
|
2977
|
+
return null;
|
|
2978
|
+
const failure = asRecord(rec.failure);
|
|
2979
|
+
return {
|
|
2980
|
+
code: asString(failure?.code, 'ACTION_FAILED') || 'ACTION_FAILED',
|
|
2981
|
+
message: asString(failure?.message, 'tracked-change decision failed') || 'tracked-change decision failed',
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
async function decideExactTrackedChanges(doc, intentLabel, decision, decideFn, exactIds, pre) {
|
|
2985
|
+
const target = exactIds.length === 1 ? { kind: 'id', id: exactIds[0] } : { kind: 'ids', ids: [...exactIds] };
|
|
2986
|
+
const result = await decideFn({ decision, target });
|
|
2987
|
+
const failure = trackedChangeDecideFailure(result);
|
|
2988
|
+
const selectedTargets = exactIds.map((id) => ({
|
|
2989
|
+
selector: { kind: 'entity', entityType: 'trackedChange', entityId: id },
|
|
2990
|
+
matched: failure ? [] : [id],
|
|
2991
|
+
}));
|
|
2992
|
+
if (failure) {
|
|
2993
|
+
return {
|
|
2994
|
+
status: 'failed',
|
|
2995
|
+
intent: intentLabel,
|
|
2996
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
2997
|
+
selectedTargets,
|
|
2998
|
+
executedOperations: [{ operationId: 'doc.trackChanges.decide', result }],
|
|
2999
|
+
verification: [],
|
|
3000
|
+
errors: [
|
|
3001
|
+
{
|
|
3002
|
+
code: failure.code,
|
|
3003
|
+
message: failure.message,
|
|
3004
|
+
recovery: { kind: 'reinspect' },
|
|
3005
|
+
},
|
|
3006
|
+
],
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
const post = await buildDocumentSnapshot(doc);
|
|
3010
|
+
const verification = evaluateChecks(pre, post, [{ kind: 'revision-changed' }]);
|
|
3011
|
+
return {
|
|
3012
|
+
status: verification.every((v) => v.passed) ? 'ok' : 'failed',
|
|
3013
|
+
intent: intentLabel,
|
|
3014
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
3015
|
+
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
3016
|
+
selectedTargets,
|
|
3017
|
+
executedOperations: [{ operationId: 'doc.trackChanges.decide', result }],
|
|
3018
|
+
verification,
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
2963
3021
|
async function listAllTrackedChanges(listFn) {
|
|
2964
3022
|
const PAGE = 250;
|
|
2965
3023
|
let offset = 0;
|
|
@@ -6658,14 +6716,12 @@ async function dispatchSuperdocPerformAction(doc, args) {
|
|
|
6658
6716
|
case 'accept_tracked_changes':
|
|
6659
6717
|
return runAcceptTrackedChanges(doc, {
|
|
6660
6718
|
action,
|
|
6661
|
-
|
|
6662
|
-
changeType: parseTrackedChangeKind(args.changeType),
|
|
6719
|
+
...parseTrackedChangeDecisionArgs(action, args),
|
|
6663
6720
|
});
|
|
6664
6721
|
case 'reject_tracked_changes':
|
|
6665
6722
|
return runRejectTrackedChanges(doc, {
|
|
6666
6723
|
action,
|
|
6667
|
-
|
|
6668
|
-
changeType: parseTrackedChangeKind(args.changeType),
|
|
6724
|
+
...parseTrackedChangeDecisionArgs(action, args),
|
|
6669
6725
|
});
|
|
6670
6726
|
case 'normalize_body_font_size': {
|
|
6671
6727
|
const fontSize = asNumber(args.fontSize);
|
|
@@ -7035,6 +7091,62 @@ async function dispatchSuperdocPerformAction(doc, args) {
|
|
|
7035
7091
|
}
|
|
7036
7092
|
}
|
|
7037
7093
|
}
|
|
7094
|
+
function parseTrackedChangeDecisionArgs(action, args) {
|
|
7095
|
+
const hasId = args.id !== undefined;
|
|
7096
|
+
const hasAuthor = args.author !== undefined;
|
|
7097
|
+
const hasChangeType = args.changeType !== undefined;
|
|
7098
|
+
const id = hasId ? parseExactTrackedChangeIdList(args.id) : undefined;
|
|
7099
|
+
const author = asString(args.author)?.trim();
|
|
7100
|
+
const changeType = parseTrackedChangeKind(args.changeType);
|
|
7101
|
+
if (hasId && !id) {
|
|
7102
|
+
throw new SuperDocCliError(`${action} requires "id" to be a non-empty string or array of non-empty strings`, {
|
|
7103
|
+
code: 'INVALID_ARGUMENT',
|
|
7104
|
+
});
|
|
7105
|
+
}
|
|
7106
|
+
if (hasAuthor && !author) {
|
|
7107
|
+
throw new SuperDocCliError(`${action} requires "author" to be a non-empty string when provided`, {
|
|
7108
|
+
code: 'INVALID_ARGUMENT',
|
|
7109
|
+
});
|
|
7110
|
+
}
|
|
7111
|
+
if (hasChangeType && !changeType) {
|
|
7112
|
+
throw new SuperDocCliError(`${action} requires "changeType" to be insert, delete, replacement, or format when provided`, {
|
|
7113
|
+
code: 'INVALID_ARGUMENT',
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
if (id && (hasAuthor || hasChangeType)) {
|
|
7117
|
+
throw new SuperDocCliError(`${action} cannot combine "id" with "author" or "changeType"`, {
|
|
7118
|
+
code: 'INVALID_ARGUMENT',
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
if (id)
|
|
7122
|
+
return { id };
|
|
7123
|
+
return {
|
|
7124
|
+
...(author ? { author } : {}),
|
|
7125
|
+
...(changeType ? { changeType } : {}),
|
|
7126
|
+
};
|
|
7127
|
+
}
|
|
7128
|
+
function parseExactTrackedChangeIdList(value) {
|
|
7129
|
+
if (typeof value === 'string') {
|
|
7130
|
+
const trimmed = value.trim();
|
|
7131
|
+
return trimmed.length > 0 ? [trimmed] : undefined;
|
|
7132
|
+
}
|
|
7133
|
+
if (!Array.isArray(value))
|
|
7134
|
+
return undefined;
|
|
7135
|
+
const ids = [];
|
|
7136
|
+
const seen = new Set();
|
|
7137
|
+
for (const entry of value) {
|
|
7138
|
+
if (typeof entry !== 'string')
|
|
7139
|
+
return undefined;
|
|
7140
|
+
const trimmed = entry.trim();
|
|
7141
|
+
if (trimmed.length === 0)
|
|
7142
|
+
return undefined;
|
|
7143
|
+
if (seen.has(trimmed))
|
|
7144
|
+
continue;
|
|
7145
|
+
seen.add(trimmed);
|
|
7146
|
+
ids.push(trimmed);
|
|
7147
|
+
}
|
|
7148
|
+
return ids.length > 0 ? ids : undefined;
|
|
7149
|
+
}
|
|
7038
7150
|
export async function superdocPerformAction(doc, args) {
|
|
7039
7151
|
try {
|
|
7040
7152
|
return await dispatchSuperdocPerformAction(doc, args);
|
package/dist/agent/catalog.cjs
CHANGED
|
@@ -158,6 +158,12 @@ const ACTION_ARG_SCHEMA = {
|
|
|
158
158
|
commentText: { type: 'string' },
|
|
159
159
|
scope: { type: 'string', enum: ['all', 'body'] },
|
|
160
160
|
excludeBlockQuotes: { type: 'boolean' },
|
|
161
|
+
id: {
|
|
162
|
+
oneOf: [
|
|
163
|
+
{ type: 'string', minLength: 1 },
|
|
164
|
+
{ type: 'array', minItems: 1, items: { type: 'string', minLength: 1 } },
|
|
165
|
+
],
|
|
166
|
+
},
|
|
161
167
|
author: { type: 'string' },
|
|
162
168
|
changeType: { type: 'string', enum: ['insert', 'delete', 'replacement', 'format'] },
|
|
163
169
|
fontSize: { type: 'number' },
|
|
@@ -316,7 +322,7 @@ function buildPerformActionDefinition(includedActions) {
|
|
|
316
322
|
description: buildActionDescription(included.size === actions.ACTION_NAMES_LIST.length ? undefined : included),
|
|
317
323
|
inputSchema: {
|
|
318
324
|
type: 'object',
|
|
319
|
-
additionalProperties:
|
|
325
|
+
additionalProperties: false,
|
|
320
326
|
required: ['action'],
|
|
321
327
|
properties: {
|
|
322
328
|
action: {
|
package/dist/agent/catalog.js
CHANGED
|
@@ -155,6 +155,12 @@ export const ACTION_ARG_SCHEMA = {
|
|
|
155
155
|
commentText: { type: 'string' },
|
|
156
156
|
scope: { type: 'string', enum: ['all', 'body'] },
|
|
157
157
|
excludeBlockQuotes: { type: 'boolean' },
|
|
158
|
+
id: {
|
|
159
|
+
oneOf: [
|
|
160
|
+
{ type: 'string', minLength: 1 },
|
|
161
|
+
{ type: 'array', minItems: 1, items: { type: 'string', minLength: 1 } },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
158
164
|
author: { type: 'string' },
|
|
159
165
|
changeType: { type: 'string', enum: ['insert', 'delete', 'replacement', 'format'] },
|
|
160
166
|
fontSize: { type: 'number' },
|
|
@@ -313,7 +319,7 @@ export function buildPerformActionDefinition(includedActions) {
|
|
|
313
319
|
description: buildActionDescription(included.size === ACTION_NAMES_LIST.length ? undefined : included),
|
|
314
320
|
inputSchema: {
|
|
315
321
|
type: 'object',
|
|
316
|
-
additionalProperties:
|
|
322
|
+
additionalProperties: false,
|
|
317
323
|
required: ['action'],
|
|
318
324
|
properties: {
|
|
319
325
|
action: {
|
|
@@ -141,6 +141,33 @@ function populateTableCellsFromExtract(tables, extractRaw, tableOrdinalBase = 0)
|
|
|
141
141
|
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
function normalizeSnapshotBlock(value, fallbackOrdinal, blockTextLimit) {
|
|
145
|
+
const rec = asRecord(value) ?? {};
|
|
146
|
+
const fullText = asString(rec.text);
|
|
147
|
+
const text = truncateBlockText(fullText, blockTextLimit);
|
|
148
|
+
const textPreview = typeof rec.textPreview === 'string' ? truncateBlockText(rec.textPreview, blockTextLimit) : null;
|
|
149
|
+
const block = {
|
|
150
|
+
// doc-api block.ordinal is 0-based; the model-facing convention is
|
|
151
|
+
// 1-based (matches paragraphOrdinal/tableOrdinal and ordinal selectors).
|
|
152
|
+
ordinal: asNumber(rec.ordinal, fallbackOrdinal) + 1,
|
|
153
|
+
nodeId: asString(rec.nodeId),
|
|
154
|
+
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
155
|
+
text,
|
|
156
|
+
textPreview,
|
|
157
|
+
styleId: typeof rec.styleId === 'string' ? rec.styleId : null,
|
|
158
|
+
headingLevel: typeof rec.headingLevel === 'number' ? rec.headingLevel : undefined,
|
|
159
|
+
...(asRecord(rec.numbering)
|
|
160
|
+
? {
|
|
161
|
+
numbering: {
|
|
162
|
+
marker: asString(asRecord(rec.numbering).marker) || null,
|
|
163
|
+
path: Array.isArray(asRecord(rec.numbering).path) ? asRecord(rec.numbering).path : null,
|
|
164
|
+
kind: asString(asRecord(rec.numbering).kind) || null,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
168
|
+
};
|
|
169
|
+
return block;
|
|
170
|
+
}
|
|
144
171
|
/**
|
|
145
172
|
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
146
173
|
* read-mode operations from the generated contract — it never mutates state,
|
|
@@ -259,30 +286,8 @@ async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
259
286
|
const rawBlocks = Array.isArray(blocksRec?.blocks) ? blocksRec.blocks : [];
|
|
260
287
|
const fullBlockText = new Map();
|
|
261
288
|
const normalizedBlocks = rawBlocks.map((b, index) => {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const text = truncateBlockText(fullText, blockTextLimit);
|
|
265
|
-
const textPreview = typeof rec.textPreview === 'string' ? truncateBlockText(rec.textPreview, blockTextLimit) : null;
|
|
266
|
-
const block = {
|
|
267
|
-
// doc-api block.ordinal is 0-based; the model-facing convention is
|
|
268
|
-
// 1-based (matches paragraphOrdinal/tableOrdinal and ordinal selectors).
|
|
269
|
-
ordinal: asNumber(rec.ordinal, blockOffset + index) + 1,
|
|
270
|
-
nodeId: asString(rec.nodeId),
|
|
271
|
-
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
272
|
-
text,
|
|
273
|
-
textPreview,
|
|
274
|
-
styleId: typeof rec.styleId === 'string' ? rec.styleId : null,
|
|
275
|
-
headingLevel: typeof rec.headingLevel === 'number' ? rec.headingLevel : undefined,
|
|
276
|
-
...(asRecord(rec.numbering)
|
|
277
|
-
? {
|
|
278
|
-
numbering: {
|
|
279
|
-
marker: asString(asRecord(rec.numbering).marker) || null,
|
|
280
|
-
path: Array.isArray(asRecord(rec.numbering).path) ? asRecord(rec.numbering).path : null,
|
|
281
|
-
kind: asString(asRecord(rec.numbering).kind) || null,
|
|
282
|
-
},
|
|
283
|
-
}
|
|
284
|
-
: {}),
|
|
285
|
-
};
|
|
289
|
+
const fullText = asString(asRecord(b)?.text);
|
|
290
|
+
const block = normalizeSnapshotBlock(b, blockOffset + index, blockTextLimit);
|
|
286
291
|
fullBlockText.set(block, fullText);
|
|
287
292
|
return block;
|
|
288
293
|
});
|
|
@@ -1003,4 +1008,5 @@ exports.MutationSnapshotError = MutationSnapshotError;
|
|
|
1003
1008
|
exports.buildDocumentSnapshot = buildDocumentSnapshot;
|
|
1004
1009
|
exports.buildMutationSnapshot = buildMutationSnapshot;
|
|
1005
1010
|
exports.matchRunsForBlock = matchRunsForBlock;
|
|
1011
|
+
exports.normalizeSnapshotBlock = normalizeSnapshotBlock;
|
|
1006
1012
|
exports.resolveSnapshotSelector = resolveSnapshotSelector;
|
|
@@ -306,6 +306,7 @@ type SnapshotOptions = {
|
|
|
306
306
|
/** Max blocks to enrich with runs when includeBlockRuns is set (default 30). */
|
|
307
307
|
blockRunsLimit?: number;
|
|
308
308
|
};
|
|
309
|
+
export declare function normalizeSnapshotBlock(value: unknown, fallbackOrdinal: number, blockTextLimit: number | null): SnapshotBlock;
|
|
309
310
|
/**
|
|
310
311
|
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
311
312
|
* read-mode operations from the generated contract — it never mutates state,
|
|
@@ -139,6 +139,33 @@ function populateTableCellsFromExtract(tables, extractRaw, tableOrdinalBase = 0)
|
|
|
139
139
|
table.cells = [...cellMap.values()].sort((left, right) => left.rowIndex - right.rowIndex || left.columnIndex - right.columnIndex);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
export function normalizeSnapshotBlock(value, fallbackOrdinal, blockTextLimit) {
|
|
143
|
+
const rec = asRecord(value) ?? {};
|
|
144
|
+
const fullText = asString(rec.text);
|
|
145
|
+
const text = truncateBlockText(fullText, blockTextLimit);
|
|
146
|
+
const textPreview = typeof rec.textPreview === 'string' ? truncateBlockText(rec.textPreview, blockTextLimit) : null;
|
|
147
|
+
const block = {
|
|
148
|
+
// doc-api block.ordinal is 0-based; the model-facing convention is
|
|
149
|
+
// 1-based (matches paragraphOrdinal/tableOrdinal and ordinal selectors).
|
|
150
|
+
ordinal: asNumber(rec.ordinal, fallbackOrdinal) + 1,
|
|
151
|
+
nodeId: asString(rec.nodeId),
|
|
152
|
+
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
153
|
+
text,
|
|
154
|
+
textPreview,
|
|
155
|
+
styleId: typeof rec.styleId === 'string' ? rec.styleId : null,
|
|
156
|
+
headingLevel: typeof rec.headingLevel === 'number' ? rec.headingLevel : undefined,
|
|
157
|
+
...(asRecord(rec.numbering)
|
|
158
|
+
? {
|
|
159
|
+
numbering: {
|
|
160
|
+
marker: asString(asRecord(rec.numbering).marker) || null,
|
|
161
|
+
path: Array.isArray(asRecord(rec.numbering).path) ? asRecord(rec.numbering).path : null,
|
|
162
|
+
kind: asString(asRecord(rec.numbering).kind) || null,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
: {}),
|
|
166
|
+
};
|
|
167
|
+
return block;
|
|
168
|
+
}
|
|
142
169
|
/**
|
|
143
170
|
* Build a deterministic snapshot of a document. The snapshot uses only
|
|
144
171
|
* read-mode operations from the generated contract — it never mutates state,
|
|
@@ -257,30 +284,8 @@ export async function buildDocumentSnapshot(doc, options = {}) {
|
|
|
257
284
|
const rawBlocks = Array.isArray(blocksRec?.blocks) ? blocksRec.blocks : [];
|
|
258
285
|
const fullBlockText = new Map();
|
|
259
286
|
const normalizedBlocks = rawBlocks.map((b, index) => {
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
const text = truncateBlockText(fullText, blockTextLimit);
|
|
263
|
-
const textPreview = typeof rec.textPreview === 'string' ? truncateBlockText(rec.textPreview, blockTextLimit) : null;
|
|
264
|
-
const block = {
|
|
265
|
-
// doc-api block.ordinal is 0-based; the model-facing convention is
|
|
266
|
-
// 1-based (matches paragraphOrdinal/tableOrdinal and ordinal selectors).
|
|
267
|
-
ordinal: asNumber(rec.ordinal, blockOffset + index) + 1,
|
|
268
|
-
nodeId: asString(rec.nodeId),
|
|
269
|
-
nodeType: asString(rec.nodeType, 'paragraph'),
|
|
270
|
-
text,
|
|
271
|
-
textPreview,
|
|
272
|
-
styleId: typeof rec.styleId === 'string' ? rec.styleId : null,
|
|
273
|
-
headingLevel: typeof rec.headingLevel === 'number' ? rec.headingLevel : undefined,
|
|
274
|
-
...(asRecord(rec.numbering)
|
|
275
|
-
? {
|
|
276
|
-
numbering: {
|
|
277
|
-
marker: asString(asRecord(rec.numbering).marker) || null,
|
|
278
|
-
path: Array.isArray(asRecord(rec.numbering).path) ? asRecord(rec.numbering).path : null,
|
|
279
|
-
kind: asString(asRecord(rec.numbering).kind) || null,
|
|
280
|
-
},
|
|
281
|
-
}
|
|
282
|
-
: {}),
|
|
283
|
-
};
|
|
287
|
+
const fullText = asString(asRecord(b)?.text);
|
|
288
|
+
const block = normalizeSnapshotBlock(b, blockOffset + index, blockTextLimit);
|
|
284
289
|
fullBlockText.set(block, fullText);
|
|
285
290
|
return block;
|
|
286
291
|
});
|