dsh-waifu-agents 0.2.1 → 0.2.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/client.js +48 -3
- package/index.js +177 -28
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -149,6 +149,9 @@ window.__ModuleLoader__.load({
|
|
|
149
149
|
updateInstallingFrom: '正在从 {registry} 安装(第 {index}/{total} 个源)…',
|
|
150
150
|
updateApplying: '正在应用配置,请稍候…',
|
|
151
151
|
updateCancelling: '正在停止安装…',
|
|
152
|
+
updateRemoving: '正在卸载旧版本…',
|
|
153
|
+
updateReinstalling: '正在安装新版本…',
|
|
154
|
+
updateHandedOff: '插件已随更新重启;到「插件」页确认版本即可。',
|
|
152
155
|
updateWaiting: '安装进行中,别关掉这个窗口',
|
|
153
156
|
updateElapsed: '已用 {s} 秒',
|
|
154
157
|
updateQuiet: '已经 {s} 秒没等到平台的进度回报,但 pnpm 还在跑',
|
|
@@ -395,6 +398,9 @@ window.__ModuleLoader__.load({
|
|
|
395
398
|
updateInstallingFrom: 'Installing from {registry} (source {index} of {total})…',
|
|
396
399
|
updateApplying: 'Applying the configuration, please wait…',
|
|
397
400
|
updateCancelling: 'Stopping the installation…',
|
|
401
|
+
updateRemoving: 'Removing the old version…',
|
|
402
|
+
updateReinstalling: 'Installing the new version…',
|
|
403
|
+
updateHandedOff: 'The plugin restarted with the update; confirm the version on the Plugins page.',
|
|
398
404
|
updateWaiting: 'Installing — leave this window open',
|
|
399
405
|
updateElapsed: '{s}s elapsed',
|
|
400
406
|
updateQuiet: 'no progress report from the platform for {s}s, but pnpm is still running',
|
|
@@ -1401,7 +1407,16 @@ window.__ModuleLoader__.load({
|
|
|
1401
1407
|
const phase = String(job.phase ?? 'starting');
|
|
1402
1408
|
let floor = 3;
|
|
1403
1409
|
let ceiling = 10;
|
|
1404
|
-
if (phase === '
|
|
1410
|
+
if (phase === 'removing') {
|
|
1411
|
+
// The self-removal before the reinstall: brief, but not instant.
|
|
1412
|
+
floor = 6;
|
|
1413
|
+
ceiling = 16;
|
|
1414
|
+
} else if (phase === 'reinstalling') {
|
|
1415
|
+
// The fresh install after the self-removal, before the platform's own
|
|
1416
|
+
// per-registry phases take over and redraw the band.
|
|
1417
|
+
floor = 14;
|
|
1418
|
+
ceiling = 24;
|
|
1419
|
+
} else if (phase === 'installing') {
|
|
1405
1420
|
// One band per registry tried, so a fallback to a second source is
|
|
1406
1421
|
// visible as progress instead of as a bar going backwards.
|
|
1407
1422
|
const index = Math.max(1, Number(job.attempt?.index ?? 1) || 1);
|
|
@@ -1431,10 +1446,13 @@ window.__ModuleLoader__.load({
|
|
|
1431
1446
|
*/
|
|
1432
1447
|
function progressHeadline(job, t) {
|
|
1433
1448
|
if (job === null || job === undefined) return '';
|
|
1449
|
+
if (job.handedOff === true) return t('updateHandedOff');
|
|
1434
1450
|
if (job.done === true) {
|
|
1435
1451
|
return typeof job.error === 'string' && job.error !== '' ? t('updateFailedTitle') : t('updateDoneTitle');
|
|
1436
1452
|
}
|
|
1437
1453
|
const phase = String(job.phase ?? 'starting');
|
|
1454
|
+
if (phase === 'removing') return t('updateRemoving');
|
|
1455
|
+
if (phase === 'reinstalling') return t('updateReinstalling');
|
|
1438
1456
|
if (phase === 'installing') {
|
|
1439
1457
|
const attempt = job.attempt;
|
|
1440
1458
|
const total = Number(attempt?.total ?? 1) || 1;
|
|
@@ -1495,6 +1513,7 @@ window.__ModuleLoader__.load({
|
|
|
1495
1513
|
.replace('{s}', String(seconds));
|
|
1496
1514
|
}
|
|
1497
1515
|
if (typeof job?.error === 'string' && job.error !== '') return t('updateStripFailed');
|
|
1516
|
+
if (job?.handedOff === true) return t('updateHandedOff');
|
|
1498
1517
|
return t('updateStripDone').replace('{v}', String(job?.result?.version ?? job?.version ?? ''));
|
|
1499
1518
|
}
|
|
1500
1519
|
|
|
@@ -1547,7 +1566,17 @@ window.__ModuleLoader__.load({
|
|
|
1547
1566
|
{ style: { ...L.dialog, maxWidth: '480px', width: '100%' } },
|
|
1548
1567
|
h('span', { style: L.rowTitle }, t('updateProgressTitle')),
|
|
1549
1568
|
h('div', { style: L.progressTrack }, h('div', { style: fill })),
|
|
1550
|
-
h(
|
|
1569
|
+
h(
|
|
1570
|
+
'span',
|
|
1571
|
+
{ style: failed ? L.error : L.name },
|
|
1572
|
+
failed
|
|
1573
|
+
? String(job.error)
|
|
1574
|
+
: job.handedOff === true
|
|
1575
|
+
? t('updateHandedOff')
|
|
1576
|
+
: done
|
|
1577
|
+
? progressOutcome(job, t)
|
|
1578
|
+
: progressHeadline(job, t),
|
|
1579
|
+
),
|
|
1551
1580
|
note === '' ? null : h('span', { style: L.meta }, note),
|
|
1552
1581
|
exit === '' ? null : h('span', { style: L.meta }, exit),
|
|
1553
1582
|
log === ''
|
|
@@ -3356,7 +3385,23 @@ window.__ModuleLoader__.load({
|
|
|
3356
3385
|
const poll = () => {
|
|
3357
3386
|
post({ op: 'updateProgress', requestId })
|
|
3358
3387
|
.then((payload) => {
|
|
3359
|
-
if (!alive
|
|
3388
|
+
if (!alive) return;
|
|
3389
|
+
if (payload.idle === true) {
|
|
3390
|
+
/*
|
|
3391
|
+
* The host no longer knows this job. That is the two-step
|
|
3392
|
+
* update succeeding the loud way: the removal plus reinstall
|
|
3393
|
+
* ended in an HMR reload, the module that owned the job is
|
|
3394
|
+
* gone, and the fresh module answering polls has never heard
|
|
3395
|
+
* of this requestId. Report it as a hand-off to the restart
|
|
3396
|
+
* instead of letting the bar sit on its last phase forever.
|
|
3397
|
+
*/
|
|
3398
|
+
setJob((prev) =>
|
|
3399
|
+
prev !== null && prev !== undefined && prev.done !== true
|
|
3400
|
+
? { ...prev, done: true, handedOff: true, error: null }
|
|
3401
|
+
: prev,
|
|
3402
|
+
);
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3360
3405
|
setJob((prev) => (prev === null || prev === undefined ? prev : { ...prev, ...payload }));
|
|
3361
3406
|
})
|
|
3362
3407
|
.catch(() => {
|
package/index.js
CHANGED
|
@@ -40,12 +40,31 @@
|
|
|
40
40
|
*
|
|
41
41
|
* `updateApply` is what makes a published fix reachable without a shell: the
|
|
42
42
|
* panel posts it, `latestVersion` asks the registry, and the install goes
|
|
43
|
-
* through the very service the Plugins page uses
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
43
|
+
* through the very service the Plugins page uses. Three layers, tried in
|
|
44
|
+
* order:
|
|
45
|
+
*
|
|
46
|
+
* 1. TARBALL (updates of an installed package): the new version's tarball URL
|
|
47
|
+
* is the one spec shape the manager's HMR precheck cannot match by name —
|
|
48
|
+
* its package name is only known after pnpm runs — so the install reaches
|
|
49
|
+
* pnpm over the installed copy, and the HMR re-composition replaces the
|
|
50
|
+
* running plugin in place. No removal, no restart in the success path.
|
|
51
|
+
* This also rewrites the profile's saved dependency from a version range
|
|
52
|
+
* to the exact URL; the next update's URL differs again, so nothing is
|
|
53
|
+
* ever refused as already-installed on this road twice.
|
|
54
|
+
* 2. NAME (fresh installs, and the two-step's second half): the plain
|
|
55
|
+
* `name@version` spec keeps a fresh install's saved dependency a clean
|
|
56
|
+
* range.
|
|
57
|
+
* 3. TWO-STEP (fallback if the tarball road is somehow refused with
|
|
58
|
+
* `already-installed`): remove the running bundle, then install the new
|
|
59
|
+
* version from the same fiber — a resolved promise never interrupts the
|
|
60
|
+
* fiber that awaited it. Whether the fiber survives its own removal is
|
|
61
|
+
* what real-machine use finds out; every failure is caught and reported
|
|
62
|
+
* with the manual remove-then-install on the Plugins page as the last
|
|
63
|
+
* resort.
|
|
64
|
+
*
|
|
65
|
+
* The personas directory and `teams.json` live outside the package, so no
|
|
66
|
+
* path through any layer — success, half-done, or failure — touches user
|
|
67
|
+
* data.
|
|
49
68
|
*
|
|
50
69
|
* No configuration is required: `resolveDir` picks the directory from the row
|
|
51
70
|
* config, then from `<home>/personas`, then — on a machine that does not have it
|
|
@@ -228,6 +247,50 @@ async function latestVersion(registry) {
|
|
|
228
247
|
return { version: latest, registry: base };
|
|
229
248
|
}
|
|
230
249
|
|
|
250
|
+
/**
|
|
251
|
+
* The tarball URL of a published version.
|
|
252
|
+
*
|
|
253
|
+
* This is the one spec shape the manager's HMR precheck CANNOT refuse with
|
|
254
|
+
* `already-installed`: the precheck matches specs it can name before pnpm
|
|
255
|
+
* runs — a registry name, a local directory, or the exact git/tarball spec the
|
|
256
|
+
* dependency was saved with — and a fresh tarball URL names a package only
|
|
257
|
+
* after pnpm has fetched it. Installing version N+1 from its own tarball
|
|
258
|
+
* therefore reaches pnpm over an installed version N, and the HMR
|
|
259
|
+
* re-composition replaces the running plugin in place (the documented
|
|
260
|
+
* behavior). The abbreviated packument names the URL authoritatively
|
|
261
|
+
* (`dist.tarball`), so one small GET asks the registry; a registry that will
|
|
262
|
+
* not answer falls back to the npm tarball path convention, which every
|
|
263
|
+
* registry this plugin can be pointed at follows
|
|
264
|
+
* (`<base>/<name>/-/<name>-<version>.tgz`). Constructing never throws, so this
|
|
265
|
+
* helper always returns a usable URL.
|
|
266
|
+
* @param {string} [registry] registry base URL; the row config may override it
|
|
267
|
+
* @param {string} version exact published version
|
|
268
|
+
* @returns {Promise<string>}
|
|
269
|
+
*/
|
|
270
|
+
async function tarballUrlOf(registry, version) {
|
|
271
|
+
const base = String(registry ?? "").trim() || DEFAULT_REGISTRY;
|
|
272
|
+
const construct = () =>
|
|
273
|
+
new URL(
|
|
274
|
+
`${PACKAGE_NAME}/-/${PACKAGE_NAME}-${version}.tgz`,
|
|
275
|
+
base.endsWith("/") ? base : `${base}/`,
|
|
276
|
+
).href;
|
|
277
|
+
if (typeof fetch !== "function") return construct();
|
|
278
|
+
try {
|
|
279
|
+
const url = new URL(PACKAGE_NAME, base.endsWith("/") ? base : `${base}/`)
|
|
280
|
+
.href;
|
|
281
|
+
const response = await fetch(url, {
|
|
282
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
283
|
+
signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS),
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok) return construct();
|
|
286
|
+
const meta = await response.json();
|
|
287
|
+
const tarball = meta?.versions?.[version]?.dist?.tarball;
|
|
288
|
+
return typeof tarball === "string" && tarball !== "" ? tarball : construct();
|
|
289
|
+
} catch {
|
|
290
|
+
return construct();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
231
294
|
/**
|
|
232
295
|
* The shared skeleton: a reserved name in the persona directory that is never a
|
|
233
296
|
* wife. It is the frame a `template:`-using persona is poured into, with
|
|
@@ -4295,20 +4358,20 @@ export function apply(ctx, config = {}) {
|
|
|
4295
4358
|
* Install the newest published version, through the same service the Plugins
|
|
4296
4359
|
* page uses.
|
|
4297
4360
|
*
|
|
4298
|
-
* The exact version is passed, never a range.
|
|
4299
|
-
*
|
|
4300
|
-
*
|
|
4301
|
-
*
|
|
4302
|
-
*
|
|
4303
|
-
*
|
|
4304
|
-
*
|
|
4305
|
-
*
|
|
4306
|
-
* first
|
|
4307
|
-
* `
|
|
4308
|
-
*
|
|
4309
|
-
*
|
|
4310
|
-
*
|
|
4311
|
-
*
|
|
4361
|
+
* The exact version is passed, never a range. The spec it installs FROM
|
|
4362
|
+
* depends on what is already on disk (see the comment at the choice below):
|
|
4363
|
+
* an update goes through the new version's tarball URL — the spec shape the
|
|
4364
|
+
* HMR precheck cannot refuse — and a fresh install keeps the plain
|
|
4365
|
+
* `name@version`, whose install rewrites the profile's declared range.
|
|
4366
|
+
*
|
|
4367
|
+
* `minimumReleaseAge`: a registry-name install is subject to the 24h
|
|
4368
|
+
* freshness policy and must be recorded in the workspace's
|
|
4369
|
+
* `minimumReleaseAgeExclude` first, or the lockfile check refuses outright
|
|
4370
|
+
* with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`. A tarball URL install
|
|
4371
|
+
* fetches the package file directly and plausibly never consults publish
|
|
4372
|
+
* times — expected but NOT verified against the manager's pnpm invocation,
|
|
4373
|
+
* so the failure, should it come, is surfaced verbatim rather than promised
|
|
4374
|
+
* away.
|
|
4312
4375
|
*
|
|
4313
4376
|
* Returns as soon as the install is STARTED, not when it finishes, and hands
|
|
4314
4377
|
* back the `requestId` that makes the platform publish progress for it. The
|
|
@@ -4375,6 +4438,25 @@ export function apply(ctx, config = {}) {
|
|
|
4375
4438
|
},
|
|
4376
4439
|
};
|
|
4377
4440
|
}
|
|
4441
|
+
/*
|
|
4442
|
+
* Which spec to install from, decided by what is already on disk.
|
|
4443
|
+
*
|
|
4444
|
+
* An UPDATE of an installed package installs from the new version's
|
|
4445
|
+
* tarball URL: that is the one spec shape the HMR precheck cannot match by
|
|
4446
|
+
* name, so it reaches pnpm over the installed copy and the re-composition
|
|
4447
|
+
* replaces the running plugin in place — no removal, no fiber gamble. It
|
|
4448
|
+
* also rewrites the profile's saved dependency from a version range to the
|
|
4449
|
+
* exact URL, which is deliberate: the next update's URL differs again, so
|
|
4450
|
+
* no future install is ever refused as already-installed.
|
|
4451
|
+
*
|
|
4452
|
+
* A FRESH install (nothing readable on disk) keeps the registry name:
|
|
4453
|
+
* there is nothing installed for the precheck to refuse, and the saved
|
|
4454
|
+
* spec stays a plain dependency. If that judgement is wrong — the copy on
|
|
4455
|
+
* disk was unreadable, not absent — the name spec is refused with
|
|
4456
|
+
* `already-installed` and the two-step below takes over.
|
|
4457
|
+
*/
|
|
4458
|
+
let spec = `${PACKAGE_NAME}@${version}`;
|
|
4459
|
+
if (before !== null) spec = await tarballUrlOf(config.registry, version);
|
|
4378
4460
|
/*
|
|
4379
4461
|
* The id is minted here, or accepted from the panel when it sent one. It
|
|
4380
4462
|
* exists because the platform publishes progress ONLY for an installation
|
|
@@ -4411,25 +4493,92 @@ export function apply(ctx, config = {}) {
|
|
|
4411
4493
|
*/
|
|
4412
4494
|
void (async () => {
|
|
4413
4495
|
try {
|
|
4414
|
-
const outcome = await manager.installBundle(
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
);
|
|
4418
|
-
job.done = true;
|
|
4496
|
+
const outcome = await manager.installBundle(spec, {
|
|
4497
|
+
enabled: true,
|
|
4498
|
+
requestId,
|
|
4499
|
+
});
|
|
4419
4500
|
if (outcome?.application === "failed") {
|
|
4501
|
+
/*
|
|
4502
|
+
* The tarball spec should never be refused with `already-installed`
|
|
4503
|
+
* (its package name is only known after pnpm runs), and a fresh
|
|
4504
|
+
* install has nothing installed to refuse. Landing here anyway
|
|
4505
|
+
* means the manager named the installed package from the spec
|
|
4506
|
+
* after all, or the disk judgement above was wrong — either way the
|
|
4507
|
+
* documented cross-version path is remove-then-install, and the
|
|
4508
|
+
* fiber that awaited the removal is never interrupted by the
|
|
4509
|
+
* resolution itself, so this fallback performs the two-step right
|
|
4510
|
+
* here: remove, then install the new version from the same fiber
|
|
4511
|
+
* under its plain registry name. Whether the host lets the fiber
|
|
4512
|
+
* survive its own removal is what real-machine use finds out; every
|
|
4513
|
+
* way it can die is caught below and reported with the manual path
|
|
4514
|
+
* as the last resort. Nothing here can touch user data — personas
|
|
4515
|
+
* and teams.json are outside the package.
|
|
4516
|
+
*/
|
|
4517
|
+
const code = String(outcome.error?.code ?? outcome.error?.message ?? "");
|
|
4518
|
+
if (code.includes("already-installed")) {
|
|
4519
|
+
ctx.logger?.warn?.(
|
|
4520
|
+
`dsh-waifu-agents: installBundle refused ${version} (already-installed); performing remove-then-install.`,
|
|
4521
|
+
);
|
|
4522
|
+
job.phase = "removing";
|
|
4523
|
+
try {
|
|
4524
|
+
await manager.removeBundle(PACKAGE_NAME);
|
|
4525
|
+
} catch (removeFailure) {
|
|
4526
|
+
job.done = true;
|
|
4527
|
+
job.error = `卸载旧版本失败(${removeFailure?.message ?? removeFailure}),已安装的版本没有变动。请到「插件」页手动卸载后重新安装 ${version}。`;
|
|
4528
|
+
return;
|
|
4529
|
+
}
|
|
4530
|
+
/*
|
|
4531
|
+
* Still running — the fiber survived its own removal. The spec no
|
|
4532
|
+
* longer names an installed package, so the precheck lets the new
|
|
4533
|
+
* version through to pnpm.
|
|
4534
|
+
*/
|
|
4535
|
+
job.phase = "reinstalling";
|
|
4536
|
+
let retry = null;
|
|
4537
|
+
try {
|
|
4538
|
+
retry = await manager.installBundle(`${PACKAGE_NAME}@${version}`, {
|
|
4539
|
+
enabled: true,
|
|
4540
|
+
requestId,
|
|
4541
|
+
});
|
|
4542
|
+
} catch (installFailure) {
|
|
4543
|
+
job.done = true;
|
|
4544
|
+
job.error = `旧版本已卸载,但安装 ${version} 失败:${installFailure?.message ?? installFailure}。请到「插件」页重新安装——人设目录(personas)和团队配置(teams.json)不在包里,不受影响。`;
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
if (retry?.application === "failed") {
|
|
4548
|
+
job.done = true;
|
|
4549
|
+
job.error = `旧版本已卸载,但安装 ${version} 失败:${retry.error?.message ?? retry.error?.code ?? JSON.stringify(retry.error)}。请到「插件」页重新安装——人设目录(personas)和团队配置(teams.json)不在包里,不受影响。`;
|
|
4550
|
+
if (job.log === "") job.log = String(retry?.packageResult?.output ?? "");
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
job.done = true;
|
|
4554
|
+
job.result = {
|
|
4555
|
+
ok: true,
|
|
4556
|
+
version,
|
|
4557
|
+
previous: before,
|
|
4558
|
+
application: retry?.application ?? null,
|
|
4559
|
+
changed: retry?.changed === true,
|
|
4560
|
+
installed: readOwnVersion(),
|
|
4561
|
+
log: String(retry?.packageResult?.output ?? ""),
|
|
4562
|
+
twoStep: true,
|
|
4563
|
+
};
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
job.done = true;
|
|
4420
4567
|
job.error = `安装失败:${outcome.error?.message ?? outcome.error?.code ?? JSON.stringify(outcome.error)}`;
|
|
4421
4568
|
// The manager's own transcript is the useful half of this failure,
|
|
4422
4569
|
// and it is only here if the streaming feed never spoke.
|
|
4423
4570
|
if (job.log === "") job.log = String(outcome?.packageResult?.output ?? "");
|
|
4424
4571
|
return;
|
|
4425
4572
|
}
|
|
4573
|
+
job.done = true;
|
|
4426
4574
|
job.result = {
|
|
4427
4575
|
ok: true,
|
|
4428
4576
|
version,
|
|
4429
4577
|
previous: before,
|
|
4430
|
-
//
|
|
4431
|
-
//
|
|
4432
|
-
// rather than a promise this code cannot
|
|
4578
|
+
// Without HMR this is `restart-required`; with HMR a fresh install
|
|
4579
|
+
// (the two-step above included) may genuinely apply. The manager's
|
|
4580
|
+
// own word is passed through rather than a promise this code cannot
|
|
4581
|
+
// keep.
|
|
4433
4582
|
application: outcome?.application ?? null,
|
|
4434
4583
|
changed: outcome?.changed === true,
|
|
4435
4584
|
installed: readOwnVersion(),
|
package/package.json
CHANGED