@cueai/omni-reader-mcp 1.5.2 → 1.5.4

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
@@ -43,6 +43,14 @@ Bridge reads only these product configuration variables at runtime:
43
43
  authorizes; colon-separated on macOS/Linux, semicolon-separated on Windows. The current
44
44
  agent workspace remains the default allowed scope.
45
45
 
46
+ ## How parsing works
47
+
48
+ For a local file, Bridge requests a Cube parse grant and uses the returned controlled
49
+ granted-data-plane URL to upload the file and stream the result. A `BRIDGE_UPGRADE_REQUIRED`
50
+ error means the running Bridge release is not accepted for direct local-file parsing —
51
+ install `@cueai/omni-reader-mcp@1.5.4` using the official setup instructions, then retry the
52
+ same parse. Always run `doctor --json` first when diagnosing a parse failure.
53
+
46
54
  ## Free credits
47
55
 
48
56
  As of 2026-08-14:
@@ -64,7 +72,7 @@ differ from the numbers above, report the live values.
64
72
  Always use an audited exact version, never an implicit `latest`:
65
73
 
66
74
  ```sh
67
- npx -y @cueai/omni-reader-mcp@1.5.2 setup
75
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup
68
76
  ```
69
77
 
70
78
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -72,9 +80,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
72
80
  logic:
73
81
 
74
82
  ```sh
75
- npx -y @cueai/omni-reader-mcp@1.5.2 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
76
- npx -y @cueai/omni-reader-mcp@1.5.2 setup --client cursor --add-root /absolute/minimum/root --yes --json
77
- npx -y @cueai/omni-reader-mcp@1.5.2 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
83
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
84
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --add-root /absolute/minimum/root --yes --json
85
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
78
86
  ```
79
87
 
80
88
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -82,7 +90,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
82
90
  stdin is never read:
83
91
 
84
92
  ```sh
85
- npx -y @cueai/omni-reader-mcp@1.5.2 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
93
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
86
94
  ```
87
95
 
88
96
  ## Windows configuration
@@ -101,14 +109,14 @@ automatically; the second is the most robust when you configure by hand.
101
109
  1. **`cmd` + `npx` (what `setup` writes on Windows)** — `cmd.exe` is a real executable,
102
110
  so a client's no-shell spawn works:
103
111
  ```json
104
- { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.2"] }
112
+ { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.4"] }
105
113
  ```
106
114
  2. **`node` + the package entry (most robust)** — skips `npx` entirely; the absolute
107
115
  path must be a stable install you control:
108
116
  ```json
109
117
  { "command": "node", "args": ["C:\\path\\to\\node_modules\\@cueai\\omni-reader-mcp\\dist\\index.js"] }
110
118
  ```
111
- A deliberate local `npm install @cueai/omni-reader-mcp@1.5.2` gives you that stable
119
+ A deliberate local `npm install @cueai/omni-reader-mcp@1.5.4` gives you that stable
112
120
  path. Do not point the entry at an `_npx` cache directory — its hash subdirectory
113
121
  changes between installs.
114
122
  3. **Never use a session/timestamp directory** — such paths are cleaned up, and the
@@ -136,7 +144,9 @@ project-overlap check; mutual containment with a project directory remains forbi
136
144
  1. show the user-scope configuration target and changes;
137
145
  2. write a single, exact-version `omni-reader` facade;
138
146
  3. use a private lock, trusted backup, and atomic replacement;
139
- 4. verify the Omni secure parse environment;
147
+ 4. verify the Cue API Key, Cube control reachability, and client configuration
148
+ (setup does not claim to verify the granted data plane or the ability to parse —
149
+ only a real local-file parse proves that);
140
150
  5. restore the previous configuration when verification fails;
141
151
  6. prompt the corresponding agent to reload or restart.
142
152
 
@@ -251,47 +261,60 @@ satisfies the constraints.
251
261
  ## Commands
252
262
 
253
263
  ```sh
254
- npx -y @cueai/omni-reader-mcp@1.5.2 doctor
255
- npx -y @cueai/omni-reader-mcp@1.5.2 doctor --json
256
- npx -y @cueai/omni-reader-mcp@1.5.2 clean
257
- npx -y @cueai/omni-reader-mcp@1.5.2 uninstall --yes --json
264
+ npx -y @cueai/omni-reader-mcp@1.5.4 doctor
265
+ npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json
266
+ npx -y @cueai/omni-reader-mcp@1.5.4 clean
267
+ npx -y @cueai/omni-reader-mcp@1.5.4 uninstall --yes --json
258
268
  ```
259
269
 
260
270
  Running the pinned version without a command starts the stdio MCP server:
261
271
 
262
272
  ```sh
263
- npx -y @cueai/omni-reader-mcp@1.5.2
273
+ npx -y @cueai/omni-reader-mcp@1.5.4
264
274
  ```
265
275
 
266
276
  `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
267
- safety status, endpoint compatibility, artifacts, cache mode, onboarding, and reload
268
- status; it never prints the Key, private source paths, or content.
277
+ safety status, the authenticated Cube control/configuration facts, artifacts, cache
278
+ mode, onboarding, and reload status; it never prints the Key, private source paths,
279
+ or content. The `endpoints.direct_upload` field is a fact about the release, not a
280
+ probe: the granted data plane is reached only through a Cube grant and is never
281
+ definitively "available" until a real local-file parse validates the route.
282
+
269
283
 
270
284
  ## Network diagnostics
271
285
 
272
- Bridge connects to the Omni parse API at `https://cubefile.ai.iiis.co:9443`. When a
273
- connection fails, run `doctor` first — its endpoint-compatibility check covers the
274
- API reachability from your machine. The common symptoms:
286
+ Run `npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json` first. Diagnose failures by
287
+ stage instead of guessing an endpoint:
275
288
 
276
- | Symptom | Most likely cause | Check |
289
+ | Code | Stage | Meaning |
277
290
  |---|---|---|
278
- | `MCP error -32000: Connection closed` on Windows | launcher shape (see Windows configuration) | `doctor` package/client rows |
279
- | `CUBE_PROTOCOL_ERROR` or connection refused/timeout on connect | the hostname resolves to a private address on your network, or the route cannot reach the public endpoint | `nslookup cubefile.ai.iiis.co` the public record is `122.200.68.28` |
280
- | Works in the browser / on another machine but not this one | a local hosts-file override hides a DNS or route problem | check `/etc/hosts` (or `C:\Windows\System32\drivers\etc\hosts`) for a `cubefile.ai.iiis.co` line; test `curl --noproxy '*' -w '%{remote_ip}\n' https://cubefile.ai.iiis.co:9443/omni/health` |
281
-
282
- If the API is reachable but a specific public URL fails to parse (`parse_timeout` /
283
- parse failed) while it works in your browser, that is a fetch-layer condition of the
284
- source on the parse API side — not a Bridge configuration problem — and it affects the
285
- URL the same way from every client.
291
+ | `CUBE_UNAVAILABLE` | before upload | the control plane could not create a parse grant |
292
+ | `PRINCIPAL_CONCURRENCY_LIMIT` | before upload | the principal already has the maximum active parses; wait for `retry_after`, then retry the same parse |
293
+ | `BRIDGE_UPGRADE_REQUIRED` | before upload | this Bridge release is too old; install `@cueai/omni-reader-mcp@1.5.4` via the official setup, then retry the same parse |
294
+ | `IIIS_UNAVAILABLE` | after grant creation | the granted upload stage could not complete |
295
+ | `CUBE_PROTOCOL_ERROR` | control-plane response | the response did not match the required contract |
296
+ | `MCP error -32000: Connection closed` | before Bridge starts | the client launcher shape is invalid; see Windows configuration |
297
+
298
+ Use only the control-plane and configuration facts reported by `doctor`. Do not guess
299
+ or publish service hosts or ports, hard-code IP addresses, modify hosts files, or probe
300
+ an endpoint copied from logs. `doctor` does not probe the granted data plane: only a
301
+ real local-file parse proves it end-to-end. If an enterprise network policy blocks a
302
+ required connection, provide the redacted `doctor --json` result to support for the
303
+ applicable egress requirements.
304
+
305
+ If the secure parse environment is reachable but a specific public URL fails to parse
306
+ (`parse_timeout` / parse failed) while it works in a browser, that is a fetch-layer
307
+ condition of the source — not a Bridge configuration problem — and it affects the URL
308
+ the same way from every client.
286
309
 
287
310
  ## Uninstall and rollback
288
311
 
289
- `uninstall --yes --json` removes only a trusted 1.5.1 or 1.5.2 Bridge entry; when a
290
- matching trusted backup exists, it restores the original URL-only `omni-reader` entry.
291
- Uninstall never deletes user source files and never silently removes unexpired local
292
- results.
312
+ `uninstall --yes --json` removes only a trusted 1.5.3 or 1.5.4 Bridge entry, plus
313
+ the exact broken bare-`npx` Windows entry written by 1.5.1. When a matching trusted
314
+ backup exists, it restores the original URL-only `omni-reader` entry. Uninstall never
315
+ deletes user source files and never silently removes unexpired local results.
293
316
 
294
- To roll back from 1.5.2:
317
+ To roll back from 1.5.4:
295
318
 
296
319
  1. stop recommending or installing that version;
297
320
  2. run `uninstall --yes --json` to restore the trusted URL-only entry;
@@ -4,12 +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.1";
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.3";
11
11
  const PREVIOUS_PACKAGE_SPEC = `@cueai/omni-reader-mcp@${PREVIOUS_RELEASE_VERSION}`;
12
- const LEGACY_PACKAGE_SPEC = "@cueai/omni-reader-mcp";
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}`;
13
17
  const TRUSTED_EXACT_PACKAGE_SPECS = new Set([PREVIOUS_PACKAGE_SPEC, PACKAGE_SPEC]);
14
18
  function isRecord(value) {
15
19
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -146,10 +150,9 @@ function entryPackageSpec(entry, platform) {
146
150
  /**
147
151
  * Matches the trusted invocation shape for the platform: `cmd /d /c npx -y <spec>` on
148
152
  * Windows (runnable by a no-shell spawn; `/d` skips the AutoRun command whose stdout
149
- * would corrupt MCP stdio frames), `npx -y <spec>` everywhere else. On Windows the
150
- * previous release's bare-`npx` shape is additionally recognized as the migration
151
- * source 1.5.1's setup wrote it there and it can never run, but upgrade and
152
- * uninstall must still identify it as a trusted entry so 1.5.2 can replace it.
153
+ * would corrupt MCP stdio frames), `npx -y <spec>` everywhere else. Windows also keeps
154
+ * the exact 1.5.1 bare-`npx` migration source: that release wrote an unrunnable entry,
155
+ * while 1.5.2 and later write `cmd` and therefore need no widening exception.
153
156
  */
154
157
  function entryInvocationMatches(entry, platform) {
155
158
  const args = entry.args;
@@ -163,12 +166,12 @@ function entryInvocationMatches(entry, platform) {
163
166
  && args[3] === "-y"
164
167
  && typeof args[4] === "string"
165
168
  && TRUSTED_EXACT_PACKAGE_SPECS.has(args[4]);
166
- const previousNpxForm = entry.command === "npx"
169
+ const migrationNpxForm = entry.command === "npx"
167
170
  && Array.isArray(args)
168
171
  && args.length === 2
169
172
  && args[0] === "-y"
170
- && args[1] === PREVIOUS_PACKAGE_SPEC;
171
- return cmdForm || previousNpxForm;
173
+ && args[1] === WINDOWS_BARE_NPX_MIGRATION_SPEC;
174
+ return cmdForm || migrationNpxForm;
172
175
  }
173
176
  return entry.command === "npx"
174
177
  && Array.isArray(args)
@@ -629,26 +632,21 @@ export function expectedBridgeVersion(target, value, platform) {
629
632
  if (!isExpectedOmniEntry(target, value, platform))
630
633
  return undefined;
631
634
  const packageSpec = entryPackageSpec(value, platform);
632
- return packageSpec === PREVIOUS_PACKAGE_SPEC ? PREVIOUS_RELEASE_VERSION : BRIDGE_RELEASE_VERSION;
635
+ if (packageSpec === PREVIOUS_PACKAGE_SPEC)
636
+ return PREVIOUS_RELEASE_VERSION;
637
+ if (packageSpec === WINDOWS_BARE_NPX_MIGRATION_SPEC) {
638
+ return WINDOWS_BARE_NPX_MIGRATION_VERSION;
639
+ }
640
+ return BRIDGE_RELEASE_VERSION;
633
641
  }
634
642
  function isLegacyBridgeEntry(target, value, platform) {
635
- // The legacy npx form (what every pre-1.5.2 setup wrote, incl. on Windows) plus, on
636
- // Windows, the current cmd form both are recognizable Bridge entries.
637
- const legacyNpx = (entry) => {
638
- const args = entry.args;
639
- return entry.command === "npx"
640
- && Array.isArray(args)
641
- && args.length === 2
642
- && args[0] === "-y"
643
- && typeof args[1] === "string"
644
- && (args[1] === LEGACY_PACKAGE_SPEC || TRUSTED_EXACT_PACKAGE_SPECS.has(args[1]));
645
- };
646
- if (!isRecord(value))
647
- return false;
648
- const shapeOk = platform === "win32"
649
- ? (legacyNpx(value) || entryInvocationMatches(value, platform))
650
- : legacyNpx(value);
651
- if (!shapeOk)
643
+ // Defer the entire invocation-shape decision to the single platform policy:
644
+ // exact pinned {1.5.3 previous, 1.5.4 current} plus the exact Windows 1.5.1
645
+ // bare-npx migration source. The historical platform-agnostic acceptance of a
646
+ // bare unpinned `npx -y @cueai/omni-reader-mcp` is removed: it widened the
647
+ // frozen trust set to a floating latest on every OS, which expectedBridgeVersion
648
+ // already rejects but the predecessor gate still admitted.
649
+ if (!isRecord(value) || !entryInvocationMatches(value, platform))
652
650
  return false;
653
651
  if (value.env === undefined)
654
652
  return target !== "hermes";
@@ -19,7 +19,7 @@ export declare function fetchLatestPublishedVersion(fetchImpl: typeof fetch, tim
19
19
  export declare function versionCheckFacts(installed: string, latest: string | undefined): VersionCheck;
20
20
  export interface HealthResult {
21
21
  readonly cubeProtocol: string;
22
- readonly iiisProtocol: string;
22
+ readonly relayStatus: "reporting-only";
23
23
  }
24
24
  export interface DoctorOptions extends AgentConfigEnvironment {
25
25
  readonly env: NodeJS.ProcessEnv;
@@ -70,7 +70,6 @@ export interface DoctorReport {
70
70
  }
71
71
  type AgentConfigStatusForReport = "configured" | "not configured" | "invalid or unreadable";
72
72
  export declare function checkCubeHealth(fetchImpl: typeof fetch, apiKey: string): Promise<string>;
73
- export declare function checkIiisHealth(fetchImpl: typeof fetch): Promise<string>;
74
73
  export declare function checkHealth(fetchImpl: typeof fetch, apiKey: string): Promise<HealthResult>;
75
74
  export declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
76
75
  export declare function renderDoctor(report: DoctorReport): string[];
@@ -1,12 +1,18 @@
1
1
  import { constants as fsConstants } from "node:fs";
2
2
  import { lstat, open, readdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { DEFAULT_CUBE_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
4
+ import { DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
5
5
  import { API_KEY_URL, getOnboardingPolicyWithTimeout, onboardingGuidance, } from "../onboarding-policy.js";
6
6
  import { agentConfigPath, inspectAgentConfigDetails, } from "./agent-config.js";
7
7
  const CUBE_HEALTH_PATH = "/api/omni-reader/direct-upload/v1/health";
8
- const GRANTED_UPLOAD_HEALTH_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/health";
9
8
  const NPM_LATEST_URL = "https://registry.npmjs.org/@cueai/omni-reader-mcp/latest";
9
+ // Local-file parsing reaches the data plane only through the controlled origin
10
+ // returned by a Cube grant. There is no public relay /health endpoint to probe and
11
+ // no compatibility claim to make from a retired direct path, so doctor reports a
12
+ // static, zero-side-effect fact. It deliberately does NOT assert "available": the
13
+ // granted data-plane route is validated only by the first real local-file parse,
14
+ // so the status is explicitly "not probed" until then.
15
+ const RELAY_DATA_PLANE_STATUS = "not probed (validated on the first real local-file parse)";
10
16
  const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
11
17
  // Numeric semver comparison, not string equality: "1.3.9" < "1.3.10" lexically reverses
12
18
  // (string comparison would see "1.3.9" > "1.3.10"), and a local/dev build can legitimately
@@ -95,19 +101,14 @@ export async function checkCubeHealth(fetchImpl, apiKey) {
95
101
  }
96
102
  return cube.protocol_version;
97
103
  }
98
- export async function checkIiisHealth(fetchImpl) {
99
- const granted = await fetchHealth(fetchImpl, GRANTED_UPLOAD_HEALTH_URL);
100
- if (!granted.enabled || granted.protocol_version !== GRANTED_STREAM_PROTOCOL_VERSION) {
101
- throw new Error("Omni direct upload is disabled or incompatible");
102
- }
103
- return granted.protocol_version;
104
- }
105
104
  export async function checkHealth(fetchImpl, apiKey) {
106
- const [cubeProtocol, iiisProtocol] = await Promise.all([
107
- checkCubeHealth(fetchImpl, apiKey),
108
- checkIiisHealth(fetchImpl),
109
- ]);
110
- return { cubeProtocol, iiisProtocol };
105
+ const cubeProtocol = await checkCubeHealth(fetchImpl, apiKey);
106
+ // Local-file parsing reaches the granted data-plane only through the controlled
107
+ // origin returned by a Cube grant, so there is no separate endpoint to probe and
108
+ // no compatibility claim to make from the retired direct path. Doctor reports a
109
+ // static, zero-side-effect fact that is explicitly NOT "available": the route is
110
+ // validated by the first real local-file parse.
111
+ return { cubeProtocol, relayStatus: "reporting-only" };
111
112
  }
112
113
  async function artifactExpiry(metadataPath, resultId) {
113
114
  let handle;
@@ -227,13 +228,12 @@ export async function runDoctor(options) {
227
228
  urlControl = "unavailable or incompatible";
228
229
  }
229
230
  }
230
- let directUpload;
231
- try {
232
- directUpload = await checkIiisHealth(options.fetchImpl);
233
- }
234
- catch {
235
- directUpload = "unavailable or incompatible";
236
- }
231
+ // The granted data-plane has no public health endpoint; doctor makes no probe
232
+ // and reports a static fact so it never claims compatibility or availability from
233
+ // a retired path. This is deliberate: the only way to know local-file parsing
234
+ // works end-to-end is to run a real parse and inspect the grant response, so the
235
+ // status stays "not probed" until the first real local-file parse validates it.
236
+ const directUpload = RELAY_DATA_PLANE_STATUS;
237
237
  return {
238
238
  package_version: packageVersion,
239
239
  version_check: SEMVER_PATTERN.test(packageVersion)
@@ -295,10 +295,7 @@ export function renderDoctor(report) {
295
295
  if (report.endpoints.url_control === "unavailable or incompatible") {
296
296
  lines.push(" WARNING: URL parsing will fail until this is reachable.");
297
297
  }
298
- lines.push(`Omni direct upload protocol: ${report.endpoints.direct_upload}`);
299
- if (report.endpoints.direct_upload === "unavailable or incompatible") {
300
- lines.push(" WARNING: local file parsing will fail until this is reachable.");
301
- }
298
+ lines.push(`Omni granted data plane: ${report.endpoints.direct_upload}`);
302
299
  lines.push(`Artifacts: ${report.artifacts.count} file(s), ${report.artifacts.bytes} byte(s)`);
303
300
  lines.push(`Artifact expiry: ${report.artifacts.earliest_expiry ?? (report.artifacts.count === 0 ? "none" : "unknown")}`);
304
301
  lines.push(`Cache: ${report.cache.mode}`);
package/dist/cli/setup.js CHANGED
@@ -96,9 +96,9 @@ async function validateConfiguredEnvironment(options, prepared, apiKey) {
96
96
  await rollbackPreparedAgentConfig(prepared);
97
97
  }
98
98
  catch {
99
- throw new Error("Omni 安全解析环境验证失败,且无法确认 Agent 配置已自动恢复;请停止重试并检查 user-scope 配置。");
99
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败,且无法确认 Agent 配置已自动恢复;请停止重试并检查 user-scope 配置。");
100
100
  }
101
- throw new Error("Omni 安全解析环境验证失败;原 Agent 配置已自动恢复。");
101
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败;原 Agent 配置已自动恢复。");
102
102
  }
103
103
  }
104
104
  function writeSuccess(options, prepared, roots) {
@@ -139,7 +139,7 @@ export async function runSetup(options) {
139
139
  await checkHealth(options.fetchImpl, apiKey);
140
140
  }
141
141
  catch {
142
- throw new Error("Omni 安全解析环境验证失败;未写入 Agent 配置。");
142
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败;未写入 Agent 配置。");
143
143
  }
144
144
  }
145
145
  else {
@@ -5,8 +5,8 @@ export declare const RESULT_CHUNK_MAX_BYTES = 65536;
5
5
  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
- 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.2";
8
+ export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://omni-upload.cuecue.cn/omni/granted/";
9
+ export declare const BRIDGE_RELEASE_VERSION = "1.5.4";
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
@@ -5,8 +5,8 @@ export const RESULT_CHUNK_MAX_BYTES = 65536;
5
5
  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
- export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export const BRIDGE_RELEASE_VERSION = "1.5.2";
8
+ export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://omni-upload.cuecue.cn/omni/granted/";
9
+ export const BRIDGE_RELEASE_VERSION = "1.5.4";
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,29 @@ 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();
38
+ // The strict 426 Bridge-upgrade marker: exactly six closed fields, all literal.
39
+ // Any drift (status, header, body, schema) falls through to the generic
40
+ // nonretryable CUBE_UNAVAILABLE — never a new public code and never server text.
41
+ const bridgeUpgradeRequiredSchema = z
42
+ .object({
43
+ code: z.literal("BRIDGE_UPGRADE_REQUIRED"),
44
+ message: z.literal("This Bridge release is not accepted for direct local-file parsing."),
45
+ file_uploaded: z.literal(false),
46
+ billed: z.literal(false),
47
+ content_released: z.literal(false),
48
+ retryable: z.literal(false),
49
+ })
50
+ .strict();
28
51
  // Closed v3 grant response: the exact tuple of the selected direct profile is
29
52
  // required before the upload phase may start. The tuple is re-checked against
30
53
  // the profile after schema parsing so a drifting server cannot slip through.
@@ -47,7 +70,7 @@ const grantResponseV3Schema = z
47
70
  approved_result_max_bytes: z.literal(67108864),
48
71
  })
49
72
  .strict();
50
- function bridgeError(code, message, retryable) {
73
+ function bridgeError(code, message, retryable, options = {}) {
51
74
  return new OmniBridgeError({
52
75
  code,
53
76
  message,
@@ -57,6 +80,9 @@ function bridgeError(code, message, retryable) {
57
80
  billed: false,
58
81
  contentReleased: false,
59
82
  retryable,
83
+ ...(options.failureScope === undefined ? {} : { failureScope: options.failureScope }),
84
+ ...(options.userAction === undefined ? {} : { userAction: options.userAction }),
85
+ ...(options.retryAfter === undefined ? {} : { retryAfter: options.retryAfter }),
60
86
  });
61
87
  }
62
88
  function grantRequestBody(input, profile) {
@@ -137,6 +163,51 @@ function responseError(status) {
137
163
  }
138
164
  return bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", status >= 500);
139
165
  }
166
+ function retryAfterSeconds(response) {
167
+ const value = response.headers.get("retry-after");
168
+ if (value === null || !/^\d+$/u.test(value))
169
+ return undefined;
170
+ const seconds = Number(value);
171
+ return Number.isSafeInteger(seconds) && seconds > 0 ? seconds : undefined;
172
+ }
173
+ async function grantResponseError(response) {
174
+ // The strict 426 marker is only recognized when there is exactly a 426 status,
175
+ // no Retry-After, and a body matching the six frozen literal fields. Any drift
176
+ // degrades through `responseError` to the generic nonretryable CUBE_UNAVAILABLE.
177
+ if (response.status === 426 && !response.headers.has("retry-after")) {
178
+ let body;
179
+ try {
180
+ body = await response.json();
181
+ }
182
+ catch {
183
+ return responseError(response.status);
184
+ }
185
+ if (bridgeUpgradeRequiredSchema.safeParse(body).success) {
186
+ return bridgeError("BRIDGE_UPGRADE_REQUIRED", "This Bridge release is not accepted for direct local-file parsing.", false, {
187
+ failureScope: "service",
188
+ userAction: "Upgrade to @cueai/omni-reader-mcp@1.5.4 using the official Omni setup instructions, then retry the same local-file parse.",
189
+ });
190
+ }
191
+ }
192
+ const retryAfter = retryAfterSeconds(response);
193
+ if (response.status === 429 && retryAfter !== undefined) {
194
+ let body;
195
+ try {
196
+ body = await response.json();
197
+ }
198
+ catch {
199
+ return responseError(response.status);
200
+ }
201
+ if (principalConcurrencyErrorSchema.safeParse(body).success) {
202
+ return bridgeError("PRINCIPAL_CONCURRENCY_LIMIT", "The maximum number of concurrent parsing operations is already active.", true, {
203
+ failureScope: "service",
204
+ userAction: "Wait for retry_after seconds, then retry the same local-file parse.",
205
+ retryAfter,
206
+ });
207
+ }
208
+ }
209
+ return responseError(response.status);
210
+ }
140
211
  export class CubeGrantClient {
141
212
  #journal;
142
213
  #apiKey;
@@ -256,7 +327,7 @@ export class CubeGrantClient {
256
327
  throw bridgeError("CUBE_UNAVAILABLE", "Cube could not create the parse grant. Retry this same grant request later.", true);
257
328
  }
258
329
  if (!response.ok) {
259
- throw responseError(response.status);
330
+ throw await grantResponseError(response);
260
331
  }
261
332
  // The v3 response tuple is required before this promise resolves: the
262
333
  // upload phase can only start after the negotiated representation is
@@ -88,6 +88,19 @@ function throwIfCanceled(input) {
88
88
  if (input.signal?.aborted)
89
89
  throw canceledAfterUpload();
90
90
  }
91
+ // Cancel (without parsing or draining) a response body we are choosing not to
92
+ // consume, so a drifting long body cannot leave a network stream or connection
93
+ // alive after we return. Never adds an unbounded drain. User-abort precedence is
94
+ // preserved by the callers: they re-check `throwIfCanceled` around this helper.
95
+ function cancelResponseBody(response) {
96
+ try {
97
+ if (response.body !== null)
98
+ void response.body.cancel().catch(() => undefined);
99
+ }
100
+ catch {
101
+ // The body may already be cancelled/locked; nothing more to release.
102
+ }
103
+ }
91
104
  function secureIiisUrl(value) {
92
105
  const url = new URL(value);
93
106
  if (url.protocol !== "https:" || url.username !== "" || url.password !== "") {
@@ -209,6 +222,71 @@ function parserProgress(done, total) {
209
222
  : 40;
210
223
  }
211
224
  async function errorFromResponse(response, input) {
225
+ // Strict post-grant 460 relay marker: status 460, Content-Length exactly 0,
226
+ // Cache-Control exactly no-store, no Content-Type, empty body. Only that exact
227
+ // tuple maps to PROTOCOL_MISMATCH (kept operationCreated=true, post-grant); any
228
+ // header/body/status drift maps to IIIS_UNAVAILABLE and never reflects server
229
+ // text. Stable L1 JSON errors below are unchanged.
230
+ if (response.status === 460) {
231
+ // Reject obvious header drift BEFORE consuming the body: only the exact
232
+ // {Content-Length: 0, Cache-Control: no-store, no Content-Type} header tuple
233
+ // is a candidate for the bodyless PROTOCOL_MISMATCH marker. Any header drift
234
+ // maps straight to IIIS_UNAVAILABLE without reading the body.
235
+ const exactHeaderMarker = response.headers.get("content-length") === "0"
236
+ && response.headers.get("cache-control") === "no-store"
237
+ && !response.headers.has("content-type");
238
+ if (!exactHeaderMarker) {
239
+ // Cancel the unconsumed body so a drifting long response cannot leave a
240
+ // connection alive; never parse or drain it.
241
+ cancelResponseBody(response);
242
+ // User-abort precedence: a cancellation during the request must surface as
243
+ // CANCELED_AFTER_UPLOAD_STARTED, not a generic transport failure.
244
+ if (input.signal?.aborted)
245
+ throw canceledAfterUpload();
246
+ return bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
247
+ }
248
+ // Bounded emptiness probe: read a bounded single read (one chunk from the
249
+ // default reader), never an unbounded arrayBuffer. The default reader bounds
250
+ // total-body buffering but a first read may deliver >1 byte, so the chunk may
251
+ // be multi-byte — the probe only needs the first read, not BYOB. A null body
252
+ // is empty by inspection; otherwise peek the first chunk and close the reader.
253
+ // Bodyless is proven ONLY by a closed stream on the first read (done === true)
254
+ // — a zero-length non-final chunk is NOT bodyless (secret bytes may follow) and
255
+ // must degrade to IIIS_UNAVAILABLE. Re-check cancellation immediately after the
256
+ // await and again before classification so an abort during the probe maps to
257
+ // CANCELED_AFTER_UPLOAD_STARTED (operationCreated=true, post-grant).
258
+ let empty = true;
259
+ try {
260
+ if (response.body !== null) {
261
+ const reader = response.body.getReader();
262
+ try {
263
+ const first = await reader.read();
264
+ if (input.signal?.aborted)
265
+ throw canceledAfterUpload();
266
+ empty = first.done === true;
267
+ }
268
+ finally {
269
+ reader.releaseLock();
270
+ }
271
+ }
272
+ }
273
+ catch (error) {
274
+ if (input.signal?.aborted)
275
+ throw canceledAfterUpload();
276
+ cancelResponseBody(response);
277
+ throw bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
278
+ }
279
+ if (input.signal?.aborted)
280
+ throw canceledAfterUpload();
281
+ if (empty) {
282
+ return bridgeError("PROTOCOL_MISMATCH", "IIIS returned an invalid response contract.", { retryable: false });
283
+ }
284
+ // Non-bodyless stream: cancel the remainder (bounded, no drain) so secret bytes
285
+ // or a long body cannot linger on the wire. The one-byte probe already read the
286
+ // first chunk; cancel releases the rest without parsing or reflecting it.
287
+ cancelResponseBody(response);
288
+ return bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
289
+ }
212
290
  try {
213
291
  const body = await response.json();
214
292
  if (isRecord(body) && typeof body.code === "string") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueai/omni-reader-mcp",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Local stdio MCP bridge for direct Omni document parsing",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",