@superdoc/sdk 2.7.0 → 2.9.0-next.1

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 CHANGED
@@ -25,6 +25,27 @@ await document.close();
25
25
  await client.dispose();
26
26
  ```
27
27
 
28
+ For token-authenticated Hocuspocus or y-websocket rooms, pass the credential as
29
+ an option on that document's open request:
30
+
31
+ ```js
32
+ const document = await client.open(
33
+ {
34
+ doc: './contract.docx',
35
+ collaboration: {
36
+ providerType: 'hocuspocus',
37
+ url: 'wss://collaboration.example.com',
38
+ documentId: 'contract-123',
39
+ },
40
+ },
41
+ { collaborationAuth: { type: 'token', token: userToken } },
42
+ );
43
+ ```
44
+
45
+ The credential applies only to that open request. It is not part of the
46
+ collaboration profile or command-line arguments. Per-open token authentication
47
+ is not supported for Liveblocks or the standalone document host.
48
+
28
49
  See the [Node.js SDK guide](https://docs.superdoc.dev/agents/automation/node-sdk) for a complete workflow.
29
50
 
30
51
  ## License
@@ -29333,6 +29333,7 @@ export interface DocOpenParams {
29333
29333
  url: string;
29334
29334
  documentId?: string;
29335
29335
  tokenEnv?: string;
29336
+ headersEnv?: string;
29336
29337
  params?: Record<string, unknown>;
29337
29338
  syncTimeoutMs?: number;
29338
29339
  roomMode?: "join" | "create";
@@ -17,6 +17,7 @@ const CONTRACT = {
17
17
  "protocolVersion": "1.0",
18
18
  "features": [
19
19
  "cli.invoke",
20
+ "collaboration.auth.perOpen",
20
21
  "document.open",
21
22
  "document.open.defaultChangeMode",
22
23
  "document.currentRevision",
@@ -296902,6 +296903,10 @@ const CONTRACT = {
296902
296903
  "type": "string",
296903
296904
  "description": "Environment variable name containing the auth token."
296904
296905
  },
296906
+ "headersEnv": {
296907
+ "type": "string",
296908
+ "description": "Environment variable name containing JSON WebSocket headers. Supported by y-websocket."
296909
+ },
296905
296910
  "params": {
296906
296911
  "type": "object",
296907
296912
  "description": "Custom query parameters appended to the WebSocket URL. Values must be strings. Reserved keys: token.",
@@ -297149,6 +297154,10 @@ const CONTRACT = {
297149
297154
  "type": "string",
297150
297155
  "description": "Environment variable name containing the auth token."
297151
297156
  },
297157
+ "headersEnv": {
297158
+ "type": "string",
297159
+ "description": "Environment variable name containing JSON WebSocket headers. Supported by y-websocket."
297160
+ },
297152
297161
  "params": {
297153
297162
  "type": "object",
297154
297163
  "description": "Custom query parameters appended to the WebSocket URL. Values must be strings. Reserved keys: token.",
@@ -2901,6 +2901,7 @@ export const CONTRACT = {
2901
2901
  "transport": "stdio",
2902
2902
  "features": [
2903
2903
  "cli.invoke",
2904
+ "collaboration.auth.perOpen",
2904
2905
  "document.open",
2905
2906
  "document.open.defaultChangeMode",
2906
2907
  "document.currentRevision",
@@ -2931,6 +2932,7 @@ export const CONTRACT = {
2931
2932
  "protocolVersion": "1.0",
2932
2933
  "features": [
2933
2934
  "cli.invoke",
2935
+ "collaboration.auth.perOpen",
2934
2936
  "document.open",
2935
2937
  "document.open.defaultChangeMode",
2936
2938
  "document.currentRevision",
@@ -299816,6 +299818,10 @@ export const CONTRACT = {
299816
299818
  "type": "string",
299817
299819
  "description": "Environment variable name containing the auth token."
299818
299820
  },
299821
+ "headersEnv": {
299822
+ "type": "string",
299823
+ "description": "Environment variable name containing JSON WebSocket headers. Supported by y-websocket."
299824
+ },
299819
299825
  "params": {
299820
299826
  "type": "object",
299821
299827
  "description": "Custom query parameters appended to the WebSocket URL. Values must be strings. Reserved keys: token.",
@@ -300063,6 +300069,10 @@ export const CONTRACT = {
300063
300069
  "type": "string",
300064
300070
  "description": "Environment variable name containing the auth token."
300065
300071
  },
300072
+ "headersEnv": {
300073
+ "type": "string",
300074
+ "description": "Environment variable name containing JSON WebSocket headers. Supported by y-websocket."
300075
+ },
300066
300076
  "params": {
300067
300077
  "type": "object",
300068
300078
  "description": "Custom query parameters appended to the WebSocket URL. Values must be strings. Reserved keys: token.",
package/dist/index.cjs CHANGED
@@ -113,6 +113,7 @@ class SuperDocClient {
113
113
  rawApi;
114
114
  defaultDocumentRuntime;
115
115
  handles = new Map();
116
+ pendingSessionIds = new Set();
116
117
  constructor(options = {}) {
117
118
  this.defaultDocumentRuntime = options.runtime;
118
119
  this.runtime = new process.SuperDocRuntime(options);
@@ -133,18 +134,27 @@ class SuperDocClient {
133
134
  ? { ...params, runtime: this.defaultDocumentRuntime }
134
135
  : params;
135
136
  const explicitSessionId = openParams.sessionId;
136
- if (typeof explicitSessionId === 'string' && this.handles.has(explicitSessionId)) {
137
- throw new errors.SuperDocCliError(`Session id already open in this client: ${explicitSessionId}`, {
138
- code: 'SESSION_ALREADY_OPEN',
139
- details: { sessionId: explicitSessionId },
140
- });
137
+ if (typeof explicitSessionId === 'string') {
138
+ if (this.handles.has(explicitSessionId) || this.pendingSessionIds.has(explicitSessionId)) {
139
+ throw new errors.SuperDocCliError(`Session id already open in this client: ${explicitSessionId}`, {
140
+ code: 'SESSION_ALREADY_OPEN',
141
+ details: { sessionId: explicitSessionId },
142
+ });
143
+ }
144
+ this.pendingSessionIds.add(explicitSessionId);
145
+ }
146
+ try {
147
+ const result = (await this.rawApi.open(openParams, options));
148
+ const contextId = result.contextId;
149
+ const boundRuntime = new BoundRuntime(this.runtime, contextId);
150
+ const handle = new SuperDocDocument(boundRuntime, contextId, result, this);
151
+ this.handles.set(contextId, handle);
152
+ return handle;
153
+ }
154
+ finally {
155
+ if (typeof explicitSessionId === 'string')
156
+ this.pendingSessionIds.delete(explicitSessionId);
141
157
  }
142
- const result = (await this.rawApi.open(openParams, options));
143
- const contextId = result.contextId;
144
- const boundRuntime = new BoundRuntime(this.runtime, contextId);
145
- const handle = new SuperDocDocument(boundRuntime, contextId, result, this);
146
- this.handles.set(contextId, handle);
147
- return handle;
148
158
  }
149
159
  async describe(_params = {}, _options) {
150
160
  return introspection.describeContract();
@@ -153,11 +163,11 @@ class SuperDocClient {
153
163
  return introspection.describeContractOperation(params);
154
164
  }
155
165
  async dispose() {
166
+ await this.runtime.dispose();
156
167
  for (const handle of this.handles.values()) {
157
168
  handle.markClosed();
158
169
  }
159
170
  this.handles.clear();
160
- await this.runtime.dispose();
161
171
  }
162
172
  /** @internal */
163
173
  removeHandle(sessionId) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type BoundDocApi, type DocCloseBoundParams, type DocCloseResult, type DocFormatApplyBoundParams, type DocFormatApplyResult, type DocOpenParams as GeneratedDocOpenParams, type DocOpenResult, type DocSaveBoundParams, type DocSaveResult } from './generated/client.js';
2
- import { SuperDocRuntime, type SuperDocClientOptions, type InvokeOptions, type OperationSpec, type RuntimeInvoker } from './runtime/process.js';
2
+ import { SuperDocRuntime, type SuperDocClientOptions, type DocOpenOptions, type InvokeOptions, type OperationSpec, type RuntimeInvoker } from './runtime/process.js';
3
3
  /**
4
4
  * Wraps a raw runtime and injects a fixed sessionId into every invoke call.
5
5
  * Implements RuntimeInvoker so generated code can use it directly.
@@ -64,6 +64,7 @@ export declare class SuperDocClient {
64
64
  private readonly rawApi;
65
65
  private readonly defaultDocumentRuntime;
66
66
  private readonly handles;
67
+ private readonly pendingSessionIds;
67
68
  constructor(options?: SuperDocClientOptions);
68
69
  connect(): Promise<void>;
69
70
  /**
@@ -73,7 +74,7 @@ export declare class SuperDocClient {
73
74
  * automatically. The same file can be opened multiple times with
74
75
  * different session ids (useful for diff workflows).
75
76
  */
76
- open(params: DocOpenParams, options?: InvokeOptions): Promise<SuperDocDocument>;
77
+ open(params: DocOpenParams, options?: DocOpenOptions): Promise<SuperDocDocument>;
77
78
  describe(_params?: Record<string, unknown>, _options?: InvokeOptions): Promise<unknown>;
78
79
  describeCommand(params: DocDescribeCommandParams, _options?: InvokeOptions): Promise<unknown>;
79
80
  dispose(): Promise<void>;
@@ -93,5 +94,5 @@ export type { GetSystemPromptOptions, GetToolsOptions, GetToolsResult, PresetDes
93
94
  export { createAgentToolkit } from './tools.js';
94
95
  export type { AgentToolkit, CreateAgentToolkitInput } from './tools.js';
95
96
  export { SuperDocCliError } from './runtime/errors.js';
96
- export type { InvokeOptions, OperationSpec, OperationParamSpec, RuntimeInvoker, SuperDocClientOptions, } from './runtime/process.js';
97
+ export type { CollaborationAuth, DocOpenOptions, InvokeOptions, OperationSpec, OperationParamSpec, RuntimeInvoker, SuperDocClientOptions, } from './runtime/process.js';
97
98
  export type { DocOpenResult } from './generated/client.js';
package/dist/index.js CHANGED
@@ -105,6 +105,7 @@ export class SuperDocClient {
105
105
  rawApi;
106
106
  defaultDocumentRuntime;
107
107
  handles = new Map();
108
+ pendingSessionIds = new Set();
108
109
  constructor(options = {}) {
109
110
  this.defaultDocumentRuntime = options.runtime;
110
111
  this.runtime = new SuperDocRuntime(options);
@@ -125,18 +126,27 @@ export class SuperDocClient {
125
126
  ? { ...params, runtime: this.defaultDocumentRuntime }
126
127
  : params;
127
128
  const explicitSessionId = openParams.sessionId;
128
- if (typeof explicitSessionId === 'string' && this.handles.has(explicitSessionId)) {
129
- throw new SuperDocCliError(`Session id already open in this client: ${explicitSessionId}`, {
130
- code: 'SESSION_ALREADY_OPEN',
131
- details: { sessionId: explicitSessionId },
132
- });
129
+ if (typeof explicitSessionId === 'string') {
130
+ if (this.handles.has(explicitSessionId) || this.pendingSessionIds.has(explicitSessionId)) {
131
+ throw new SuperDocCliError(`Session id already open in this client: ${explicitSessionId}`, {
132
+ code: 'SESSION_ALREADY_OPEN',
133
+ details: { sessionId: explicitSessionId },
134
+ });
135
+ }
136
+ this.pendingSessionIds.add(explicitSessionId);
137
+ }
138
+ try {
139
+ const result = (await this.rawApi.open(openParams, options));
140
+ const contextId = result.contextId;
141
+ const boundRuntime = new BoundRuntime(this.runtime, contextId);
142
+ const handle = new SuperDocDocument(boundRuntime, contextId, result, this);
143
+ this.handles.set(contextId, handle);
144
+ return handle;
145
+ }
146
+ finally {
147
+ if (typeof explicitSessionId === 'string')
148
+ this.pendingSessionIds.delete(explicitSessionId);
133
149
  }
134
- const result = (await this.rawApi.open(openParams, options));
135
- const contextId = result.contextId;
136
- const boundRuntime = new BoundRuntime(this.runtime, contextId);
137
- const handle = new SuperDocDocument(boundRuntime, contextId, result, this);
138
- this.handles.set(contextId, handle);
139
- return handle;
140
150
  }
141
151
  async describe(_params = {}, _options) {
142
152
  return describeContract();
@@ -145,11 +155,11 @@ export class SuperDocClient {
145
155
  return describeContractOperation(params);
146
156
  }
147
157
  async dispose() {
158
+ await this.runtime.dispose();
148
159
  for (const handle of this.handles.values()) {
149
160
  handle.markClosed();
150
161
  }
151
162
  this.handles.clear();
152
- await this.runtime.dispose();
153
163
  }
154
164
  /** @internal */
155
165
  removeHandle(sessionId) {
@@ -392,26 +392,78 @@ async function coreGetCatalog() {
392
392
  };
393
393
  }
394
394
  /**
395
- * Drop the per-action documentation lines for excluded actions. Entries render
396
- * as single "- name: ..." lines (the drift-guard test enforces the format), so
397
- * line-level filtering is deterministic. Prose cross-references elsewhere in
398
- * the prompt are left alone a mention costs a few tokens; a full per-action
399
- * manual for an uncallable action teaches the model to call it.
395
+ * Sentence boundary: terminal punctuation, whitespace, then something that
396
+ * plausibly opens a sentence including lowercase, because prompt sentences
397
+ * regularly start with an action name ("comment_paragraphs applies ...").
398
+ * The lookbehind guards keep "e.g."/"i.e." from splitting mid-sentence; a
399
+ * false split is still safe because kept fragments are rejoined with their
400
+ * original whitespace, and erring toward MORE splits only narrows what a
401
+ * mention takes down with it.
402
+ */
403
+ const SENTENCE_BOUNDARY = /((?<![ei]\.[ge]\.)(?<=[.!?])\s+(?=[A-Za-z"(]))/;
404
+ function mentionsAnyAction(text, excludedActions) {
405
+ for (const name of excludedActions) {
406
+ if (new RegExp(`\\b${name}\\b`).test(text))
407
+ return true;
408
+ }
409
+ return false;
410
+ }
411
+ /**
412
+ * Drop the sentences of one prompt line that mention an excluded action.
413
+ * Returns null when nothing usable is left, so the caller drops the line.
414
+ */
415
+ function stripExcludedActionSentences(line, excludedActions) {
416
+ if (!mentionsAnyAction(line, excludedActions))
417
+ return line;
418
+ // split() with a capturing group keeps the inter-sentence whitespace as
419
+ // separate array elements: even indexes are sentences, odd their separators.
420
+ const parts = line.split(SENTENCE_BOUNDARY);
421
+ const kept = [];
422
+ for (let i = 0; i < parts.length; i += 2) {
423
+ if (mentionsAnyAction(parts[i], excludedActions))
424
+ continue;
425
+ kept.push(parts[i]);
426
+ if (i + 1 < parts.length)
427
+ kept.push(parts[i + 1]);
428
+ }
429
+ let result = kept.join('').trimEnd();
430
+ // Losing the first sentence must not lose the bullet marker with it.
431
+ if (result && /^- /.test(line) && !/^- /.test(result))
432
+ result = `- ${result}`;
433
+ // Nothing left beyond the marker means the whole line was about excluded
434
+ // actions — drop it entirely rather than leaving an empty bullet.
435
+ return /[a-zA-Z0-9]/.test(result.replace(/^- /, '')) ? result : null;
436
+ }
437
+ /**
438
+ * Remove excluded actions from the prompt: drop their per-action
439
+ * documentation lines (entries render as single "- name: ..." lines — the
440
+ * drift-guard test enforces the format), then drop any remaining sentence
441
+ * that still mentions one. Guidance prose ("use move_range to relocate a
442
+ * section") is instruction, not a passing cross-reference: leaving it coaches
443
+ * the model into calls the schema forbids and dispatch rejects.
400
444
  */
401
445
  function stripExcludedActionLines(prompt, excludedActions) {
402
446
  if (excludedActions.size === 0)
403
447
  return prompt;
404
448
  return prompt
405
449
  .split('\n')
406
- .filter((line) => {
450
+ .map((line) => {
407
451
  const match = /^- ([a-z_]+)(?: \/ ([a-z_]+))?:/.exec(line);
408
452
  if (!match)
409
- return true;
453
+ return line;
410
454
  const names = [match[1], match[2]].filter((n) => Boolean(n));
411
- // Drop the line only when EVERY action it documents is excluded (the
412
- // paired accept/reject line survives if one side remains callable).
413
- return !names.every((name) => excludedActions.has(name));
455
+ const callable = names.filter((name) => !excludedActions.has(name));
456
+ // Drop the line only when EVERY action it documents is excluded; a
457
+ // paired accept/reject line survives if one side remains callable, but
458
+ // its header keeps only the callable name.
459
+ if (callable.length === 0)
460
+ return null;
461
+ if (callable.length === names.length)
462
+ return line;
463
+ return `- ${callable.join(' / ')}:${line.slice(match[0].length)}`;
414
464
  })
465
+ .map((line) => (line === null ? null : stripExcludedActionSentences(line, excludedActions)))
466
+ .filter((line) => line !== null)
415
467
  .join('\n');
416
468
  }
417
469
  async function coreGetSystemPrompt(options) {
@@ -388,26 +388,78 @@ async function coreGetCatalog() {
388
388
  };
389
389
  }
390
390
  /**
391
- * Drop the per-action documentation lines for excluded actions. Entries render
392
- * as single "- name: ..." lines (the drift-guard test enforces the format), so
393
- * line-level filtering is deterministic. Prose cross-references elsewhere in
394
- * the prompt are left alone a mention costs a few tokens; a full per-action
395
- * manual for an uncallable action teaches the model to call it.
391
+ * Sentence boundary: terminal punctuation, whitespace, then something that
392
+ * plausibly opens a sentence including lowercase, because prompt sentences
393
+ * regularly start with an action name ("comment_paragraphs applies ...").
394
+ * The lookbehind guards keep "e.g."/"i.e." from splitting mid-sentence; a
395
+ * false split is still safe because kept fragments are rejoined with their
396
+ * original whitespace, and erring toward MORE splits only narrows what a
397
+ * mention takes down with it.
398
+ */
399
+ const SENTENCE_BOUNDARY = /((?<![ei]\.[ge]\.)(?<=[.!?])\s+(?=[A-Za-z"(]))/;
400
+ function mentionsAnyAction(text, excludedActions) {
401
+ for (const name of excludedActions) {
402
+ if (new RegExp(`\\b${name}\\b`).test(text))
403
+ return true;
404
+ }
405
+ return false;
406
+ }
407
+ /**
408
+ * Drop the sentences of one prompt line that mention an excluded action.
409
+ * Returns null when nothing usable is left, so the caller drops the line.
410
+ */
411
+ function stripExcludedActionSentences(line, excludedActions) {
412
+ if (!mentionsAnyAction(line, excludedActions))
413
+ return line;
414
+ // split() with a capturing group keeps the inter-sentence whitespace as
415
+ // separate array elements: even indexes are sentences, odd their separators.
416
+ const parts = line.split(SENTENCE_BOUNDARY);
417
+ const kept = [];
418
+ for (let i = 0; i < parts.length; i += 2) {
419
+ if (mentionsAnyAction(parts[i], excludedActions))
420
+ continue;
421
+ kept.push(parts[i]);
422
+ if (i + 1 < parts.length)
423
+ kept.push(parts[i + 1]);
424
+ }
425
+ let result = kept.join('').trimEnd();
426
+ // Losing the first sentence must not lose the bullet marker with it.
427
+ if (result && /^- /.test(line) && !/^- /.test(result))
428
+ result = `- ${result}`;
429
+ // Nothing left beyond the marker means the whole line was about excluded
430
+ // actions — drop it entirely rather than leaving an empty bullet.
431
+ return /[a-zA-Z0-9]/.test(result.replace(/^- /, '')) ? result : null;
432
+ }
433
+ /**
434
+ * Remove excluded actions from the prompt: drop their per-action
435
+ * documentation lines (entries render as single "- name: ..." lines — the
436
+ * drift-guard test enforces the format), then drop any remaining sentence
437
+ * that still mentions one. Guidance prose ("use move_range to relocate a
438
+ * section") is instruction, not a passing cross-reference: leaving it coaches
439
+ * the model into calls the schema forbids and dispatch rejects.
396
440
  */
397
441
  function stripExcludedActionLines(prompt, excludedActions) {
398
442
  if (excludedActions.size === 0)
399
443
  return prompt;
400
444
  return prompt
401
445
  .split('\n')
402
- .filter((line) => {
446
+ .map((line) => {
403
447
  const match = /^- ([a-z_]+)(?: \/ ([a-z_]+))?:/.exec(line);
404
448
  if (!match)
405
- return true;
449
+ return line;
406
450
  const names = [match[1], match[2]].filter((n) => Boolean(n));
407
- // Drop the line only when EVERY action it documents is excluded (the
408
- // paired accept/reject line survives if one side remains callable).
409
- return !names.every((name) => excludedActions.has(name));
451
+ const callable = names.filter((name) => !excludedActions.has(name));
452
+ // Drop the line only when EVERY action it documents is excluded; a
453
+ // paired accept/reject line survives if one side remains callable, but
454
+ // its header keeps only the callable name.
455
+ if (callable.length === 0)
456
+ return null;
457
+ if (callable.length === names.length)
458
+ return line;
459
+ return `- ${callable.join(' / ')}:${line.slice(match[0].length)}`;
410
460
  })
461
+ .map((line) => (line === null ? null : stripExcludedActionSentences(line, excludedActions)))
462
+ .filter((line) => line !== null)
411
463
  .join('\n');
412
464
  }
413
465
  async function coreGetSystemPrompt(options) {
@@ -4,6 +4,7 @@ var node_fs = require('node:fs');
4
4
  var node_module = require('node:module');
5
5
  var path = require('node:path');
6
6
  var node_url = require('node:url');
7
+ var sdkVersion_generated = require('./sdk-version.generated.cjs');
7
8
 
8
9
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
10
  const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('runtime/embedded-platform.cjs', document.baseURI).href)));
@@ -50,31 +51,36 @@ function resolveFromPackageLocal(target, binaryName) {
50
51
  * Resolve the platform package through Node, and reject a version that is not
51
52
  * this SDK's own.
52
53
  *
53
- * `require.resolve` falls back to NODE_PATH, which package managers set to a
54
- * flat store directory (pnpm does it for every binary shim). A host binary from
55
- * an unrelated SDK version installed anywhere in that store then satisfies this
56
- * lookup, and the SDK silently drives a foreign engine: seen in the wild as an
57
- * SDK 2.2.1 install executing a hoisted 2.0.0 host, which failed operations the
58
- * matching host performs correctly. optionalDependencies pin the platform
59
- * package to an exact version, so a mismatch here is never legitimate.
54
+ * Resolution starts from the installed SDK so pnpm's isolated optional
55
+ * dependency remains visible after the SDK itself is bundled. `require.resolve`
56
+ * can also fall back to NODE_PATH, so the version check prevents a host binary
57
+ * from an unrelated SDK version in a flat store from satisfying this lookup.
58
+ * optionalDependencies pin the platform package to an exact version, so a
59
+ * mismatch here is never legitimate.
60
60
  */
61
61
  function resolveFromPlatformPackage(target, binaryName) {
62
62
  const pkg = TARGET_TO_PACKAGE[target];
63
+ let sdkRequire;
64
+ try {
65
+ sdkRequire = node_module.createRequire(require$1.resolve('@superdoc/sdk'));
66
+ }
67
+ catch {
68
+ sdkRequire = require$1;
69
+ }
63
70
  let binaryPath;
64
71
  try {
65
- binaryPath = require$1.resolve(`${pkg}/bin/${binaryName}`);
72
+ binaryPath = sdkRequire.resolve(`${pkg}/bin/${binaryName}`);
66
73
  }
67
74
  catch {
68
75
  return null;
69
76
  }
70
77
  try {
71
- const platformVersion = require$1(require$1.resolve(`${pkg}/package.json`)).version;
72
- const ownVersion = require$1(node_url.fileURLToPath(new URL('../../package.json', (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('runtime/embedded-platform.cjs', document.baseURI).href))))).version;
73
- if (platformVersion !== ownVersion)
78
+ const platformVersion = sdkRequire(sdkRequire.resolve(`${pkg}/package.json`)).version;
79
+ if (platformVersion !== sdkVersion_generated.SDK_VERSION)
74
80
  return null;
75
81
  }
76
82
  catch {
77
- // Either manifest being unreadable is itself disqualifying: an unverifiable
83
+ // An unreadable platform manifest is itself disqualifying: an unverifiable
78
84
  // host is exactly the case this guard exists to reject.
79
85
  return null;
80
86
  }
@@ -2,6 +2,7 @@ import { chmodSync, existsSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
+ import { SDK_VERSION } from './sdk-version.generated.js';
5
6
  const require = createRequire(import.meta.url);
6
7
  const TARGET_TO_PACKAGE = {
7
8
  'darwin-arm64': '@superdoc/sdk-darwin-arm64',
@@ -46,31 +47,36 @@ function resolveFromPackageLocal(target, binaryName) {
46
47
  * Resolve the platform package through Node, and reject a version that is not
47
48
  * this SDK's own.
48
49
  *
49
- * `require.resolve` falls back to NODE_PATH, which package managers set to a
50
- * flat store directory (pnpm does it for every binary shim). A host binary from
51
- * an unrelated SDK version installed anywhere in that store then satisfies this
52
- * lookup, and the SDK silently drives a foreign engine: seen in the wild as an
53
- * SDK 2.2.1 install executing a hoisted 2.0.0 host, which failed operations the
54
- * matching host performs correctly. optionalDependencies pin the platform
55
- * package to an exact version, so a mismatch here is never legitimate.
50
+ * Resolution starts from the installed SDK so pnpm's isolated optional
51
+ * dependency remains visible after the SDK itself is bundled. `require.resolve`
52
+ * can also fall back to NODE_PATH, so the version check prevents a host binary
53
+ * from an unrelated SDK version in a flat store from satisfying this lookup.
54
+ * optionalDependencies pin the platform package to an exact version, so a
55
+ * mismatch here is never legitimate.
56
56
  */
57
57
  function resolveFromPlatformPackage(target, binaryName) {
58
58
  const pkg = TARGET_TO_PACKAGE[target];
59
+ let sdkRequire;
60
+ try {
61
+ sdkRequire = createRequire(require.resolve('@superdoc/sdk'));
62
+ }
63
+ catch {
64
+ sdkRequire = require;
65
+ }
59
66
  let binaryPath;
60
67
  try {
61
- binaryPath = require.resolve(`${pkg}/bin/${binaryName}`);
68
+ binaryPath = sdkRequire.resolve(`${pkg}/bin/${binaryName}`);
62
69
  }
63
70
  catch {
64
71
  return null;
65
72
  }
66
73
  try {
67
- const platformVersion = require(require.resolve(`${pkg}/package.json`)).version;
68
- const ownVersion = require(fileURLToPath(new URL('../../package.json', import.meta.url))).version;
69
- if (platformVersion !== ownVersion)
74
+ const platformVersion = sdkRequire(sdkRequire.resolve(`${pkg}/package.json`)).version;
75
+ if (platformVersion !== SDK_VERSION)
70
76
  return null;
71
77
  }
72
78
  catch {
73
- // Either manifest being unreadable is itself disqualifying: an unverifiable
79
+ // An unreadable platform manifest is itself disqualifying: an unverifiable
74
80
  // host is exactly the case this guard exists to reject.
75
81
  return null;
76
82
  }