@superdoc/sdk 2.23.0-next.8 → 2.23.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.
Files changed (39) hide show
  1. package/dist/action-primitives/tools/structure-insert.cjs +3 -5
  2. package/dist/action-primitives/tools/structure-insert.js +3 -5
  3. package/dist/action-primitives/tools/text-transform.cjs +3 -5
  4. package/dist/action-primitives/tools/text-transform.js +3 -5
  5. package/dist/actions/define.cjs +15 -17
  6. package/dist/actions/define.js +1 -3
  7. package/dist/agent/actions.cjs +438 -52
  8. package/dist/agent/actions.d.ts +13 -0
  9. package/dist/agent/actions.js +394 -11
  10. package/dist/agent/runtime.cjs +7 -9
  11. package/dist/agent/runtime.js +1 -3
  12. package/dist/agent/v2-preset-compat.cjs +5 -7
  13. package/dist/agent/v2-preset-compat.js +1 -3
  14. package/dist/embedded-prompts.generated.cjs +1 -1
  15. package/dist/embedded-prompts.generated.js +1 -1
  16. package/dist/embedded-tools.generated.cjs +5 -5
  17. package/dist/embedded-tools.generated.js +5 -5
  18. package/dist/generated/client.d.ts +193 -177
  19. package/dist/generated/contract.cjs +1 -1
  20. package/dist/generated/contract.js +1 -1
  21. package/dist/guards.cjs +7 -0
  22. package/dist/guards.d.ts +1 -0
  23. package/dist/guards.js +3 -0
  24. package/dist/presets/core.cjs +10 -12
  25. package/dist/presets/core.js +1 -3
  26. package/dist/presets/legacy.cjs +6 -8
  27. package/dist/presets/legacy.js +1 -3
  28. package/dist/prompts/system-prompt.md +6 -6
  29. package/dist/runtime/sdk-version.generated.cjs +1 -1
  30. package/dist/runtime/sdk-version.generated.d.ts +1 -1
  31. package/dist/runtime/sdk-version.generated.js +1 -1
  32. package/package.json +6 -6
  33. package/tools/catalog.json +8 -0
  34. package/tools/generation-receipt.json +18 -18
  35. package/tools/tools-policy.json +1 -1
  36. package/tools/tools.anthropic.json +8 -0
  37. package/tools/tools.generic.json +8 -0
  38. package/tools/tools.openai.json +8 -0
  39. package/tools/tools.vercel.json +8 -0
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ function isRecord(value) {
4
+ return typeof value === 'object' && value != null && !Array.isArray(value);
5
+ }
6
+
7
+ exports.isRecord = isRecord;
@@ -0,0 +1 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
package/dist/guards.js ADDED
@@ -0,0 +1,3 @@
1
+ export function isRecord(value) {
2
+ return typeof value === 'object' && value != null && !Array.isArray(value);
3
+ }
@@ -9,6 +9,7 @@ var catalog = require('../agent/catalog.cjs');
9
9
  var runtime = require('../agent/runtime.cjs');
10
10
  var actions = require('../agent/actions.cjs');
11
11
  var embeddedPrompts_generated = require('../embedded-prompts.generated.cjs');
12
+ var guards = require('../guards.cjs');
12
13
 
13
14
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
15
  /**
@@ -74,9 +75,6 @@ async function readPromptFile(fileName, label, candidateDirs = PROMPT_DIR_CANDID
74
75
  details: { triedPaths: tried },
75
76
  });
76
77
  }
77
- function isRecord(value) {
78
- return typeof value === 'object' && value != null && !Array.isArray(value);
79
- }
80
78
  function isObviouslyCorruptedToolArgKey(key) {
81
79
  const trimmed = key.trim();
82
80
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -85,7 +83,7 @@ function stripCorruptedToolArgKeys(value) {
85
83
  if (Array.isArray(value)) {
86
84
  return value.map((item) => stripCorruptedToolArgKeys(item));
87
85
  }
88
- if (!isRecord(value))
86
+ if (!guards.isRecord(value))
89
87
  return value;
90
88
  const clean = {};
91
89
  for (const [key, entryValue] of Object.entries(value)) {
@@ -109,7 +107,7 @@ function validateAgentToolArgs(toolName, args) {
109
107
  if (definition == null)
110
108
  return;
111
109
  const schema = definition.inputSchema;
112
- const properties = isRecord(schema.properties) ? schema.properties : {};
110
+ const properties = guards.isRecord(schema.properties) ? schema.properties : {};
113
111
  const required = Array.isArray(schema.required) ? schema.required : [];
114
112
  const knownKeys = new Set(Object.keys(properties));
115
113
  const unknownKeys = Object.keys(args).filter((key) => !knownKeys.has(key));
@@ -162,7 +160,7 @@ function compactCountMap(counts) {
162
160
  return compact;
163
161
  }
164
162
  function pickScalarFields(value, keys, limit = 6) {
165
- if (!isRecord(value))
163
+ if (!guards.isRecord(value))
166
164
  return {};
167
165
  const entries = keys
168
166
  ? keys.filter((key) => key in value).map((key) => [key, value[key]])
@@ -186,12 +184,12 @@ function compactOperationResult(result) {
186
184
  }
187
185
  if (Array.isArray(result))
188
186
  return { itemCount: result.length };
189
- if (!isRecord(result))
187
+ if (!guards.isRecord(result))
190
188
  return { kind: typeof result };
191
189
  const compact = {
192
190
  ...pickScalarFields(result, ['success', 'status', 'count', 'total', 'applied', 'created', 'deleted']),
193
191
  };
194
- if (isRecord(result.revision)) {
192
+ if (guards.isRecord(result.revision)) {
195
193
  const revision = pickScalarFields(result.revision, ['before', 'after', 'current']);
196
194
  if (Object.keys(revision).length > 0)
197
195
  compact.revision = revision;
@@ -200,7 +198,7 @@ function compactOperationResult(result) {
200
198
  compact.stepCount = result.steps.length;
201
199
  compact.steps = result.steps
202
200
  .slice(0, 4)
203
- .map((step) => (isRecord(step) ? pickScalarFields(step, ['stepId', 'op', 'effect', 'matchCount'], 4) : {}));
201
+ .map((step) => (guards.isRecord(step) ? pickScalarFields(step, ['stepId', 'op', 'effect', 'matchCount'], 4) : {}));
204
202
  }
205
203
  if (Array.isArray(result.items))
206
204
  compact.itemCount = result.items.length;
@@ -532,14 +530,14 @@ const ENFORCEMENT_SIDE_DOORS = new Set([
532
530
  ]);
533
531
  const POLICY_OPTION_KEYS = ['excludeActions', 'enforceChangeMode', 'allowDirectActions'];
534
532
  async function coreDispatch(documentHandle, toolName, args, invokeOptions) {
535
- if (!isRecord(args)) {
533
+ if (!guards.isRecord(args)) {
536
534
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
537
535
  code: 'INVALID_ARGUMENT',
538
536
  details: { toolName },
539
537
  });
540
538
  }
541
539
  const sanitizedArgs = stripCorruptedToolArgKeys(args);
542
- if (!isRecord(sanitizedArgs)) {
540
+ if (!guards.isRecord(sanitizedArgs)) {
543
541
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
544
542
  code: 'INVALID_ARGUMENT',
545
543
  details: { toolName },
@@ -601,7 +599,7 @@ async function coreDispatch(documentHandle, toolName, args, invokeOptions) {
601
599
  if (catalog.isAgentToolName(toolName)) {
602
600
  validateAgentToolArgs(toolName, dispatchArgs);
603
601
  const result = await dispatchAgentTool(documentHandle, toolName, dispatchArgs, forwardOptions);
604
- if (enforcing && toolName === 'superdoc_perform_action' && isRecord(result)) {
602
+ if (enforcing && toolName === 'superdoc_perform_action' && guards.isRecord(result)) {
605
603
  // Receipts under enforcement state the mode that actually ran, so a host
606
604
  // can audit "was this edit tracked?" without re-deriving the policy.
607
605
  const changeMode = dispatchArgs.changeMode === 'tracked' ? 'tracked' : 'direct';
@@ -22,6 +22,7 @@ import { AGENT_TOOL_DEFINITIONS, isAgentToolName, listAgentTools, resolveActionP
22
22
  import { agentApply, agentInspect, agentOperation, agentVerify } from '../agent/runtime.js';
23
23
  import { ACTION_ARGS, ACTION_DISPOSITIONS, isActionName, superdocPerformAction } from '../agent/actions.js';
24
24
  import { EMBEDDED_PROMPTS } from '../embedded-prompts.generated.js';
25
+ import { isRecord } from '../guards.js';
25
26
  // Prompts ship as <dist>/prompts/*.md. Resolve relative to the compiled
26
27
  // module, but the module's depth differs by build: in the SDK package this
27
28
  // file is dist/presets/core.js (so prompts are at ../prompts), while the
@@ -70,9 +71,6 @@ export async function readPromptFile(fileName, label, candidateDirs = PROMPT_DIR
70
71
  details: { triedPaths: tried },
71
72
  });
72
73
  }
73
- function isRecord(value) {
74
- return typeof value === 'object' && value != null && !Array.isArray(value);
75
- }
76
74
  function isObviouslyCorruptedToolArgKey(key) {
77
75
  const trimmed = key.trim();
78
76
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -6,6 +6,7 @@ var node_url = require('node:url');
6
6
  var errors = require('../runtime/errors.cjs');
7
7
  var intentDispatch_generated = require('../generated/intent-dispatch.generated.cjs');
8
8
  var embeddedTools_generated = require('../embedded-tools.generated.cjs');
9
+ var guards = require('../guards.cjs');
9
10
 
10
11
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
12
  /**
@@ -32,9 +33,6 @@ const providerFileByName = {
32
33
  generic: 'tools.generic.json',
33
34
  };
34
35
  const STRIP_EMPTY_OPTIONAL_ARGS = new Set(['parentId', 'parentCommentId', 'id', 'status']);
35
- function isRecord(value) {
36
- return typeof value === 'object' && value != null && !Array.isArray(value);
37
- }
38
36
  function isObviouslyCorruptedToolArgKey(key) {
39
37
  const trimmed = key.trim();
40
38
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -43,7 +41,7 @@ function stripCorruptedToolArgKeys(value) {
43
41
  if (Array.isArray(value)) {
44
42
  return value.map((item) => stripCorruptedToolArgKeys(item));
45
43
  }
46
- if (!isRecord(value))
44
+ if (!guards.isRecord(value))
47
45
  return value;
48
46
  const clean = {};
49
47
  for (const [key, entryValue] of Object.entries(value)) {
@@ -139,7 +137,7 @@ function resolveDocApiMethod(documentHandle, operationId) {
139
137
  const tokens = operationId.split('.').slice(1);
140
138
  let cursor = documentHandle;
141
139
  for (const token of tokens) {
142
- if (!isRecord(cursor) || !(token in cursor)) {
140
+ if (!guards.isRecord(cursor) || !(token in cursor)) {
143
141
  throw new errors.SuperDocCliError(`No SDK doc method found for operation ${operationId}.`, {
144
142
  code: 'TOOL_DISPATCH_NOT_FOUND',
145
143
  details: { operationId, token },
@@ -165,7 +163,7 @@ function resolveDocApiMethod(documentHandle, operationId) {
165
163
  */
166
164
  function validateToolArgs(toolName, args, tool) {
167
165
  const schema = tool.inputSchema;
168
- const properties = isRecord(schema.properties) ? schema.properties : {};
166
+ const properties = guards.isRecord(schema.properties) ? schema.properties : {};
169
167
  const required = Array.isArray(schema.required) ? schema.required : [];
170
168
  // 1. Reject unknown keys (additionalProperties: false in merged schema)
171
169
  const knownKeys = new Set(Object.keys(properties));
@@ -247,14 +245,14 @@ async function legacyGetMcpPrompt() {
247
245
  return (await readToolFile('system-prompt-mcp.md', 'MCP system prompt not found.')).raw;
248
246
  }
249
247
  async function legacyDispatch(documentHandle, toolName, args, invokeOptions) {
250
- if (!isRecord(args)) {
248
+ if (!guards.isRecord(args)) {
251
249
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
252
250
  code: 'INVALID_ARGUMENT',
253
251
  details: { toolName },
254
252
  });
255
253
  }
256
254
  const sanitizedArgs = stripCorruptedToolArgKeys(args);
257
- if (!isRecord(sanitizedArgs)) {
255
+ if (!guards.isRecord(sanitizedArgs)) {
258
256
  throw new errors.SuperDocCliError(`Tool arguments for ${toolName} must be an object.`, {
259
257
  code: 'INVALID_ARGUMENT',
260
258
  details: { toolName },
@@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url';
19
19
  import { SuperDocCliError } from '../runtime/errors.js';
20
20
  import { dispatchIntentTool } from '../generated/intent-dispatch.generated.js';
21
21
  import { EMBEDDED_TOOLS } from '../embedded-tools.generated.js';
22
+ import { isRecord } from '../guards.js';
22
23
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
23
24
  const TOOLS_DIR_CANDIDATES = [path.resolve(MODULE_DIR, '..', '..', 'tools'), path.resolve(MODULE_DIR, 'tools')];
24
25
  const providerFileByName = {
@@ -28,9 +29,6 @@ const providerFileByName = {
28
29
  generic: 'tools.generic.json',
29
30
  };
30
31
  const STRIP_EMPTY_OPTIONAL_ARGS = new Set(['parentId', 'parentCommentId', 'id', 'status']);
31
- function isRecord(value) {
32
- return typeof value === 'object' && value != null && !Array.isArray(value);
33
- }
34
32
  function isObviouslyCorruptedToolArgKey(key) {
35
33
  const trimmed = key.trim();
36
34
  return trimmed.length === 0 || !/[\p{L}\p{N}]/u.test(trimmed);
@@ -16,8 +16,8 @@ Users speak in what they SEE, not in file-format terms. Translate their words in
16
16
  - PLACEMENT: when the user names a position, honor it exactly. When they DON'T, find where a professional editor would put it — inspect the structure first, then place by document convention: the title stays first (NEVER insert above it unless explicitly asked); a summary/abstract/TOC goes right after the title or intro, not at the very top of the file; new sections go in logical reading order, before back-matter (signature blocks, annexes, schedules); signature blocks go at the very end; definitions go with other definitions. Defaulting to document start or end because it is easy is wrong when the content has an obvious home.
17
17
  - LEGAL DOCUMENTS ARE CROSS-REFERENCED. Adding a party (or any defined actor) means updating EVERY structure that enumerates it: the Parties list; the Definitions section whenever one exists (ALWAYS add the '"X" means …' entry for the new actor — even if the existing definitions cover other kinds of terms); and signature blocks when present. Same for removals. An edit that touches only one of these is incomplete — finish the set in the same turn.
18
18
  - CLEANUP is part of the job. No leftover empty paragraphs, duplicate blocks, or orphaned numbering after an edit. If your edit creates debris, remove it in the same turn.
19
- - NUMBERS COME FROM SCHEMES, NEVER FROM TEXT. Do not type "11." into a paragraph and call it a numbered heading — that fakes the rendering and breaks renumbering. Real numbering = attach_numbering (existing blocks) or the automatic attach on insert. If a numbered block's text starts with a typed number, that is a bug to fix, not a pattern to copy.
20
- - NEW SECTIONS in clause-numbered documents (clauses 1.–10.): "add section 11" means a clause heading numbered 11 in the SAME scheme plus body paragraphs under it. Create the title and body with insert_paragraphs, then attach_numbering the title with likeMarker of the last top-level clause (e.g. "10.") — it renders as "11." automatically.
19
+ - CLAUSE NUMBERS follow the siblings. SCHEME numbering (inspect shows numbering.marker on the blocks): never type a number into the text; a new clause = insert_paragraphs, then attach_numbering with likeMarker of the last sibling (e.g. "10.") and the rest renumbers itself. TYPED numbering (counts.lists is 0 and the siblings' text starts with the number: "\t4.\t", "\t(b)\t", "\t(i)\t"): copy that exact shape into the inserted text (leading tab, next number, tab, caption, two spaces), style a sub-item like an existing sub-item (apply_style likeText), never attach_numbering or append_list there, and renumber what follows yourself in ONE replace_text call: the later markers of the SAME sequence (a new top-level clause shifts the top-level clauses after it, not their (a)/(i) sub-runs, not an attachment's own clauses) AND every "Section N" cross-reference to a moved clause (a clause's reference to itself takes its new number). Schedule, Exhibit, Annex and Appendix labels are not clause numbers: leave them alone. A typed number is the document's convention, not a bug to fix.
20
+ - REMOVING an item from a lettered or numbered run ("(a) …, (b) … and (c) …", sub-clauses ending "; and") reletters the items that follow and moves the closing conjunction to the new penultimate item, so the last one ends the way the list ends: deleting (iv) from "(ii) …; (iii) …; and (iv) …." takes TWO edits, (ii) "…;" becomes "…; and" and (iii) "…; and" becomes "…." (read (ii) first; it is in the paragraph above). Striking a defined term touches only clauses that use that exact term; look-alike terms ("Material Subsidiary" beside "Immaterial Subsidiary") stay. Clause text the user supplies goes in exactly as written: no added lead-ins, no rephrasing unless asked.
21
21
  - NEVER END THE TURN WITH THE DOCUMENT WORSE THAN YOU FOUND IT. If your own cleanup or undo removed content the user wanted — including text you wrote earlier this conversation — restore it IMMEDIATELY in the same turn (you know what it said; re-insert it or redo). Do not ask permission to repair damage you caused.
22
22
  - REVIEW means READING. "Comment on what can be improved" / "review this" / "give feedback" = read the actual text first (superdoc_inspect), then write comments that are SPECIFIC to each passage — quote or reference what the passage says and what to change. Identical boilerplate stamped on every paragraph is a failed review, not a review. comment_paragraphs applies ONE identical text everywhere — it is ONLY for broadcast notes ("please verify this section"); for review feedback use add_comments per target, each with its own text.
23
23
  - ACT when intent is plain; ask only when the request is genuinely ambiguous AND the wrong guess would be destructive. One clarifying question maximum, with your best-guess default stated.
@@ -42,15 +42,15 @@ ACTIONS (superdoc_perform_action with flat args)
42
42
  - insert_paragraphs: texts (in final order) — or a single text for one paragraph. headingLevel makes the first item a heading (1-6). changeMode:"tracked" if asked. Skip placement to default to document end; placement:{at:"after"|"before",selector:{...}} to position. insert_paragraphs and insert_heading AUTOMATICALLY match the formatting (style, font, size, color) and numbering of neighbouring blocks — do NOT re-format after inserting unless asked.
43
43
  - insert_heading: text, level (1-6).
44
44
  - append_list: items (string array), kind:"ordered"|"bullet". headingText/headingLevel only if asked for a heading above. placement:{at:"after"|"before",selector:{...}} builds the list at that block instead of document end — when the list belongs inside a section, ALWAYS pass placement. The receipt's placement-honored check is the truth.
45
- - add_list_items: entries:[{text, level?}] (level relative to the anchor: 0 = same level, 1 = nested sub-item) — or items:[…] plain strings at the list level. Locate the list by anchorText (text inside one of its items) or listOrdinal (1-based). THE way to ADD items into an EXISTING list — reuses its numbering + markers, matches the anchor item's font/size/bold/colour automatically (receipt.formattingMatched — do NOT re-format after adding unless asked), joins imported list-looking paragraphs in place, and is tracked-safe. NOT append_list (which starts a brand-new list).
45
+ - add_list_items: entries:[{text, level?}] (level relative to the anchor: 0 = same level, 1 = nested sub-item) — or items:[…] plain strings at the list level. Locate the list by anchorText (text inside one of its items) or listOrdinal (1-based). THE way to ADD items into an EXISTING list or scheme-numbered sequence (never a typed one) — reuses its numbering + markers, matches the anchor item's font/size/bold/colour automatically (receipt.formattingMatched — do NOT re-format after adding unless asked), joins imported list-looking paragraphs in place, and is tracked-safe. NOT append_list (which starts a brand-new list).
46
46
  - convert_list: kind:"ordered"|"bullet", with listOrdinal or anchorText for real lists, OR fromMarker+toMarker (rendered clause numbers from inspect, e.g. "2.1.", "2.3.") for NUMBERED-CLAUSE ranges — including heading-styled clauses, OR fromText+toText (exact text inside the FIRST and LAST of consecutive plain paragraphs) to convert existing paragraphs into a list IN PLACE. Sub-clauses in range included automatically. THE way to convert numbering<->bullets AND the way to make existing paragraphs a list; never rewrite text to fake it, never recreate-the-content-then-delete-the-originals (two chances to lose text — convert_list fromText/toText is one lossless call).
47
47
  - split_list: anchorText (text inside the item that should START the second list), restartNumbering? (default true). Splits ONE list into two at that item — the new list restarts at 1, nested sub-items stay with their parent. THE way to "split the list starting at item N into a new list with reset numbering"; never fake it with convert_list/attach_numbering. changeMode:"tracked" records the change for review.
48
48
  - undo_changes: untilMarker (rendered marker from the original state, e.g. "2.1.") or steps (1-25). Deterministic revert — steps history back until the marker reappears; the receipt proves it. NEVER use steps > 1 blindly: prefer untilMarker, and after ANY undo verify (superdoc_inspect) that content you meant to KEEP still exists — if you overshot, redo_changes {steps:N} steps forward again to recover it.
49
49
  - redo_changes: steps (1-25, default 1). Steps history FORWARD to re-apply edits a prior undo removed — THE recovery for an undo overshoot. Only reaches the forward branch until a NEW edit is made.
50
- - attach_numbering: anchorText (text of the block) or nodeId, likeMarker (rendered marker of a sibling clause, e.g. "10."). Makes an EXISTING block a numbered clause at the same scheme/level — "make this the section 11 heading" in a clause-numbered document is exactly this (it will render as the next number).
50
+ - attach_numbering: anchorText (text of the block) or nodeId, likeMarker (rendered marker of a sibling clause, e.g. "10."). Scheme numbering only (it fails where the numbers are typed text). Makes an EXISTING block a numbered clause at the same scheme/level — "make this the section 11 heading" in a clause-numbered document is exactly this (it will render as the next number).
51
51
  - replace_text: edits:[{find,replace,occurrence?}], caseSensitive default false. selector to scope. Keep find/replace spans MINIMAL. If the find appears more than once in the selected block and only one should change, give that edit occurrence (1 = first match in the block; needs selector; not selector.occurrence). With occurrence, also pass expectedRevision from your latest inspect or receipt; on REVISION_CONFLICT inspect again and recount. changeMode:"tracked" if asked. The receipt reports editsApplied and editsSkipped per find — READ IT: a skipped find is not in the selected block; re-target instead of assuming success.
52
52
  - delete_text: finds:[string]. selector to scope deletions to ONE block — REQUIRED for whitespace-only finds (unscoped whitespace matches document-wide and is refused). changeMode:"tracked" if asked. Deletes TEXT ONLY: the block survives, so a list item keeps its bullet/number and an accepted tracked deletion leaves an empty numbered item behind. To remove the item itself use delete_blocks.
53
- - delete_blocks: selectors:[…], each resolving to ONE block (list item, paragraph or heading). changeMode:"tracked" if asked. THE way to DELETE a whole LIST ITEM, paragraph or heading — the bullet/number goes with it and the remaining items renumber. "Delete the first item under Article II" / "remove that clause" is exactly this, NOT delete_text. Pass every target in ONE call. Use delete_table for a whole table.
53
+ - delete_blocks: selectors:[…], each resolving to ONE block (list item, paragraph or heading). changeMode:"tracked" if asked. THE way to DELETE a whole LIST ITEM, paragraph or heading — the bullet/number goes with it and scheme-numbered items renumber (typed markers after a deleted typed clause need the replace_text renumbering above). "Delete the first item under Article II" / "remove that clause" is exactly this, NOT delete_text. Pass every target in ONE call. Use delete_table for a whole table. The receipt's listPunctuation names the two edits that close the run after the deletion; apply them.
54
54
  - rewrite_block: selector, text. Inspect first to gather current text; never ask the user to paste text already in the doc.
55
55
  - create_table: rows, columns, optional cellTexts (2D array). rows counts ALL rows INCLUDING the header — header plus one data row is rows:2. placement defaults to document end. changeMode:"tracked" if asked ("track-changes table") — the insertion itself becomes a tracked change.
56
56
  - comment_paragraphs: commentText. excludeBlockQuotes:true to skip block quotes. Applies the SAME text to every BODY paragraph (it does NOT comment the title/heading) — broadcast notes only, never review feedback (see REVIEW rule). To comment a heading too, add_comments on it explicitly.
@@ -96,7 +96,7 @@ OPERATING RULES
96
96
  - REVERTS: "undo / revert / make it back" = superdoc_perform_action undo_changes with untilMarker from the original state (a convert receipt's revertHint contains the exact call) — NEVER re-convert or re-edit to approximate the old state.
97
97
  - If the request names a target descriptively ("the indented heading", "the second clause"), inspect FIRST and use the block's actual text or nodeId — never invent find text.
98
98
  - Numbered legal clauses ("2.3.") usually live on numbered HEADINGS, not lists: if counts.lists is 0 but blocks carry numbering markers, target those blocks by nodeId. "Add item 2.4" = insert_paragraphs (one text) after the "2.3." block; the tool attaches numbering and matches formatting automatically (check receipt.contextualFormatting).
99
- - ADDING SEVERAL items to an existing list or numbered sequence = ONE add_list_items call (it joins the sequence and is tracked-safe — it works even when the "list" is clause numbering and counts.lists is 0). NEVER a chain of insert_paragraphs calls: multi-paragraph inserts do not auto-join numbering, and the items will land as plain paragraphs.
99
+ - ADDING SEVERAL items to an existing list or scheme-numbered sequence = ONE add_list_items call (it joins the sequence and is tracked-safe — it works even when the "list" is clause numbering with markers and counts.lists is 0). NEVER a chain of insert_paragraphs calls there: multi-paragraph inserts do not auto-join numbering, and the items will land as plain paragraphs. TYPED sequences are the exception: ONE insert_paragraphs call whose texts carry the typed markers.
100
100
  - New SECTION headings use the SAME headingLevel as sibling section headings (title is usually level 1; sections 2+). Never default to level 1.
101
101
  - paragraphOrdinal counts visible non-empty paragraphs; bodyParagraphOrdinal counts substantive body paragraphs after front matter. Prefer paragraphOrdinal for "first/second paragraph".
102
102
  - For literal ordinal rewrites, do not switch paragraphs because the matched one looks title-like or short. If a rewrite is a no-op, keep the target and change the rewrite.
@@ -3,6 +3,6 @@
3
3
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
4
4
  // Source of truth: package.json. Regenerated on every SDK build so the
5
5
  // SDK retains its own version identity when bundled into another package.
6
- const SDK_VERSION = '2.23.0-next.8';
6
+ const SDK_VERSION = '2.23.0';
7
7
 
8
8
  exports.SDK_VERSION = SDK_VERSION;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.23.0-next.8";
1
+ export declare const SDK_VERSION = "2.23.0";
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
2
2
  // Source of truth: package.json. Regenerated on every SDK build so the
3
3
  // SDK retains its own version identity when bundled into another package.
4
- export const SDK_VERSION = '2.23.0-next.8';
4
+ export const SDK_VERSION = '2.23.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.23.0-next.8",
3
+ "version": "2.23.0",
4
4
  "description": "Node SDK for SuperDoc, wrapping the SuperDoc CLI to read and edit .docx files from JavaScript and TypeScript.",
5
5
  "private": false,
6
6
  "license": "AGPL-3.0",
@@ -38,11 +38,11 @@
38
38
  "typescript": "^5.9.2"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@superdoc/sdk-darwin-arm64": "2.23.0-next.8",
42
- "@superdoc/sdk-darwin-x64": "2.23.0-next.8",
43
- "@superdoc/sdk-linux-x64": "2.23.0-next.8",
44
- "@superdoc/sdk-linux-arm64": "2.23.0-next.8",
45
- "@superdoc/sdk-windows-x64": "2.23.0-next.8"
41
+ "@superdoc/sdk-darwin-arm64": "2.23.0",
42
+ "@superdoc/sdk-darwin-x64": "2.23.0",
43
+ "@superdoc/sdk-linux-x64": "2.23.0",
44
+ "@superdoc/sdk-linux-arm64": "2.23.0",
45
+ "@superdoc/sdk-windows-x64": "2.23.0"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"
@@ -1517,6 +1517,14 @@
1517
1517
  "type": "string",
1518
1518
  "description": "Require exact selected text before mutation. Only explicit single-paragraph text selections support this guard. Only for action 'replace'. Omit for other actions."
1519
1519
  },
1520
+ "replacementGranularity": {
1521
+ "enum": [
1522
+ "word",
1523
+ "phrase",
1524
+ "range"
1525
+ ],
1526
+ "description": "Tracked revision boundaries for an explicit single-paragraph text selection. word (default) preserves matching tokens; phrase joins edits separated only by spaces; range replaces the complete selection. Requires changeMode: tracked. Only for action 'replace'. Omit for other actions."
1527
+ },
1520
1528
  "behavior": {
1521
1529
  "$ref": "#/$defs/DeleteBehavior",
1522
1530
  "description": "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions."
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "formatVersion": 1,
3
- "contractHash": "bc924b5585efd8f81307d866cfc6ec679039be9dc2d1f2e8c0eb0c394e2be5f7",
4
- "documentApiSourceHash": "1a2d43b0207099cefeb15344c75639185d45e4cf46ffa487292771370935f92b",
5
- "generatorHash": "582aa8e90f60ac6426891df34d9d815413f4c0eab37300fd438d7a393673c614",
3
+ "contractHash": "32dbab4c9dbd876ba853d0651199b963871ac514f441674adf937e71091686e6",
4
+ "documentApiSourceHash": "10dd2593185780e2adc4aa61c0f1949dcba48eb9369bd0d560e21363d8f26803",
5
+ "generatorHash": "a1b6eff9ef92fc0bc2df86520cfc03628f887311339a645a70b4f855caa11221",
6
6
  "inputs": {
7
7
  "apps/cli/src/host/sdk-policy.ts": "8820bc2cb4694bd665cbfacbb55c262a5df5cde4ed2a1a8d0735c09c6a5e6dd8",
8
8
  "packages/sdk/codegen/src/artifacts.mjs": "aecab7eca4694cbcf2366ddd9cabc033abddad2ff8fb0960d505580dbaf4d8b7",
@@ -15,13 +15,13 @@
15
15
  "packages/sdk/codegen/src/model/schema.mjs": "29cb1167413280907ad324d8a1fcb54dc70cce829df00c1a755f43040d01cf3b",
16
16
  "packages/sdk/codegen/src/shared.mjs": "a411e7ea4fab43b5b08b00294c746ff7063ceddbae66ece8d8c5990f131e0f7c",
17
17
  "packages/sdk/codegen/src/targets.mjs": "62c5ae3e3813e07b37e62487d973bac60ef6bea558a01f3ace2e0c6e8374e147",
18
- "packages/sdk/langs/node/package.json": "4fe97943068bf593dfc284e35857a2c491e33de0939ca7fd973a0cd51d9fa5e3",
18
+ "packages/sdk/langs/node/package.json": "d018761c32c84a229d2f5bc9709c0b1e417a9f2687f6cb0d0d22b05bfaa4de3a",
19
19
  "packages/sdk/langs/node/scripts/embed-prompts.mjs": "1b1641bf89cad962a4ff11d93f301fe5a4e32fe856cd3c9586106d8aaab4ad80",
20
20
  "packages/sdk/langs/node/scripts/embed-tools.mjs": "4cd8b0605b088421ea1fdf8e4e19c70f571f4e3352309a1480491600122e7bb3",
21
21
  "packages/sdk/langs/node/scripts/embed-version.mjs": "95e2ff3a7c15cb38130e12f04c8a96c901a5e55d9960610ebefa37178fd19cde",
22
22
  "packages/sdk/langs/node/src/agent/action-descriptors.json": "427fdf2a163bbc6c25f0a165b84cfb17ea2b27a92611ec74cdc19bc77bff2594",
23
23
  "packages/sdk/langs/node/src/prompts/mcp-prompt.md": "ba1e239877b2b316744477e3ca7b6982fa265edb1358759209cc439457728e8b",
24
- "packages/sdk/langs/node/src/prompts/system-prompt.md": "4881073df51001a5e448ef7f755cd858cf752c66374cb8bddc328b1356b3cc6a",
24
+ "packages/sdk/langs/node/src/prompts/system-prompt.md": "a756ec031837351f5a47b73968472910211c826deaf7afe401cef5c2690e3ac3",
25
25
  "packages/sdk/scripts/sdk-generate.mjs": "fc5b0cb07a39972694a0c60cfef19b2e166ec12890fb5060e97b2b818f746ef3",
26
26
  "packages/sdk/scripts/sdk-platform-targets.mjs": "6bdcd55fec9695c7af44ee5c7f67065450f61c27cdd9552d68b92e88abb9d7ab",
27
27
  "packages/sdk/tools/prompt-templates/system-prompt-core.md": "98632908dbece800115db92181b2a48823725b6e2612383de6bc6c34f57ac72e",
@@ -32,14 +32,14 @@
32
32
  "outputs": {
33
33
  "packages/sdk/langs/node/src/generated/contract.ts": {
34
34
  "id": "node-contract",
35
- "sha256": "a4d9b0d437b5bc477a220114610f067b34adc48a02ff9a2e450977c0ac07f446",
35
+ "sha256": "fd3a7a4536ee8b0bb85b55ff4f3bb6e0a68c9fead3a75f549ee6b5bf98c8a6ca",
36
36
  "consumers": [
37
37
  "node"
38
38
  ]
39
39
  },
40
40
  "packages/sdk/langs/node/src/generated/client.ts": {
41
41
  "id": "node-client",
42
- "sha256": "92d71ceeb9a53d372c3a076c5340b2f7e1208de5cf151d0dc8850a0c70d9c348",
42
+ "sha256": "a05cb2fbdd4ef253fdb23526820a949a699fadf89b8b2fd387b744650dd2f10f",
43
43
  "consumers": [
44
44
  "node"
45
45
  ]
@@ -53,14 +53,14 @@
53
53
  },
54
54
  "packages/sdk/langs/python/superdoc/generated/contract.py": {
55
55
  "id": "python-contract",
56
- "sha256": "2217b912fadcd741d4232927d6c1f30d27f0666fcd9532949972eafbf327ecca",
56
+ "sha256": "977efd304ec4bbedbd7a3dcb28b8b5a0adc9b4b756114e1b70907006d7bc7027",
57
57
  "consumers": [
58
58
  "python"
59
59
  ]
60
60
  },
61
61
  "packages/sdk/langs/python/superdoc/generated/client.py": {
62
62
  "id": "python-client",
63
- "sha256": "05827d8d3a1e8b1e01eabd84297af16d247ec94dd2573715e114195c43dc93e6",
63
+ "sha256": "fc810d54b24e30baff720730fd97f6bcd2719f1095a75544723eb93a92643155",
64
64
  "consumers": [
65
65
  "python"
66
66
  ]
@@ -95,7 +95,7 @@
95
95
  },
96
96
  "packages/sdk/tools/catalog.json": {
97
97
  "id": "tool-catalog",
98
- "sha256": "386c15c0640ef0c9c74ac392e6089fe597b9b76d3455bb2f1b641d763b219f76",
98
+ "sha256": "3ca76b8c9bac9a2908b68aeb146eb7953f6e35cfa0c3543bc57df1ba37eb686d",
99
99
  "consumers": [
100
100
  "node",
101
101
  "python",
@@ -104,7 +104,7 @@
104
104
  },
105
105
  "packages/sdk/tools/tools-policy.json": {
106
106
  "id": "tool-policy",
107
- "sha256": "0b91ea9c92e69af4e2773cac2616878b2bbb6f6e0885518169f3f8ab53da249c",
107
+ "sha256": "730298670a730973228ac207a76c66c90de3f7d9a38756dbbdb732ccaafcc348",
108
108
  "consumers": [
109
109
  "node",
110
110
  "python"
@@ -112,7 +112,7 @@
112
112
  },
113
113
  "packages/sdk/tools/tools.openai.json": {
114
114
  "id": "tools-openai",
115
- "sha256": "3934e84c92697a6ab9fb47a5f0444b1387f9d1809bef90dce09b7d0db586b41b",
115
+ "sha256": "bd99a0510e50a3482b389f6f6be7f1dbc160a9570c12a3e7be3298585df957de",
116
116
  "consumers": [
117
117
  "node",
118
118
  "python",
@@ -121,7 +121,7 @@
121
121
  },
122
122
  "packages/sdk/tools/tools.anthropic.json": {
123
123
  "id": "tools-anthropic",
124
- "sha256": "e58e88bb52e755e565bc81999388cb97cf82155818e3f4d48d62d6b2e818d27d",
124
+ "sha256": "5b9defbc04492e901e555661f4e1d7536ac0f5ad9358dfdfffc9b22103022bdf",
125
125
  "consumers": [
126
126
  "node",
127
127
  "python",
@@ -130,7 +130,7 @@
130
130
  },
131
131
  "packages/sdk/tools/tools.vercel.json": {
132
132
  "id": "tools-vercel",
133
- "sha256": "3934e84c92697a6ab9fb47a5f0444b1387f9d1809bef90dce09b7d0db586b41b",
133
+ "sha256": "bd99a0510e50a3482b389f6f6be7f1dbc160a9570c12a3e7be3298585df957de",
134
134
  "consumers": [
135
135
  "node",
136
136
  "python",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "packages/sdk/tools/tools.generic.json": {
141
141
  "id": "tools-generic",
142
- "sha256": "46e3852dd401a2587d04c3d4213f6cc4ba3417de1b88e3536c582da212243e3c",
142
+ "sha256": "604e62b4227958b18c54832d6a63b30e02d559b76062978488c8d523e86d2530",
143
143
  "consumers": [
144
144
  "node",
145
145
  "python",
@@ -172,21 +172,21 @@
172
172
  },
173
173
  "packages/sdk/langs/node/src/embedded-prompts.generated.ts": {
174
174
  "id": "node-embedded-prompts",
175
- "sha256": "d9cca82bd3d4593285e5eb274fd0b9b309aacb7217c60b7c7d9754ad5b5426c6",
175
+ "sha256": "c1d21c0865063d6ebb6058f71949865cd7d0a7ee30d3a7341297a38684a2c540",
176
176
  "consumers": [
177
177
  "node"
178
178
  ]
179
179
  },
180
180
  "packages/sdk/langs/node/src/embedded-tools.generated.ts": {
181
181
  "id": "node-embedded-tools",
182
- "sha256": "e1ddeac5e7f5a753548e606130422d102654e3d9d6a77184acab37bb2650e726",
182
+ "sha256": "ee3db5082ad8cc2384d79f1b990c5f08e588fa50ac001541cbc1328f6309d4c4",
183
183
  "consumers": [
184
184
  "node"
185
185
  ]
186
186
  },
187
187
  "packages/sdk/langs/node/src/runtime/sdk-version.generated.ts": {
188
188
  "id": "node-sdk-version",
189
- "sha256": "34ca6483ff670a0a901a765131951adf69689fae23f4cbaf653a15cb1956157e",
189
+ "sha256": "663cf136c3bdd4673f3cd8e5ec274f76b895c4cac93d4c6662b1a66118ec13e9",
190
190
  "consumers": [
191
191
  "node"
192
192
  ]
@@ -43,5 +43,5 @@
43
43
  "mutates": true
44
44
  }
45
45
  ],
46
- "contractHash": "bc924b5585efd8f81307d866cfc6ec679039be9dc2d1f2e8c0eb0c394e2be5f7"
46
+ "contractHash": "32dbab4c9dbd876ba853d0651199b963871ac514f441674adf937e71091686e6"
47
47
  }
@@ -1480,6 +1480,14 @@
1480
1480
  "type": "string",
1481
1481
  "description": "Require exact selected text before mutation. Only explicit single-paragraph text selections support this guard. Only for action 'replace'. Omit for other actions."
1482
1482
  },
1483
+ "replacementGranularity": {
1484
+ "enum": [
1485
+ "word",
1486
+ "phrase",
1487
+ "range"
1488
+ ],
1489
+ "description": "Tracked revision boundaries for an explicit single-paragraph text selection. word (default) preserves matching tokens; phrase joins edits separated only by spaces; range replaces the complete selection. Requires changeMode: tracked. Only for action 'replace'. Omit for other actions."
1490
+ },
1483
1491
  "behavior": {
1484
1492
  "$ref": "#/$defs/DeleteBehavior",
1485
1493
  "description": "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions."
@@ -1503,6 +1503,14 @@
1503
1503
  "type": "string",
1504
1504
  "description": "Require exact selected text before mutation. Only explicit single-paragraph text selections support this guard. Only for action 'replace'. Omit for other actions."
1505
1505
  },
1506
+ "replacementGranularity": {
1507
+ "enum": [
1508
+ "word",
1509
+ "phrase",
1510
+ "range"
1511
+ ],
1512
+ "description": "Tracked revision boundaries for an explicit single-paragraph text selection. word (default) preserves matching tokens; phrase joins edits separated only by spaces; range replaces the complete selection. Requires changeMode: tracked. Only for action 'replace'. Omit for other actions."
1513
+ },
1506
1514
  "behavior": {
1507
1515
  "$ref": "#/$defs/DeleteBehavior",
1508
1516
  "description": "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions."
@@ -1485,6 +1485,14 @@
1485
1485
  "type": "string",
1486
1486
  "description": "Require exact selected text before mutation. Only explicit single-paragraph text selections support this guard. Only for action 'replace'. Omit for other actions."
1487
1487
  },
1488
+ "replacementGranularity": {
1489
+ "enum": [
1490
+ "word",
1491
+ "phrase",
1492
+ "range"
1493
+ ],
1494
+ "description": "Tracked revision boundaries for an explicit single-paragraph text selection. word (default) preserves matching tokens; phrase joins edits separated only by spaces; range replaces the complete selection. Requires changeMode: tracked. Only for action 'replace'. Omit for other actions."
1495
+ },
1488
1496
  "behavior": {
1489
1497
  "$ref": "#/$defs/DeleteBehavior",
1490
1498
  "description": "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions."
@@ -1485,6 +1485,14 @@
1485
1485
  "type": "string",
1486
1486
  "description": "Require exact selected text before mutation. Only explicit single-paragraph text selections support this guard. Only for action 'replace'. Omit for other actions."
1487
1487
  },
1488
+ "replacementGranularity": {
1489
+ "enum": [
1490
+ "word",
1491
+ "phrase",
1492
+ "range"
1493
+ ],
1494
+ "description": "Tracked revision boundaries for an explicit single-paragraph text selection. word (default) preserves matching tokens; phrase joins edits separated only by spaces; range replaces the complete selection. Requires changeMode: tracked. Only for action 'replace'. Omit for other actions."
1495
+ },
1488
1496
  "behavior": {
1489
1497
  "$ref": "#/$defs/DeleteBehavior",
1490
1498
  "description": "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions."