@deepseek-ai/dsh-plugin-manager 0.1.7-alpha.2 → 0.1.7-rc.2
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.i18n.yaml +54 -5
- package/README.md +17 -2
- package/README.zh.md +17 -2
- package/lib/index.js +757 -154
- package/lib/typert.host.js +237 -26
- package/lib/typert.remote-client.d.ts +4 -0
- package/lib/typert.remote-client.js +215 -22
- package/lib/types/failure.d.ts +15 -3
- package/lib/types/failure.js +19 -2
- package/lib/types/github-connection.d.ts +21 -0
- package/lib/types/github-connection.js +61 -0
- package/lib/types/index.d.ts +28 -4
- package/lib/types/index.js +111 -21
- package/lib/types/install-failure.js +1 -0
- package/lib/types/operations.d.ts +15 -1
- package/lib/types/operations.js +403 -22
- package/lib/types/run-tree.d.ts +44 -0
- package/lib/types/run-tree.js +57 -0
- package/lib/types/tools.js +13 -3
- package/lib/types/types.d.ts +17 -3
- package/package.json +28 -28
package/lib/types/index.js
CHANGED
|
@@ -42,14 +42,15 @@ import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
|
42
42
|
import z from '@deepseek-ai/schemastery';
|
|
43
43
|
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol';
|
|
44
44
|
import { pluginEntryId, readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory';
|
|
45
|
-
import { readPluginMeta, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches, OPTIONAL_BUNDLES, bundlePatchPaths, } from '@deepseek-ai/dsh-app-boot';
|
|
45
|
+
import { readPluginMeta, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches, OPTIONAL_BUNDLES, bundlePatchPaths, evaluatePluginCompatibility, readProfileCompatibility, readProfileVersionExemptions, setProfileVersionExemption, PROFILE_COMPATIBILITY_FILENAME, } from '@deepseek-ai/dsh-app-boot';
|
|
46
46
|
import { bundleManifest, readProfileRegistry, registryArguments, runProfilePnpm, saveManifest, viewProfilePackage } from "./operations.js";
|
|
47
47
|
import { classifyInstallFailure } from "./install-failure.js";
|
|
48
48
|
import { InvalidInstallSpecError, parseInstallSpec } from "./install-spec.js";
|
|
49
49
|
import { attributeFailure, normalizeRegistry, NPMMIRROR_REGISTRY, registryPlan } from "./registry.js";
|
|
50
50
|
import { writePluginEnabled } from "./patch.js";
|
|
51
|
-
import { ManagementFailure } from "./failure.js";
|
|
51
|
+
import { incompatiblePlugin, ManagementFailure } from "./failure.js";
|
|
52
52
|
import { approveBuilds, readPendingBuilds } from "./build-approval.js";
|
|
53
|
+
import { checkGithubConnection } from "./github-connection.js";
|
|
53
54
|
export { classifyInstallFailure } from "./install-failure.js";
|
|
54
55
|
export { InvalidInstallSpecError, parseInstallSpec } from "./install-spec.js";
|
|
55
56
|
/** An http(s) URL, as pnpm's `--registry` takes it. */
|
|
@@ -77,7 +78,9 @@ function flatten(rows) {
|
|
|
77
78
|
function messageOf(error) { return error instanceof Error ? error.message : String(error); }
|
|
78
79
|
/** An expected refusal keeps its code; anything else becomes an operation error carrying its exact diagnostic. */
|
|
79
80
|
function managementError(error) {
|
|
80
|
-
|
|
81
|
+
if (!(error instanceof ManagementFailure))
|
|
82
|
+
return { code: 'operation-error', diagnostic: messageOf(error) };
|
|
83
|
+
return { code: error.code, ...error.incompatible === undefined ? {} : { incompatible: error.incompatible } };
|
|
81
84
|
}
|
|
82
85
|
/** The caller stopped an installation; its files are restored before this is thrown. */
|
|
83
86
|
class InstallCancelledError extends Error {
|
|
@@ -140,6 +143,8 @@ function parsedForRegistry(spec) {
|
|
|
140
143
|
let PluginManager = (() => {
|
|
141
144
|
let _classSuper = TypertRemoteService;
|
|
142
145
|
let _instanceExtraInitializers = [];
|
|
146
|
+
let _listVersionExemptions_decorators;
|
|
147
|
+
let _setVersionExemption_decorators;
|
|
143
148
|
let _listPlugins_decorators;
|
|
144
149
|
let _listBundles_decorators;
|
|
145
150
|
let _registries_decorators;
|
|
@@ -153,6 +158,8 @@ let PluginManager = (() => {
|
|
|
153
158
|
return class PluginManager extends _classSuper {
|
|
154
159
|
static {
|
|
155
160
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
161
|
+
_listVersionExemptions_decorators = [Remote];
|
|
162
|
+
_setVersionExemption_decorators = [Remote];
|
|
156
163
|
_listPlugins_decorators = [Remote];
|
|
157
164
|
_listBundles_decorators = [Remote];
|
|
158
165
|
_registries_decorators = [Remote];
|
|
@@ -163,6 +170,8 @@ let PluginManager = (() => {
|
|
|
163
170
|
_waitForInstall_decorators = [Remote];
|
|
164
171
|
_cancelInstall_decorators = [Remote];
|
|
165
172
|
_removeBundle_decorators = [Remote];
|
|
173
|
+
__esDecorate(this, null, _listVersionExemptions_decorators, { kind: "method", name: "listVersionExemptions", static: false, private: false, access: { has: obj => "listVersionExemptions" in obj, get: obj => obj.listVersionExemptions }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
174
|
+
__esDecorate(this, null, _setVersionExemption_decorators, { kind: "method", name: "setVersionExemption", static: false, private: false, access: { has: obj => "setVersionExemption" in obj, get: obj => obj.setVersionExemption }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
166
175
|
__esDecorate(this, null, _listPlugins_decorators, { kind: "method", name: "listPlugins", static: false, private: false, access: { has: obj => "listPlugins" in obj, get: obj => obj.listPlugins }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
167
176
|
__esDecorate(this, null, _listBundles_decorators, { kind: "method", name: "listBundles", static: false, private: false, access: { has: obj => "listBundles" in obj, get: obj => obj.listBundles }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
168
177
|
__esDecorate(this, null, _registries_decorators, { kind: "method", name: "registries", static: false, private: false, access: { has: obj => "registries" in obj, get: obj => obj.registries }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -181,6 +190,8 @@ let PluginManager = (() => {
|
|
|
181
190
|
outputBytes: z.number().step(1).min(1).default(16384),
|
|
182
191
|
lockWaitMs: z.number().step(1).min(0).default(120000),
|
|
183
192
|
inspectTimeoutMs: z.number().step(1).min(1000).default(20000),
|
|
193
|
+
githubConnectionTimeoutMs: z.number().step(1).min(1000).default(5000),
|
|
194
|
+
idleTimeoutMs: z.number().step(1).min(1000).default(600000),
|
|
184
195
|
registry: z.string().pattern(REGISTRY_URL),
|
|
185
196
|
fallbackRegistries: z.array(z.string().pattern(REGISTRY_URL)).default([NPMMIRROR_REGISTRY]),
|
|
186
197
|
});
|
|
@@ -192,6 +203,8 @@ let PluginManager = (() => {
|
|
|
192
203
|
outputBytes;
|
|
193
204
|
lockWaitMs;
|
|
194
205
|
inspectTimeoutMs;
|
|
206
|
+
githubConnectionTimeoutMs;
|
|
207
|
+
idleTimeoutMs;
|
|
195
208
|
pnpmCommand;
|
|
196
209
|
configuredRegistries;
|
|
197
210
|
ownerContext;
|
|
@@ -208,6 +221,8 @@ let PluginManager = (() => {
|
|
|
208
221
|
this.outputBytes = config.outputBytes;
|
|
209
222
|
this.lockWaitMs = config.lockWaitMs;
|
|
210
223
|
this.inspectTimeoutMs = config.inspectTimeoutMs;
|
|
224
|
+
this.githubConnectionTimeoutMs = config.githubConnectionTimeoutMs;
|
|
225
|
+
this.idleTimeoutMs = config.idleTimeoutMs;
|
|
211
226
|
this.pnpmCommand = config.pnpmCommand;
|
|
212
227
|
this.configuredRegistries = {
|
|
213
228
|
registry: config.registry === undefined ? null : normalizeRegistry(config.registry),
|
|
@@ -218,6 +233,27 @@ let PluginManager = (() => {
|
|
|
218
233
|
await Promise.allSettled([...this.packageOperations]);
|
|
219
234
|
}, 'plugin-manager: package cancellation');
|
|
220
235
|
}
|
|
236
|
+
/** Read exact plugin-version exemptions saved in this profile.
|
|
237
|
+
* @returns Accepted package-name@version keys with the runtime versions they may run on, and any
|
|
238
|
+
* record or file problem the reader rejected, which the caller reports instead of failing.
|
|
239
|
+
*/
|
|
240
|
+
listVersionExemptions() {
|
|
241
|
+
const { exemptions, warnings } = readProfileCompatibility(this.profile.dir);
|
|
242
|
+
return { exemptions, warnings };
|
|
243
|
+
}
|
|
244
|
+
/** Grant or revoke one exact plugin/runtime exemption and reevaluate live plugins.
|
|
245
|
+
* @param packageVersion Exact manifest package name followed by @ and its version; never an installation spec or alias.
|
|
246
|
+
* @param runtimeVersion Exact current DSH version for grants; revocation may name a previous runtime.
|
|
247
|
+
* @param enabled Whether to grant rather than revoke the exemption.
|
|
248
|
+
* @param acceptRisk Required true for grants after the user accepts possible crashes and data loss.
|
|
249
|
+
* @returns Saved and runtime outcomes. Startup-only profiles require restart.
|
|
250
|
+
*/
|
|
251
|
+
setVersionExemption(packageVersion, runtimeVersion, enabled, acceptRisk) {
|
|
252
|
+
return this.change(result => this.configure(async () => {
|
|
253
|
+
await setProfileVersionExemption(this.profile.dir, packageVersion, runtimeVersion, enabled, acceptRisk === true);
|
|
254
|
+
result.warnings = await this.reload();
|
|
255
|
+
}), { stage: 'enable', target: packageVersion, enabled }, 'bundle');
|
|
256
|
+
}
|
|
221
257
|
/** Read current plugins, including why a row cannot be changed through the profile patch.
|
|
222
258
|
* @returns Current runtime entries with persistent patch targets.
|
|
223
259
|
*/
|
|
@@ -245,6 +281,7 @@ let PluginManager = (() => {
|
|
|
245
281
|
*/
|
|
246
282
|
listBundles() {
|
|
247
283
|
const manifest = readProfileManifest('dsh', this.profile.dir);
|
|
284
|
+
const exemptions = readProfileVersionExemptions(this.profile.dir);
|
|
248
285
|
const selected = manifest.dsh?.profile?.bundles ?? [];
|
|
249
286
|
const dependencies = Object.keys(manifest.dependencies ?? {});
|
|
250
287
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8'));
|
|
@@ -264,6 +301,9 @@ let PluginManager = (() => {
|
|
|
264
301
|
...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: { code: 'not-bundle' }, rows: [], overrides: [] });
|
|
265
302
|
continue;
|
|
266
303
|
}
|
|
304
|
+
const compatibility = evaluatePluginCompatibility(info, exemptions);
|
|
305
|
+
if (compatibility !== undefined && !compatibility.exempted)
|
|
306
|
+
throw new ManagementFailure('incompatible-version', [incompatiblePlugin(compatibility)]);
|
|
267
307
|
const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir);
|
|
268
308
|
const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, 'package.json')).href);
|
|
269
309
|
bundles.push({ name, ...(info.version === undefined ? {} : { version: info.version }),
|
|
@@ -420,7 +460,9 @@ let PluginManager = (() => {
|
|
|
420
460
|
}), { stage: 'enable', target: name, enabled }, 'bundle');
|
|
421
461
|
}
|
|
422
462
|
/**
|
|
423
|
-
* Install a package using the same pnpm implementation as dsh plugin.
|
|
463
|
+
* Install a package using the same pnpm implementation as dsh plugin. GitHub
|
|
464
|
+
* repositories get a connection check bounded by githubConnectionTimeoutMs before pnpm starts;
|
|
465
|
+
* only network failures or timeouts stop installation, while pnpm owns authentication and transport fallback. A run
|
|
424
466
|
* that fails, is cancelled, or adds a package without a bundle patch restores
|
|
425
467
|
* `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay.
|
|
426
468
|
* @param spec One package spec, including local paths relative to the invocation directory.
|
|
@@ -431,7 +473,7 @@ let PluginManager = (() => {
|
|
|
431
473
|
installBundle(spec, options) {
|
|
432
474
|
const requestId = options?.requestId;
|
|
433
475
|
const control = { abort: new AbortController(), phase: 'installing', result: Promise.resolve(null) };
|
|
434
|
-
const stopped = () => control.abort.signal.aborted;
|
|
476
|
+
const stopped = () => control.abort.signal.aborted || this.abort.signal.aborted;
|
|
435
477
|
if (requestId !== undefined)
|
|
436
478
|
this.installs.set(requestId, control);
|
|
437
479
|
const announce = (phase, attempt) => {
|
|
@@ -449,12 +491,32 @@ let PluginManager = (() => {
|
|
|
449
491
|
}
|
|
450
492
|
const files = await this.readRestoredFiles();
|
|
451
493
|
const before = readProfileManifest('dsh', this.profile.dir).dependencies ?? {};
|
|
452
|
-
const plan = registryPlan(options?.registry, await this.registries());
|
|
453
494
|
let name;
|
|
454
495
|
try {
|
|
496
|
+
result.registries = [];
|
|
497
|
+
const connection = checkGithubConnection(parsedForRegistry(spec), this.profile.dir, {
|
|
498
|
+
timeoutMs: this.githubConnectionTimeoutMs, outputBytes: this.outputBytes,
|
|
499
|
+
signal: AbortSignal.any([this.abort.signal, control.abort.signal]),
|
|
500
|
+
...this.profile.packageManager?.env === undefined ? {} : { env: this.profile.packageManager.env },
|
|
501
|
+
});
|
|
502
|
+
this.packageOperations.add(connection);
|
|
503
|
+
let connectionFailure;
|
|
504
|
+
try {
|
|
505
|
+
connectionFailure = await connection;
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
this.packageOperations.delete(connection);
|
|
509
|
+
}
|
|
510
|
+
if (stopped())
|
|
511
|
+
throw new InstallCancelledError();
|
|
512
|
+
if (connectionFailure?.kind === 'network' || connectionFailure?.kind === 'timeout') {
|
|
513
|
+
result.packageResult = connectionFailure;
|
|
514
|
+
result.failedAt = 'spec-host';
|
|
515
|
+
throw new Error(connectionFailure.output);
|
|
516
|
+
}
|
|
455
517
|
// The last run is the result's; the registries asked stay listed whatever the outcome.
|
|
518
|
+
const plan = registryPlan(options?.registry, await this.registries());
|
|
456
519
|
let run;
|
|
457
|
-
result.registries = [];
|
|
458
520
|
for (const [index, registry] of plan.entries()) {
|
|
459
521
|
if (index > 0)
|
|
460
522
|
await this.restoreFiles(files);
|
|
@@ -467,12 +529,21 @@ let PluginManager = (() => {
|
|
|
467
529
|
result.packageResult = run;
|
|
468
530
|
if (stopped())
|
|
469
531
|
throw new InstallCancelledError();
|
|
470
|
-
|
|
471
|
-
if (run.
|
|
532
|
+
// A compatibility refusal is the package's own answer, so no other registry is asked.
|
|
533
|
+
if (run.incompatible !== undefined)
|
|
534
|
+
throw new ManagementFailure('incompatible-version', run.incompatible);
|
|
535
|
+
// A run this manager terminated is not a success, even when pnpm trapped the signal and exited 0.
|
|
536
|
+
if (run.exitCode === 0 && run.timedOut !== true)
|
|
537
|
+
break;
|
|
538
|
+
/* v8 ignore next 2 -- runPnpm classifies every run it does not report as succeeded */
|
|
539
|
+
if (run.kind === undefined)
|
|
472
540
|
break;
|
|
473
|
-
const failedAt = attributeFailure(run.kind, run.output, parsedForRegistry(spec));
|
|
474
541
|
// What the last failed run could not reach; a later run that succeeds leaves nothing to say.
|
|
475
542
|
delete result.failedAt;
|
|
543
|
+
// A run this manager terminated got no answer from the registry at all, so no registry explains it.
|
|
544
|
+
if (run.timedOut === true)
|
|
545
|
+
break;
|
|
546
|
+
const failedAt = attributeFailure(run.kind, run.output, parsedForRegistry(spec));
|
|
476
547
|
if (failedAt !== 'other')
|
|
477
548
|
result.failedAt = failedAt;
|
|
478
549
|
if (failedAt !== 'registry' || index === plan.length - 1)
|
|
@@ -481,9 +552,11 @@ let PluginManager = (() => {
|
|
|
481
552
|
/* v8 ignore next -- the plan is never empty, so a run always settled */
|
|
482
553
|
if (run === undefined)
|
|
483
554
|
throw new Error('no registry was asked');
|
|
484
|
-
|
|
555
|
+
// A terminated run reports no usable exit status, so neither its files nor its bundle are trusted.
|
|
556
|
+
const succeeded = run.exitCode === 0 && run.timedOut !== true;
|
|
557
|
+
if (succeeded)
|
|
485
558
|
delete result.failedAt;
|
|
486
|
-
if (
|
|
559
|
+
if (!succeeded) {
|
|
487
560
|
// pnpm-workspace.yaml is not restored, so the names pnpm left undecided there can be offered for approval.
|
|
488
561
|
try {
|
|
489
562
|
result.pendingBuilds = await readPendingBuilds(this.profile.dir);
|
|
@@ -506,6 +579,9 @@ let PluginManager = (() => {
|
|
|
506
579
|
const manifest = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
507
580
|
if (manifest?.dsh?.bundle === undefined)
|
|
508
581
|
throw new ManagementFailure('not-bundle');
|
|
582
|
+
const compatibility = evaluatePluginCompatibility(manifest, readProfileVersionExemptions(this.profile.dir));
|
|
583
|
+
if (compatibility !== undefined && !compatibility.exempted)
|
|
584
|
+
throw new ManagementFailure('incompatible-version', [incompatiblePlugin(compatibility)]);
|
|
509
585
|
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle))
|
|
510
586
|
loadOverlayPatches('dsh', file);
|
|
511
587
|
}
|
|
@@ -542,7 +618,7 @@ let PluginManager = (() => {
|
|
|
542
618
|
}
|
|
543
619
|
/** Stop an installation this manager owns and wait until its files are back.
|
|
544
620
|
* @param requestId The id the installation was started with.
|
|
545
|
-
* @returns `cancelled` once pnpm exited and the files are restored, `too-late` once the bundle is being
|
|
621
|
+
* @returns `cancelled` once the Git check or pnpm exited and the files are restored, `too-late` once the bundle is being
|
|
546
622
|
* applied, `not-running` for any other id.
|
|
547
623
|
*/
|
|
548
624
|
async cancelInstall(requestId) {
|
|
@@ -583,8 +659,9 @@ let PluginManager = (() => {
|
|
|
583
659
|
}
|
|
584
660
|
});
|
|
585
661
|
result.packageResult = await this.runPnpm(['remove', name]);
|
|
586
|
-
if (result.packageResult.exitCode !== 0)
|
|
662
|
+
if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) {
|
|
587
663
|
throw new Error(result.packageResult.output);
|
|
664
|
+
}
|
|
588
665
|
}, { stage: 'remove', target: name }, 'remove');
|
|
589
666
|
}
|
|
590
667
|
/** The rows a bundle's patch inserts and the existing rows it changes; an unreadable patch throws. */
|
|
@@ -629,7 +706,8 @@ let PluginManager = (() => {
|
|
|
629
706
|
const task = runProfilePnpm({ ...this.profile, profile: this.profile.name }, args, {
|
|
630
707
|
execution: 'service', ...this.profile.packageManager ?? { command: this.pnpmCommand },
|
|
631
708
|
signal: signal === undefined ? this.abort.signal : AbortSignal.any([this.abort.signal, signal]),
|
|
632
|
-
outputBytes: this.outputBytes, activateNewBundles: false,
|
|
709
|
+
outputBytes: this.outputBytes, activateNewBundles: false, idleTimeoutMs: this.idleTimeoutMs,
|
|
710
|
+
lookupTimeoutMs: this.inspectTimeoutMs,
|
|
633
711
|
onOutput: (text, stream) => {
|
|
634
712
|
this.ownerContext.emit('plugin-manager/install-log', { ...identity, jobId, argv, cwd, stream, text });
|
|
635
713
|
},
|
|
@@ -640,7 +718,13 @@ let PluginManager = (() => {
|
|
|
640
718
|
this.ownerContext.emit('plugin-manager/install-log', {
|
|
641
719
|
...identity, jobId, argv, cwd, stream: 'stdout', text: '', exitCode: signal?.aborted === true ? null : result.exitCode,
|
|
642
720
|
});
|
|
643
|
-
|
|
721
|
+
// A terminated run keeps its own kind even when pnpm trapped the signal and exited 0.
|
|
722
|
+
if (result.exitCode === 0 && result.timedOut !== true)
|
|
723
|
+
return result;
|
|
724
|
+
return {
|
|
725
|
+
...result,
|
|
726
|
+
kind: classifyInstallFailure({ log: result.output, ...result.timedOut === true ? { timedOut: true } : {} }),
|
|
727
|
+
};
|
|
644
728
|
}
|
|
645
729
|
catch (error) {
|
|
646
730
|
this.ownerContext.emit('plugin-manager/install-log', { ...identity, jobId, argv, cwd, stream: 'stderr', text: messageOf(error), exitCode: null });
|
|
@@ -671,11 +755,17 @@ let PluginManager = (() => {
|
|
|
671
755
|
async selectBundle(name, enabled) {
|
|
672
756
|
const manifest = readProfileManifest('dsh', this.profile.dir);
|
|
673
757
|
const previous = manifest.dsh?.profile?.bundles ?? [];
|
|
674
|
-
if (
|
|
675
|
-
|
|
758
|
+
if (enabled || !previous.includes(name)) {
|
|
759
|
+
const metadata = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
760
|
+
if (metadata === undefined)
|
|
761
|
+
throw new ManagementFailure('not-bundle');
|
|
762
|
+
if (enabled) {
|
|
763
|
+
const compatibility = evaluatePluginCompatibility(metadata, readProfileVersionExemptions(this.profile.dir));
|
|
764
|
+
if (compatibility !== undefined && !compatibility.exempted)
|
|
765
|
+
throw new ManagementFailure('incompatible-version', [incompatiblePlugin(compatibility)]);
|
|
766
|
+
this.bundleRows(name);
|
|
767
|
+
}
|
|
676
768
|
}
|
|
677
|
-
if (enabled)
|
|
678
|
-
this.bundleRows(name);
|
|
679
769
|
if (!enabled && previous.includes(name)) {
|
|
680
770
|
if (this.protectsManager(name))
|
|
681
771
|
throw new ManagementFailure('management-required');
|
|
@@ -745,7 +835,7 @@ let PluginManager = (() => {
|
|
|
745
835
|
}, { waitMs: this.lockWaitMs });
|
|
746
836
|
}
|
|
747
837
|
diskState() {
|
|
748
|
-
return ['package.json', 'cordis.patch.yml', 'pnpm-workspace.yaml'].map((file) => {
|
|
838
|
+
return ['package.json', 'cordis.patch.yml', 'pnpm-workspace.yaml', PROFILE_COMPATIBILITY_FILENAME].map((file) => {
|
|
749
839
|
try {
|
|
750
840
|
return readFileSync(join(this.profile.dir, file), 'utf8');
|
|
751
841
|
}
|
|
@@ -12,6 +12,7 @@ const LOG_KINDS = [
|
|
|
12
12
|
['disk-full', /\bENOSPC\b|no space left on device/i],
|
|
13
13
|
['permission', /\bEACCES\b|\bEPERM\b|permission denied/i],
|
|
14
14
|
['integrity', /ERR_PNPM_TARBALL_INTEGRITY|ERR_PNPM_BAD_TARBALL_SIZE|\bEINTEGRITY\b/],
|
|
15
|
+
['unknown', /The requested URL returned error: 40[134]\b/],
|
|
15
16
|
['network', /\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|ERR_PNPM_META_FETCH_FAIL|ERR_PNPM_FETCH_5\d\d|ERR_PNPM_FETCH_TIMEOUT|\bFETCH_ERROR\b|socket hang up|Could not resolve host|unable to access/],
|
|
16
17
|
];
|
|
17
18
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ProfileManifest } from '@deepseek-ai/dsh-app-boot';
|
|
2
2
|
import type { PackageResult, Registry } from './types.ts';
|
|
3
|
+
export { setProfileVersionExemption, readProfileVersionExemptions } from '@deepseek-ai/dsh-app-boot';
|
|
3
4
|
/** Profile and invocation locations supplied by the launcher. */
|
|
4
5
|
export interface PackageOperationContext {
|
|
5
6
|
profile: string;
|
|
@@ -24,6 +25,15 @@ export interface PackageOperationOptions {
|
|
|
24
25
|
onOutput?: (text: string, stream: 'stdout' | 'stderr') => void;
|
|
25
26
|
activateNewBundles?: boolean;
|
|
26
27
|
lockWaitMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Terminate the run once its captured output has been silent for this long, in
|
|
30
|
+
* milliseconds. A run stopped this way reports `timedOut`; without a bound, a
|
|
31
|
+
* child that stops progressing without exiting holds its caller forever. A run
|
|
32
|
+
* with inherited descriptors captures nothing and is never bound.
|
|
33
|
+
*/
|
|
34
|
+
idleTimeoutMs?: number;
|
|
35
|
+
/** Bound on the pre-install registry lookup, in milliseconds; without one a fixed bound applies. */
|
|
36
|
+
lookupTimeoutMs?: number;
|
|
27
37
|
}
|
|
28
38
|
/** Resolve relative package specs against the caller's directory.
|
|
29
39
|
* @param argument One pnpm argument.
|
|
@@ -44,10 +54,14 @@ export declare function bundleManifest(name: string, dir: string, anchor: string
|
|
|
44
54
|
*/
|
|
45
55
|
export declare function saveManifest(dir: string, manifest: ProfileManifest): Promise<void>;
|
|
46
56
|
/** Execute pnpm inside a profile whose caller already holds the profile write lock.
|
|
57
|
+
* Newly installed or updated direct dependencies are checked even when activation is disabled;
|
|
58
|
+
* an untouched dependency never blocks an unrelated operation and stays denied at startup.
|
|
59
|
+
* Compatibility denial restores the profile manifest and lockfile, but leaves downloaded modules on disk.
|
|
47
60
|
* @param context Launcher-owned profile and resolution locations.
|
|
48
61
|
* @param args Pnpm arguments, before relative path anchoring.
|
|
49
62
|
* @param options Output, activation and cancellation policy.
|
|
50
|
-
* @returns Exit status
|
|
63
|
+
* @returns Exit status, whether the silence bound stopped the run, and the diagnostic path.
|
|
64
|
+
* A compatibility denial returns exit code 1. Service output is bounded; CLI output uses inherited descriptors.
|
|
51
65
|
*/
|
|
52
66
|
export declare function runProfilePnpm(context: PackageOperationContext, args: readonly string[], options: PackageOperationOptions): Promise<PackageResult>;
|
|
53
67
|
/** Initialize and run the dsh plugin command with the same write lock as the service.
|