dsh-waifu-agents 0.2.2 → 0.2.3
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 +34 -5
- package/index.js +170 -9
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -259,12 +259,14 @@ window.__ModuleLoader__.load({
|
|
|
259
259
|
purgeHint: '把本插件留在文件系统里的东西一次清掉:会话覆盖记录、全局默认人设指向。打开右边开关会连人设目录一起删。',
|
|
260
260
|
purgeWithDir: '连人设目录一起删',
|
|
261
261
|
purgeAskTitle: '彻底清理本插件的数据',
|
|
262
|
-
purgeAskBody: '
|
|
262
|
+
purgeAskBody: '会删除:会话覆盖记录、全局默认人设指向,以及 pnpm-workspace.yaml 里属于本包的放行记录。安装日志、pnpm 索引属于平台,这里不动它们,清理完会列给你。',
|
|
263
263
|
purgeAskAck: '我知道这会清掉本插件的数据',
|
|
264
264
|
updateBanner: '有新版本 {latest}(当前 {installed})',
|
|
265
265
|
updateGo: '查看',
|
|
266
266
|
checkUpdate: '检查更新',
|
|
267
267
|
recheck: '重新检查更新',
|
|
268
|
+
updateUpToDate: '已是最新版本 {installed}',
|
|
269
|
+
updateCheckFailed: '检查更新失败',
|
|
268
270
|
upToDate: '已是最新版本',
|
|
269
271
|
canUpdateTo: '可更新到',
|
|
270
272
|
nowVersion: '当前',
|
|
@@ -505,12 +507,14 @@ window.__ModuleLoader__.load({
|
|
|
505
507
|
purgeHint: 'Remove everything this plugin left on disk in one go: the session override store and the global-default pointer. The switch on the right also deletes the persona directory.',
|
|
506
508
|
purgeWithDir: 'Also delete the persona directory',
|
|
507
509
|
purgeAskTitle: 'Purge this plugin\'s data',
|
|
508
|
-
purgeAskBody: 'Deletes the session override store
|
|
510
|
+
purgeAskBody: 'Deletes the session override store, the global-default pointer, and this package\'s own entries in pnpm-workspace.yaml\'s release-age allow-list. The install logs and the pnpm store index belong to the platform and are listed afterwards instead of touched.',
|
|
509
511
|
purgeAskAck: 'I know this removes the plugin\'s data',
|
|
510
512
|
updateBanner: 'Version {latest} is out (running {installed})',
|
|
511
513
|
updateGo: 'Details',
|
|
512
514
|
checkUpdate: 'Check for updates',
|
|
513
515
|
recheck: 'Check again',
|
|
516
|
+
updateUpToDate: 'Already on the newest version {installed}',
|
|
517
|
+
updateCheckFailed: 'The update check failed',
|
|
514
518
|
upToDate: 'Up to date',
|
|
515
519
|
canUpdateTo: 'Update to',
|
|
516
520
|
nowVersion: 'running',
|
|
@@ -2391,8 +2395,10 @@ window.__ModuleLoader__.load({
|
|
|
2391
2395
|
),
|
|
2392
2396
|
// No preset, no switch: a default has to name something the
|
|
2393
2397
|
// registry holds, and this team is not registered yet (the sync
|
|
2394
|
-
// is in flight, or it failed).
|
|
2395
|
-
|
|
2398
|
+
// is in flight, or it failed). The disabled switch says it on
|
|
2399
|
+
// its own — an inline hint used to sit here and squeezed the
|
|
2400
|
+
// row out of shape on long copy, so it is gone; the refusal
|
|
2401
|
+
// stays said, in the editor, if someone forces the write.
|
|
2396
2402
|
h('span', { style: L.push }),
|
|
2397
2403
|
known
|
|
2398
2404
|
? h(SwitchBox, {
|
|
@@ -4159,9 +4165,32 @@ window.__ModuleLoader__.load({
|
|
|
4159
4165
|
async function checkUpdate() {
|
|
4160
4166
|
setBusy(true);
|
|
4161
4167
|
try {
|
|
4162
|
-
|
|
4168
|
+
const payload = await post({ op: 'updateCheck' });
|
|
4169
|
+
setUpdate(payload);
|
|
4170
|
+
/*
|
|
4171
|
+
* The banner re-renders from `update`, but a silent re-render is not
|
|
4172
|
+
* an answer: the person pressed a button and gets told, out loud,
|
|
4173
|
+
* which of the three outcomes happened — newer version, already
|
|
4174
|
+
* current, or the registry could not be reached.
|
|
4175
|
+
*/
|
|
4176
|
+
if (payload?.updateAvailable === true) {
|
|
4177
|
+
setNote({
|
|
4178
|
+
kind: 'ok',
|
|
4179
|
+
text: t('updateBanner')
|
|
4180
|
+
.replace('{latest}', payload.latest ?? '')
|
|
4181
|
+
.replace('{installed}', payload.installed ?? payload.running ?? ''),
|
|
4182
|
+
});
|
|
4183
|
+
} else if (typeof payload?.error === 'string' && payload.error !== '') {
|
|
4184
|
+
setNote({ kind: 'error', text: `${t('updateCheckFailed')}:${payload.error}` });
|
|
4185
|
+
} else {
|
|
4186
|
+
setNote({
|
|
4187
|
+
kind: 'ok',
|
|
4188
|
+
text: t('updateUpToDate').replace('{installed}', payload?.installed ?? payload?.running ?? ''),
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4163
4191
|
} catch (failure) {
|
|
4164
4192
|
setUpdate({ error: String(failure?.message ?? failure) });
|
|
4193
|
+
setNote({ kind: 'error', text: `${t('updateCheckFailed')}:${failure?.message ?? failure}` });
|
|
4165
4194
|
} finally {
|
|
4166
4195
|
setBusy(false);
|
|
4167
4196
|
}
|
package/index.js
CHANGED
|
@@ -2522,6 +2522,32 @@ export function apply(ctx, config = {}) {
|
|
|
2522
2522
|
owned.set(id, { dispose, rev: registrationRev(skeleton, body) });
|
|
2523
2523
|
}
|
|
2524
2524
|
|
|
2525
|
+
/**
|
|
2526
|
+
* Does this roster row carry OUR fingerprint?
|
|
2527
|
+
*
|
|
2528
|
+
* Both persona presets and team presets are registered through
|
|
2529
|
+
* `buildDefinition`, whose `plugins` rows include `@deepseek-ai/dsh-persona`
|
|
2530
|
+
* rewritten with `PERSONA_PLACEHOLDER` as the prefix — a combination nothing
|
|
2531
|
+
* else registers. A roster row matching it is this plugin's own previous
|
|
2532
|
+
* instance, not a foreign owner.
|
|
2533
|
+
*
|
|
2534
|
+
* The shape of `agentPresets.list()` rows is NOT promised to include the
|
|
2535
|
+
* registered `plugins` array; if it does not, this answers `false` and the
|
|
2536
|
+
* scan reports the conflict exactly as it did before — the fingerprint is
|
|
2537
|
+
* only ever evidence FOR a reclaim, never against a row.
|
|
2538
|
+
* @param {unknown} row - one entry of `ctx.agentPresets.list()`
|
|
2539
|
+
* @returns {boolean}
|
|
2540
|
+
*/
|
|
2541
|
+
function isOwnPresetShape(row) {
|
|
2542
|
+
const plugins = row?.plugins;
|
|
2543
|
+
if (!Array.isArray(plugins)) return false;
|
|
2544
|
+
return plugins.some(
|
|
2545
|
+
(plugin) =>
|
|
2546
|
+
plugin?.name === "@deepseek-ai/dsh-persona" &&
|
|
2547
|
+
plugin?.config?.prefix === PERSONA_PLACEHOLDER,
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2525
2551
|
/**
|
|
2526
2552
|
* Register every file that is not already taken by this or another owner, and
|
|
2527
2553
|
* RE-register the ones whose text changed since they were registered.
|
|
@@ -2539,9 +2565,10 @@ export function apply(ctx, config = {}) {
|
|
|
2539
2565
|
* entry would be a lie that survives a fix.
|
|
2540
2566
|
*/
|
|
2541
2567
|
foreign.clear();
|
|
2542
|
-
const
|
|
2568
|
+
const roster = new Map();
|
|
2543
2569
|
try {
|
|
2544
|
-
for (const preset of await ctx.agentPresets.list())
|
|
2570
|
+
for (const preset of await ctx.agentPresets.list())
|
|
2571
|
+
roster.set(preset.id, preset);
|
|
2545
2572
|
} catch (error) {
|
|
2546
2573
|
return {
|
|
2547
2574
|
added: [],
|
|
@@ -2589,14 +2616,40 @@ export function apply(ctx, config = {}) {
|
|
|
2589
2616
|
}
|
|
2590
2617
|
continue;
|
|
2591
2618
|
}
|
|
2592
|
-
if (
|
|
2619
|
+
if (roster.has(item.id)) {
|
|
2593
2620
|
/*
|
|
2594
|
-
* `
|
|
2621
|
+
* `roster` counts OUR rows too, and an id that is mid-registration is
|
|
2595
2622
|
* still ours. Calling that a foreign conflict is how the panel ended up
|
|
2596
2623
|
* accusing a wife of stealing her own id; a read that lands here simply
|
|
2597
2624
|
* has nothing to do.
|
|
2598
2625
|
*/
|
|
2599
2626
|
if (registering.has(item.id)) continue;
|
|
2627
|
+
/*
|
|
2628
|
+
* Ghost reclaim. Within one DSH process, an uninstall leaves the
|
|
2629
|
+
* preset table holding this plugin's old entries (observed on a
|
|
2630
|
+
* remove-then-reinstall with HMR: every row read 「被别的所有者占用」
|
|
2631
|
+
* until a restart cleared the table). Such a ghost is not another
|
|
2632
|
+
* owner — it is OUR previous instance, and it carries our fingerprint:
|
|
2633
|
+
* only this plugin registers presets whose plugins rows contain
|
|
2634
|
+
* `@deepseek-ai/dsh-persona` with our placeholder as the prefix.
|
|
2635
|
+
* Re-registering over it is therefore reclaiming, not stealing — and
|
|
2636
|
+
* if the registry refuses the duplicate, the catch falls through to
|
|
2637
|
+
* the same foreign report as before, so the attempt cannot make
|
|
2638
|
+
* anything worse.
|
|
2639
|
+
*/
|
|
2640
|
+
if (isOwnPresetShape(roster.get(item.id))) {
|
|
2641
|
+
holdRegistration(item.id);
|
|
2642
|
+
try {
|
|
2643
|
+
await registerOne(item.id, parsed.meta, parsed.body);
|
|
2644
|
+
added.push(item.id);
|
|
2645
|
+
continue;
|
|
2646
|
+
} catch {
|
|
2647
|
+
// The registry refused the overwrite: report the conflict below,
|
|
2648
|
+
// exactly as if no reclaim had been attempted.
|
|
2649
|
+
} finally {
|
|
2650
|
+
releaseRegistration(item.id);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2600
2653
|
foreign.add(item.id);
|
|
2601
2654
|
skipped.push({ id: item.id, reason: "已经有人注册了这个 id" });
|
|
2602
2655
|
continue;
|
|
@@ -5225,16 +5278,117 @@ export function apply(ctx, config = {}) {
|
|
|
5225
5278
|
return { ok: true, order: ids, changed, reloaded: reloaded.reloaded ?? [] };
|
|
5226
5279
|
}
|
|
5227
5280
|
|
|
5281
|
+
/**
|
|
5282
|
+
* Remove THIS package's entries from `minimumReleaseAgeExclude`.
|
|
5283
|
+
*
|
|
5284
|
+
* The list lives in a profile's `pnpm-workspace.yaml` — a file pnpm owns —
|
|
5285
|
+
* but every entry naming `dsh-waifu-agents` exists BECAUSE of this plugin:
|
|
5286
|
+
* pnpm wrote it to let our install past the 24h freshness policy. Deleting
|
|
5287
|
+
* our own lines is not reaching into another owner's file any more than
|
|
5288
|
+
* unregistering our presets is; the entries that name other packages, and
|
|
5289
|
+
* every other byte of the file, pass through untouched.
|
|
5290
|
+
*
|
|
5291
|
+
* The profile name is only visible when `DSH_PROFILE_DIR` reaches this
|
|
5292
|
+
* process; without it every profile is scanned, which is safe precisely
|
|
5293
|
+
* because the filter matches our name and nothing else. A profile that
|
|
5294
|
+
* never installed this plugin has no matching line and is left identical —
|
|
5295
|
+
* the write happens only when a line was actually removed.
|
|
5296
|
+
* @returns {{ cleaned: string[], failed: string[] }}
|
|
5297
|
+
*/
|
|
5298
|
+
function cleanReleaseAgeExcludes() {
|
|
5299
|
+
const cleaned = [];
|
|
5300
|
+
const failed = [];
|
|
5301
|
+
const profileDir = process.env.DSH_PROFILE_DIR
|
|
5302
|
+
? path.resolve(process.env.DSH_PROFILE_DIR)
|
|
5303
|
+
: null;
|
|
5304
|
+
let candidates;
|
|
5305
|
+
if (profileDir !== null) {
|
|
5306
|
+
candidates = [path.join(profileDir, "pnpm-workspace.yaml")];
|
|
5307
|
+
} else {
|
|
5308
|
+
const profiles = path.join(homeFromEnv(process.env).home, "profiles");
|
|
5309
|
+
try {
|
|
5310
|
+
candidates = fs
|
|
5311
|
+
.readdirSync(profiles, { withFileTypes: true })
|
|
5312
|
+
.filter((row) => row.isDirectory())
|
|
5313
|
+
.map((row) => path.join(profiles, row.name, "pnpm-workspace.yaml"));
|
|
5314
|
+
} catch {
|
|
5315
|
+
candidates = [];
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
for (const full of candidates) {
|
|
5319
|
+
let raw;
|
|
5320
|
+
try {
|
|
5321
|
+
raw = fs.readFileSync(full, "utf8");
|
|
5322
|
+
} catch {
|
|
5323
|
+
continue; // no such file — a profile without pnpm state, nothing to do
|
|
5324
|
+
}
|
|
5325
|
+
const lines = raw.split("\n");
|
|
5326
|
+
const keep = [];
|
|
5327
|
+
let changed = false;
|
|
5328
|
+
for (let at = 0; at < lines.length; ) {
|
|
5329
|
+
const line = lines[at];
|
|
5330
|
+
if (!/^minimumReleaseAgeExclude\s*:/.test(line)) {
|
|
5331
|
+
keep.push(line);
|
|
5332
|
+
at += 1;
|
|
5333
|
+
continue;
|
|
5334
|
+
}
|
|
5335
|
+
// Flow form `key: [a, b]`: filter inside the brackets, drop the key
|
|
5336
|
+
// when nothing of ours was in it (then the line is reproduced as-is).
|
|
5337
|
+
const flow = /^minimumReleaseAgeExclude\s*:\s*\[(.*)\]\s*\r?$/.exec(line);
|
|
5338
|
+
if (flow !== null) {
|
|
5339
|
+
const items = flow[1]
|
|
5340
|
+
.split(",")
|
|
5341
|
+
.filter((item) => item.trim() !== "");
|
|
5342
|
+
const rest = items.filter((item) => !item.includes(PACKAGE_NAME));
|
|
5343
|
+
if (rest.length === items.length) {
|
|
5344
|
+
keep.push(line);
|
|
5345
|
+
} else {
|
|
5346
|
+
changed = true;
|
|
5347
|
+
if (rest.length > 0)
|
|
5348
|
+
keep.push(`minimumReleaseAgeExclude: [${rest.join(", ")}]`);
|
|
5349
|
+
}
|
|
5350
|
+
at += 1;
|
|
5351
|
+
continue;
|
|
5352
|
+
}
|
|
5353
|
+
// Block form: gather the list items that follow.
|
|
5354
|
+
const items = [];
|
|
5355
|
+
let cursor = at + 1;
|
|
5356
|
+
while (cursor < lines.length && /^\s+-\s/.test(lines[cursor])) {
|
|
5357
|
+
items.push(lines[cursor]);
|
|
5358
|
+
cursor += 1;
|
|
5359
|
+
}
|
|
5360
|
+
const rest = items.filter((item) => !item.includes(PACKAGE_NAME));
|
|
5361
|
+
if (rest.length === items.length) {
|
|
5362
|
+
keep.push(line, ...items);
|
|
5363
|
+
} else {
|
|
5364
|
+
changed = true;
|
|
5365
|
+
if (rest.length > 0) keep.push(line, ...rest);
|
|
5366
|
+
// zero left: the key goes too — an empty list is noise
|
|
5367
|
+
}
|
|
5368
|
+
at = cursor;
|
|
5369
|
+
}
|
|
5370
|
+
if (!changed) continue;
|
|
5371
|
+
try {
|
|
5372
|
+
writeAtomic(full, keep.join("\n"));
|
|
5373
|
+
cleaned.push(full);
|
|
5374
|
+
} catch (error) {
|
|
5375
|
+
failed.push(`${full}:${error?.message ?? error}`);
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
return { cleaned, failed };
|
|
5379
|
+
}
|
|
5380
|
+
|
|
5228
5381
|
/**
|
|
5229
5382
|
* Undo everything this plugin put outside its own package.
|
|
5230
5383
|
*
|
|
5231
5384
|
* Only three things are ours and outlive an uninstall: the per-session
|
|
5232
5385
|
* override store, the `selectedDefault` line this panel writes into the
|
|
5233
5386
|
* agent-preset registry's config, and — only when explicitly asked — the
|
|
5234
|
-
* persona directory itself.
|
|
5235
|
-
*
|
|
5236
|
-
*
|
|
5237
|
-
*
|
|
5387
|
+
* persona directory itself. The freshness allow-list lines naming this
|
|
5388
|
+
* package are ours too (pnpm wrote them for OUR install), and are stripped.
|
|
5389
|
+
* Everything else an install leaves behind belongs to
|
|
5390
|
+
* the platform (the plugin-manager's operation logs, pnpm's store index),
|
|
5391
|
+
* so this op names them instead of pretending to clean them.
|
|
5238
5392
|
* @param {{ dir?: boolean }} request
|
|
5239
5393
|
*/
|
|
5240
5394
|
async function purge(request) {
|
|
@@ -5251,6 +5405,14 @@ export function apply(ctx, config = {}) {
|
|
|
5251
5405
|
}
|
|
5252
5406
|
}
|
|
5253
5407
|
overrides = {};
|
|
5408
|
+
try {
|
|
5409
|
+
const excludes = cleanReleaseAgeExcludes();
|
|
5410
|
+
for (const full of excludes.cleaned)
|
|
5411
|
+
done.push(`清掉放行记录 ${full} 里属于本包的条目`);
|
|
5412
|
+
for (const message of excludes.failed) failed.push(`放行记录:${message}`);
|
|
5413
|
+
} catch (error) {
|
|
5414
|
+
failed.push(`放行记录:${error?.message ?? error}`);
|
|
5415
|
+
}
|
|
5254
5416
|
try {
|
|
5255
5417
|
const configEditor = ctx.get("configEditor");
|
|
5256
5418
|
const entry =
|
|
@@ -5335,7 +5497,6 @@ export function apply(ctx, config = {}) {
|
|
|
5335
5497
|
platform: [
|
|
5336
5498
|
".plugin-manager\\logs\\operation-*(安装日志,平台自己累积)",
|
|
5337
5499
|
"pnpm 内容库索引:需要 pnpm store prune",
|
|
5338
|
-
"pnpm-workspace.yaml 的 minimumReleaseAgeExclude(安装时 pnpm 自己写的放行记录)",
|
|
5339
5500
|
],
|
|
5340
5501
|
};
|
|
5341
5502
|
}
|
package/package.json
CHANGED