dsh-rewind-plugin 0.6.2 → 0.7.0
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.en.md +16 -12
- package/README.md +12 -9
- package/SECURITY.md +14 -7
- package/assets/screenshots/cleanup-setting.png +0 -0
- package/docs/architecture.md +1 -1
- package/docs/compat/audit.md +31 -20
- package/docs/format.md +11 -0
- package/docs/release/release.md +6 -6
- package/docs/release/release.zh.md +5 -4
- package/docs/snapshot-auto-cleanup.md +4 -12
- package/docs/snapshot-auto-cleanup.zh.md +4 -8
- package/lib/client.js +528 -17
- package/lib/index.js +82 -34
- package/lib/types/client/hidden.d.ts +10 -0
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/portals.d.ts +8 -6
- package/lib/types/client/settings-card.d.ts +76 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +0 -2
- package/lib/types/settings-locale.d.ts +32 -0
- package/lib/types/snapshot-cleanup.d.ts +67 -7
- package/package.json +24 -13
package/lib/client.js
CHANGED
|
@@ -40,6 +40,18 @@ function chatSnapshotOf(face, chatView) {
|
|
|
40
40
|
if (legacy !== void 0) return legacy;
|
|
41
41
|
return chatView?.getSnapshot() ?? void 0;
|
|
42
42
|
}
|
|
43
|
+
function messageTextAt(chat, seq) {
|
|
44
|
+
if (chat === void 0) return void 0;
|
|
45
|
+
for (const key of chat.order) {
|
|
46
|
+
const node = chat.nodes.get(key);
|
|
47
|
+
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
48
|
+
const data = node.data;
|
|
49
|
+
if (data.seq === seq) {
|
|
50
|
+
return data.content?.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
43
55
|
function targetSeqOfArgs(args) {
|
|
44
56
|
if (args === void 0 || args === null) return void 0;
|
|
45
57
|
const match = args.match(/@(\d+)/);
|
|
@@ -307,6 +319,249 @@ var STYLE = `
|
|
|
307
319
|
color: var(--dsw-alias-label-primary);
|
|
308
320
|
pointer-events: none;
|
|
309
321
|
}
|
|
322
|
+
|
|
323
|
+
/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
|
|
324
|
+
.dsh-rewind-cleanup-card {
|
|
325
|
+
list-style: none;
|
|
326
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
327
|
+
border-radius: 12px;
|
|
328
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
329
|
+
transition: border-color .16s, background .16s;
|
|
330
|
+
}
|
|
331
|
+
.dsh-rewind-cleanup-card:hover {
|
|
332
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
333
|
+
}
|
|
334
|
+
.dsh-rewind-cleanup-card-open {
|
|
335
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
336
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
337
|
+
}
|
|
338
|
+
.dsh-rewind-cleanup-header {
|
|
339
|
+
width: 100%;
|
|
340
|
+
appearance: none;
|
|
341
|
+
border: 0;
|
|
342
|
+
background: none;
|
|
343
|
+
font: inherit;
|
|
344
|
+
color: inherit;
|
|
345
|
+
text-align: left;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
display: flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 12px;
|
|
350
|
+
padding: 14px 16px;
|
|
351
|
+
border-radius: 12px;
|
|
352
|
+
}
|
|
353
|
+
.dsh-rewind-cleanup-header:focus-visible {
|
|
354
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
355
|
+
outline-offset: -2px;
|
|
356
|
+
}
|
|
357
|
+
.dsh-rewind-cleanup-head-text {
|
|
358
|
+
flex: 1;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 4px;
|
|
363
|
+
}
|
|
364
|
+
.dsh-rewind-cleanup-name {
|
|
365
|
+
font-size: 15px;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
line-height: 1.4;
|
|
368
|
+
color: var(--dsw-alias-label-primary);
|
|
369
|
+
}
|
|
370
|
+
.dsh-rewind-cleanup-desc {
|
|
371
|
+
font-size: 13px;
|
|
372
|
+
line-height: 1.5;
|
|
373
|
+
color: var(--dsw-alias-label-tertiary);
|
|
374
|
+
}
|
|
375
|
+
.dsh-rewind-cleanup-chevron {
|
|
376
|
+
flex: none;
|
|
377
|
+
color: var(--dsw-alias-label-tertiary);
|
|
378
|
+
transition: transform .16s;
|
|
379
|
+
}
|
|
380
|
+
.dsh-rewind-cleanup-chevron-open {
|
|
381
|
+
transform: rotate(180deg);
|
|
382
|
+
}
|
|
383
|
+
.dsh-rewind-cleanup-pending {
|
|
384
|
+
flex: none;
|
|
385
|
+
border-radius: 999px;
|
|
386
|
+
padding: 1px 8px;
|
|
387
|
+
font-size: 11px;
|
|
388
|
+
line-height: 17px;
|
|
389
|
+
font-weight: 500;
|
|
390
|
+
white-space: nowrap;
|
|
391
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
392
|
+
color: var(--dsw-alias-label-secondary);
|
|
393
|
+
}
|
|
394
|
+
.dsh-rewind-cleanup-body {
|
|
395
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
396
|
+
margin: 0 16px;
|
|
397
|
+
padding: 4px 0 8px;
|
|
398
|
+
}
|
|
399
|
+
.dsh-rewind-cleanup-readonly {
|
|
400
|
+
margin: 12px 0 0;
|
|
401
|
+
font-size: 12px;
|
|
402
|
+
line-height: 1.5;
|
|
403
|
+
color: var(--dsw-alias-label-tertiary);
|
|
404
|
+
}
|
|
405
|
+
.dsh-rewind-cleanup-permission {
|
|
406
|
+
display: grid;
|
|
407
|
+
gap: 6px;
|
|
408
|
+
padding: 12px 0;
|
|
409
|
+
}
|
|
410
|
+
.dsh-rewind-cleanup-field {
|
|
411
|
+
display: flex;
|
|
412
|
+
flex-direction: column;
|
|
413
|
+
gap: 6px;
|
|
414
|
+
padding: 12px 0;
|
|
415
|
+
}
|
|
416
|
+
.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
|
|
417
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
418
|
+
}
|
|
419
|
+
.dsh-rewind-cleanup-head {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
gap: 8px;
|
|
423
|
+
}
|
|
424
|
+
.dsh-rewind-cleanup-label {
|
|
425
|
+
flex: 1;
|
|
426
|
+
min-width: 0;
|
|
427
|
+
font-size: 13px;
|
|
428
|
+
font-weight: 500;
|
|
429
|
+
line-height: 1.5;
|
|
430
|
+
color: var(--dsw-alias-label-primary);
|
|
431
|
+
}
|
|
432
|
+
.dsh-rewind-cleanup-hint {
|
|
433
|
+
margin: 0;
|
|
434
|
+
font-size: 12px;
|
|
435
|
+
line-height: 1.5;
|
|
436
|
+
color: var(--dsw-alias-label-tertiary);
|
|
437
|
+
}
|
|
438
|
+
.dsh-rewind-cleanup-error {
|
|
439
|
+
margin: 0;
|
|
440
|
+
font-size: 12px;
|
|
441
|
+
line-height: 1.5;
|
|
442
|
+
color: var(--dsw-alias-label-error);
|
|
443
|
+
}
|
|
444
|
+
/* Switch row: label left, role=switch button right, hint below (Subagent module). */
|
|
445
|
+
.dsh-rewind-cleanup-toggle-row {
|
|
446
|
+
display: flex;
|
|
447
|
+
align-items: flex-start;
|
|
448
|
+
justify-content: space-between;
|
|
449
|
+
gap: 16px;
|
|
450
|
+
font-size: 13px;
|
|
451
|
+
line-height: 1.5;
|
|
452
|
+
color: var(--dsw-alias-label-primary);
|
|
453
|
+
}
|
|
454
|
+
.dsh-rewind-cleanup-toggle-label {
|
|
455
|
+
flex: 1;
|
|
456
|
+
min-width: 0;
|
|
457
|
+
}
|
|
458
|
+
.dsh-rewind-cleanup-switch {
|
|
459
|
+
box-sizing: border-box;
|
|
460
|
+
position: relative;
|
|
461
|
+
flex: 0 0 auto;
|
|
462
|
+
width: 36px;
|
|
463
|
+
height: 20px;
|
|
464
|
+
padding: 2px;
|
|
465
|
+
border: 0;
|
|
466
|
+
border-radius: 10px;
|
|
467
|
+
background: var(--dsw-alias-border-l3);
|
|
468
|
+
cursor: pointer;
|
|
469
|
+
}
|
|
470
|
+
.dsh-rewind-cleanup-switch-on {
|
|
471
|
+
background: var(--dsw-alias-brand-primary);
|
|
472
|
+
}
|
|
473
|
+
.dsh-rewind-cleanup-switch:disabled {
|
|
474
|
+
cursor: default;
|
|
475
|
+
opacity: 0.5;
|
|
476
|
+
}
|
|
477
|
+
.dsh-rewind-cleanup-switch:focus-visible {
|
|
478
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
479
|
+
outline-offset: 2px;
|
|
480
|
+
}
|
|
481
|
+
.dsh-rewind-cleanup-thumb {
|
|
482
|
+
display: block;
|
|
483
|
+
width: 16px;
|
|
484
|
+
height: 16px;
|
|
485
|
+
border-radius: 50%;
|
|
486
|
+
background: var(--dsw-alias-label-primary-foreground);
|
|
487
|
+
transition: transform 120ms ease;
|
|
488
|
+
}
|
|
489
|
+
.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {
|
|
490
|
+
transform: translateX(16px);
|
|
491
|
+
}
|
|
492
|
+
.dsh-rewind-cleanup-input {
|
|
493
|
+
box-sizing: border-box;
|
|
494
|
+
height: 34px;
|
|
495
|
+
padding: 0 12px;
|
|
496
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
497
|
+
border-radius: 8px;
|
|
498
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
499
|
+
font: inherit;
|
|
500
|
+
font-size: 13px;
|
|
501
|
+
line-height: 1.5;
|
|
502
|
+
color: var(--dsw-alias-label-primary);
|
|
503
|
+
}
|
|
504
|
+
.dsh-rewind-cleanup-input:focus-visible {
|
|
505
|
+
outline: none;
|
|
506
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
507
|
+
}
|
|
508
|
+
.dsh-rewind-cleanup-input:disabled {
|
|
509
|
+
color: var(--dsw-alias-label-tertiary);
|
|
510
|
+
cursor: default;
|
|
511
|
+
}
|
|
512
|
+
.dsh-rewind-cleanup-input-invalid {
|
|
513
|
+
border-color: var(--dsw-alias-label-error);
|
|
514
|
+
}
|
|
515
|
+
.dsh-rewind-cleanup-footer {
|
|
516
|
+
display: flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
justify-content: flex-end;
|
|
519
|
+
gap: 8px;
|
|
520
|
+
padding: 12px 0 4px;
|
|
521
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
522
|
+
}
|
|
523
|
+
.dsh-rewind-cleanup-failed {
|
|
524
|
+
flex: 1;
|
|
525
|
+
min-width: 0;
|
|
526
|
+
margin: 0;
|
|
527
|
+
font-size: 12px;
|
|
528
|
+
line-height: 1.5;
|
|
529
|
+
color: var(--dsw-alias-label-error);
|
|
530
|
+
}
|
|
531
|
+
.dsh-rewind-cleanup-discard,
|
|
532
|
+
.dsh-rewind-cleanup-save {
|
|
533
|
+
appearance: none;
|
|
534
|
+
border: 1px solid transparent;
|
|
535
|
+
border-radius: 8px;
|
|
536
|
+
padding: 5px 14px;
|
|
537
|
+
font: inherit;
|
|
538
|
+
font-size: 13px;
|
|
539
|
+
line-height: 1.5;
|
|
540
|
+
cursor: pointer;
|
|
541
|
+
}
|
|
542
|
+
.dsh-rewind-cleanup-discard {
|
|
543
|
+
border-color: var(--dsw-alias-border-l2);
|
|
544
|
+
background: none;
|
|
545
|
+
color: var(--dsw-alias-label-secondary);
|
|
546
|
+
}
|
|
547
|
+
.dsh-rewind-cleanup-discard:hover:not(:disabled) {
|
|
548
|
+
color: var(--dsw-alias-label-primary);
|
|
549
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
550
|
+
}
|
|
551
|
+
.dsh-rewind-cleanup-save {
|
|
552
|
+
background: var(--dsw-alias-label-primary);
|
|
553
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
554
|
+
}
|
|
555
|
+
.dsh-rewind-cleanup-discard:disabled,
|
|
556
|
+
.dsh-rewind-cleanup-save:disabled {
|
|
557
|
+
opacity: 0.4;
|
|
558
|
+
cursor: default;
|
|
559
|
+
}
|
|
560
|
+
.dsh-rewind-cleanup-discard:focus-visible,
|
|
561
|
+
.dsh-rewind-cleanup-save:focus-visible {
|
|
562
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
563
|
+
outline-offset: 1px;
|
|
564
|
+
}
|
|
310
565
|
`;
|
|
311
566
|
|
|
312
567
|
// src/client/popover.ts
|
|
@@ -677,18 +932,6 @@ function retractSpan(steering, targetId) {
|
|
|
677
932
|
|
|
678
933
|
// src/client/portals.tsx
|
|
679
934
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
680
|
-
function userTextAt(chat, seq) {
|
|
681
|
-
if (chat === void 0) return void 0;
|
|
682
|
-
for (const key of chat.order) {
|
|
683
|
-
const node = chat.nodes.get(key);
|
|
684
|
-
if (node === void 0 || node.kind !== "user") continue;
|
|
685
|
-
const user = node.data;
|
|
686
|
-
if (user.seq === seq) {
|
|
687
|
-
return user.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
return void 0;
|
|
691
|
-
}
|
|
692
935
|
function fillComposerTextarea(text) {
|
|
693
936
|
const textarea = document.querySelector(COMPOSER_TEXTAREA_SELECTOR);
|
|
694
937
|
if (textarea === null) return false;
|
|
@@ -750,7 +993,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
750
993
|
return;
|
|
751
994
|
}
|
|
752
995
|
if (currentSessionId() !== session.sessionId) return;
|
|
753
|
-
const text =
|
|
996
|
+
const text = messageTextAt(chatOf(session), seq);
|
|
754
997
|
if (text === void 0 || text === "") return;
|
|
755
998
|
setComposerText(session.sessionId, text);
|
|
756
999
|
}
|
|
@@ -776,8 +1019,8 @@ var COMPOSER_TEXTAREA_SELECTOR = "[data-input-scroll] textarea, textarea[data-ph
|
|
|
776
1019
|
var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
|
|
777
1020
|
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
|
|
778
1021
|
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
779
|
-
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
|
|
780
|
-
var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root]";
|
|
1022
|
+
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root], [data-actions-reveal]";
|
|
1023
|
+
var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root], [data-pending-steering][data-actions-reveal]";
|
|
781
1024
|
function collectTargets(chat, hiddenSeqs) {
|
|
782
1025
|
const rows = /* @__PURE__ */ new Map();
|
|
783
1026
|
for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
|
|
@@ -841,6 +1084,40 @@ function sameTargets(left, right) {
|
|
|
841
1084
|
return other.kind === "pending" && target.itemId === other.itemId;
|
|
842
1085
|
});
|
|
843
1086
|
}
|
|
1087
|
+
function hasExecutedRewindCommand(chat) {
|
|
1088
|
+
for (const key of chat.order) {
|
|
1089
|
+
const node = chat.nodes.get(key);
|
|
1090
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1091
|
+
const command = node.data;
|
|
1092
|
+
if (command.name !== "rewind") continue;
|
|
1093
|
+
const args = command.args ?? "";
|
|
1094
|
+
if (args.includes("preview") || args.includes("__candidates")) continue;
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
function describeHiding(chat, hiddenSeqs) {
|
|
1100
|
+
const commands = [];
|
|
1101
|
+
for (const key of chat.order) {
|
|
1102
|
+
const node = chat.nodes.get(key);
|
|
1103
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1104
|
+
const c = node.data;
|
|
1105
|
+
commands.push(`#${c.seq} name=${c.name} outcome=${c.outcome?.kind ?? "-"} marker=${c.outcome?.sourceEventSeq ?? "-"} args=${JSON.stringify(c.args)}`);
|
|
1106
|
+
}
|
|
1107
|
+
const scoped = hiddenSeqs.size > 0 ? `hidden=[${[...hiddenSeqs].slice(0, 20).join(",")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]` : "";
|
|
1108
|
+
let seats = 0;
|
|
1109
|
+
let resolved = 0;
|
|
1110
|
+
let inHidden = 0;
|
|
1111
|
+
for (const seat of document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
|
|
1112
|
+
seats += 1;
|
|
1113
|
+
const key = seat.dataset.chatAnchorKey;
|
|
1114
|
+
const node = key === void 0 ? void 0 : chat.nodes.get(key);
|
|
1115
|
+
if (node === void 0) continue;
|
|
1116
|
+
resolved += 1;
|
|
1117
|
+
if (hiddenSeqs.has(node.anchorSeq)) inHidden += 1;
|
|
1118
|
+
}
|
|
1119
|
+
return `commands=[${commands.join(" | ")}] ${scoped} seats=${seats} resolved=${resolved} inHidden=${inHidden}`;
|
|
1120
|
+
}
|
|
844
1121
|
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
|
|
845
1122
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
846
1123
|
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
@@ -881,6 +1158,9 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
881
1158
|
`[dsh-rewind] hiding: ${hiddenCount} rows, seqs [${[...hiddenSeqs].slice(0, 20).join(", ")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]`
|
|
882
1159
|
);
|
|
883
1160
|
}
|
|
1161
|
+
if (chat !== void 0 && hiddenCount === 0 && hasExecutedRewindCommand(chat)) {
|
|
1162
|
+
console.warn(`[dsh-rewind] rewind not hidden: ${describeHiding(chat, hiddenSeqs)}`);
|
|
1163
|
+
}
|
|
884
1164
|
const durable = chat === void 0 ? [] : collectTargets(chat, hiddenSeqs);
|
|
885
1165
|
const next = [...durable, ...collectPendingTargets(snapshot)];
|
|
886
1166
|
setTargets((current) => sameTargets(current, next) ? current : next);
|
|
@@ -1042,7 +1322,24 @@ var zh = {
|
|
|
1042
1322
|
"popover.impact.restore": "\u8FD8\u539F {path}",
|
|
1043
1323
|
"popover.impact.delete": "\u5220\u9664 {path}",
|
|
1044
1324
|
"popover.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
1045
|
-
"popover.back": "\u8FD4\u56DE"
|
|
1325
|
+
"popover.back": "\u8FD4\u56DE",
|
|
1326
|
+
"cleanup.title": "\u5FEB\u7167\u6E05\u7406",
|
|
1327
|
+
"cleanup.desc": "dsh-rewind \xB7 \u7BA1\u7406\u4F1A\u8BDD\u5FEB\u7167\u5907\u4EFD\u7684\u81EA\u52A8\u6E05\u7406\u7B56\u7565",
|
|
1328
|
+
"cleanup.expand": "\u5C55\u5F00",
|
|
1329
|
+
"cleanup.collapse": "\u6536\u8D77",
|
|
1330
|
+
"cleanup.unsaved": "\u672A\u4FDD\u5B58\u4FEE\u6539",
|
|
1331
|
+
"cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
|
|
1332
|
+
"cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
|
|
1333
|
+
"cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
|
|
1334
|
+
"cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
|
|
1335
|
+
"cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
|
|
1336
|
+
"cleanup.invalid": "\u4EC5\u63A5\u53D7\u6B63\u6574\u6570",
|
|
1337
|
+
"cleanup.discard": "\u653E\u5F03\u4FEE\u6539",
|
|
1338
|
+
"cleanup.save": "\u4FDD\u5B58",
|
|
1339
|
+
"cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
|
|
1340
|
+
"cleanup.saved": "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
|
|
1341
|
+
"cleanup.saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A{message}",
|
|
1342
|
+
"cleanup.readonly": "\u8BBE\u7F6E\u6E90\u53EA\u8BFB"
|
|
1046
1343
|
};
|
|
1047
1344
|
var en = {
|
|
1048
1345
|
"button.aria": "Rewind to this message",
|
|
@@ -1068,9 +1365,189 @@ var en = {
|
|
|
1068
1365
|
"popover.impact.restore": "Restore {path}",
|
|
1069
1366
|
"popover.impact.delete": "Delete {path}",
|
|
1070
1367
|
"popover.confirm": "Confirm rewind",
|
|
1071
|
-
"popover.back": "Back"
|
|
1368
|
+
"popover.back": "Back",
|
|
1369
|
+
"cleanup.title": "Snapshot cleanup",
|
|
1370
|
+
"cleanup.desc": "dsh-rewind \xB7 Manage the auto-cleanup policy of session snapshot backups",
|
|
1371
|
+
"cleanup.expand": "Expand",
|
|
1372
|
+
"cleanup.collapse": "Collapse",
|
|
1373
|
+
"cleanup.unsaved": "Unsaved changes",
|
|
1374
|
+
"cleanup.auto": "Auto cleanup",
|
|
1375
|
+
"cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
|
|
1376
|
+
"cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
|
|
1377
|
+
"cleanup.maxAge": "Idle time (days)",
|
|
1378
|
+
"cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
|
|
1379
|
+
"cleanup.invalid": "A positive integer only",
|
|
1380
|
+
"cleanup.discard": "Discard changes",
|
|
1381
|
+
"cleanup.save": "Save",
|
|
1382
|
+
"cleanup.saving": "Saving\u2026",
|
|
1383
|
+
"cleanup.saved": "Saved and applied",
|
|
1384
|
+
"cleanup.saveFailed": "Save failed: {message}",
|
|
1385
|
+
"cleanup.readonly": "Read-only settings source"
|
|
1072
1386
|
};
|
|
1073
1387
|
|
|
1388
|
+
// src/client/settings-card.tsx
|
|
1389
|
+
var import_react2 = require("react");
|
|
1390
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1391
|
+
var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
|
|
1392
|
+
var DEFAULT_MAX_AGE_DAYS = 30;
|
|
1393
|
+
function draftFrom(policy) {
|
|
1394
|
+
return { enabled: policy?.enabled ?? false, maxAgeDays: String(policy?.maxAgeDays ?? "") };
|
|
1395
|
+
}
|
|
1396
|
+
function maxAgeOf(text) {
|
|
1397
|
+
const trimmed = text.trim();
|
|
1398
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
1399
|
+
const days = Number(trimmed);
|
|
1400
|
+
return Number.isSafeInteger(days) && days > 0 ? days : null;
|
|
1401
|
+
}
|
|
1402
|
+
function configOf(draft) {
|
|
1403
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1404
|
+
if (days === null) return null;
|
|
1405
|
+
return { enabled: draft.enabled, maxAgeDays: days };
|
|
1406
|
+
}
|
|
1407
|
+
function dirtyOf(base, draft) {
|
|
1408
|
+
return base.enabled !== draft.enabled || base.maxAgeDays !== draft.maxAgeDays;
|
|
1409
|
+
}
|
|
1410
|
+
function SettingsCleanupCard({ api, t }) {
|
|
1411
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
1412
|
+
const [baseline, setBaseline] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1413
|
+
const [draft, setDraft] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1414
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1415
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1416
|
+
const writable = api.writable();
|
|
1417
|
+
(0, import_react2.useEffect)(() => api.subscribe(() => {
|
|
1418
|
+
const next = draftFrom(api.read());
|
|
1419
|
+
setBaseline((base) => {
|
|
1420
|
+
setDraft((cur) => dirtyOf(base, cur) ? cur : next);
|
|
1421
|
+
return next;
|
|
1422
|
+
});
|
|
1423
|
+
}), [api]);
|
|
1424
|
+
const dirty = dirtyOf(baseline, draft);
|
|
1425
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1426
|
+
const invalid = days === null;
|
|
1427
|
+
const disabled = busy || !writable;
|
|
1428
|
+
const edit = (patch) => {
|
|
1429
|
+
setDraft((cur) => ({ ...cur, ...patch }));
|
|
1430
|
+
setError(null);
|
|
1431
|
+
};
|
|
1432
|
+
const save = async () => {
|
|
1433
|
+
if (busy || !writable || !dirty) return;
|
|
1434
|
+
const next = configOf(draft);
|
|
1435
|
+
if (next === null) {
|
|
1436
|
+
setError(t("cleanup.invalid"));
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
setBusy(true);
|
|
1440
|
+
setError(null);
|
|
1441
|
+
try {
|
|
1442
|
+
await api.save(next);
|
|
1443
|
+
setBaseline(draft);
|
|
1444
|
+
setError(null);
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
setError(t("cleanup.saveFailed", { message: e instanceof Error ? e.message : String(e) }));
|
|
1447
|
+
} finally {
|
|
1448
|
+
setBusy(false);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
const discard = () => {
|
|
1452
|
+
if (busy) return;
|
|
1453
|
+
setDraft(baseline);
|
|
1454
|
+
setError(null);
|
|
1455
|
+
};
|
|
1456
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: `dsh-rewind-cleanup-card${open ? " dsh-rewind-cleanup-card-open" : ""}`, children: [
|
|
1457
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1458
|
+
"button",
|
|
1459
|
+
{
|
|
1460
|
+
type: "button",
|
|
1461
|
+
className: "dsh-rewind-cleanup-header",
|
|
1462
|
+
"aria-expanded": open,
|
|
1463
|
+
"aria-label": `${t(open ? "cleanup.collapse" : "cleanup.expand")}: ${t("cleanup.title")}`,
|
|
1464
|
+
onClick: () => setOpen(!open),
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-head-text", children: [
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-name", children: t("cleanup.title") }),
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-desc", children: t("cleanup.desc") })
|
|
1469
|
+
] }),
|
|
1470
|
+
dirty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-pending", children: t("cleanup.unsaved") }) : null,
|
|
1471
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1472
|
+
"svg",
|
|
1473
|
+
{
|
|
1474
|
+
className: `dsh-rewind-cleanup-chevron${open ? " dsh-rewind-cleanup-chevron-open" : ""}`,
|
|
1475
|
+
width: "14",
|
|
1476
|
+
height: "14",
|
|
1477
|
+
viewBox: "0 0 16 16",
|
|
1478
|
+
"aria-hidden": "true",
|
|
1479
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1480
|
+
}
|
|
1481
|
+
)
|
|
1482
|
+
]
|
|
1483
|
+
}
|
|
1484
|
+
),
|
|
1485
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-body", children: [
|
|
1486
|
+
!writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
|
|
1487
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
|
|
1488
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
|
|
1489
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", id: "dsh-rewind-cleanup-enabled-label", children: t("cleanup.auto") }),
|
|
1490
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1491
|
+
"button",
|
|
1492
|
+
{
|
|
1493
|
+
type: "button",
|
|
1494
|
+
role: "switch",
|
|
1495
|
+
className: `dsh-rewind-cleanup-switch${draft.enabled ? " dsh-rewind-cleanup-switch-on" : ""}`,
|
|
1496
|
+
"aria-checked": draft.enabled,
|
|
1497
|
+
"aria-labelledby": "dsh-rewind-cleanup-enabled-label",
|
|
1498
|
+
disabled,
|
|
1499
|
+
onClick: () => edit({ enabled: !draft.enabled }),
|
|
1500
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-thumb" })
|
|
1501
|
+
}
|
|
1502
|
+
)
|
|
1503
|
+
] }),
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(draft.enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
|
|
1505
|
+
] }),
|
|
1506
|
+
draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
|
|
1507
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-rewind-cleanup-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "dsh-rewind-cleanup-label", htmlFor: "dsh-rewind-cleanup-maxage", children: t("cleanup.maxAge") }) }),
|
|
1508
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1509
|
+
"input",
|
|
1510
|
+
{
|
|
1511
|
+
className: `dsh-rewind-cleanup-input${invalid ? " dsh-rewind-cleanup-input-invalid" : ""}`,
|
|
1512
|
+
type: "text",
|
|
1513
|
+
inputMode: "numeric",
|
|
1514
|
+
id: "dsh-rewind-cleanup-maxage",
|
|
1515
|
+
value: draft.maxAgeDays,
|
|
1516
|
+
disabled,
|
|
1517
|
+
"aria-invalid": invalid || void 0,
|
|
1518
|
+
placeholder: String(DEFAULT_MAX_AGE_DAYS),
|
|
1519
|
+
onChange: (e) => edit({ maxAgeDays: e.target.value })
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: invalid ? "dsh-rewind-cleanup-error" : "dsh-rewind-cleanup-hint", children: invalid ? t("cleanup.invalid") : t("cleanup.maxAge.hint") })
|
|
1523
|
+
] }) : null,
|
|
1524
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
|
|
1525
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: error }) : null,
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1527
|
+
"button",
|
|
1528
|
+
{
|
|
1529
|
+
type: "button",
|
|
1530
|
+
className: "dsh-rewind-cleanup-discard",
|
|
1531
|
+
disabled: !dirty || busy || !writable,
|
|
1532
|
+
onClick: discard,
|
|
1533
|
+
children: t("cleanup.discard")
|
|
1534
|
+
}
|
|
1535
|
+
),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1537
|
+
"button",
|
|
1538
|
+
{
|
|
1539
|
+
type: "button",
|
|
1540
|
+
className: "dsh-rewind-cleanup-save",
|
|
1541
|
+
disabled: !dirty || busy || !writable || invalid,
|
|
1542
|
+
onClick: save,
|
|
1543
|
+
children: busy ? t("cleanup.saving") : t("cleanup.save")
|
|
1544
|
+
}
|
|
1545
|
+
)
|
|
1546
|
+
] })
|
|
1547
|
+
] }) : null
|
|
1548
|
+
] });
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1074
1551
|
// src/client/index.ts
|
|
1075
1552
|
var name = "dsh-rewind";
|
|
1076
1553
|
var inject = ["slots", "sessions", "locale", "commandUi"];
|
|
@@ -1122,6 +1599,40 @@ function apply(ctx) {
|
|
|
1122
1599
|
},
|
|
1123
1600
|
createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
|
|
1124
1601
|
));
|
|
1602
|
+
const clientCtx = ctx;
|
|
1603
|
+
clientCtx.inject(["settingsScope"], (scoped) => {
|
|
1604
|
+
try {
|
|
1605
|
+
const scope = scoped.settingsScope.bind({ namespace: CLEANUP_SETTINGS_NAMESPACE });
|
|
1606
|
+
const cardApi = {
|
|
1607
|
+
read: () => {
|
|
1608
|
+
const value = scope.getSnapshot().value;
|
|
1609
|
+
return value === void 0 ? void 0 : { enabled: value.enabled, maxAgeDays: value.maxAgeDays };
|
|
1610
|
+
},
|
|
1611
|
+
writable: () => {
|
|
1612
|
+
return scope.getSnapshot().writable === true;
|
|
1613
|
+
},
|
|
1614
|
+
save: async (next) => {
|
|
1615
|
+
await scope.set("enabled", next.enabled);
|
|
1616
|
+
await scope.set("maxAgeDays", next.maxAgeDays);
|
|
1617
|
+
},
|
|
1618
|
+
subscribe: (cb) => scope.subscribe(cb)
|
|
1619
|
+
};
|
|
1620
|
+
scoped.slots.inject("settings.plugin.item", () => scoped.slots.register(
|
|
1621
|
+
{
|
|
1622
|
+
name: "settings.plugin.item",
|
|
1623
|
+
key: CLEANUP_SETTINGS_NAMESPACE,
|
|
1624
|
+
// Match the official cards / dsh-market: locale + inject provide
|
|
1625
|
+
// the card its props through the slot renderer (the keyed card owns
|
|
1626
|
+
// its internals, but the page feeds it locale + the bound api).
|
|
1627
|
+
locale: NS,
|
|
1628
|
+
inject: () => ({ t, api: cardApi })
|
|
1629
|
+
},
|
|
1630
|
+
SettingsCleanupCard
|
|
1631
|
+
));
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
console.error("[dsh-rewind] settings card register failed:", error);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1125
1636
|
const commandUi = ctx.get("commandUi");
|
|
1126
1637
|
const hasCandidates = (sessionId) => {
|
|
1127
1638
|
const face = sessionId === void 0 ? void 0 : sessionOf(sessionId);
|