dsh-rewind-plugin 0.6.3 → 0.7.1
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 +14 -10
- package/README.md +10 -7
- package/assets/screenshots/cleanup-setting.png +0 -0
- package/docs/compat/audit.md +18 -22
- package/docs/format.md +11 -0
- package/docs/snapshot-auto-cleanup.md +4 -12
- package/docs/snapshot-auto-cleanup.zh.md +4 -8
- package/lib/client.js +528 -9
- package/lib/index.js +85 -39
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/portals.d.ts +29 -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/session-events.d.ts +34 -0
- package/lib/types/snapshot-cleanup.d.ts +67 -7
- package/package.json +12 -2
package/lib/client.js
CHANGED
|
@@ -319,6 +319,250 @@ var STYLE = `
|
|
|
319
319
|
color: var(--dsw-alias-label-primary);
|
|
320
320
|
pointer-events: none;
|
|
321
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
|
+
corner-shape: round;
|
|
487
|
+
background: var(--dsw-alias-label-primary-foreground);
|
|
488
|
+
transition: transform 120ms ease;
|
|
489
|
+
}
|
|
490
|
+
.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {
|
|
491
|
+
transform: translateX(16px);
|
|
492
|
+
}
|
|
493
|
+
.dsh-rewind-cleanup-input {
|
|
494
|
+
box-sizing: border-box;
|
|
495
|
+
height: 34px;
|
|
496
|
+
padding: 0 12px;
|
|
497
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
498
|
+
border-radius: 8px;
|
|
499
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
500
|
+
font: inherit;
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
line-height: 1.5;
|
|
503
|
+
color: var(--dsw-alias-label-primary);
|
|
504
|
+
}
|
|
505
|
+
.dsh-rewind-cleanup-input:focus-visible {
|
|
506
|
+
outline: none;
|
|
507
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
508
|
+
}
|
|
509
|
+
.dsh-rewind-cleanup-input:disabled {
|
|
510
|
+
color: var(--dsw-alias-label-tertiary);
|
|
511
|
+
cursor: default;
|
|
512
|
+
}
|
|
513
|
+
.dsh-rewind-cleanup-input-invalid {
|
|
514
|
+
border-color: var(--dsw-alias-label-error);
|
|
515
|
+
}
|
|
516
|
+
.dsh-rewind-cleanup-footer {
|
|
517
|
+
display: flex;
|
|
518
|
+
align-items: center;
|
|
519
|
+
justify-content: flex-end;
|
|
520
|
+
gap: 8px;
|
|
521
|
+
padding: 12px 0 4px;
|
|
522
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
523
|
+
}
|
|
524
|
+
.dsh-rewind-cleanup-failed {
|
|
525
|
+
flex: 1;
|
|
526
|
+
min-width: 0;
|
|
527
|
+
margin: 0;
|
|
528
|
+
font-size: 12px;
|
|
529
|
+
line-height: 1.5;
|
|
530
|
+
color: var(--dsw-alias-label-error);
|
|
531
|
+
}
|
|
532
|
+
.dsh-rewind-cleanup-discard,
|
|
533
|
+
.dsh-rewind-cleanup-save {
|
|
534
|
+
appearance: none;
|
|
535
|
+
border: 1px solid transparent;
|
|
536
|
+
border-radius: 8px;
|
|
537
|
+
padding: 5px 14px;
|
|
538
|
+
font: inherit;
|
|
539
|
+
font-size: 13px;
|
|
540
|
+
line-height: 1.5;
|
|
541
|
+
cursor: pointer;
|
|
542
|
+
}
|
|
543
|
+
.dsh-rewind-cleanup-discard {
|
|
544
|
+
border-color: var(--dsw-alias-border-l2);
|
|
545
|
+
background: none;
|
|
546
|
+
color: var(--dsw-alias-label-secondary);
|
|
547
|
+
}
|
|
548
|
+
.dsh-rewind-cleanup-discard:hover:not(:disabled) {
|
|
549
|
+
color: var(--dsw-alias-label-primary);
|
|
550
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
551
|
+
}
|
|
552
|
+
.dsh-rewind-cleanup-save {
|
|
553
|
+
background: var(--dsw-alias-label-primary);
|
|
554
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
555
|
+
}
|
|
556
|
+
.dsh-rewind-cleanup-discard:disabled,
|
|
557
|
+
.dsh-rewind-cleanup-save:disabled {
|
|
558
|
+
opacity: 0.4;
|
|
559
|
+
cursor: default;
|
|
560
|
+
}
|
|
561
|
+
.dsh-rewind-cleanup-discard:focus-visible,
|
|
562
|
+
.dsh-rewind-cleanup-save:focus-visible {
|
|
563
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
564
|
+
outline-offset: 1px;
|
|
565
|
+
}
|
|
322
566
|
`;
|
|
323
567
|
|
|
324
568
|
// src/client/popover.ts
|
|
@@ -777,7 +1021,16 @@ var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
|
|
|
777
1021
|
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
|
|
778
1022
|
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
779
1023
|
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root], [data-actions-reveal]";
|
|
780
|
-
var PENDING_SEAT_SELECTOR = "[data-pending-steering]
|
|
1024
|
+
var PENDING_SEAT_SELECTOR = "[data-pending-steering]";
|
|
1025
|
+
function actionsContainerOf(row) {
|
|
1026
|
+
const root = row?.matches(ACTIONS_ROOT_SELECTOR) ? row : row?.querySelector(ACTIONS_ROOT_SELECTOR);
|
|
1027
|
+
const actions = root?.lastElementChild;
|
|
1028
|
+
if (actions instanceof HTMLElement && actions.querySelector("button") !== null) return actions;
|
|
1029
|
+
const copy = row?.querySelector("button");
|
|
1030
|
+
const structural = copy?.parentElement;
|
|
1031
|
+
if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
|
|
1032
|
+
return void 0;
|
|
1033
|
+
}
|
|
781
1034
|
function collectTargets(chat, hiddenSeqs) {
|
|
782
1035
|
const rows = /* @__PURE__ */ new Map();
|
|
783
1036
|
for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
|
|
@@ -791,9 +1044,8 @@ function collectTargets(chat, hiddenSeqs) {
|
|
|
791
1044
|
const user = node.data;
|
|
792
1045
|
if (hiddenSeqs.has(node.anchorSeq ?? user.seq)) continue;
|
|
793
1046
|
const row = rows.get(key);
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
if (!(actions instanceof HTMLElement) || actions.querySelector("button") === null) continue;
|
|
1047
|
+
const actions = actionsContainerOf(row);
|
|
1048
|
+
if (actions === void 0) continue;
|
|
797
1049
|
targets.push({ kind: "durable", key, container: actions, seq: user.seq, time: user.time, preview: messagePreviewOf(user) });
|
|
798
1050
|
}
|
|
799
1051
|
return targets;
|
|
@@ -818,9 +1070,8 @@ function collectPendingTargets(snapshot) {
|
|
|
818
1070
|
if (itemId === null) continue;
|
|
819
1071
|
const row = rows[i];
|
|
820
1072
|
if (row === void 0) continue;
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
if (!(actions instanceof HTMLElement) || actions.querySelector("button") === null) continue;
|
|
1073
|
+
const actions = actionsContainerOf(row);
|
|
1074
|
+
if (actions === void 0) continue;
|
|
824
1075
|
const item = steering[i];
|
|
825
1076
|
targets.push({
|
|
826
1077
|
kind: "pending",
|
|
@@ -841,6 +1092,40 @@ function sameTargets(left, right) {
|
|
|
841
1092
|
return other.kind === "pending" && target.itemId === other.itemId;
|
|
842
1093
|
});
|
|
843
1094
|
}
|
|
1095
|
+
function hasExecutedRewindCommand(chat) {
|
|
1096
|
+
for (const key of chat.order) {
|
|
1097
|
+
const node = chat.nodes.get(key);
|
|
1098
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1099
|
+
const command = node.data;
|
|
1100
|
+
if (command.name !== "rewind") continue;
|
|
1101
|
+
const args = command.args ?? "";
|
|
1102
|
+
if (args.includes("preview") || args.includes("__candidates")) continue;
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
function describeHiding(chat, hiddenSeqs) {
|
|
1108
|
+
const commands = [];
|
|
1109
|
+
for (const key of chat.order) {
|
|
1110
|
+
const node = chat.nodes.get(key);
|
|
1111
|
+
if (node === void 0 || node.kind !== "command") continue;
|
|
1112
|
+
const c = node.data;
|
|
1113
|
+
commands.push(`#${c.seq} name=${c.name} outcome=${c.outcome?.kind ?? "-"} marker=${c.outcome?.sourceEventSeq ?? "-"} args=${JSON.stringify(c.args)}`);
|
|
1114
|
+
}
|
|
1115
|
+
const scoped = hiddenSeqs.size > 0 ? `hidden=[${[...hiddenSeqs].slice(0, 20).join(",")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]` : "";
|
|
1116
|
+
let seats = 0;
|
|
1117
|
+
let resolved = 0;
|
|
1118
|
+
let inHidden = 0;
|
|
1119
|
+
for (const seat of document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
|
|
1120
|
+
seats += 1;
|
|
1121
|
+
const key = seat.dataset.chatAnchorKey;
|
|
1122
|
+
const node = key === void 0 ? void 0 : chat.nodes.get(key);
|
|
1123
|
+
if (node === void 0) continue;
|
|
1124
|
+
resolved += 1;
|
|
1125
|
+
if (hiddenSeqs.has(node.anchorSeq)) inHidden += 1;
|
|
1126
|
+
}
|
|
1127
|
+
return `commands=[${commands.join(" | ")}] ${scoped} seats=${seats} resolved=${resolved} inHidden=${inHidden}`;
|
|
1128
|
+
}
|
|
844
1129
|
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
|
|
845
1130
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
846
1131
|
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
@@ -881,6 +1166,9 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
881
1166
|
`[dsh-rewind] hiding: ${hiddenCount} rows, seqs [${[...hiddenSeqs].slice(0, 20).join(", ")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]`
|
|
882
1167
|
);
|
|
883
1168
|
}
|
|
1169
|
+
if (chat !== void 0 && hiddenCount === 0 && hasExecutedRewindCommand(chat)) {
|
|
1170
|
+
console.warn(`[dsh-rewind] rewind not hidden: ${describeHiding(chat, hiddenSeqs)}`);
|
|
1171
|
+
}
|
|
884
1172
|
const durable = chat === void 0 ? [] : collectTargets(chat, hiddenSeqs);
|
|
885
1173
|
const next = [...durable, ...collectPendingTargets(snapshot)];
|
|
886
1174
|
setTargets((current) => sameTargets(current, next) ? current : next);
|
|
@@ -1042,7 +1330,24 @@ var zh = {
|
|
|
1042
1330
|
"popover.impact.restore": "\u8FD8\u539F {path}",
|
|
1043
1331
|
"popover.impact.delete": "\u5220\u9664 {path}",
|
|
1044
1332
|
"popover.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
1045
|
-
"popover.back": "\u8FD4\u56DE"
|
|
1333
|
+
"popover.back": "\u8FD4\u56DE",
|
|
1334
|
+
"cleanup.title": "\u5FEB\u7167\u6E05\u7406",
|
|
1335
|
+
"cleanup.desc": "dsh-rewind \xB7 \u7BA1\u7406\u4F1A\u8BDD\u5FEB\u7167\u5907\u4EFD\u7684\u81EA\u52A8\u6E05\u7406\u7B56\u7565",
|
|
1336
|
+
"cleanup.expand": "\u5C55\u5F00",
|
|
1337
|
+
"cleanup.collapse": "\u6536\u8D77",
|
|
1338
|
+
"cleanup.unsaved": "\u672A\u4FDD\u5B58\u4FEE\u6539",
|
|
1339
|
+
"cleanup.auto": "\u81EA\u52A8\u6E05\u7406",
|
|
1340
|
+
"cleanup.auto.on": "\u5F00\u542F\u540E\u6309\u4E0D\u6D3B\u8DC3\u5929\u6570\u81EA\u52A8\u6E05\u7406\u8FC7\u671F\u4F1A\u8BDD\u5FEB\u7167",
|
|
1341
|
+
"cleanup.auto.off": "\u5173\u95ED\u540E\u4FDD\u7559\u5168\u90E8\u5FEB\u7167\uFF0C\u4E0D\u81EA\u52A8\u6E05\u7406",
|
|
1342
|
+
"cleanup.maxAge": "\u4E0D\u6D3B\u8DC3\u65F6\u95F4\uFF08\u5929\uFF09",
|
|
1343
|
+
"cleanup.maxAge.hint": "\u8D85\u8FC7\u8BE5\u5929\u6570\u672A\u6D3B\u52A8\u7684\u4F1A\u8BDD\u5FEB\u7167\u4F1A\u88AB\u6E05\u7406",
|
|
1344
|
+
"cleanup.invalid": "\u4EC5\u63A5\u53D7\u6B63\u6574\u6570",
|
|
1345
|
+
"cleanup.discard": "\u653E\u5F03\u4FEE\u6539",
|
|
1346
|
+
"cleanup.save": "\u4FDD\u5B58",
|
|
1347
|
+
"cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
|
|
1348
|
+
"cleanup.saved": "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
|
|
1349
|
+
"cleanup.saveFailed": "\u4FDD\u5B58\u5931\u8D25\uFF1A{message}",
|
|
1350
|
+
"cleanup.readonly": "\u8BBE\u7F6E\u6E90\u53EA\u8BFB"
|
|
1046
1351
|
};
|
|
1047
1352
|
var en = {
|
|
1048
1353
|
"button.aria": "Rewind to this message",
|
|
@@ -1068,9 +1373,189 @@ var en = {
|
|
|
1068
1373
|
"popover.impact.restore": "Restore {path}",
|
|
1069
1374
|
"popover.impact.delete": "Delete {path}",
|
|
1070
1375
|
"popover.confirm": "Confirm rewind",
|
|
1071
|
-
"popover.back": "Back"
|
|
1376
|
+
"popover.back": "Back",
|
|
1377
|
+
"cleanup.title": "Snapshot cleanup",
|
|
1378
|
+
"cleanup.desc": "dsh-rewind \xB7 Manage the auto-cleanup policy of session snapshot backups",
|
|
1379
|
+
"cleanup.expand": "Expand",
|
|
1380
|
+
"cleanup.collapse": "Collapse",
|
|
1381
|
+
"cleanup.unsaved": "Unsaved changes",
|
|
1382
|
+
"cleanup.auto": "Auto cleanup",
|
|
1383
|
+
"cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically",
|
|
1384
|
+
"cleanup.auto.off": "When disabled, all snapshot backups are kept; nothing is cleaned automatically.",
|
|
1385
|
+
"cleanup.maxAge": "Idle time (days)",
|
|
1386
|
+
"cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned",
|
|
1387
|
+
"cleanup.invalid": "A positive integer only",
|
|
1388
|
+
"cleanup.discard": "Discard changes",
|
|
1389
|
+
"cleanup.save": "Save",
|
|
1390
|
+
"cleanup.saving": "Saving\u2026",
|
|
1391
|
+
"cleanup.saved": "Saved and applied",
|
|
1392
|
+
"cleanup.saveFailed": "Save failed: {message}",
|
|
1393
|
+
"cleanup.readonly": "Read-only settings source"
|
|
1072
1394
|
};
|
|
1073
1395
|
|
|
1396
|
+
// src/client/settings-card.tsx
|
|
1397
|
+
var import_react2 = require("react");
|
|
1398
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1399
|
+
var CLEANUP_SETTINGS_NAMESPACE = "dsh-rewind-snapshot-cleanup";
|
|
1400
|
+
var DEFAULT_MAX_AGE_DAYS = 30;
|
|
1401
|
+
function draftFrom(policy) {
|
|
1402
|
+
return { enabled: policy?.enabled ?? false, maxAgeDays: String(policy?.maxAgeDays ?? "") };
|
|
1403
|
+
}
|
|
1404
|
+
function maxAgeOf(text) {
|
|
1405
|
+
const trimmed = text.trim();
|
|
1406
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
1407
|
+
const days = Number(trimmed);
|
|
1408
|
+
return Number.isSafeInteger(days) && days > 0 ? days : null;
|
|
1409
|
+
}
|
|
1410
|
+
function configOf(draft) {
|
|
1411
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1412
|
+
if (days === null) return null;
|
|
1413
|
+
return { enabled: draft.enabled, maxAgeDays: days };
|
|
1414
|
+
}
|
|
1415
|
+
function dirtyOf(base, draft) {
|
|
1416
|
+
return base.enabled !== draft.enabled || base.maxAgeDays !== draft.maxAgeDays;
|
|
1417
|
+
}
|
|
1418
|
+
function SettingsCleanupCard({ api, t }) {
|
|
1419
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
1420
|
+
const [baseline, setBaseline] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1421
|
+
const [draft, setDraft] = (0, import_react2.useState)(() => draftFrom(api.read()));
|
|
1422
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1423
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1424
|
+
const writable = api.writable();
|
|
1425
|
+
(0, import_react2.useEffect)(() => api.subscribe(() => {
|
|
1426
|
+
const next = draftFrom(api.read());
|
|
1427
|
+
setBaseline((base) => {
|
|
1428
|
+
setDraft((cur) => dirtyOf(base, cur) ? cur : next);
|
|
1429
|
+
return next;
|
|
1430
|
+
});
|
|
1431
|
+
}), [api]);
|
|
1432
|
+
const dirty = dirtyOf(baseline, draft);
|
|
1433
|
+
const days = maxAgeOf(draft.maxAgeDays);
|
|
1434
|
+
const invalid = days === null;
|
|
1435
|
+
const disabled = busy || !writable;
|
|
1436
|
+
const edit = (patch) => {
|
|
1437
|
+
setDraft((cur) => ({ ...cur, ...patch }));
|
|
1438
|
+
setError(null);
|
|
1439
|
+
};
|
|
1440
|
+
const save = async () => {
|
|
1441
|
+
if (busy || !writable || !dirty) return;
|
|
1442
|
+
const next = configOf(draft);
|
|
1443
|
+
if (next === null) {
|
|
1444
|
+
setError(t("cleanup.invalid"));
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
setBusy(true);
|
|
1448
|
+
setError(null);
|
|
1449
|
+
try {
|
|
1450
|
+
await api.save(next);
|
|
1451
|
+
setBaseline(draft);
|
|
1452
|
+
setError(null);
|
|
1453
|
+
} catch (e) {
|
|
1454
|
+
setError(t("cleanup.saveFailed", { message: e instanceof Error ? e.message : String(e) }));
|
|
1455
|
+
} finally {
|
|
1456
|
+
setBusy(false);
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
const discard = () => {
|
|
1460
|
+
if (busy) return;
|
|
1461
|
+
setDraft(baseline);
|
|
1462
|
+
setError(null);
|
|
1463
|
+
};
|
|
1464
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: `dsh-rewind-cleanup-card${open ? " dsh-rewind-cleanup-card-open" : ""}`, children: [
|
|
1465
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1466
|
+
"button",
|
|
1467
|
+
{
|
|
1468
|
+
type: "button",
|
|
1469
|
+
className: "dsh-rewind-cleanup-header",
|
|
1470
|
+
"aria-expanded": open,
|
|
1471
|
+
"aria-label": `${t(open ? "cleanup.collapse" : "cleanup.expand")}: ${t("cleanup.title")}`,
|
|
1472
|
+
onClick: () => setOpen(!open),
|
|
1473
|
+
children: [
|
|
1474
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-rewind-cleanup-head-text", children: [
|
|
1475
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-name", children: t("cleanup.title") }),
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-desc", children: t("cleanup.desc") })
|
|
1477
|
+
] }),
|
|
1478
|
+
dirty ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-pending", children: t("cleanup.unsaved") }) : null,
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1480
|
+
"svg",
|
|
1481
|
+
{
|
|
1482
|
+
className: `dsh-rewind-cleanup-chevron${open ? " dsh-rewind-cleanup-chevron-open" : ""}`,
|
|
1483
|
+
width: "14",
|
|
1484
|
+
height: "14",
|
|
1485
|
+
viewBox: "0 0 16 16",
|
|
1486
|
+
"aria-hidden": "true",
|
|
1487
|
+
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" })
|
|
1488
|
+
}
|
|
1489
|
+
)
|
|
1490
|
+
]
|
|
1491
|
+
}
|
|
1492
|
+
),
|
|
1493
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-body", children: [
|
|
1494
|
+
!writable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-readonly", role: "status", children: t("cleanup.readonly") }) : null,
|
|
1495
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-permission", children: [
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-toggle-row", children: [
|
|
1497
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-toggle-label", id: "dsh-rewind-cleanup-enabled-label", children: t("cleanup.auto") }),
|
|
1498
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1499
|
+
"button",
|
|
1500
|
+
{
|
|
1501
|
+
type: "button",
|
|
1502
|
+
role: "switch",
|
|
1503
|
+
className: `dsh-rewind-cleanup-switch${draft.enabled ? " dsh-rewind-cleanup-switch-on" : ""}`,
|
|
1504
|
+
"aria-checked": draft.enabled,
|
|
1505
|
+
"aria-labelledby": "dsh-rewind-cleanup-enabled-label",
|
|
1506
|
+
disabled,
|
|
1507
|
+
onClick: () => edit({ enabled: !draft.enabled }),
|
|
1508
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-rewind-cleanup-thumb" })
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
] }),
|
|
1512
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-hint", children: t(draft.enabled ? "cleanup.auto.on" : "cleanup.auto.off") })
|
|
1513
|
+
] }),
|
|
1514
|
+
draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-field", children: [
|
|
1515
|
+
/* @__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") }) }),
|
|
1516
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1517
|
+
"input",
|
|
1518
|
+
{
|
|
1519
|
+
className: `dsh-rewind-cleanup-input${invalid ? " dsh-rewind-cleanup-input-invalid" : ""}`,
|
|
1520
|
+
type: "text",
|
|
1521
|
+
inputMode: "numeric",
|
|
1522
|
+
id: "dsh-rewind-cleanup-maxage",
|
|
1523
|
+
value: draft.maxAgeDays,
|
|
1524
|
+
disabled,
|
|
1525
|
+
"aria-invalid": invalid || void 0,
|
|
1526
|
+
placeholder: String(DEFAULT_MAX_AGE_DAYS),
|
|
1527
|
+
onChange: (e) => edit({ maxAgeDays: e.target.value })
|
|
1528
|
+
}
|
|
1529
|
+
),
|
|
1530
|
+
/* @__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") })
|
|
1531
|
+
] }) : null,
|
|
1532
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-rewind-cleanup-footer", children: [
|
|
1533
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dsh-rewind-cleanup-failed", role: "status", children: error }) : null,
|
|
1534
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1535
|
+
"button",
|
|
1536
|
+
{
|
|
1537
|
+
type: "button",
|
|
1538
|
+
className: "dsh-rewind-cleanup-discard",
|
|
1539
|
+
disabled: !dirty || busy || !writable,
|
|
1540
|
+
onClick: discard,
|
|
1541
|
+
children: t("cleanup.discard")
|
|
1542
|
+
}
|
|
1543
|
+
),
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1545
|
+
"button",
|
|
1546
|
+
{
|
|
1547
|
+
type: "button",
|
|
1548
|
+
className: "dsh-rewind-cleanup-save",
|
|
1549
|
+
disabled: !dirty || busy || !writable || invalid,
|
|
1550
|
+
onClick: save,
|
|
1551
|
+
children: busy ? t("cleanup.saving") : t("cleanup.save")
|
|
1552
|
+
}
|
|
1553
|
+
)
|
|
1554
|
+
] })
|
|
1555
|
+
] }) : null
|
|
1556
|
+
] });
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1074
1559
|
// src/client/index.ts
|
|
1075
1560
|
var name = "dsh-rewind";
|
|
1076
1561
|
var inject = ["slots", "sessions", "locale", "commandUi"];
|
|
@@ -1122,6 +1607,40 @@ function apply(ctx) {
|
|
|
1122
1607
|
},
|
|
1123
1608
|
createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
|
|
1124
1609
|
));
|
|
1610
|
+
const clientCtx = ctx;
|
|
1611
|
+
clientCtx.inject(["settingsScope"], (scoped) => {
|
|
1612
|
+
try {
|
|
1613
|
+
const scope = scoped.settingsScope.bind({ namespace: CLEANUP_SETTINGS_NAMESPACE });
|
|
1614
|
+
const cardApi = {
|
|
1615
|
+
read: () => {
|
|
1616
|
+
const value = scope.getSnapshot().value;
|
|
1617
|
+
return value === void 0 ? void 0 : { enabled: value.enabled, maxAgeDays: value.maxAgeDays };
|
|
1618
|
+
},
|
|
1619
|
+
writable: () => {
|
|
1620
|
+
return scope.getSnapshot().writable === true;
|
|
1621
|
+
},
|
|
1622
|
+
save: async (next) => {
|
|
1623
|
+
await scope.set("enabled", next.enabled);
|
|
1624
|
+
await scope.set("maxAgeDays", next.maxAgeDays);
|
|
1625
|
+
},
|
|
1626
|
+
subscribe: (cb) => scope.subscribe(cb)
|
|
1627
|
+
};
|
|
1628
|
+
scoped.slots.inject("settings.plugin.item", () => scoped.slots.register(
|
|
1629
|
+
{
|
|
1630
|
+
name: "settings.plugin.item",
|
|
1631
|
+
key: CLEANUP_SETTINGS_NAMESPACE,
|
|
1632
|
+
// Match the official cards / dsh-market: locale + inject provide
|
|
1633
|
+
// the card its props through the slot renderer (the keyed card owns
|
|
1634
|
+
// its internals, but the page feeds it locale + the bound api).
|
|
1635
|
+
locale: NS,
|
|
1636
|
+
inject: () => ({ t, api: cardApi })
|
|
1637
|
+
},
|
|
1638
|
+
SettingsCleanupCard
|
|
1639
|
+
));
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
console.error("[dsh-rewind] settings card register failed:", error);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1125
1644
|
const commandUi = ctx.get("commandUi");
|
|
1126
1645
|
const hasCandidates = (sessionId) => {
|
|
1127
1646
|
const face = sessionId === void 0 ? void 0 : sessionOf(sessionId);
|