@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 +55 -32
- package/dist/cli/agent-config.js +28 -30
- package/dist/cli/doctor.d.ts +1 -2
- package/dist/cli/doctor.js +22 -25
- package/dist/cli/setup.js +3 -3
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/cube-client.js +73 -2
- package/dist/iiis-client.js +78 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
76
|
-
npx -y @cueai/omni-reader-mcp@1.5.
|
|
77
|
-
npx -y @cueai/omni-reader-mcp@1.5.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
255
|
-
npx -y @cueai/omni-reader-mcp@1.5.
|
|
256
|
-
npx -y @cueai/omni-reader-mcp@1.5.
|
|
257
|
-
npx -y @cueai/omni-reader-mcp@1.5.
|
|
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.
|
|
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,
|
|
268
|
-
status; it never prints the Key, private source paths,
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
|
289
|
+
| Code | Stage | Meaning |
|
|
277
290
|
|---|---|---|
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
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;
|
package/dist/cli/agent-config.js
CHANGED
|
@@ -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:
|
|
8
|
-
// rollback recognize exactly {previous, current}
|
|
9
|
-
//
|
|
10
|
-
const PREVIOUS_RELEASE_VERSION = "1.5.
|
|
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
|
-
|
|
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.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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
|
|
169
|
+
const migrationNpxForm = entry.command === "npx"
|
|
167
170
|
&& Array.isArray(args)
|
|
168
171
|
&& args.length === 2
|
|
169
172
|
&& args[0] === "-y"
|
|
170
|
-
&& args[1] ===
|
|
171
|
-
return cmdForm ||
|
|
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
|
-
|
|
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
|
-
//
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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";
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -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
|
|
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[];
|
package/dist/cli/doctor.js
CHANGED
|
@@ -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,
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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
|
|
99
|
+
throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败,且无法确认 Agent 配置已自动恢复;请停止重试并检查 user-scope 配置。");
|
|
100
100
|
}
|
|
101
|
-
throw new Error("Omni
|
|
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
|
|
142
|
+
throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败;未写入 Agent 配置。");
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
else {
|
package/dist/constants.d.ts
CHANGED
|
@@ -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://
|
|
9
|
-
export declare const BRIDGE_RELEASE_VERSION = "1.5.
|
|
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://
|
|
9
|
-
export const BRIDGE_RELEASE_VERSION = "1.5.
|
|
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;
|
package/dist/cube-client.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/iiis-client.js
CHANGED
|
@@ -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") {
|