@cueai/omni-reader-mcp 1.5.1 → 1.5.3

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
@@ -64,7 +64,7 @@ differ from the numbers above, report the live values.
64
64
  Always use an audited exact version, never an implicit `latest`:
65
65
 
66
66
  ```sh
67
- npx -y @cueai/omni-reader-mcp@1.5.1 setup
67
+ npx -y @cueai/omni-reader-mcp@1.5.3 setup
68
68
  ```
69
69
 
70
70
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -72,9 +72,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
72
72
  logic:
73
73
 
74
74
  ```sh
75
- npx -y @cueai/omni-reader-mcp@1.5.1 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
76
- npx -y @cueai/omni-reader-mcp@1.5.1 setup --client cursor --add-root /absolute/minimum/root --yes --json
77
- npx -y @cueai/omni-reader-mcp@1.5.1 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
75
+ npx -y @cueai/omni-reader-mcp@1.5.3 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
76
+ npx -y @cueai/omni-reader-mcp@1.5.3 setup --client cursor --add-root /absolute/minimum/root --yes --json
77
+ npx -y @cueai/omni-reader-mcp@1.5.3 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
78
78
  ```
79
79
 
80
80
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -82,9 +82,39 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
82
82
  stdin is never read:
83
83
 
84
84
  ```sh
85
- npx -y @cueai/omni-reader-mcp@1.5.1 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
85
+ npx -y @cueai/omni-reader-mcp@1.5.3 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
86
86
  ```
87
87
 
88
+ ## Windows configuration
89
+
90
+ On Windows, `npx` on the command line is actually `npx.cmd` — a batch-file wrapper, not
91
+ an executable. An MCP client that launches the server with a bare `npx` command and no
92
+ shell therefore fails immediately with `MCP error -32000: Connection closed` before the
93
+ server even starts. This is a launcher-shape problem, not an Omni problem: the same
94
+ entry works unchanged on macOS and Linux.
95
+
96
+ If the agent reports `Connection closed` right after connecting on Windows, use one of
97
+ the runnable forms below. From 1.5.2 on, `setup` writes the first form automatically on
98
+ Windows and migrates an entry that 1.5.1's `setup` wrote (the bare `npx` shape) to it
99
+ automatically; the second is the most robust when you configure by hand.
100
+
101
+ 1. **`cmd` + `npx` (what `setup` writes on Windows)** — `cmd.exe` is a real executable,
102
+ so a client's no-shell spawn works:
103
+ ```json
104
+ { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.3"] }
105
+ ```
106
+ 2. **`node` + the package entry (most robust)** — skips `npx` entirely; the absolute
107
+ path must be a stable install you control:
108
+ ```json
109
+ { "command": "node", "args": ["C:\\path\\to\\node_modules\\@cueai\\omni-reader-mcp\\dist\\index.js"] }
110
+ ```
111
+ A deliberate local `npm install @cueai/omni-reader-mcp@1.5.3` gives you that stable
112
+ path. Do not point the entry at an `_npx` cache directory — its hash subdirectory
113
+ changes between installs.
114
+ 3. **Never use a session/timestamp directory** — such paths are cleaned up, and the
115
+ connection silently breaks at the next session. Re-run `setup` or install into a
116
+ stable directory instead.
117
+
88
118
  ## Cache and journal isolation
89
119
 
90
120
  The local parse cache and operation journal default to the user-level cache directory
@@ -221,30 +251,54 @@ satisfies the constraints.
221
251
  ## Commands
222
252
 
223
253
  ```sh
224
- npx -y @cueai/omni-reader-mcp@1.5.1 doctor
225
- npx -y @cueai/omni-reader-mcp@1.5.1 doctor --json
226
- npx -y @cueai/omni-reader-mcp@1.5.1 clean
227
- npx -y @cueai/omni-reader-mcp@1.5.1 uninstall --yes --json
254
+ npx -y @cueai/omni-reader-mcp@1.5.3 doctor
255
+ npx -y @cueai/omni-reader-mcp@1.5.3 doctor --json
256
+ npx -y @cueai/omni-reader-mcp@1.5.3 clean
257
+ npx -y @cueai/omni-reader-mcp@1.5.3 uninstall --yes --json
228
258
  ```
229
259
 
230
260
  Running the pinned version without a command starts the stdio MCP server:
231
261
 
232
262
  ```sh
233
- npx -y @cueai/omni-reader-mcp@1.5.1
263
+ npx -y @cueai/omni-reader-mcp@1.5.3
234
264
  ```
235
265
 
236
266
  `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
237
267
  safety status, endpoint compatibility, artifacts, cache mode, onboarding, and reload
238
268
  status; it never prints the Key, private source paths, or content.
239
269
 
270
+ ## Network diagnostics
271
+
272
+ Run `npx -y @cueai/omni-reader-mcp@1.5.3 doctor --json` first. Diagnose failures by
273
+ stage instead of guessing an endpoint:
274
+
275
+ | Code | Stage | Meaning |
276
+ |---|---|---|
277
+ | `CUBE_UNAVAILABLE` | before upload | the control plane could not create a parse grant |
278
+ | `PRINCIPAL_CONCURRENCY_LIMIT` | before upload | the principal already has the maximum active parses; wait for `retry_after`, then retry the same parse |
279
+ | `IIIS_UNAVAILABLE` | after grant creation | the granted upload stage could not complete |
280
+ | `CUBE_PROTOCOL_ERROR` | control-plane response | the response did not match the required contract |
281
+ | `MCP error -32000: Connection closed` | before Bridge starts | the client launcher shape is invalid; see Windows configuration |
282
+
283
+ Use only the compatibility and endpoint facts reported by `doctor`. Do not guess or
284
+ publish service hosts or ports, hard-code IP addresses, modify hosts files, or probe an
285
+ endpoint copied from logs. If an enterprise network policy blocks a required connection,
286
+ provide the redacted `doctor --json` result to support for the applicable egress
287
+ requirements.
288
+
289
+ If the secure parse environment is reachable but a specific public URL fails to parse
290
+ (`parse_timeout` / parse failed) while it works in a browser, that is a fetch-layer
291
+ condition of the source — not a Bridge configuration problem — and it affects the URL
292
+ the same way from every client.
293
+
240
294
  ## Uninstall and rollback
241
295
 
242
- `uninstall --yes --json` removes only a trusted 1.5.0 or 1.5.1 Bridge entry; when a
243
- matching trusted backup exists, it restores the original URL-only `omni-reader` entry.
244
- Uninstall never deletes user source files and never silently removes unexpired local
245
- results.
296
+ `uninstall --yes --json` removes only a trusted 1.5.2 or 1.5.3 Bridge entry, plus
297
+ the exact broken bare-`npx` Windows entry written by 1.5.1. When a matching trusted
298
+ backup exists, it restores the original URL-only `omni-reader` entry. Uninstall never
299
+ deletes user source files and never silently removes unexpired local results.
246
300
 
247
- To roll back from 1.5.1:
301
+ To roll back from 1.5.3:
248
302
 
249
303
  1. stop recommending or installing that version;
250
304
  2. run `uninstall --yes --json` to restore the trusted URL-only entry;
@@ -41,6 +41,7 @@ export declare function agentConfigPath(target: AgentTarget, environment: AgentC
41
41
  export declare function agentBackupPath(configPath: string): string;
42
42
  export declare function displayConfigPath(configPath: string, environment: AgentConfigEnvironment): string;
43
43
  export declare function buildAgentEntry(target: AgentTarget, extraRoots: readonly string[], platform: NodeJS.Platform): Record<string, unknown>;
44
+ export declare function expectedBridgeVersion(target: AgentTarget, value: unknown, platform: NodeJS.Platform): string | undefined;
44
45
  export declare function prepareAgentConfig(target: AgentTarget, extraRoots: readonly string[], environment: AgentConfigEnvironment): Promise<PreparedAgentConfig>;
45
46
  export declare function verifyPreparedAgentConfig(prepared: PreparedAgentConfig): Promise<void>;
46
47
  export declare function writePreparedAgentConfig(prepared: PreparedAgentConfig): Promise<void>;
@@ -4,11 +4,16 @@ import { chmod, lstat, mkdir, open, realpath, rename, unlink, } from "node:fs/pr
4
4
  import path from "node:path";
5
5
  import { BRIDGE_RELEASE_VERSION, REMOTE_OMNI_MCP_URL, } from "../constants.js";
6
6
  const PACKAGE_SPEC = `@cueai/omni-reader-mcp@${BRIDGE_RELEASE_VERSION}`;
7
- // The release that the current version is a trusted upgrade from: uninstall and
8
- // rollback recognize exactly {previous, current} and nothing else. Bump this to
9
- // the version we just published when BRIDGE_RELEASE_VERSION advances.
10
- const PREVIOUS_RELEASE_VERSION = "1.5.0";
7
+ // The release that the current version is a trusted upgrade from: normal upgrade,
8
+ // uninstall, and rollback recognize exactly {previous, current}. Bump this to the
9
+ // version we just published when BRIDGE_RELEASE_VERSION advances.
10
+ const PREVIOUS_RELEASE_VERSION = "1.5.2";
11
11
  const PREVIOUS_PACKAGE_SPEC = `@cueai/omni-reader-mcp@${PREVIOUS_RELEASE_VERSION}`;
12
+ // Preserve the one evidenced two-release migration exception: 1.5.1 setup wrote a
13
+ // bare `npx` launcher on Windows, so later setup/uninstall versions must still be able
14
+ // to identify and replace that otherwise-unrunnable entry. 1.5.2 already writes `cmd`.
15
+ const WINDOWS_BARE_NPX_MIGRATION_VERSION = "1.5.1";
16
+ const WINDOWS_BARE_NPX_MIGRATION_SPEC = `@cueai/omni-reader-mcp@${WINDOWS_BARE_NPX_MIGRATION_VERSION}`;
12
17
  const LEGACY_PACKAGE_SPEC = "@cueai/omni-reader-mcp";
13
18
  const TRUSTED_EXACT_PACKAGE_SPECS = new Set([PREVIOUS_PACKAGE_SPEC, PACKAGE_SPEC]);
14
19
  function isRecord(value) {
@@ -116,10 +121,14 @@ export function displayConfigPath(configPath, environment) {
116
121
  return configPath;
117
122
  }
118
123
  export function buildAgentEntry(target, extraRoots, platform) {
119
- const entry = {
120
- command: "npx",
121
- args: ["-y", PACKAGE_SPEC],
122
- };
124
+ // On Windows `npx` is `npx.cmd` — a batch wrapper a client cannot spawn without a
125
+ // shell (spawn("npx") → ENOENT, spawn("npx.cmd") → EINVAL). `cmd /d /c npx ...` is a
126
+ // real executable, so a no-shell spawn works (`/d` skips the AutoRun registry command,
127
+ // whose stdout would corrupt the MCP stdio frames); macOS/Linux keep the bare `npx`
128
+ // form.
129
+ const entry = platform === "win32"
130
+ ? { command: "cmd", args: ["/d", "/c", "npx", "-y", PACKAGE_SPEC] }
131
+ : { command: "npx", args: ["-y", PACKAGE_SPEC] };
123
132
  const env = {};
124
133
  if (target === "hermes")
125
134
  env.CUE_API_KEY = "${CUE_API_KEY}";
@@ -130,6 +139,48 @@ export function buildAgentEntry(target, extraRoots, platform) {
130
139
  entry.env = env;
131
140
  return entry;
132
141
  }
142
+ /** The exact package spec at the invocation's spec position, given its platform shape. */
143
+ function entryPackageSpec(entry, platform) {
144
+ const args = entry.args;
145
+ if (!Array.isArray(args))
146
+ return undefined;
147
+ if (platform !== "win32")
148
+ return args[1];
149
+ return entry.command === "npx" ? args[1] : args[4];
150
+ }
151
+ /**
152
+ * Matches the trusted invocation shape for the platform: `cmd /d /c npx -y <spec>` on
153
+ * Windows (runnable by a no-shell spawn; `/d` skips the AutoRun command whose stdout
154
+ * would corrupt MCP stdio frames), `npx -y <spec>` everywhere else. Windows also keeps
155
+ * the exact 1.5.1 bare-`npx` migration source: that release wrote an unrunnable entry,
156
+ * while 1.5.2 and later write `cmd` and therefore need no widening exception.
157
+ */
158
+ function entryInvocationMatches(entry, platform) {
159
+ const args = entry.args;
160
+ if (platform === "win32") {
161
+ const cmdForm = entry.command === "cmd"
162
+ && Array.isArray(args)
163
+ && args.length === 5
164
+ && args[0] === "/d"
165
+ && args[1] === "/c"
166
+ && args[2] === "npx"
167
+ && args[3] === "-y"
168
+ && typeof args[4] === "string"
169
+ && TRUSTED_EXACT_PACKAGE_SPECS.has(args[4]);
170
+ const migrationNpxForm = entry.command === "npx"
171
+ && Array.isArray(args)
172
+ && args.length === 2
173
+ && args[0] === "-y"
174
+ && args[1] === WINDOWS_BARE_NPX_MIGRATION_SPEC;
175
+ return cmdForm || migrationNpxForm;
176
+ }
177
+ return entry.command === "npx"
178
+ && Array.isArray(args)
179
+ && args.length === 2
180
+ && args[0] === "-y"
181
+ && typeof args[1] === "string"
182
+ && TRUSTED_EXACT_PACKAGE_SPECS.has(args[1]);
183
+ }
133
184
  function configFormat(target) {
134
185
  return target === "hermes" ? "hermes-yaml" : "json";
135
186
  }
@@ -500,7 +551,7 @@ function yamlEntry(entry, newline) {
500
551
  const args = entry.args;
501
552
  const lines = [
502
553
  " omni-reader:",
503
- " command: npx",
554
+ ` command: ${entry.command}`,
504
555
  " args:",
505
556
  ...args.map((argument) => ` - ${JSON.stringify(argument)}`),
506
557
  ];
@@ -541,16 +592,8 @@ function entryDigest(entry) {
541
592
  };
542
593
  return `sha256:${createHash("sha256").update(JSON.stringify(identity), "utf8").digest("hex")}`;
543
594
  }
544
- function isExpectedOmniEntry(target, value) {
545
- if (!isRecord(value))
546
- return false;
547
- const args = value.args;
548
- if (value.command !== "npx" ||
549
- !Array.isArray(args) ||
550
- args.length !== 2 ||
551
- args[0] !== "-y" ||
552
- typeof args[1] !== "string" ||
553
- !TRUSTED_EXACT_PACKAGE_SPECS.has(args[1]))
595
+ function isExpectedOmniEntry(target, value, platform) {
596
+ if (!isRecord(value) || !entryInvocationMatches(value, platform))
554
597
  return false;
555
598
  const keys = Object.keys(value).sort();
556
599
  if (value.env === undefined) {
@@ -586,21 +629,32 @@ function isExpectedOmniEntry(target, value) {
586
629
  && keys[1] === "command"
587
630
  && keys[2] === "env";
588
631
  }
589
- function expectedBridgeVersion(target, value) {
590
- if (!isExpectedOmniEntry(target, value))
632
+ export function expectedBridgeVersion(target, value, platform) {
633
+ if (!isExpectedOmniEntry(target, value, platform))
591
634
  return undefined;
592
- const packageSpec = value.args[1];
593
- return packageSpec === PREVIOUS_PACKAGE_SPEC ? PREVIOUS_RELEASE_VERSION : BRIDGE_RELEASE_VERSION;
594
- }
595
- function isLegacyBridgeEntry(target, value) {
596
- if (!isRecord(value) || value.command !== "npx")
635
+ const packageSpec = entryPackageSpec(value, platform);
636
+ if (packageSpec === PREVIOUS_PACKAGE_SPEC)
637
+ return PREVIOUS_RELEASE_VERSION;
638
+ if (packageSpec === WINDOWS_BARE_NPX_MIGRATION_SPEC) {
639
+ return WINDOWS_BARE_NPX_MIGRATION_VERSION;
640
+ }
641
+ return BRIDGE_RELEASE_VERSION;
642
+ }
643
+ function isLegacyBridgeEntry(target, value, platform) {
644
+ // Keep the historical unpinned package form recognizable for setup migration;
645
+ // every exact-version form delegates to the single platform policy above.
646
+ const legacyUnpinnedNpx = (entry) => {
647
+ const args = entry.args;
648
+ return entry.command === "npx"
649
+ && Array.isArray(args)
650
+ && args.length === 2
651
+ && args[0] === "-y"
652
+ && args[1] === LEGACY_PACKAGE_SPEC;
653
+ };
654
+ if (!isRecord(value))
597
655
  return false;
598
- const args = value.args;
599
- if (!Array.isArray(args) ||
600
- args.length !== 2 ||
601
- args[0] !== "-y" ||
602
- typeof args[1] !== "string" ||
603
- (args[1] !== LEGACY_PACKAGE_SPEC && !TRUSTED_EXACT_PACKAGE_SPECS.has(args[1])))
656
+ const shapeOk = legacyUnpinnedNpx(value) || entryInvocationMatches(value, platform);
657
+ if (!shapeOk)
604
658
  return false;
605
659
  if (value.env === undefined)
606
660
  return target !== "hermes";
@@ -640,12 +694,12 @@ function hermesOmniEntry(document, displayPath) {
640
694
  }
641
695
  return entry;
642
696
  }
643
- function previousJsonEntry(value, displayPath) {
697
+ function previousJsonEntry(value, displayPath, platform) {
644
698
  if (value === undefined)
645
699
  return null;
646
700
  if (isCanonicalRemoteEntry(value))
647
701
  return { format: "json", value };
648
- if (isLegacyBridgeEntry("cursor", value))
702
+ if (isLegacyBridgeEntry("cursor", value, platform))
649
703
  return null;
650
704
  throw fileError(`The existing "omni-reader" entry in ${displayPath} is not a trusted remote or Bridge `
651
705
  + "configuration; no changes were written. Remove that entry manually, or restore a backup, "
@@ -663,7 +717,7 @@ function hermesCredentialsAreReferences(serialized) {
663
717
  }
664
718
  return true;
665
719
  }
666
- function previousHermesEntry(entry, displayPath) {
720
+ function previousHermesEntry(entry, displayPath, platform) {
667
721
  if (entry === undefined)
668
722
  return null;
669
723
  const value = parseHermesEntry(entry);
@@ -675,7 +729,7 @@ function previousHermesEntry(entry, displayPath) {
675
729
  }
676
730
  return { format: "hermes-yaml", serialized: entry.serialized };
677
731
  }
678
- if (isLegacyBridgeEntry("hermes", value))
732
+ if (isLegacyBridgeEntry("hermes", value, platform))
679
733
  return null;
680
734
  throw fileError(`The existing "omni-reader" entry in ${displayPath} is not a trusted remote or Bridge `
681
735
  + "configuration; no changes were written. Remove that entry manually, or restore a backup, "
@@ -710,7 +764,7 @@ export async function prepareAgentConfig(target, extraRoots, environment) {
710
764
  }
711
765
  const servers = existingServers ?? {};
712
766
  const existing = onlyOmniEntry(servers, displayPath);
713
- const previousEntry = previousJsonEntry(existing.value, displayPath);
767
+ const previousEntry = previousJsonEntry(existing.value, displayPath, environment.platform);
714
768
  const afterValue = {
715
769
  ...value,
716
770
  mcpServers: {
@@ -737,7 +791,7 @@ export async function prepareAgentConfig(target, extraRoots, environment) {
737
791
  }
738
792
  const document = parseHermesDocument(loaded.serialized);
739
793
  const existing = hermesOmniEntry(document, displayPath);
740
- const previousEntry = previousHermesEntry(existing, displayPath);
794
+ const previousEntry = previousHermesEntry(existing, displayPath, environment.platform);
741
795
  const serializedAfter = replaceHermesEntry(loaded.serialized, document, yamlEntry(entry, document.newline));
742
796
  return {
743
797
  target: "hermes",
@@ -964,7 +1018,7 @@ export async function writePreparedAgentConfig(prepared) {
964
1018
  const existingBackup = await readTrustedBackup(configPath);
965
1019
  let replacementBackup = backup;
966
1020
  if (existingBackup !== undefined) {
967
- const currentEntry = expectedConfigEntry(prepared.target, current);
1021
+ const currentEntry = expectedConfigEntry(prepared.target, current, prepared.environment?.platform ?? "darwin");
968
1022
  if (currentEntry !== undefined && existingBackup.target === prepared.target) {
969
1023
  if (existingBackup.bridge_entry_digest !== entryDigest(currentEntry)) {
970
1024
  throw fileError("A conflicting trusted Omni backup already exists; no changes were written.");
@@ -1011,21 +1065,21 @@ function jsonConfigEntry(value) {
1011
1065
  return undefined;
1012
1066
  return onlyOmniEntry(value.mcpServers, "the Agent configuration").value;
1013
1067
  }
1014
- function expectedConfigEntry(target, loaded) {
1068
+ function expectedConfigEntry(target, loaded, platform) {
1015
1069
  if (target === "hermes") {
1016
1070
  const document = parseHermesDocument(loaded.serialized);
1017
1071
  const entry = hermesOmniEntry(document, "the Hermes configuration");
1018
1072
  if (entry === undefined)
1019
1073
  return undefined;
1020
1074
  const value = parseHermesEntry(entry);
1021
- return isExpectedOmniEntry(target, value) ? value : undefined;
1075
+ return isExpectedOmniEntry(target, value, platform) ? value : undefined;
1022
1076
  }
1023
1077
  assertNoDuplicateJsonOmniEntries(loaded.serialized);
1024
1078
  const value = jsonConfigEntry(parseJsonConfig(loaded));
1025
- return isExpectedOmniEntry(target, value) ? value : undefined;
1079
+ return isExpectedOmniEntry(target, value, platform) ? value : undefined;
1026
1080
  }
1027
- async function configHasExpectedEntry(target, loaded) {
1028
- return expectedConfigEntry(target, loaded) !== undefined;
1081
+ async function configHasExpectedEntry(target, loaded, platform) {
1082
+ return expectedConfigEntry(target, loaded, platform) !== undefined;
1029
1083
  }
1030
1084
  export async function inspectAgentConfigDetails(configPath, environment, target) {
1031
1085
  try {
@@ -1041,7 +1095,7 @@ export async function inspectAgentConfigDetails(configPath, environment, target)
1041
1095
  if (entry === undefined)
1042
1096
  return { status: "not configured" };
1043
1097
  const value = parseHermesEntry(entry);
1044
- const version = expectedBridgeVersion("hermes", value);
1098
+ const version = expectedBridgeVersion("hermes", value, environment?.platform ?? "darwin");
1045
1099
  if (version !== undefined)
1046
1100
  return { status: "configured", version };
1047
1101
  return {
@@ -1055,7 +1109,7 @@ export async function inspectAgentConfigDetails(configPath, environment, target)
1055
1109
  const entry = onlyOmniEntry(value.mcpServers, configPath).value;
1056
1110
  if (entry === undefined)
1057
1111
  return { status: "not configured" };
1058
- const version = expectedBridgeVersion(resolvedTarget, entry);
1112
+ const version = expectedBridgeVersion(resolvedTarget, entry, environment?.platform ?? "darwin");
1059
1113
  if (version !== undefined)
1060
1114
  return { status: "configured", version };
1061
1115
  return {
@@ -1133,7 +1187,7 @@ export async function rollbackPreparedAgentConfig(prepared) {
1133
1187
  existed: true,
1134
1188
  serialized: prepared.serializedBefore,
1135
1189
  fingerprint: prepared.sourceFingerprint ?? "unused",
1136
- })
1190
+ }, prepared.environment?.platform ?? "darwin")
1137
1191
  : undefined;
1138
1192
  if (prepared.existed) {
1139
1193
  await atomicReplace(prepared.configPath, prepared.serializedBefore);
@@ -1192,11 +1246,11 @@ export async function uninstallAgentConfig(target, environment) {
1192
1246
  return undefined;
1193
1247
  await validateUserConfigPath(configPath, environment);
1194
1248
  const initial = await readConfig(configPath);
1195
- if (!initial.existed || !await configHasExpectedEntry(target, initial))
1249
+ if (!initial.existed || !await configHasExpectedEntry(target, initial, environment.platform))
1196
1250
  return undefined;
1197
1251
  return await withConfigLock(configPath, environment, async () => {
1198
1252
  const current = await readConfig(configPath);
1199
- if (!await configHasExpectedEntry(target, current)) {
1253
+ if (!await configHasExpectedEntry(target, current, environment.platform)) {
1200
1254
  throw fileError("The Agent configuration changed before uninstall; no changes were written.");
1201
1255
  }
1202
1256
  const currentEntry = target === "hermes"
@@ -6,7 +6,7 @@ export declare const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
6
6
  export declare const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
7
7
  export declare const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
8
8
  export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export declare const BRIDGE_RELEASE_VERSION = "1.5.1";
9
+ export declare const BRIDGE_RELEASE_VERSION = "1.5.3";
10
10
  export declare const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
11
11
  export declare const FOREGROUND_BUDGET_MS = 15000;
12
12
  export declare const STATUS_LONG_POLL_MAX_MS = 20000;
package/dist/constants.js CHANGED
@@ -6,7 +6,7 @@ export const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
6
6
  export const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
7
7
  export const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
8
8
  export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export const BRIDGE_RELEASE_VERSION = "1.5.1";
9
+ export const BRIDGE_RELEASE_VERSION = "1.5.3";
10
10
  export const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
11
11
  export const FOREGROUND_BUDGET_MS = 15_000;
12
12
  export const STATUS_LONG_POLL_MAX_MS = 20_000;
@@ -25,6 +25,16 @@ const grantResponseSchema = z
25
25
  protocol_version: z.enum([CUBE_GRANT_PROTOCOL_VERSION, "omni.parse_grant.v2"]),
26
26
  })
27
27
  .strict();
28
+ const principalConcurrencyErrorSchema = z
29
+ .object({
30
+ code: z.literal("PRINCIPAL_CONCURRENCY_LIMIT"),
31
+ message: z.literal("The maximum number of concurrent parsing operations is already active."),
32
+ file_uploaded: z.literal(false),
33
+ billed: z.literal(false),
34
+ content_released: z.literal(false),
35
+ retryable: z.literal(true),
36
+ })
37
+ .strict();
28
38
  // Closed v3 grant response: the exact tuple of the selected direct profile is
29
39
  // required before the upload phase may start. The tuple is re-checked against
30
40
  // the profile after schema parsing so a drifting server cannot slip through.
@@ -47,7 +57,7 @@ const grantResponseV3Schema = z
47
57
  approved_result_max_bytes: z.literal(67108864),
48
58
  })
49
59
  .strict();
50
- function bridgeError(code, message, retryable) {
60
+ function bridgeError(code, message, retryable, options = {}) {
51
61
  return new OmniBridgeError({
52
62
  code,
53
63
  message,
@@ -57,6 +67,9 @@ function bridgeError(code, message, retryable) {
57
67
  billed: false,
58
68
  contentReleased: false,
59
69
  retryable,
70
+ ...(options.failureScope === undefined ? {} : { failureScope: options.failureScope }),
71
+ ...(options.userAction === undefined ? {} : { userAction: options.userAction }),
72
+ ...(options.retryAfter === undefined ? {} : { retryAfter: options.retryAfter }),
60
73
  });
61
74
  }
62
75
  function grantRequestBody(input, profile) {
@@ -137,6 +150,33 @@ function responseError(status) {
137
150
  }
138
151
  return bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", status >= 500);
139
152
  }
153
+ function retryAfterSeconds(response) {
154
+ const value = response.headers.get("retry-after");
155
+ if (value === null || !/^\d+$/u.test(value))
156
+ return undefined;
157
+ const seconds = Number(value);
158
+ return Number.isSafeInteger(seconds) && seconds > 0 ? seconds : undefined;
159
+ }
160
+ async function grantResponseError(response) {
161
+ const retryAfter = retryAfterSeconds(response);
162
+ if (response.status === 429 && retryAfter !== undefined) {
163
+ let body;
164
+ try {
165
+ body = await response.json();
166
+ }
167
+ catch {
168
+ return responseError(response.status);
169
+ }
170
+ if (principalConcurrencyErrorSchema.safeParse(body).success) {
171
+ return bridgeError("PRINCIPAL_CONCURRENCY_LIMIT", "The maximum number of concurrent parsing operations is already active.", true, {
172
+ failureScope: "service",
173
+ userAction: "Wait for retry_after seconds, then retry the same local-file parse.",
174
+ retryAfter,
175
+ });
176
+ }
177
+ }
178
+ return responseError(response.status);
179
+ }
140
180
  export class CubeGrantClient {
141
181
  #journal;
142
182
  #apiKey;
@@ -256,7 +296,7 @@ export class CubeGrantClient {
256
296
  throw bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", true);
257
297
  }
258
298
  if (!response.ok) {
259
- throw responseError(response.status);
299
+ throw await grantResponseError(response);
260
300
  }
261
301
  // The v3 response tuple is required before this promise resolves: the
262
302
  // upload phase can only start after the negotiated representation is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueai/omni-reader-mcp",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Local stdio MCP bridge for direct Omni document parsing",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",