@superdoc/sdk 2.0.0-next.100 → 2.1.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/README.md +12 -12
- package/dist/agent/actions.cjs +183 -1
- package/dist/agent/actions.d.ts +8 -2
- package/dist/agent/actions.js +183 -1
- package/dist/embedded-prompts.generated.cjs +1 -1
- package/dist/embedded-prompts.generated.js +1 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.d.ts +414 -0
- package/dist/generated/contract.cjs +1135 -331
- package/dist/generated/contract.js +1141 -331
- package/dist/prompts/system-prompt.md +2 -1
- package/dist/runtime/transport-common.cjs +10 -4
- package/dist/runtime/transport-common.js +10 -4
- package/package.json +17 -6
- package/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/tools/__pycache__/intent_dispatch_generated.cpython-312.pyc +0 -0
- package/tools/catalog.json +38 -1
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +38 -1
- package/tools/tools.generic.json +38 -1
- package/tools/tools.openai.json +38 -1
- package/tools/tools.vercel.json +38 -1
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# @superdoc/sdk
|
|
1
|
+
# @superdoc-dev/sdk
|
|
2
2
|
|
|
3
3
|
Programmatic SDK for deterministic DOCX operations through SuperDoc's Document API.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @superdoc/sdk
|
|
8
|
+
npm install @superdoc-dev/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
The package automatically installs a native CLI binary for your platform via optionalDependencies. Supported platforms:
|
|
12
12
|
|
|
13
13
|
| Platform | Package |
|
|
14
14
|
|----------|---------|
|
|
15
|
-
| macOS (Apple Silicon) | `@superdoc/sdk-darwin-arm64` |
|
|
16
|
-
| macOS (Intel) | `@superdoc/sdk-darwin-x64` |
|
|
17
|
-
| Linux (x64) | `@superdoc/sdk-linux-x64` |
|
|
18
|
-
| Linux (ARM64) | `@superdoc/sdk-linux-arm64` |
|
|
19
|
-
| Windows (x64) | `@superdoc/sdk-windows-x64` |
|
|
15
|
+
| macOS (Apple Silicon) | `@superdoc-dev/sdk-darwin-arm64` |
|
|
16
|
+
| macOS (Intel) | `@superdoc-dev/sdk-darwin-x64` |
|
|
17
|
+
| Linux (x64) | `@superdoc-dev/sdk-linux-x64` |
|
|
18
|
+
| Linux (ARM64) | `@superdoc-dev/sdk-linux-arm64` |
|
|
19
|
+
| Windows (x64) | `@superdoc-dev/sdk-windows-x64` |
|
|
20
20
|
|
|
21
21
|
## Quick Start
|
|
22
22
|
|
|
@@ -24,14 +24,14 @@ Both ESM and CommonJS are supported.
|
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
// ESM
|
|
27
|
-
import { createSuperDocClient } from '@superdoc/sdk';
|
|
27
|
+
import { createSuperDocClient } from '@superdoc-dev/sdk';
|
|
28
28
|
|
|
29
29
|
// CJS
|
|
30
|
-
const { createSuperDocClient } = require('@superdoc/sdk');
|
|
30
|
+
const { createSuperDocClient } = require('@superdoc-dev/sdk');
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
import { createSuperDocClient } from '@superdoc/sdk';
|
|
34
|
+
import { createSuperDocClient } from '@superdoc-dev/sdk';
|
|
35
35
|
|
|
36
36
|
const client = createSuperDocClient();
|
|
37
37
|
await client.connect();
|
|
@@ -74,7 +74,7 @@ The password is forwarded only for the initial open and is not persisted. If the
|
|
|
74
74
|
### Client
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
import { SuperDocClient, createSuperDocClient } from '@superdoc/sdk';
|
|
77
|
+
import { SuperDocClient, createSuperDocClient } from '@superdoc-dev/sdk';
|
|
78
78
|
|
|
79
79
|
const client = createSuperDocClient(options?);
|
|
80
80
|
await client.connect(); // start the host process
|
|
@@ -118,7 +118,7 @@ import {
|
|
|
118
118
|
chooseTools,
|
|
119
119
|
dispatchSuperDocTool,
|
|
120
120
|
getToolCatalog,
|
|
121
|
-
} from '@superdoc/sdk';
|
|
121
|
+
} from '@superdoc-dev/sdk';
|
|
122
122
|
|
|
123
123
|
// Get the full grouped tool set for your AI provider
|
|
124
124
|
const { tools, meta } = await chooseTools({
|
package/dist/agent/actions.cjs
CHANGED
|
@@ -11,6 +11,7 @@ const ACTION_NAMES = [
|
|
|
11
11
|
'insert_heading',
|
|
12
12
|
'replace_text',
|
|
13
13
|
'delete_text',
|
|
14
|
+
'delete_blocks',
|
|
14
15
|
'append_list',
|
|
15
16
|
'create_table',
|
|
16
17
|
'comment_paragraphs',
|
|
@@ -59,7 +60,8 @@ const ACTION_HINTS = {
|
|
|
59
60
|
insert_paragraphs: 'texts[] (or text for a single paragraph), placement?, headingLevel? (first item as heading 1-6), changeMode?',
|
|
60
61
|
insert_heading: 'text, level, placement?, changeMode?',
|
|
61
62
|
replace_text: 'edits[{find,replace}], optional selector to scope replacements to one inspected block, caseSensitive?, changeMode?',
|
|
62
|
-
delete_text:
|
|
63
|
+
delete_text: "finds[], optional selector to scope deletions to ONE inspected block (required to delete stray whitespace — an unscoped whitespace find matches document-wide), caseSensitive?, changeMode? — deletes TEXT ONLY, leaving the block (and a list item's bullet/number) in place. To remove a whole list item, paragraph or heading use delete_blocks",
|
|
64
|
+
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)',
|
|
63
65
|
append_list: 'items[], kind?: ordered|bullet, headingText?, headingLevel?, placement? {at:"after"|"before",selector} builds the list at that block instead of document end',
|
|
64
66
|
create_table: 'rows, columns, cellTexts?, placement?, changeMode? — changeMode:"tracked" makes the table insertion itself a tracked change',
|
|
65
67
|
rewrite_block: 'selector, text, changeMode?',
|
|
@@ -106,6 +108,7 @@ const ACTION_GROUPS = [
|
|
|
106
108
|
'insert_heading',
|
|
107
109
|
'replace_text',
|
|
108
110
|
'delete_text',
|
|
111
|
+
'delete_blocks',
|
|
109
112
|
'append_list',
|
|
110
113
|
'create_table',
|
|
111
114
|
'rewrite_block',
|
|
@@ -167,6 +170,7 @@ const ACTION_ARGS = {
|
|
|
167
170
|
insert_heading: ['text', 'level', 'placement', 'changeMode'],
|
|
168
171
|
replace_text: ['edits', 'selector', 'caseSensitive', 'changeMode'],
|
|
169
172
|
delete_text: ['finds', 'selector', 'caseSensitive', 'changeMode'],
|
|
173
|
+
delete_blocks: ['selectors', 'selector', 'changeMode'],
|
|
170
174
|
append_list: ['items', 'kind', 'headingText', 'headingLevel', 'placement', 'changeMode'],
|
|
171
175
|
create_table: ['rows', 'columns', 'cellTexts', 'placement', 'changeMode'],
|
|
172
176
|
comment_paragraphs: ['commentText', 'scope', 'excludeBlockQuotes'],
|
|
@@ -1225,6 +1229,157 @@ async function runDeleteText(doc, args) {
|
|
|
1225
1229
|
return failedReceipt('delete_text', err, pre);
|
|
1226
1230
|
}
|
|
1227
1231
|
}
|
|
1232
|
+
/** Block node types `doc.blocks.delete` removes as a whole paragraph-shaped block. */
|
|
1233
|
+
const DELETABLE_BLOCK_NODE_TYPES = new Set(['paragraph', 'heading', 'listItem']);
|
|
1234
|
+
/**
|
|
1235
|
+
* delete_blocks — remove ENTIRE blocks (list items, paragraphs, headings) in
|
|
1236
|
+
* ONE call. Wraps doc.blocks.delete, which removes the block NODE, its
|
|
1237
|
+
* paragraph properties (`w:pPr`, including a list item's `w:numPr`) included.
|
|
1238
|
+
*
|
|
1239
|
+
* delete_text only strikes the RUNS. Routing "delete this list item"
|
|
1240
|
+
* through it leaves the numbered paragraph behind, so accepting the tracked
|
|
1241
|
+
* change yields an empty `1.` and the following items never renumber. Whole-
|
|
1242
|
+
* block removal has to go through blocks.delete, and in tracked mode that
|
|
1243
|
+
* records ONE structural revision whose acceptance removes the paragraph and
|
|
1244
|
+
* whose rejection restores it intact.
|
|
1245
|
+
*/
|
|
1246
|
+
async function runDeleteBlocks(doc, args) {
|
|
1247
|
+
const domains = new Set(['blocks', 'trackedChanges']);
|
|
1248
|
+
for (const selector of args.selectors) {
|
|
1249
|
+
for (const domain of snapshotDomainsForSelector(selector))
|
|
1250
|
+
domains.add(domain);
|
|
1251
|
+
}
|
|
1252
|
+
const pre = await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: [...domains] });
|
|
1253
|
+
try {
|
|
1254
|
+
if (args.selectors.length === 0) {
|
|
1255
|
+
return failedReceipt('delete_blocks', new Error('selectors must be non-empty'), pre);
|
|
1256
|
+
}
|
|
1257
|
+
const deleteFn = maybeMethod(doc, ['blocks', 'delete']);
|
|
1258
|
+
if (!deleteFn) {
|
|
1259
|
+
throw new errors.SuperDocCliError('doc.blocks.delete is not available on the document handle.', {
|
|
1260
|
+
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
// Resolve EVERY selector against the same pre-snapshot before deleting
|
|
1264
|
+
// anything. nodeIds are stable paragraph ids, so a target resolved up front
|
|
1265
|
+
// stays addressable after a sibling is removed — whereas an ordinal or
|
|
1266
|
+
// text-search selector re-resolved mid-run would drift onto the wrong block.
|
|
1267
|
+
const targets = [];
|
|
1268
|
+
const unresolved = [];
|
|
1269
|
+
const wrongShape = [];
|
|
1270
|
+
const seen = new Set();
|
|
1271
|
+
for (const selector of args.selectors) {
|
|
1272
|
+
// A tableCell selector resolves to the FIRST PARAGRAPH inside the cell,
|
|
1273
|
+
// so it would otherwise pass the paragraph-shape gate below. Removing it
|
|
1274
|
+
// is not a whole-block delete: a `<w:tc>` must keep at least one
|
|
1275
|
+
// paragraph, so deleting the only one leaves invalid OOXML, and in
|
|
1276
|
+
// tracked mode the compiler can only strike the runs anyway. Refuse the
|
|
1277
|
+
// shape rather than half-honour it.
|
|
1278
|
+
if (selector.kind === 'tableCell') {
|
|
1279
|
+
wrongShape.push({ selector, nodeType: 'tableCell' });
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
const target = selectorToBlockTarget(selector, pre);
|
|
1283
|
+
if (!target) {
|
|
1284
|
+
unresolved.push(selector);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (!DELETABLE_BLOCK_NODE_TYPES.has(target.nodeType)) {
|
|
1288
|
+
wrongShape.push({ selector, nodeType: target.nodeType });
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (seen.has(target.nodeId))
|
|
1292
|
+
continue;
|
|
1293
|
+
seen.add(target.nodeId);
|
|
1294
|
+
targets.push({ selector, nodeId: target.nodeId, nodeType: target.nodeType });
|
|
1295
|
+
}
|
|
1296
|
+
const errors$1 = [];
|
|
1297
|
+
for (const selector of unresolved) {
|
|
1298
|
+
errors$1.push({
|
|
1299
|
+
code: 'ACTION_FAILED',
|
|
1300
|
+
message: `selector did not resolve to a unique body block: ${JSON.stringify(selector)}`,
|
|
1301
|
+
recovery: { kind: 'reinspect' },
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
for (const entry of wrongShape) {
|
|
1305
|
+
errors$1.push({
|
|
1306
|
+
code: 'INVALID_ARGUMENT',
|
|
1307
|
+
message: `delete_blocks removes paragraph-shaped blocks (list items, paragraphs, headings); ` +
|
|
1308
|
+
`${JSON.stringify(entry.selector)} resolved to a "${entry.nodeType}"` +
|
|
1309
|
+
(entry.nodeType === 'table'
|
|
1310
|
+
? ' — use delete_table for a whole table.'
|
|
1311
|
+
: entry.nodeType === 'tableCell'
|
|
1312
|
+
? ' — a cell must keep a paragraph; use delete_text to clear it, or delete_table_row/delete_table_column.'
|
|
1313
|
+
: '.'),
|
|
1314
|
+
recovery: { kind: 'reinspect' },
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
if (targets.length === 0) {
|
|
1318
|
+
return {
|
|
1319
|
+
status: 'failed',
|
|
1320
|
+
intent: 'delete_blocks',
|
|
1321
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
1322
|
+
selectedTargets: [],
|
|
1323
|
+
executedOperations: [],
|
|
1324
|
+
verification: [],
|
|
1325
|
+
errors: errors$1,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
const changeMode = parseChangeMode(args.changeMode);
|
|
1329
|
+
const executedOperations = [];
|
|
1330
|
+
const deleted = [];
|
|
1331
|
+
for (const target of targets) {
|
|
1332
|
+
try {
|
|
1333
|
+
const result = await deleteFn({ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } }, changeMode ? { changeMode } : undefined);
|
|
1334
|
+
executedOperations.push({ operationId: 'doc.blocks.delete', result: compactOpResult(result) });
|
|
1335
|
+
deleted.push({ nodeId: target.nodeId, nodeType: target.nodeType });
|
|
1336
|
+
}
|
|
1337
|
+
catch (err) {
|
|
1338
|
+
errors$1.push({
|
|
1339
|
+
code: 'ACTION_FAILED',
|
|
1340
|
+
message: `blocks.delete failed for ${target.nodeType} ${target.nodeId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1341
|
+
recovery: { kind: 'reinspect' },
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
const post = await docSnapshot.buildDocumentSnapshot(doc, { includeDomains: [...domains] });
|
|
1346
|
+
// Tracked deletions leave the block in place until the revision is decided,
|
|
1347
|
+
// so the block count cannot move — count the structural revisions instead.
|
|
1348
|
+
// Direct deletions must show one fewer block of each deleted node type.
|
|
1349
|
+
const checks = changeMode === 'tracked'
|
|
1350
|
+
? [{ kind: 'tracked-change-count-delta', delta: deleted.length }]
|
|
1351
|
+
: [...new Set(deleted.map((entry) => entry.nodeType))].map((nodeType) => ({
|
|
1352
|
+
kind: 'block-count-delta',
|
|
1353
|
+
nodeType,
|
|
1354
|
+
delta: -deleted.filter((entry) => entry.nodeType === nodeType).length,
|
|
1355
|
+
}));
|
|
1356
|
+
const verification = evaluateChecks(pre, post, checks);
|
|
1357
|
+
// Compare against the DEDUPED target list: two selectors naming the same
|
|
1358
|
+
// block are one deletion, and counting the raw selectors would report a
|
|
1359
|
+
// fully applied call as `partial` with no error to explain it. Selectors
|
|
1360
|
+
// that never became a target (unresolved or wrong-shape) already recorded
|
|
1361
|
+
// an error, so the `errors.length` term still catches a short request.
|
|
1362
|
+
const allApplied = deleted.length === targets.length && errors$1.length === 0;
|
|
1363
|
+
const verified = verification.every((v) => v.passed);
|
|
1364
|
+
return {
|
|
1365
|
+
status: !verified || deleted.length === 0 ? 'failed' : allApplied ? 'ok' : 'partial',
|
|
1366
|
+
intent: `delete_blocks: ${deleted.length} block(s)`,
|
|
1367
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
1368
|
+
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
1369
|
+
selectedTargets: targets.map((target) => ({ selector: target.selector, matched: [target.nodeId] })),
|
|
1370
|
+
executedOperations,
|
|
1371
|
+
verification,
|
|
1372
|
+
deletedBlocks: deleted,
|
|
1373
|
+
...(errors$1.length ? { errors: errors$1 } : {}),
|
|
1374
|
+
...(errors$1.length
|
|
1375
|
+
? { nextStep: 'Re-inspect the document and retry the selectors that did not resolve to one block.' }
|
|
1376
|
+
: {}),
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
catch (err) {
|
|
1380
|
+
return failedReceipt('delete_blocks', err, pre);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1228
1383
|
async function runAppendList(doc, args) {
|
|
1229
1384
|
const pre = await docSnapshot.buildDocumentSnapshot(doc);
|
|
1230
1385
|
try {
|
|
@@ -4692,6 +4847,33 @@ async function superdocPerformAction(doc, args) {
|
|
|
4692
4847
|
changeMode: parseChangeMode(args.changeMode),
|
|
4693
4848
|
});
|
|
4694
4849
|
}
|
|
4850
|
+
case 'delete_blocks': {
|
|
4851
|
+
// Accept a single `selector` too: the model reaches for the singular form
|
|
4852
|
+
// by analogy with delete_text/rewrite_block, and rejecting it would cost a
|
|
4853
|
+
// turn for no reason.
|
|
4854
|
+
const rawSelectors = Array.isArray(args.selectors)
|
|
4855
|
+
? args.selectors
|
|
4856
|
+
: args.selectors != null
|
|
4857
|
+
? [args.selectors]
|
|
4858
|
+
: args.selector != null
|
|
4859
|
+
? [args.selector]
|
|
4860
|
+
: [];
|
|
4861
|
+
if (rawSelectors.length === 0) {
|
|
4862
|
+
throw new errors.SuperDocCliError('delete_blocks requires a non-empty "selectors" array, each entry resolving to one block (e.g. {kind:"nodeId",nodeId:"…"})', { code: 'INVALID_ARGUMENT' });
|
|
4863
|
+
}
|
|
4864
|
+
// EVERY entry has to parse. Dropping the unparseable ones would delete the
|
|
4865
|
+
// rest and report success, so the model would never learn that part of its
|
|
4866
|
+
// request went nowhere — the same false-success loop this action ends.
|
|
4867
|
+
const selectors = [];
|
|
4868
|
+
for (const [index, raw] of rawSelectors.entries()) {
|
|
4869
|
+
const parsed = parseSelector(raw);
|
|
4870
|
+
if (!parsed) {
|
|
4871
|
+
throw new errors.SuperDocCliError(`delete_blocks selectors[${index}] is not a valid selector (e.g. {kind:"nodeId",nodeId:"…"}); no blocks were deleted`, { code: 'INVALID_ARGUMENT' });
|
|
4872
|
+
}
|
|
4873
|
+
selectors.push(parsed);
|
|
4874
|
+
}
|
|
4875
|
+
return runDeleteBlocks(doc, { action, selectors, changeMode: parseChangeMode(args.changeMode) });
|
|
4876
|
+
}
|
|
4695
4877
|
case 'append_list': {
|
|
4696
4878
|
const items = parseStringArray(args.items);
|
|
4697
4879
|
if (!items || items.length === 0) {
|
package/dist/agent/actions.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import type { BoundDocApi } from '../generated/client.js';
|
|
21
21
|
import type { AgentReceipt } from './runtime.js';
|
|
22
22
|
import type { AgentChangeMode, AgentSelector } from './ir.js';
|
|
23
|
-
export type ActionName = 'insert_paragraphs' | 'insert_heading' | 'replace_text' | 'delete_text' | 'append_list' | 'create_table' | 'comment_paragraphs' | 'add_comments' | 'resolve_comments' | 'reply_to_comment' | 'rewrite_block' | 'accept_tracked_changes' | 'reject_tracked_changes' | 'normalize_body_font_size' | 'set_font_family' | 'apply_letter_spacing' | 'fill_placeholders' | 'move_range' | 'insert_toc' | 'insert_table_row' | 'insert_table_column' | 'delete_table_row' | 'delete_table_column' | 'split_table' | 'convert_list' | 'split_list' | 'undo_changes' | 'redo_changes' | 'attach_numbering' | 'add_list_items' | 'format_text' | 'apply_style' | 'format_paragraph' | 'move_text' | 'style_table' | 'move_table' | 'delete_table' | 'set_paragraph_spacing' | 'insert_page_break' | 'add_hyperlink';
|
|
23
|
+
export type ActionName = 'insert_paragraphs' | 'insert_heading' | 'replace_text' | 'delete_text' | 'delete_blocks' | 'append_list' | 'create_table' | 'comment_paragraphs' | 'add_comments' | 'resolve_comments' | 'reply_to_comment' | 'rewrite_block' | 'accept_tracked_changes' | 'reject_tracked_changes' | 'normalize_body_font_size' | 'set_font_family' | 'apply_letter_spacing' | 'fill_placeholders' | 'move_range' | 'insert_toc' | 'insert_table_row' | 'insert_table_column' | 'delete_table_row' | 'delete_table_column' | 'split_table' | 'convert_list' | 'split_list' | 'undo_changes' | 'redo_changes' | 'attach_numbering' | 'add_list_items' | 'format_text' | 'apply_style' | 'format_paragraph' | 'move_text' | 'style_table' | 'move_table' | 'delete_table' | 'set_paragraph_spacing' | 'insert_page_break' | 'add_hyperlink';
|
|
24
24
|
export type ActionPlacement = {
|
|
25
25
|
at: 'document_end';
|
|
26
26
|
} | {
|
|
@@ -32,7 +32,7 @@ export type ActionPlacement = {
|
|
|
32
32
|
at: 'before';
|
|
33
33
|
selector: AgentSelector;
|
|
34
34
|
};
|
|
35
|
-
export type ActionArgs = InsertParagraphsArgs | InsertHeadingArgs | ReplaceTextArgs | DeleteTextArgs | AppendListArgs | AddListItemsArgs | ConvertListArgs | AttachNumberingArgs | SplitListArgs | CreateTableArgs | CommentParagraphsArgs | AddCommentsArgs | ResolveCommentsArgs | ReplyToCommentArgs | RewriteBlockArgs | FormatTextArgs | FormatParagraphArgs | ApplyStyleArgs | MoveTextArgs | UndoChangesArgs | RedoChangesArgs | AcceptTrackedChangesArgs | RejectTrackedChangesArgs | NormalizeBodyFontSizeArgs | SetFontFamilyArgs | ApplyLetterSpacingArgs | FillPlaceholdersArgs | MoveRangeArgs | InsertTocArgs | StyleTableArgs | MoveTableArgs | DeleteTableArgs | SetParagraphSpacingArgs | InsertPageBreakArgs | AddHyperlinkArgs | InsertTableRowArgs | InsertTableColumnArgs | DeleteTableRowArgs | DeleteTableColumnArgs | SplitTableArgs;
|
|
35
|
+
export type ActionArgs = InsertParagraphsArgs | InsertHeadingArgs | ReplaceTextArgs | DeleteTextArgs | DeleteBlocksArgs | AppendListArgs | AddListItemsArgs | ConvertListArgs | AttachNumberingArgs | SplitListArgs | CreateTableArgs | CommentParagraphsArgs | AddCommentsArgs | ResolveCommentsArgs | ReplyToCommentArgs | RewriteBlockArgs | FormatTextArgs | FormatParagraphArgs | ApplyStyleArgs | MoveTextArgs | UndoChangesArgs | RedoChangesArgs | AcceptTrackedChangesArgs | RejectTrackedChangesArgs | NormalizeBodyFontSizeArgs | SetFontFamilyArgs | ApplyLetterSpacingArgs | FillPlaceholdersArgs | MoveRangeArgs | InsertTocArgs | StyleTableArgs | MoveTableArgs | DeleteTableArgs | SetParagraphSpacingArgs | InsertPageBreakArgs | AddHyperlinkArgs | InsertTableRowArgs | InsertTableColumnArgs | DeleteTableRowArgs | DeleteTableColumnArgs | SplitTableArgs;
|
|
36
36
|
export type InsertParagraphsArgs = {
|
|
37
37
|
action: 'insert_paragraphs';
|
|
38
38
|
texts?: readonly string[];
|
|
@@ -66,6 +66,12 @@ export type DeleteTextArgs = {
|
|
|
66
66
|
caseSensitive?: boolean;
|
|
67
67
|
changeMode?: AgentChangeMode;
|
|
68
68
|
};
|
|
69
|
+
export type DeleteBlocksArgs = {
|
|
70
|
+
action: 'delete_blocks';
|
|
71
|
+
/** One or more selectors, each resolving to exactly ONE body block. */
|
|
72
|
+
selectors: readonly AgentSelector[];
|
|
73
|
+
changeMode?: AgentChangeMode;
|
|
74
|
+
};
|
|
69
75
|
export type AppendListArgs = {
|
|
70
76
|
action: 'append_list';
|
|
71
77
|
items: readonly string[];
|
package/dist/agent/actions.js
CHANGED
|
@@ -8,6 +8,7 @@ const ACTION_NAMES = [
|
|
|
8
8
|
'insert_heading',
|
|
9
9
|
'replace_text',
|
|
10
10
|
'delete_text',
|
|
11
|
+
'delete_blocks',
|
|
11
12
|
'append_list',
|
|
12
13
|
'create_table',
|
|
13
14
|
'comment_paragraphs',
|
|
@@ -56,7 +57,8 @@ export const ACTION_HINTS = {
|
|
|
56
57
|
insert_paragraphs: 'texts[] (or text for a single paragraph), placement?, headingLevel? (first item as heading 1-6), changeMode?',
|
|
57
58
|
insert_heading: 'text, level, placement?, changeMode?',
|
|
58
59
|
replace_text: 'edits[{find,replace}], optional selector to scope replacements to one inspected block, caseSensitive?, changeMode?',
|
|
59
|
-
delete_text:
|
|
60
|
+
delete_text: "finds[], optional selector to scope deletions to ONE inspected block (required to delete stray whitespace — an unscoped whitespace find matches document-wide), caseSensitive?, changeMode? — deletes TEXT ONLY, leaving the block (and a list item's bullet/number) in place. To remove a whole list item, paragraph or heading use delete_blocks",
|
|
61
|
+
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)',
|
|
60
62
|
append_list: 'items[], kind?: ordered|bullet, headingText?, headingLevel?, placement? {at:"after"|"before",selector} builds the list at that block instead of document end',
|
|
61
63
|
create_table: 'rows, columns, cellTexts?, placement?, changeMode? — changeMode:"tracked" makes the table insertion itself a tracked change',
|
|
62
64
|
rewrite_block: 'selector, text, changeMode?',
|
|
@@ -103,6 +105,7 @@ export const ACTION_GROUPS = [
|
|
|
103
105
|
'insert_heading',
|
|
104
106
|
'replace_text',
|
|
105
107
|
'delete_text',
|
|
108
|
+
'delete_blocks',
|
|
106
109
|
'append_list',
|
|
107
110
|
'create_table',
|
|
108
111
|
'rewrite_block',
|
|
@@ -164,6 +167,7 @@ export const ACTION_ARGS = {
|
|
|
164
167
|
insert_heading: ['text', 'level', 'placement', 'changeMode'],
|
|
165
168
|
replace_text: ['edits', 'selector', 'caseSensitive', 'changeMode'],
|
|
166
169
|
delete_text: ['finds', 'selector', 'caseSensitive', 'changeMode'],
|
|
170
|
+
delete_blocks: ['selectors', 'selector', 'changeMode'],
|
|
167
171
|
append_list: ['items', 'kind', 'headingText', 'headingLevel', 'placement', 'changeMode'],
|
|
168
172
|
create_table: ['rows', 'columns', 'cellTexts', 'placement', 'changeMode'],
|
|
169
173
|
comment_paragraphs: ['commentText', 'scope', 'excludeBlockQuotes'],
|
|
@@ -1222,6 +1226,157 @@ async function runDeleteText(doc, args) {
|
|
|
1222
1226
|
return failedReceipt('delete_text', err, pre);
|
|
1223
1227
|
}
|
|
1224
1228
|
}
|
|
1229
|
+
/** Block node types `doc.blocks.delete` removes as a whole paragraph-shaped block. */
|
|
1230
|
+
const DELETABLE_BLOCK_NODE_TYPES = new Set(['paragraph', 'heading', 'listItem']);
|
|
1231
|
+
/**
|
|
1232
|
+
* delete_blocks — remove ENTIRE blocks (list items, paragraphs, headings) in
|
|
1233
|
+
* ONE call. Wraps doc.blocks.delete, which removes the block NODE, its
|
|
1234
|
+
* paragraph properties (`w:pPr`, including a list item's `w:numPr`) included.
|
|
1235
|
+
*
|
|
1236
|
+
* delete_text only strikes the RUNS. Routing "delete this list item"
|
|
1237
|
+
* through it leaves the numbered paragraph behind, so accepting the tracked
|
|
1238
|
+
* change yields an empty `1.` and the following items never renumber. Whole-
|
|
1239
|
+
* block removal has to go through blocks.delete, and in tracked mode that
|
|
1240
|
+
* records ONE structural revision whose acceptance removes the paragraph and
|
|
1241
|
+
* whose rejection restores it intact.
|
|
1242
|
+
*/
|
|
1243
|
+
async function runDeleteBlocks(doc, args) {
|
|
1244
|
+
const domains = new Set(['blocks', 'trackedChanges']);
|
|
1245
|
+
for (const selector of args.selectors) {
|
|
1246
|
+
for (const domain of snapshotDomainsForSelector(selector))
|
|
1247
|
+
domains.add(domain);
|
|
1248
|
+
}
|
|
1249
|
+
const pre = await buildDocumentSnapshot(doc, { includeDomains: [...domains] });
|
|
1250
|
+
try {
|
|
1251
|
+
if (args.selectors.length === 0) {
|
|
1252
|
+
return failedReceipt('delete_blocks', new Error('selectors must be non-empty'), pre);
|
|
1253
|
+
}
|
|
1254
|
+
const deleteFn = maybeMethod(doc, ['blocks', 'delete']);
|
|
1255
|
+
if (!deleteFn) {
|
|
1256
|
+
throw new SuperDocCliError('doc.blocks.delete is not available on the document handle.', {
|
|
1257
|
+
code: 'TOOL_DISPATCH_NOT_FOUND',
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
// Resolve EVERY selector against the same pre-snapshot before deleting
|
|
1261
|
+
// anything. nodeIds are stable paragraph ids, so a target resolved up front
|
|
1262
|
+
// stays addressable after a sibling is removed — whereas an ordinal or
|
|
1263
|
+
// text-search selector re-resolved mid-run would drift onto the wrong block.
|
|
1264
|
+
const targets = [];
|
|
1265
|
+
const unresolved = [];
|
|
1266
|
+
const wrongShape = [];
|
|
1267
|
+
const seen = new Set();
|
|
1268
|
+
for (const selector of args.selectors) {
|
|
1269
|
+
// A tableCell selector resolves to the FIRST PARAGRAPH inside the cell,
|
|
1270
|
+
// so it would otherwise pass the paragraph-shape gate below. Removing it
|
|
1271
|
+
// is not a whole-block delete: a `<w:tc>` must keep at least one
|
|
1272
|
+
// paragraph, so deleting the only one leaves invalid OOXML, and in
|
|
1273
|
+
// tracked mode the compiler can only strike the runs anyway. Refuse the
|
|
1274
|
+
// shape rather than half-honour it.
|
|
1275
|
+
if (selector.kind === 'tableCell') {
|
|
1276
|
+
wrongShape.push({ selector, nodeType: 'tableCell' });
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
const target = selectorToBlockTarget(selector, pre);
|
|
1280
|
+
if (!target) {
|
|
1281
|
+
unresolved.push(selector);
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
if (!DELETABLE_BLOCK_NODE_TYPES.has(target.nodeType)) {
|
|
1285
|
+
wrongShape.push({ selector, nodeType: target.nodeType });
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
if (seen.has(target.nodeId))
|
|
1289
|
+
continue;
|
|
1290
|
+
seen.add(target.nodeId);
|
|
1291
|
+
targets.push({ selector, nodeId: target.nodeId, nodeType: target.nodeType });
|
|
1292
|
+
}
|
|
1293
|
+
const errors = [];
|
|
1294
|
+
for (const selector of unresolved) {
|
|
1295
|
+
errors.push({
|
|
1296
|
+
code: 'ACTION_FAILED',
|
|
1297
|
+
message: `selector did not resolve to a unique body block: ${JSON.stringify(selector)}`,
|
|
1298
|
+
recovery: { kind: 'reinspect' },
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
for (const entry of wrongShape) {
|
|
1302
|
+
errors.push({
|
|
1303
|
+
code: 'INVALID_ARGUMENT',
|
|
1304
|
+
message: `delete_blocks removes paragraph-shaped blocks (list items, paragraphs, headings); ` +
|
|
1305
|
+
`${JSON.stringify(entry.selector)} resolved to a "${entry.nodeType}"` +
|
|
1306
|
+
(entry.nodeType === 'table'
|
|
1307
|
+
? ' — use delete_table for a whole table.'
|
|
1308
|
+
: entry.nodeType === 'tableCell'
|
|
1309
|
+
? ' — a cell must keep a paragraph; use delete_text to clear it, or delete_table_row/delete_table_column.'
|
|
1310
|
+
: '.'),
|
|
1311
|
+
recovery: { kind: 'reinspect' },
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
if (targets.length === 0) {
|
|
1315
|
+
return {
|
|
1316
|
+
status: 'failed',
|
|
1317
|
+
intent: 'delete_blocks',
|
|
1318
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
1319
|
+
selectedTargets: [],
|
|
1320
|
+
executedOperations: [],
|
|
1321
|
+
verification: [],
|
|
1322
|
+
errors,
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
const changeMode = parseChangeMode(args.changeMode);
|
|
1326
|
+
const executedOperations = [];
|
|
1327
|
+
const deleted = [];
|
|
1328
|
+
for (const target of targets) {
|
|
1329
|
+
try {
|
|
1330
|
+
const result = await deleteFn({ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } }, changeMode ? { changeMode } : undefined);
|
|
1331
|
+
executedOperations.push({ operationId: 'doc.blocks.delete', result: compactOpResult(result) });
|
|
1332
|
+
deleted.push({ nodeId: target.nodeId, nodeType: target.nodeType });
|
|
1333
|
+
}
|
|
1334
|
+
catch (err) {
|
|
1335
|
+
errors.push({
|
|
1336
|
+
code: 'ACTION_FAILED',
|
|
1337
|
+
message: `blocks.delete failed for ${target.nodeType} ${target.nodeId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1338
|
+
recovery: { kind: 'reinspect' },
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
const post = await buildDocumentSnapshot(doc, { includeDomains: [...domains] });
|
|
1343
|
+
// Tracked deletions leave the block in place until the revision is decided,
|
|
1344
|
+
// so the block count cannot move — count the structural revisions instead.
|
|
1345
|
+
// Direct deletions must show one fewer block of each deleted node type.
|
|
1346
|
+
const checks = changeMode === 'tracked'
|
|
1347
|
+
? [{ kind: 'tracked-change-count-delta', delta: deleted.length }]
|
|
1348
|
+
: [...new Set(deleted.map((entry) => entry.nodeType))].map((nodeType) => ({
|
|
1349
|
+
kind: 'block-count-delta',
|
|
1350
|
+
nodeType,
|
|
1351
|
+
delta: -deleted.filter((entry) => entry.nodeType === nodeType).length,
|
|
1352
|
+
}));
|
|
1353
|
+
const verification = evaluateChecks(pre, post, checks);
|
|
1354
|
+
// Compare against the DEDUPED target list: two selectors naming the same
|
|
1355
|
+
// block are one deletion, and counting the raw selectors would report a
|
|
1356
|
+
// fully applied call as `partial` with no error to explain it. Selectors
|
|
1357
|
+
// that never became a target (unresolved or wrong-shape) already recorded
|
|
1358
|
+
// an error, so the `errors.length` term still catches a short request.
|
|
1359
|
+
const allApplied = deleted.length === targets.length && errors.length === 0;
|
|
1360
|
+
const verified = verification.every((v) => v.passed);
|
|
1361
|
+
return {
|
|
1362
|
+
status: !verified || deleted.length === 0 ? 'failed' : allApplied ? 'ok' : 'partial',
|
|
1363
|
+
intent: `delete_blocks: ${deleted.length} block(s)`,
|
|
1364
|
+
preSnapshot: { revision: pre.revision, counts: pre.counts },
|
|
1365
|
+
postSnapshot: { revision: post.revision, counts: post.counts },
|
|
1366
|
+
selectedTargets: targets.map((target) => ({ selector: target.selector, matched: [target.nodeId] })),
|
|
1367
|
+
executedOperations,
|
|
1368
|
+
verification,
|
|
1369
|
+
deletedBlocks: deleted,
|
|
1370
|
+
...(errors.length ? { errors } : {}),
|
|
1371
|
+
...(errors.length
|
|
1372
|
+
? { nextStep: 'Re-inspect the document and retry the selectors that did not resolve to one block.' }
|
|
1373
|
+
: {}),
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
catch (err) {
|
|
1377
|
+
return failedReceipt('delete_blocks', err, pre);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1225
1380
|
async function runAppendList(doc, args) {
|
|
1226
1381
|
const pre = await buildDocumentSnapshot(doc);
|
|
1227
1382
|
try {
|
|
@@ -4689,6 +4844,33 @@ export async function superdocPerformAction(doc, args) {
|
|
|
4689
4844
|
changeMode: parseChangeMode(args.changeMode),
|
|
4690
4845
|
});
|
|
4691
4846
|
}
|
|
4847
|
+
case 'delete_blocks': {
|
|
4848
|
+
// Accept a single `selector` too: the model reaches for the singular form
|
|
4849
|
+
// by analogy with delete_text/rewrite_block, and rejecting it would cost a
|
|
4850
|
+
// turn for no reason.
|
|
4851
|
+
const rawSelectors = Array.isArray(args.selectors)
|
|
4852
|
+
? args.selectors
|
|
4853
|
+
: args.selectors != null
|
|
4854
|
+
? [args.selectors]
|
|
4855
|
+
: args.selector != null
|
|
4856
|
+
? [args.selector]
|
|
4857
|
+
: [];
|
|
4858
|
+
if (rawSelectors.length === 0) {
|
|
4859
|
+
throw new SuperDocCliError('delete_blocks requires a non-empty "selectors" array, each entry resolving to one block (e.g. {kind:"nodeId",nodeId:"…"})', { code: 'INVALID_ARGUMENT' });
|
|
4860
|
+
}
|
|
4861
|
+
// EVERY entry has to parse. Dropping the unparseable ones would delete the
|
|
4862
|
+
// rest and report success, so the model would never learn that part of its
|
|
4863
|
+
// request went nowhere — the same false-success loop this action ends.
|
|
4864
|
+
const selectors = [];
|
|
4865
|
+
for (const [index, raw] of rawSelectors.entries()) {
|
|
4866
|
+
const parsed = parseSelector(raw);
|
|
4867
|
+
if (!parsed) {
|
|
4868
|
+
throw new SuperDocCliError(`delete_blocks selectors[${index}] is not a valid selector (e.g. {kind:"nodeId",nodeId:"…"}); no blocks were deleted`, { code: 'INVALID_ARGUMENT' });
|
|
4869
|
+
}
|
|
4870
|
+
selectors.push(parsed);
|
|
4871
|
+
}
|
|
4872
|
+
return runDeleteBlocks(doc, { action, selectors, changeMode: parseChangeMode(args.changeMode) });
|
|
4873
|
+
}
|
|
4692
4874
|
case 'append_list': {
|
|
4693
4875
|
const items = parseStringArray(args.items);
|
|
4694
4876
|
if (!items || items.length === 0) {
|