@superdoc/sdk 2.13.0-next.9 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools.d.ts CHANGED
@@ -43,6 +43,22 @@ export type ToolChooserInput = {
43
43
  * ignore it. Unknown names throw.
44
44
  */
45
45
  excludeActions?: readonly string[];
46
+ /**
47
+ * Fail-closed tracked-changes-only agent (core preset). `'tracked'` hides
48
+ * every direct-only action (see `getActionDispositions()`) from the tools and
49
+ * prompt, refuses it at dispatch, and upgrades every tracked-capable action
50
+ * to `changeMode: "tracked"` no matter what the model passed. Only
51
+ * `'tracked'` is accepted.
52
+ */
53
+ enforceChangeMode?: 'tracked';
54
+ /**
55
+ * Direct-only actions this host still permits under `enforceChangeMode`
56
+ * (e.g. `['add_comments', 'insert_page_break']`). They stay advertised and
57
+ * run directly. `['*']` allows every direct-only action while tracked-capable
58
+ * actions stay forced tracked. Requires `enforceChangeMode`; unknown names
59
+ * throw; tracked-capable names are ignored.
60
+ */
61
+ allowDirectActions?: readonly string[];
46
62
  };
47
63
  /**
48
64
  * Select tools for a specific provider from a preset.
@@ -91,6 +107,10 @@ export declare function dispatchSuperDocTool(documentHandle: BoundDocApi, toolNa
91
107
  preset?: string;
92
108
  /** Refuse dispatch of these actions (defense-in-depth for exclusion configs). */
93
109
  excludeActions?: readonly string[];
110
+ /** Refuse direct-only actions and force tracked mode (see {@link ToolChooserInput.enforceChangeMode}). */
111
+ enforceChangeMode?: 'tracked';
112
+ /** Direct-only actions still permitted under `enforceChangeMode`. */
113
+ allowDirectActions?: readonly string[];
94
114
  }): Promise<unknown>;
95
115
  /**
96
116
  * Read the SDK system prompt for the given preset (default: {@link DEFAULT_PRESET}).
@@ -136,9 +156,10 @@ export type AgentToolkit = {
136
156
  };
137
157
  /**
138
158
  * One-call agent surface: tools, system prompt, and a pre-bound dispatcher
139
- * that are coherent BY CONSTRUCTION — the same preset and `excludeActions`
140
- * apply to all three, so an action can never linger in the prompt after
141
- * being excluded from the tool array (or vice versa).
159
+ * that are coherent BY CONSTRUCTION — the same preset, `excludeActions`,
160
+ * `enforceChangeMode`, and `allowDirectActions` apply to all three, so an
161
+ * action can never linger in the prompt after being excluded from the tool
162
+ * array (or vice versa).
142
163
  *
143
164
  * The legacy preset ignores exclusion options everywhere (it has no action
144
165
  * surface); passing `excludeActions` with `preset: 'legacy'` is a no-op,
@@ -207,4 +228,8 @@ export declare function getSystemPromptForProvider(input: {
207
228
  cache?: boolean;
208
229
  /** Same exclusions as chooseTools — keeps the provider-shaped prompt and the tool surface narrowed together. */
209
230
  excludeActions?: readonly string[];
231
+ /** Same policy as chooseTools (see {@link ToolChooserInput.enforceChangeMode}). */
232
+ enforceChangeMode?: 'tracked';
233
+ /** Same allowlist as chooseTools. */
234
+ allowDirectActions?: readonly string[];
210
235
  }): Promise<SystemPromptForProviderResult>;
package/dist/tools.js CHANGED
@@ -32,6 +32,8 @@ export async function chooseTools(input) {
32
32
  const { tools, cacheStrategy } = await preset.getTools(input.provider, {
33
33
  cache: input.cache === true,
34
34
  excludeActions: input.excludeActions,
35
+ enforceChangeMode: input.enforceChangeMode,
36
+ allowDirectActions: input.allowDirectActions,
35
37
  });
36
38
  return {
37
39
  tools,
@@ -100,16 +102,25 @@ const CUSTOM_TOOLKIT_PRESET_ID = 'custom_superdoc_preset';
100
102
  const TOOL_PROVIDERS = new Set(['openai', 'anthropic', 'vercel', 'generic']);
101
103
  /**
102
104
  * One-call agent surface: tools, system prompt, and a pre-bound dispatcher
103
- * that are coherent BY CONSTRUCTION — the same preset and `excludeActions`
104
- * apply to all three, so an action can never linger in the prompt after
105
- * being excluded from the tool array (or vice versa).
105
+ * that are coherent BY CONSTRUCTION — the same preset, `excludeActions`,
106
+ * `enforceChangeMode`, and `allowDirectActions` apply to all three, so an
107
+ * action can never linger in the prompt after being excluded from the tool
108
+ * array (or vice versa).
106
109
  *
107
110
  * The legacy preset ignores exclusion options everywhere (it has no action
108
111
  * surface); passing `excludeActions` with `preset: 'legacy'` is a no-op,
109
112
  * matching the standalone functions.
110
113
  */
111
114
  export async function createAgentToolkit(input) {
112
- const excludeActions = input.excludeActions ? [...input.excludeActions] : undefined;
115
+ // Copy the lists so later caller mutation cannot desync dispatch from the
116
+ // tools and prompt that were generated here.
117
+ const policy = {
118
+ ...(input.excludeActions ? { excludeActions: [...input.excludeActions] } : {}),
119
+ ...(input.enforceChangeMode !== undefined ? { enforceChangeMode: input.enforceChangeMode } : {}),
120
+ ...(input.allowDirectActions ? { allowDirectActions: [...input.allowDirectActions] } : {}),
121
+ };
122
+ const hasPolicy = Object.keys(policy).length > 0;
123
+ const excludeActions = policy.excludeActions;
113
124
  // One-call custom-actions path: define your actions, hand them here as
114
125
  // `customActions`, use the
115
126
  // returned toolkit. We build an ephemeral extended/composed preset over
@@ -120,6 +131,16 @@ export async function createAgentToolkit(input) {
120
131
  // otherwise `{ base: 'core', customActions: [] }` would silently fall through to
121
132
  // the default (legacy) preset, a different tool family than the one named.
122
133
  if ((input.customActions && input.customActions.length > 0) || input.includeActions != null || input.base != null) {
134
+ // A custom action runs its own step list through runCustomAction, which
135
+ // never passes the core dispatch guard, so the tracked-only policy cannot
136
+ // cover it. Enforcing built-ins while custom actions edit directly would be
137
+ // a policy that silently does not hold — refuse the combination instead.
138
+ if (input.enforceChangeMode !== undefined) {
139
+ throw new SuperDocCliError('enforceChangeMode is not supported together with custom actions; a custom action bypasses the tracked-changes guard.', {
140
+ code: 'INVALID_ARGUMENT',
141
+ details: { enforceChangeMode: input.enforceChangeMode, reason: 'custom-actions-unsupported' },
142
+ });
143
+ }
123
144
  // This path builds tools directly (no chooseTools), so it must validate
124
145
  // the provider itself; an unknown value would otherwise yield a silently
125
146
  // mixed-dialect tool list. Mirrors the Python `create_agent_toolkit` guard.
@@ -154,12 +175,12 @@ export async function createAgentToolkit(input) {
154
175
  };
155
176
  }
156
177
  const presetId = input.preset ?? DEFAULT_PRESET;
157
- const { tools, meta } = await chooseTools({ ...input, preset: presetId, excludeActions });
158
- const systemPrompt = await getSystemPrompt(presetId, excludeActions ? { excludeActions } : undefined);
178
+ const { tools, meta } = await chooseTools({ ...input, preset: presetId, ...policy });
179
+ const systemPrompt = await getSystemPrompt(presetId, hasPolicy ? policy : undefined);
159
180
  const dispatch = (documentHandle, toolName, args = {}, invokeOptions) => dispatchSuperDocTool(documentHandle, toolName, args, {
160
181
  ...invokeOptions,
161
182
  preset: presetId,
162
- ...(excludeActions ? { excludeActions } : {}),
183
+ ...policy,
163
184
  });
164
185
  return { tools, meta, systemPrompt, dispatch };
165
186
  }
@@ -200,7 +221,12 @@ export async function getMcpPrompt(preset) {
200
221
  * ```
201
222
  */
202
223
  export async function getSystemPromptForProvider(input) {
203
- const text = await getSystemPrompt(input.preset, input.excludeActions ? { excludeActions: input.excludeActions } : undefined);
224
+ const promptOptions = {
225
+ ...(input.excludeActions ? { excludeActions: input.excludeActions } : {}),
226
+ ...(input.enforceChangeMode !== undefined ? { enforceChangeMode: input.enforceChangeMode } : {}),
227
+ ...(input.allowDirectActions ? { allowDirectActions: input.allowDirectActions } : {}),
228
+ };
229
+ const text = await getSystemPrompt(input.preset, Object.keys(promptOptions).length > 0 ? promptOptions : undefined);
204
230
  const cacheRequested = input.cache === true;
205
231
  if (input.provider === 'anthropic') {
206
232
  const block = { type: 'text', text };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.13.0-next.9",
3
+ "version": "2.13.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.13.0-next.9",
42
- "@superdoc/sdk-darwin-x64": "2.13.0-next.9",
43
- "@superdoc/sdk-linux-x64": "2.13.0-next.9",
44
- "@superdoc/sdk-linux-arm64": "2.13.0-next.9",
45
- "@superdoc/sdk-windows-x64": "2.13.0-next.9"
41
+ "@superdoc/sdk-darwin-arm64": "2.13.0",
42
+ "@superdoc/sdk-darwin-x64": "2.13.0",
43
+ "@superdoc/sdk-linux-x64": "2.13.0",
44
+ "@superdoc/sdk-linux-arm64": "2.13.0",
45
+ "@superdoc/sdk-windows-x64": "2.13.0"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"
@@ -51,7 +51,7 @@
51
51
  "build": "rm -rf dist && node scripts/embed-version.mjs && node scripts/embed-prompts.mjs && node scripts/embed-tools.mjs && tsc && pnpm run typecheck:consumer && rollup -c rollup.cjs.config.mjs && rm -rf dist/prompts && mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/ && pnpm run audit:publish",
52
52
  "audit:publish": "node ../../../../scripts/audit-publish-artifact.mjs dist --label sdk-node-dist",
53
53
  "typecheck": "tsc --noEmit",
54
- "typecheck:consumer": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext ../../../../tests/consumer-typecheck/src/sdk-per-open-collaboration-auth.mts ../../../../tests/consumer-typecheck/src/sdk-replace-file.mts ../../../../tests/consumer-typecheck/src/sdk-agent-evidence.mts ../../../../tests/consumer-typecheck/src/sdk-custom-actions.mts ../../../../tests/consumer-typecheck/src/sd4807-sdk-inspect.mts",
54
+ "typecheck:consumer": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext ../../../../tests/consumer-typecheck/src/sdk-per-open-collaboration-auth.mts ../../../../tests/consumer-typecheck/src/sdk-replace-file.mts ../../../../tests/consumer-typecheck/src/sdk-agent-evidence.mts ../../../../tests/consumer-typecheck/src/sdk-custom-actions.mts ../../../../tests/consumer-typecheck/src/sd4807-sdk-inspect.mts ../../../../tests/consumer-typecheck/src/sdk-tracked-only-policy.mts",
55
55
  "test:agent-actions": "bun test src/__tests__/actions.test.ts src/__tests__/agent-runtime.test.ts src/__tests__/custom-actions.test.ts src/__tests__/textbox-snapshot.test.ts",
56
56
  "test:document-host": "bun test src/runtime/__tests__/host-spawn-args.test.ts src/runtime/__tests__/document-rpc.test.ts src/__tests__/structured-document-rpc.e2e.test.ts src/__tests__/request-timeout-ms.e2e.test.ts src/__tests__/agent-evidence.e2e.test.ts src/__tests__/agent-apply.e2e.test.ts src/__tests__/find-text.e2e.test.ts src/__tests__/custom-actions.e2e.test.ts",
57
57
  "smoke:product-action": "node scripts/product-action-smoke.mjs"