@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7
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/CHANGELOG.md +140 -7
- package/OAUTH.md +13 -16
- package/README.md +8 -8
- package/cli.js +67 -28
- package/dist/abort.js +18 -9
- package/dist/client-operations.js +4 -2
- package/dist/commands.js +77 -43
- package/dist/config.js +194 -105
- package/dist/connection-runtime.d.ts +2 -2
- package/dist/connection-runtime.js +29 -18
- package/dist/consent-manager.d.ts +2 -2
- package/dist/consent-manager.js +5 -2
- package/dist/direct-tools.d.ts +1 -1
- package/dist/direct-tools.js +13 -7
- package/dist/elicitation-handler.d.ts +2 -3
- package/dist/elicitation-handler.js +113 -58
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/gateway-arguments.js +90 -23
- package/dist/glimpse-ui.js +10 -5
- package/dist/host-html-template.d.ts +3 -1
- package/dist/host-html-template.js +9 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +264 -119
- package/dist/init.d.ts +1 -3
- package/dist/init.js +91 -51
- package/dist/legacy-migration.d.ts +1 -1
- package/dist/legacy-migration.js +17 -12
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +33 -17
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +4 -2
- package/dist/mcp-auth-flow.js +150 -58
- package/dist/mcp-auth.d.ts +7 -7
- package/dist/mcp-auth.js +189 -138
- package/dist/mcp-callback-server.js +19 -8
- package/dist/mcp-code.js +168 -54
- package/dist/mcp-keyring-helper.cjs +57 -23
- package/dist/mcp-oauth-provider.d.ts +6 -7
- package/dist/mcp-oauth-provider.js +146 -72
- package/dist/mcp-oauth-runtime.js +29 -16
- package/dist/mcp-output-guard.js +193 -65
- package/dist/mcp-output-preview.js +41 -20
- package/dist/mcp-panel.d.ts +9 -9
- package/dist/mcp-panel.js +141 -70
- package/dist/mcp-script-worker.mjs +135 -43
- package/dist/mcp-setup-panel.d.ts +8 -8
- package/dist/mcp-setup-panel.js +130 -44
- package/dist/mcp-status.js +14 -7
- package/dist/mcp-trace.d.ts +10 -5
- package/dist/mcp-trace.js +127 -61
- package/dist/metadata-cache.d.ts +3 -4
- package/dist/metadata-cache.js +83 -43
- package/dist/npx-resolver.js +101 -60
- package/dist/onboarding-state.js +4 -2
- package/dist/plain-result-reader.js +78 -31
- package/dist/prompts.js +36 -20
- package/dist/proxy-modes.js +670 -193
- package/dist/resource-tools.js +30 -9
- package/dist/runtime-owner.js +36 -18
- package/dist/sampling-handler.d.ts +1 -2
- package/dist/sampling-handler.js +41 -21
- package/dist/search-ranking.d.ts +1 -1
- package/dist/search-ranking.js +67 -34
- package/dist/server-manager.d.ts +11 -11
- package/dist/server-manager.js +283 -166
- package/dist/session-recovery.js +113 -61
- package/dist/tool-approval.js +9 -3
- package/dist/tool-loader.js +154 -55
- package/dist/tool-metadata.d.ts +1 -1
- package/dist/tool-metadata.js +51 -23
- package/dist/tool-registrar.js +32 -10
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +65 -44
- package/dist/types.d.ts +2 -2
- package/dist/types.js +39 -18
- package/dist/ui-message-history.js +33 -13
- package/dist/ui-resource-handler.d.ts +3 -3
- package/dist/ui-resource-handler.js +37 -21
- package/dist/ui-server.js +101 -43
- package/dist/ui-session.js +66 -29
- package/dist/ui-stream-types.js +14 -4
- package/dist/ui-tool-visibility.js +12 -6
- package/dist/unix-socket-transport.d.ts +1 -2
- package/dist/unix-socket-transport.js +19 -12
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +60 -35
- package/package.json +29 -5
- package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/npx-resolver.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// npx-resolver.ts - Resolve npx/npm exec binaries to avoid npm parent processes
|
|
2
|
-
import { existsSync, readFileSync, realpathSync, readdirSync, statSync, writeFileSync, renameSync, mkdirSync, openSync, readSync, closeSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, readdirSync, statSync, writeFileSync, renameSync, mkdirSync, openSync, readSync, closeSync, } from "node:fs";
|
|
3
3
|
import { join, dirname, extname, resolve, sep } from "node:path";
|
|
4
4
|
import { getAdapterPath } from "./agent-dir.js";
|
|
5
5
|
import { throwIfAborted } from "./abort.js";
|
|
@@ -9,23 +9,21 @@ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
9
9
|
const EXACT_PACKAGE_VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/;
|
|
10
10
|
export async function resolveNpxBinary(command, args, signal) {
|
|
11
11
|
throwIfAborted(signal);
|
|
12
|
-
const parsed = command === "npx"
|
|
13
|
-
|
|
14
|
-
: command === "npm"
|
|
15
|
-
? parseNpmExecArgs(args)
|
|
16
|
-
: null;
|
|
17
|
-
if (!parsed)
|
|
12
|
+
const parsed = command === "npx" ? parseNpxArgs(args) : command === "npm" ? parseNpmExecArgs(args) : null;
|
|
13
|
+
if (!parsed) {
|
|
18
14
|
return null;
|
|
15
|
+
}
|
|
19
16
|
const packageSpec = parsePackageSpec(parsed.packageSpec);
|
|
20
|
-
if (!packageSpec)
|
|
17
|
+
if (!packageSpec) {
|
|
21
18
|
return null;
|
|
19
|
+
}
|
|
22
20
|
const cacheKey = JSON.stringify([command, ...args]);
|
|
23
21
|
const cache = loadCache();
|
|
24
22
|
const cached = cache?.entries?.[cacheKey];
|
|
25
|
-
if (cached
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (cached &&
|
|
24
|
+
Date.now() - cached.resolvedAt < CACHE_TTL_MS &&
|
|
25
|
+
existsSync(cached.resolvedBin) &&
|
|
26
|
+
(!packageSpec.exactVersion || cached.packageVersion === packageSpec.exactVersion)) {
|
|
29
27
|
return { binPath: cached.resolvedBin, extraArgs: parsed.extraArgs, isJs: cached.isJs };
|
|
30
28
|
}
|
|
31
29
|
const resolved = resolveFromNpmCache(parsed.packageSpec, parsed.binName);
|
|
@@ -38,7 +36,11 @@ export async function resolveNpxBinary(command, args, signal) {
|
|
|
38
36
|
const resolvedAfterInstall = resolveFromNpmCache(parsed.packageSpec, parsed.binName);
|
|
39
37
|
if (resolvedAfterInstall) {
|
|
40
38
|
saveCacheEntry(cacheKey, resolvedAfterInstall);
|
|
41
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
binPath: resolvedAfterInstall.resolvedBin,
|
|
41
|
+
extraArgs: parsed.extraArgs,
|
|
42
|
+
isJs: resolvedAfterInstall.isJs,
|
|
43
|
+
};
|
|
42
44
|
}
|
|
43
45
|
return null;
|
|
44
46
|
}
|
|
@@ -52,30 +54,36 @@ function parseNpxArgs(args) {
|
|
|
52
54
|
let foundFirstPositional = false;
|
|
53
55
|
for (let i = 0; i < before.length; i++) {
|
|
54
56
|
const arg = before[i];
|
|
55
|
-
if (arg === undefined)
|
|
57
|
+
if (arg === undefined) {
|
|
56
58
|
return null;
|
|
59
|
+
}
|
|
57
60
|
if (foundFirstPositional) {
|
|
58
61
|
positionals.push(arg);
|
|
59
62
|
continue;
|
|
60
63
|
}
|
|
61
|
-
if (arg === "-y" || arg === "--yes")
|
|
64
|
+
if (arg === "-y" || arg === "--yes") {
|
|
62
65
|
continue;
|
|
66
|
+
}
|
|
63
67
|
if (arg === "-p" || arg === "--package") {
|
|
64
68
|
const value = before[i + 1];
|
|
65
|
-
if (!value || value.startsWith("-"))
|
|
69
|
+
if (!value || value.startsWith("-")) {
|
|
66
70
|
return null;
|
|
67
|
-
|
|
71
|
+
}
|
|
72
|
+
if (!packageSpec) {
|
|
68
73
|
packageSpec = value;
|
|
74
|
+
}
|
|
69
75
|
sawPackageFlag = true;
|
|
70
76
|
i++;
|
|
71
77
|
continue;
|
|
72
78
|
}
|
|
73
79
|
if (arg.startsWith("--package=")) {
|
|
74
80
|
const value = arg.slice("--package=".length);
|
|
75
|
-
if (!value)
|
|
81
|
+
if (!value) {
|
|
76
82
|
return null;
|
|
77
|
-
|
|
83
|
+
}
|
|
84
|
+
if (!packageSpec) {
|
|
78
85
|
packageSpec = value;
|
|
86
|
+
}
|
|
79
87
|
sawPackageFlag = true;
|
|
80
88
|
continue;
|
|
81
89
|
}
|
|
@@ -88,48 +96,58 @@ function parseNpxArgs(args) {
|
|
|
88
96
|
const separatedAfter = separatorIndex >= 0 && after.length > 0 ? ["--", ...after] : after;
|
|
89
97
|
if (sawPackageFlag) {
|
|
90
98
|
const binName = positionals[0];
|
|
91
|
-
if (!packageSpec || !binName)
|
|
99
|
+
if (!packageSpec || !binName) {
|
|
92
100
|
return null;
|
|
101
|
+
}
|
|
93
102
|
const extraArgs = positionals.slice(1).concat(separatedAfter);
|
|
94
103
|
return { packageSpec, binName, extraArgs };
|
|
95
104
|
}
|
|
96
105
|
const packagePositional = positionals[0];
|
|
97
|
-
if (!packagePositional)
|
|
106
|
+
if (!packagePositional) {
|
|
98
107
|
return null;
|
|
108
|
+
}
|
|
99
109
|
const extraArgs = positionals.slice(1).concat(separatedAfter);
|
|
100
110
|
return { packageSpec: packagePositional, extraArgs };
|
|
101
111
|
}
|
|
102
112
|
function parseNpmExecArgs(args) {
|
|
103
|
-
if (args[0] !== "exec")
|
|
113
|
+
if (args[0] !== "exec") {
|
|
104
114
|
return null;
|
|
115
|
+
}
|
|
105
116
|
const execArgs = args.slice(1);
|
|
106
117
|
const separatorIndex = execArgs.indexOf("--");
|
|
107
|
-
if (separatorIndex < 0)
|
|
118
|
+
if (separatorIndex < 0) {
|
|
108
119
|
return null;
|
|
120
|
+
}
|
|
109
121
|
const before = execArgs.slice(0, separatorIndex);
|
|
110
122
|
const after = execArgs.slice(separatorIndex + 1);
|
|
111
123
|
let packageSpec;
|
|
112
124
|
for (let i = 0; i < before.length; i++) {
|
|
113
125
|
const arg = before[i];
|
|
114
|
-
if (arg === undefined)
|
|
126
|
+
if (arg === undefined) {
|
|
115
127
|
return null;
|
|
116
|
-
|
|
128
|
+
}
|
|
129
|
+
if (arg === "-y" || arg === "--yes") {
|
|
117
130
|
continue;
|
|
131
|
+
}
|
|
118
132
|
if (arg === "--package") {
|
|
119
133
|
const value = before[i + 1];
|
|
120
|
-
if (!value || value.startsWith("-"))
|
|
134
|
+
if (!value || value.startsWith("-")) {
|
|
121
135
|
return null;
|
|
122
|
-
|
|
136
|
+
}
|
|
137
|
+
if (!packageSpec) {
|
|
123
138
|
packageSpec = value;
|
|
139
|
+
}
|
|
124
140
|
i++;
|
|
125
141
|
continue;
|
|
126
142
|
}
|
|
127
143
|
if (arg.startsWith("--package=")) {
|
|
128
144
|
const value = arg.slice("--package=".length);
|
|
129
|
-
if (!value)
|
|
145
|
+
if (!value) {
|
|
130
146
|
return null;
|
|
131
|
-
|
|
147
|
+
}
|
|
148
|
+
if (!packageSpec) {
|
|
132
149
|
packageSpec = value;
|
|
150
|
+
}
|
|
133
151
|
continue;
|
|
134
152
|
}
|
|
135
153
|
if (arg.startsWith("-")) {
|
|
@@ -137,25 +155,30 @@ function parseNpmExecArgs(args) {
|
|
|
137
155
|
}
|
|
138
156
|
}
|
|
139
157
|
const binName = after[0];
|
|
140
|
-
if (!packageSpec || !binName)
|
|
158
|
+
if (!packageSpec || !binName) {
|
|
141
159
|
return null;
|
|
160
|
+
}
|
|
142
161
|
const extraArgs = after.slice(1);
|
|
143
162
|
return { packageSpec, binName, extraArgs };
|
|
144
163
|
}
|
|
145
164
|
function resolveFromNpmCache(packageSpec, binName) {
|
|
146
165
|
const cacheDir = getNpmCacheDir();
|
|
147
|
-
if (!cacheDir)
|
|
166
|
+
if (!cacheDir) {
|
|
148
167
|
return null;
|
|
168
|
+
}
|
|
149
169
|
const parsedSpec = parsePackageSpec(packageSpec);
|
|
150
|
-
if (!parsedSpec)
|
|
170
|
+
if (!parsedSpec) {
|
|
151
171
|
return null;
|
|
172
|
+
}
|
|
152
173
|
const { packageName, exactVersion } = parsedSpec;
|
|
153
174
|
const packageDir = findCachedPackageDir(cacheDir, packageName, exactVersion);
|
|
154
|
-
if (!packageDir)
|
|
175
|
+
if (!packageDir) {
|
|
155
176
|
return null;
|
|
177
|
+
}
|
|
156
178
|
const packageJsonPath = join(packageDir, "package.json");
|
|
157
|
-
if (!existsSync(packageJsonPath))
|
|
179
|
+
if (!existsSync(packageJsonPath)) {
|
|
158
180
|
return null;
|
|
181
|
+
}
|
|
159
182
|
let pkg = null;
|
|
160
183
|
try {
|
|
161
184
|
pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
@@ -164,8 +187,9 @@ function resolveFromNpmCache(packageSpec, binName) {
|
|
|
164
187
|
return null;
|
|
165
188
|
}
|
|
166
189
|
const binField = pkg?.bin;
|
|
167
|
-
if (!binField)
|
|
190
|
+
if (!binField) {
|
|
168
191
|
return null;
|
|
192
|
+
}
|
|
169
193
|
const candidates = buildBinCandidates(packageName, binName);
|
|
170
194
|
let chosenBinName;
|
|
171
195
|
let binRel;
|
|
@@ -189,15 +213,17 @@ function resolveFromNpmCache(packageSpec, binName) {
|
|
|
189
213
|
}
|
|
190
214
|
}
|
|
191
215
|
}
|
|
192
|
-
if (!binRel)
|
|
216
|
+
if (!binRel) {
|
|
193
217
|
return null;
|
|
218
|
+
}
|
|
194
219
|
const nodeModulesDir = findNodeModulesDir(packageDir);
|
|
195
220
|
const binLink = chosenBinName ? join(nodeModulesDir, ".bin", chosenBinName) : null;
|
|
196
221
|
let resolvedBin = binLink && existsSync(binLink) ? safeRealpath(binLink) : "";
|
|
197
222
|
if (!resolvedBin) {
|
|
198
223
|
resolvedBin = resolve(packageDir, binRel);
|
|
199
|
-
if (!existsSync(resolvedBin))
|
|
224
|
+
if (!existsSync(resolvedBin)) {
|
|
200
225
|
return null;
|
|
226
|
+
}
|
|
201
227
|
}
|
|
202
228
|
const isJs = detectJsBinary(resolvedBin);
|
|
203
229
|
return {
|
|
@@ -236,23 +262,27 @@ async function forceNpxCache(packageSpec, signal) {
|
|
|
236
262
|
});
|
|
237
263
|
}
|
|
238
264
|
catch (error) {
|
|
239
|
-
if (signal?.aborted)
|
|
265
|
+
if (signal?.aborted) {
|
|
240
266
|
throwIfAborted(signal);
|
|
267
|
+
}
|
|
241
268
|
// Ignore failures, resolution will fall back to original command
|
|
242
269
|
}
|
|
243
270
|
throwIfAborted(signal);
|
|
244
271
|
}
|
|
245
272
|
function buildBinCandidates(packageName, explicitBin) {
|
|
246
273
|
const candidates = [];
|
|
247
|
-
if (explicitBin)
|
|
274
|
+
if (explicitBin) {
|
|
248
275
|
candidates.push(explicitBin);
|
|
276
|
+
}
|
|
249
277
|
if (packageName.startsWith("@")) {
|
|
250
278
|
const namePart = packageName.split("/")[1] ?? "";
|
|
251
279
|
const scopePart = packageName.split("/")[0]?.replace("@", "") ?? "";
|
|
252
|
-
if (namePart)
|
|
280
|
+
if (namePart) {
|
|
253
281
|
candidates.push(namePart);
|
|
254
|
-
|
|
282
|
+
}
|
|
283
|
+
if (scopePart && namePart) {
|
|
255
284
|
candidates.push(`${scopePart}-${namePart}`);
|
|
285
|
+
}
|
|
256
286
|
}
|
|
257
287
|
else {
|
|
258
288
|
candidates.push(packageName);
|
|
@@ -261,14 +291,16 @@ function buildBinCandidates(packageName, explicitBin) {
|
|
|
261
291
|
}
|
|
262
292
|
function parsePackageSpec(spec) {
|
|
263
293
|
const trimmed = spec.trim();
|
|
264
|
-
if (!trimmed)
|
|
294
|
+
if (!trimmed) {
|
|
265
295
|
return null;
|
|
296
|
+
}
|
|
266
297
|
let packageName;
|
|
267
298
|
let requestedVersion;
|
|
268
299
|
if (trimmed.startsWith("@")) {
|
|
269
300
|
const slashIndex = trimmed.indexOf("/");
|
|
270
|
-
if (slashIndex < 0)
|
|
301
|
+
if (slashIndex < 0) {
|
|
271
302
|
return null;
|
|
303
|
+
}
|
|
272
304
|
const atIndex = trimmed.lastIndexOf("@");
|
|
273
305
|
if (atIndex > slashIndex) {
|
|
274
306
|
packageName = trimmed.slice(0, atIndex);
|
|
@@ -288,11 +320,13 @@ function parsePackageSpec(spec) {
|
|
|
288
320
|
packageName = trimmed;
|
|
289
321
|
}
|
|
290
322
|
}
|
|
291
|
-
if (!packageName)
|
|
323
|
+
if (!packageName) {
|
|
292
324
|
return null;
|
|
325
|
+
}
|
|
293
326
|
const normalizedVersion = requestedVersion?.replace(/^=/, "").replace(/^v/i, "");
|
|
294
327
|
// Let npm resolve ranges and tags rather than choosing an arbitrary cached version.
|
|
295
|
-
if (requestedVersion !== undefined &&
|
|
328
|
+
if (requestedVersion !== undefined &&
|
|
329
|
+
(!normalizedVersion || !EXACT_PACKAGE_VERSION_RE.test(normalizedVersion))) {
|
|
296
330
|
return null;
|
|
297
331
|
}
|
|
298
332
|
return {
|
|
@@ -309,14 +343,13 @@ function defaultBinName(packageName) {
|
|
|
309
343
|
}
|
|
310
344
|
function findCachedPackageDir(cacheDir, packageName, exactVersion) {
|
|
311
345
|
const npxDir = join(cacheDir, "_npx");
|
|
312
|
-
if (!existsSync(npxDir))
|
|
346
|
+
if (!existsSync(npxDir)) {
|
|
313
347
|
return null;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
: [packageName];
|
|
348
|
+
}
|
|
349
|
+
const packagePathParts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
|
|
317
350
|
const candidates = readdirSync(npxDir, { withFileTypes: true })
|
|
318
|
-
.filter(entry => entry.isDirectory())
|
|
319
|
-
.map(entry => {
|
|
351
|
+
.filter((entry) => entry.isDirectory())
|
|
352
|
+
.map((entry) => {
|
|
320
353
|
const full = join(npxDir, entry.name);
|
|
321
354
|
const mtime = safeStatMtime(full);
|
|
322
355
|
return { name: entry.name, mtime };
|
|
@@ -325,13 +358,15 @@ function findCachedPackageDir(cacheDir, packageName, exactVersion) {
|
|
|
325
358
|
for (const entry of candidates) {
|
|
326
359
|
const pkgDir = join(npxDir, entry.name, "node_modules", ...packagePathParts);
|
|
327
360
|
const packageJsonPath = join(pkgDir, "package.json");
|
|
328
|
-
if (!existsSync(packageJsonPath))
|
|
361
|
+
if (!existsSync(packageJsonPath)) {
|
|
329
362
|
continue;
|
|
363
|
+
}
|
|
330
364
|
if (exactVersion) {
|
|
331
365
|
try {
|
|
332
366
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
333
|
-
if (pkg.version !== exactVersion)
|
|
367
|
+
if (pkg.version !== exactVersion) {
|
|
334
368
|
continue;
|
|
369
|
+
}
|
|
335
370
|
}
|
|
336
371
|
catch {
|
|
337
372
|
continue;
|
|
@@ -351,8 +386,9 @@ function findNodeModulesDir(packageDir) {
|
|
|
351
386
|
}
|
|
352
387
|
function detectJsBinary(binPath) {
|
|
353
388
|
const ext = extname(binPath).toLowerCase();
|
|
354
|
-
if (ext === ".js" || ext === ".mjs" || ext === ".cjs")
|
|
389
|
+
if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
|
|
355
390
|
return true;
|
|
391
|
+
}
|
|
356
392
|
try {
|
|
357
393
|
const fd = openSync(binPath, "r");
|
|
358
394
|
try {
|
|
@@ -371,8 +407,9 @@ function detectJsBinary(binPath) {
|
|
|
371
407
|
}
|
|
372
408
|
let npmCacheDirCached;
|
|
373
409
|
function getNpmCacheDir() {
|
|
374
|
-
if (npmCacheDirCached !== undefined)
|
|
410
|
+
if (npmCacheDirCached !== undefined) {
|
|
375
411
|
return npmCacheDirCached;
|
|
412
|
+
}
|
|
376
413
|
if (process.env.NPM_CONFIG_CACHE) {
|
|
377
414
|
npmCacheDirCached = process.env.NPM_CONFIG_CACHE;
|
|
378
415
|
return npmCacheDirCached;
|
|
@@ -397,16 +434,20 @@ function getNpxCachePath() {
|
|
|
397
434
|
}
|
|
398
435
|
function loadCache() {
|
|
399
436
|
const cachePath = getNpxCachePath();
|
|
400
|
-
if (!existsSync(cachePath))
|
|
437
|
+
if (!existsSync(cachePath)) {
|
|
401
438
|
return null;
|
|
439
|
+
}
|
|
402
440
|
try {
|
|
403
441
|
const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
404
|
-
if (!raw || typeof raw !== "object")
|
|
442
|
+
if (!raw || typeof raw !== "object") {
|
|
405
443
|
return null;
|
|
406
|
-
|
|
444
|
+
}
|
|
445
|
+
if (raw.version !== CACHE_VERSION) {
|
|
407
446
|
return null;
|
|
408
|
-
|
|
447
|
+
}
|
|
448
|
+
if (!raw.entries || typeof raw.entries !== "object") {
|
|
409
449
|
return null;
|
|
450
|
+
}
|
|
410
451
|
return raw;
|
|
411
452
|
}
|
|
412
453
|
catch {
|
|
@@ -417,7 +458,7 @@ function saveCacheEntry(key, entry) {
|
|
|
417
458
|
const cachePath = getNpxCachePath();
|
|
418
459
|
const dir = dirname(cachePath);
|
|
419
460
|
mkdirSync(dir, { recursive: true });
|
|
420
|
-
|
|
461
|
+
const merged = { version: CACHE_VERSION, entries: {} };
|
|
421
462
|
try {
|
|
422
463
|
if (existsSync(cachePath)) {
|
|
423
464
|
const existing = JSON.parse(readFileSync(cachePath, "utf-8"));
|
package/dist/onboarding-state.js
CHANGED
|
@@ -11,12 +11,14 @@ export function getOnboardingStatePath() {
|
|
|
11
11
|
}
|
|
12
12
|
export function loadOnboardingState() {
|
|
13
13
|
const path = getOnboardingStatePath();
|
|
14
|
-
if (!existsSync(path))
|
|
14
|
+
if (!existsSync(path)) {
|
|
15
15
|
return { ...DEFAULT_STATE };
|
|
16
|
+
}
|
|
16
17
|
try {
|
|
17
18
|
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
18
|
-
if (!raw || typeof raw !== "object")
|
|
19
|
+
if (!raw || typeof raw !== "object") {
|
|
19
20
|
return { ...DEFAULT_STATE };
|
|
21
|
+
}
|
|
20
22
|
return {
|
|
21
23
|
version: 1,
|
|
22
24
|
sharedConfigHintShown: raw.sharedConfigHintShown === true,
|
|
@@ -8,8 +8,11 @@ const readers = new Map();
|
|
|
8
8
|
const MAX_INDEXES = 8;
|
|
9
9
|
const MAX_POINTS = 1024;
|
|
10
10
|
function sameRevision(a, b) {
|
|
11
|
-
return a.dev === b.dev &&
|
|
12
|
-
|
|
11
|
+
return (a.dev === b.dev &&
|
|
12
|
+
a.ino === b.ino &&
|
|
13
|
+
a.size === b.size &&
|
|
14
|
+
a.mtimeNs === b.mtimeNs &&
|
|
15
|
+
a.ctimeNs === b.ctimeNs);
|
|
13
16
|
}
|
|
14
17
|
/** Raw full-content SHA-256. Returns "" on a short read so a truncated verify never matches. */
|
|
15
18
|
async function digestOf(file, size) {
|
|
@@ -18,16 +21,18 @@ async function digestOf(file, size) {
|
|
|
18
21
|
let position = 0;
|
|
19
22
|
while (position < size) {
|
|
20
23
|
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, size - position), position);
|
|
21
|
-
if (!bytesRead)
|
|
24
|
+
if (!bytesRead) {
|
|
22
25
|
return "";
|
|
26
|
+
}
|
|
23
27
|
hash.update(buffer.subarray(0, bytesRead));
|
|
24
28
|
position += bytesRead;
|
|
25
29
|
}
|
|
26
30
|
return hash.digest("hex");
|
|
27
31
|
}
|
|
28
32
|
function checkpoint(index, point) {
|
|
29
|
-
if (point.bytes - index.points.at(-1).bytes < index.stride)
|
|
33
|
+
if (point.bytes - index.points.at(-1).bytes < index.stride) {
|
|
30
34
|
return;
|
|
35
|
+
}
|
|
31
36
|
index.points.push(point);
|
|
32
37
|
if (index.points.length > MAX_POINTS) {
|
|
33
38
|
index.points = index.points.filter((_, i) => i % 2 === 0);
|
|
@@ -37,8 +42,9 @@ function checkpoint(index, point) {
|
|
|
37
42
|
function remember(ref, index) {
|
|
38
43
|
indexes.delete(ref);
|
|
39
44
|
indexes.set(ref, index);
|
|
40
|
-
if (indexes.size > MAX_INDEXES)
|
|
45
|
+
if (indexes.size > MAX_INDEXES) {
|
|
41
46
|
indexes.delete(indexes.keys().next().value);
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
function extend(index, after, text) {
|
|
44
50
|
// Clone before updating so a shared cached hasher never advances.
|
|
@@ -49,8 +55,9 @@ function extend(index, after, text) {
|
|
|
49
55
|
// Index only the accepted delta, keeping checkpoints on complete UTF-8 code points.
|
|
50
56
|
for (let start = 0; start < text.length;) {
|
|
51
57
|
let end = Math.min(text.length, start + 16 * 1024);
|
|
52
|
-
if (end < text.length && text.codePointAt(end - 1) > 0xffff)
|
|
58
|
+
if (end < text.length && text.codePointAt(end - 1) > 0xffff) {
|
|
53
59
|
end++;
|
|
60
|
+
}
|
|
54
61
|
position += byteLength(text.slice(start, end));
|
|
55
62
|
checkpoint(index, { bytes: position, characters: index.total + end });
|
|
56
63
|
start = end;
|
|
@@ -66,32 +73,42 @@ export function appendPlainOutput(ref, text) {
|
|
|
66
73
|
key = indexes.size || readers.size ? realpathSync(ref) : ref;
|
|
67
74
|
cached = indexes.get(key);
|
|
68
75
|
indexes.delete(key);
|
|
69
|
-
if (cached || readers.has(key))
|
|
76
|
+
if (cached || readers.has(key)) {
|
|
70
77
|
before = statSync(ref, { bigint: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* Cache bookkeeping must not change write acceptance. */
|
|
71
82
|
}
|
|
72
|
-
catch { /* Cache bookkeeping must not change write acceptance. */ }
|
|
73
83
|
appendFileSync(ref, text, { flush: true });
|
|
74
|
-
if (!before)
|
|
84
|
+
if (!before) {
|
|
75
85
|
return;
|
|
86
|
+
}
|
|
76
87
|
try {
|
|
77
88
|
const after = statSync(ref, { bigint: true });
|
|
78
|
-
if (after.dev !== before.dev ||
|
|
79
|
-
|
|
89
|
+
if (after.dev !== before.dev ||
|
|
90
|
+
after.ino !== before.ino ||
|
|
91
|
+
after.size !== before.size + BigInt(byteLength(text))) {
|
|
80
92
|
return;
|
|
93
|
+
}
|
|
81
94
|
if (cached && sameRevision(cached.revision, before) && !text.includes("\ufffd")) {
|
|
82
95
|
const next = { ...cached, points: [...cached.points] };
|
|
83
96
|
extend(next, after, text);
|
|
84
97
|
remember(key, next);
|
|
85
98
|
}
|
|
86
99
|
for (const progress of readers.get(key) ?? []) {
|
|
87
|
-
if (!sameRevision(progress.revision, before))
|
|
100
|
+
if (!sameRevision(progress.revision, before)) {
|
|
88
101
|
continue;
|
|
102
|
+
}
|
|
89
103
|
extend(progress, after, text);
|
|
90
|
-
if (text.includes("\ufffd"))
|
|
104
|
+
if (text.includes("\ufffd")) {
|
|
91
105
|
progress.cacheable = false;
|
|
106
|
+
}
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
|
-
catch {
|
|
109
|
+
catch {
|
|
110
|
+
/* The durable append already succeeded; leave its index invalidated. */
|
|
111
|
+
}
|
|
95
112
|
}
|
|
96
113
|
/** Sparse byte/UTF16 checkpoints, never retained file contents. Changed/uncacheable files scan afresh. */
|
|
97
114
|
export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
@@ -103,12 +120,25 @@ export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
|
103
120
|
const revision = fstatSync(file.fd, { bigint: true });
|
|
104
121
|
const cached = indexes.get(ref);
|
|
105
122
|
const matched = cached !== undefined && sameRevision(cached.revision, revision);
|
|
106
|
-
const fresh = {
|
|
107
|
-
|
|
123
|
+
const fresh = {
|
|
124
|
+
revision,
|
|
125
|
+
total: 0,
|
|
126
|
+
points: [{ bytes: 0, characters: 0 }],
|
|
127
|
+
stride: 64 * 1024,
|
|
128
|
+
digest: "",
|
|
129
|
+
hasher: createHash("sha256"),
|
|
130
|
+
};
|
|
108
131
|
let index = matched ? cached : fresh;
|
|
109
132
|
// Append characters stay relative until the cold scan knows the opened prefix's total.
|
|
110
|
-
progress = {
|
|
111
|
-
|
|
133
|
+
progress = {
|
|
134
|
+
revision,
|
|
135
|
+
total: 0,
|
|
136
|
+
points: [{ bytes: Number(revision.size), characters: 0 }],
|
|
137
|
+
stride: index.stride,
|
|
138
|
+
cacheable: true,
|
|
139
|
+
digest: "",
|
|
140
|
+
hasher: index.hasher.copy(),
|
|
141
|
+
};
|
|
112
142
|
const active = readers.get(ref) ?? new Set();
|
|
113
143
|
active.add(progress);
|
|
114
144
|
readers.set(ref, active);
|
|
@@ -125,8 +155,9 @@ export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
|
125
155
|
let point = index.points[0];
|
|
126
156
|
if (valid) {
|
|
127
157
|
for (const candidate of index.points) {
|
|
128
|
-
if (candidate.characters > offset)
|
|
158
|
+
if (candidate.characters > offset) {
|
|
129
159
|
break;
|
|
160
|
+
}
|
|
130
161
|
point = candidate;
|
|
131
162
|
}
|
|
132
163
|
}
|
|
@@ -139,26 +170,31 @@ export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
|
139
170
|
let readPosition = point.bytes;
|
|
140
171
|
while (readPosition < Number(revision.size)) {
|
|
141
172
|
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, Number(revision.size) - readPosition), readPosition);
|
|
142
|
-
if (!bytesRead)
|
|
173
|
+
if (!bytesRead) {
|
|
143
174
|
break;
|
|
175
|
+
}
|
|
144
176
|
readPosition += bytesRead;
|
|
145
|
-
const chunk = decoder.write(buffer.subarray(0, bytesRead)) +
|
|
177
|
+
const chunk = decoder.write(buffer.subarray(0, bytesRead)) +
|
|
178
|
+
(readPosition === Number(revision.size) ? decoder.end() : "");
|
|
146
179
|
if (!valid) {
|
|
147
180
|
progress.hasher.update(buffer.subarray(0, bytesRead));
|
|
148
181
|
checkpoint(index, { bytes: position, characters: total });
|
|
149
182
|
// Invalid/incomplete UTF8 can re-decode differently after append. Keep the full-scan fallback.
|
|
150
|
-
if (chunk.includes("\ufffd"))
|
|
183
|
+
if (chunk.includes("\ufffd")) {
|
|
151
184
|
cacheable = false;
|
|
185
|
+
}
|
|
152
186
|
position += byteLength(chunk);
|
|
153
187
|
}
|
|
154
188
|
const start = Math.max(0, offset - total);
|
|
155
189
|
if (!finished && start < chunk.length) {
|
|
156
|
-
if (start > 0 && chunk.codePointAt(start - 1) > 0xffff)
|
|
190
|
+
if (start > 0 && chunk.codePointAt(start - 1) > 0xffff) {
|
|
157
191
|
throw new Error("offset must not split a Unicode surrogate pair");
|
|
192
|
+
}
|
|
158
193
|
for (const char of chunk.slice(start)) {
|
|
159
194
|
const nextBytes = bytes + byteLength(char);
|
|
160
195
|
const nextLines = lines + (char === "\n" ? 1 : 0);
|
|
161
|
-
if (page &&
|
|
196
|
+
if (page &&
|
|
197
|
+
(nextBytes > maxBytes || nextLines > maxLines || page.length + char.length > limit)) {
|
|
162
198
|
finished = true;
|
|
163
199
|
break;
|
|
164
200
|
}
|
|
@@ -168,21 +204,31 @@ export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
|
168
204
|
}
|
|
169
205
|
}
|
|
170
206
|
total += chunk.length;
|
|
171
|
-
if (valid && finished)
|
|
207
|
+
if (valid && finished) {
|
|
172
208
|
break;
|
|
209
|
+
}
|
|
173
210
|
}
|
|
174
211
|
}
|
|
175
212
|
// Only verified native appends may advance this snapshot. External edits still invalidate.
|
|
176
|
-
if (!sameRevision(progress.revision, statSync(ref, { bigint: true })))
|
|
213
|
+
if (!sameRevision(progress.revision, statSync(ref, { bigint: true }))) {
|
|
177
214
|
continue;
|
|
178
|
-
|
|
215
|
+
}
|
|
216
|
+
if (!valid) {
|
|
179
217
|
index.total = total;
|
|
218
|
+
}
|
|
180
219
|
// Serve the opened prefix; retain verified append checkpoints for the next read.
|
|
181
220
|
if (cacheable && progress.cacheable) {
|
|
182
|
-
const next = {
|
|
183
|
-
|
|
184
|
-
|
|
221
|
+
const next = {
|
|
222
|
+
...index,
|
|
223
|
+
revision: progress.revision,
|
|
224
|
+
total: index.total + progress.total,
|
|
225
|
+
points: [...index.points],
|
|
226
|
+
digest: progress.hasher.copy().digest("hex"),
|
|
227
|
+
hasher: progress.hasher,
|
|
228
|
+
};
|
|
229
|
+
for (const point of progress.points) {
|
|
185
230
|
checkpoint(next, { bytes: point.bytes, characters: index.total + point.characters });
|
|
231
|
+
}
|
|
186
232
|
remember(ref, next);
|
|
187
233
|
}
|
|
188
234
|
return { page, total: index.total };
|
|
@@ -191,8 +237,9 @@ export async function readPlainResult(ref, offset, limit, maxBytes, maxLines) {
|
|
|
191
237
|
if (progress) {
|
|
192
238
|
const active = readers.get(ref);
|
|
193
239
|
active.delete(progress);
|
|
194
|
-
if (!active.size)
|
|
240
|
+
if (!active.size) {
|
|
195
241
|
readers.delete(ref);
|
|
242
|
+
}
|
|
196
243
|
}
|
|
197
244
|
await file.close();
|
|
198
245
|
}
|