dsh-rewind-plugin 0.9.1 → 0.10.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/architecture.md +24 -16
- package/docs/compat/audit.md +4 -3
- package/docs/format.md +3 -4
- package/docs/harness-reference.md +1 -4
- package/docs/release/release.md +7 -0
- package/docs/release/release.zh.md +5 -0
- package/docs/rewind-fix.md +1 -1
- package/docs/rewind-fix.zh.md +1 -1
- package/lib/client.js +12 -6
- package/lib/index.js +12 -646
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +0 -20
- package/lib/types/snapshot-cleanup.d.ts +12 -48
- package/package.json +47 -44
- package/lib/types/rewind-fix.d.ts +0 -111
- package/lib/types/rewind-marker-repair.d.ts +0 -129
- package/lib/types/session-log-io.d.ts +0 -57
|
@@ -23,4 +23,4 @@ export declare const CLASS: {
|
|
|
23
23
|
/** The ↶ glyph, drawn inline so the bundle stays dependency-free. */
|
|
24
24
|
export declare const REWIND_ICON_SVG: string;
|
|
25
25
|
/** One injected stylesheet (scoped under `.dsh-rewind-*`). */
|
|
26
|
-
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px\n border-l4, 0.5px internal separators). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch-
|
|
26
|
+
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px\n border-l4, 0.5px internal separators). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module).\n * The track mirrors the harness ui-primitives Switch (0.1.3-alpha.2 line): the\n * on/off appearance keys off aria-checked rather than a parallel class, so the\n * visual state cannot disagree with the state assistive technology reads, and\n * corner-shape: round opts the capsule track out of the global superellipse\n * (which would square the capsule ends off against the round thumb inside). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n corner-shape: round;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
package/lib/types/locales.d.ts
CHANGED
|
@@ -69,26 +69,6 @@ export declare const en: {
|
|
|
69
69
|
'cleanup.clearCancelled': string;
|
|
70
70
|
'cleanup.clearFailed': string;
|
|
71
71
|
'cleanup.usage': string;
|
|
72
|
-
'rewindfix.description': string;
|
|
73
|
-
'rewindfix.inputHint': string;
|
|
74
|
-
'rewindfix.dryRun': string;
|
|
75
|
-
'rewindfix.done': string;
|
|
76
|
-
'rewindfix.cancelled': string;
|
|
77
|
-
'rewindfix.launcherGuard': string;
|
|
78
|
-
'rewindfix.counts': string;
|
|
79
|
-
'rewindfix.args': string;
|
|
80
|
-
'rewindfix.writeOk': string;
|
|
81
|
-
'rewindfix.skip.loaded': string;
|
|
82
|
-
'rewindfix.skip.noMarkers': string;
|
|
83
|
-
'rewindfix.skip.noArtifact': string;
|
|
84
|
-
'rewindfix.skip.loadedNow': string;
|
|
85
|
-
'rewindfix.fail.unreadable': string;
|
|
86
|
-
'rewindfix.fail.repair': string;
|
|
87
|
-
'rewindfix.fail.locked': string;
|
|
88
|
-
'rewindfix.fail.generic': string;
|
|
89
|
-
'rewindfix.persistenceUnavailable': string;
|
|
90
|
-
'rewindfix.sessionStoreUnavailable': string;
|
|
91
|
-
'rewindfix.locked': string;
|
|
92
72
|
};
|
|
93
73
|
/** The host rewind dictionary key union. */
|
|
94
74
|
export type HostKey = keyof typeof en;
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Snapshot cleanup policy: the
|
|
2
|
+
* Snapshot cleanup policy: the settings-backed policy, its validation, the
|
|
3
3
|
* `/snapshot-auto-cleanup` command's argument grammar, and the auto-sweep
|
|
4
4
|
* throttle. Kept free of host wiring so the policy and the parser are
|
|
5
5
|
* unit-testable in isolation; `src/index.ts` is the only consumer.
|
|
6
6
|
*
|
|
7
7
|
* Semantics (the "cleanup" vocabulary deliberately avoids "retention"):
|
|
8
8
|
* - `enabled` toggles the AUTOMATIC (24h) sweep. `false` (the default) keeps
|
|
9
|
-
* every snapshot — the pre-feature behavior — and never
|
|
9
|
+
* every snapshot — the pre-feature behavior — and never persists a policy.
|
|
10
10
|
* - `maxAgeDays` is the only "keep" knob: a finished session dir whose newest
|
|
11
11
|
* member stamp is older than this many days of idle is removed by a sweep.
|
|
12
|
-
* `0`/negative/non-integer are rejected, so a broken
|
|
12
|
+
* `0`/negative/non-integer are rejected, so a broken value can never steer
|
|
13
13
|
* the sweep into deleting everything.
|
|
14
|
-
* - The
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* - The policy lives in the dsh-settings document under
|
|
15
|
+
* `dsh-rewind-snapshot-cleanup`, created ONLY by an explicit
|
|
16
|
+
* `/snapshot-auto-cleanup` write. An absent value is the safe default (off);
|
|
17
|
+
* an unreadable or invalid value fail-closes a sweep (deletes nothing)
|
|
17
18
|
* instead of guessing.
|
|
18
19
|
*
|
|
19
20
|
* @module dsh-rewind/snapshot-cleanup
|
|
20
21
|
*/
|
|
21
22
|
import z from '@deepseek-ai/schemastery';
|
|
22
|
-
/** The cleanup policy, as persisted
|
|
23
|
+
/** The cleanup policy, as persisted in the dsh-settings document. */
|
|
23
24
|
export interface CleanupConfig {
|
|
24
25
|
readonly enabled: boolean;
|
|
25
26
|
readonly maxAgeDays: number;
|
|
26
27
|
}
|
|
27
|
-
export declare const CLEANUP_CONFIG_FILENAME = "snapshot-cleanup.json";
|
|
28
28
|
/** The default keep threshold: finished sessions idle > 30 days are pruned. */
|
|
29
29
|
export declare const DEFAULT_MAX_AGE_DAYS = 30;
|
|
30
30
|
/** The safe default policy (off) — a missing/corrupt file behaves like this. */
|
|
31
31
|
export declare const DEFAULT_CLEANUP_CONFIG: CleanupConfig;
|
|
32
32
|
/**
|
|
33
|
-
* The dsh-settings namespace that backs the cleanup policy
|
|
33
|
+
* The dsh-settings namespace that backs the cleanup policy.
|
|
34
34
|
* Namespaces must match the settings provider's `^[a-z][a-z0-9-]*$` grammar (no
|
|
35
35
|
* dots), so this is hyphenated, not dotted.
|
|
36
36
|
*/
|
|
@@ -73,30 +73,12 @@ export interface CleanupConfigStore {
|
|
|
73
73
|
* value can never reach the document (defense-in-depth below the schema).
|
|
74
74
|
*/
|
|
75
75
|
export declare function settingsCleanupStore(scope: CleanupSettingsScope): CleanupConfigStore;
|
|
76
|
-
/**
|
|
77
|
-
* One-time migration of the pre-GUI cleanup policy file into the settings
|
|
78
|
-
* document. Idempotent and cheap: it is called on every startup but only does
|
|
79
|
-
* work once — a present-and-parsed legacy file is written into the scope and
|
|
80
|
-
* then deleted, after which the read is an ENOENT no-op. A missing file is a
|
|
81
|
-
* no-op; an invalid file writes the safe default (deleting nothing) and logs.
|
|
82
|
-
* This is the ONLY consumption of {@link loadCleanupConfig} after migration.
|
|
83
|
-
* @returns whether a legacy file was actually migrated.
|
|
84
|
-
*/
|
|
85
|
-
export declare function migrateLegacyCleanupConfig(legacyPath: string, scope: CleanupSettingsScope, log: (msg: string) => void): Promise<boolean>;
|
|
86
76
|
/** Auto-sweep cadence (the user's hardcoded 24h rhythm — not user-set). */
|
|
87
77
|
export declare const AUTO_SWEEP_INTERVAL_MS: number;
|
|
88
|
-
/**
|
|
89
|
-
* Resolve the LEGACY pre-migration config file path (the only remaining use of
|
|
90
|
-
* the file store): `<harness home>/snapshot-cleanup.json`, derived from
|
|
91
|
-
* `dshHome` (config.dshHome > `$DSH_HOME` > `~/.dsh`) so the migration follows
|
|
92
|
-
* the harness home instead of hardcoding `~/.dsh`. The `DSH_SNAPSHOT_CLEANUP_CONFIG`
|
|
93
|
-
* env override was removed when the policy moved into the dsh-settings document.
|
|
94
|
-
*/
|
|
95
|
-
export declare function resolveCleanupConfigPath(dshHome?: string): string;
|
|
96
78
|
/** The state file that records the last automatic-sweep wall-clock time. */
|
|
97
79
|
export declare const STATE_FILENAME = "snapshot-cleanup-last-sweep.json";
|
|
98
80
|
/**
|
|
99
|
-
* Resolve the last-sweep state path. It sits
|
|
81
|
+
* Resolve the last-sweep state path. It sits under the harness home so the
|
|
100
82
|
* 24h cadence SURVIVES a host restart (a real deployment is rarely up 24/7,
|
|
101
83
|
* so an in-memory timestamp would reset on every boot and re-sweep too often).
|
|
102
84
|
*/
|
|
@@ -151,26 +133,8 @@ export declare function parseCleanupConfig(raw: unknown): {
|
|
|
151
133
|
error: string;
|
|
152
134
|
};
|
|
153
135
|
/**
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
* structurally-invalid file is `ok:false` so a sweep fail-closes.
|
|
157
|
-
*/
|
|
158
|
-
export declare function loadCleanupConfig(path: string): Promise<{
|
|
159
|
-
ok: true;
|
|
160
|
-
config: CleanupConfig;
|
|
161
|
-
fromFile: boolean;
|
|
162
|
-
} | {
|
|
163
|
-
ok: false;
|
|
164
|
-
error: string;
|
|
165
|
-
}>;
|
|
166
|
-
/**
|
|
167
|
-
* Persist a validated {@link CleanupConfig}, atomically (temp + rename). Any
|
|
168
|
-
* invalid value throws before the file is touched, so the command can never
|
|
169
|
-
* write a broken policy.
|
|
170
|
-
*/
|
|
171
|
-
export declare function saveCleanupConfig(path: string, config: CleanupConfig): Promise<void>;
|
|
172
|
-
/** The `/snapshot-auto-cleanup` sub-command the parser can resolve to. */
|
|
173
|
-
export type CleanupCommandAction = 'status' | 'on' | 'off' | 'max-age' | 'run';
|
|
136
|
+
* The `/snapshot-auto-cleanup` sub-command the parser can resolve to.
|
|
137
|
+
*/ export type CleanupCommandAction = 'status' | 'on' | 'off' | 'max-age' | 'run';
|
|
174
138
|
/** A parsed `/snapshot-auto-cleanup` command (excludes the error branch). */
|
|
175
139
|
export type CleanupCommand = {
|
|
176
140
|
action: 'status' | 'on' | 'off';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-alpha.2",
|
|
4
4
|
"description": "DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspace backup that restores files together with the rewind (full Claude Code /rewind semantics).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"bundle": {
|
|
50
50
|
"patch": "./cordis.patch.yml"
|
|
51
51
|
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"dsh": ">=0.1.3-alpha.2"
|
|
54
|
+
},
|
|
52
55
|
"client": {
|
|
53
56
|
"inject": [
|
|
54
57
|
"@deepseek-ai/dsh-client-locale",
|
|
@@ -73,18 +76,18 @@
|
|
|
73
76
|
"peerDependencies": {
|
|
74
77
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
75
78
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
76
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-commands": "^0.1.
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
79
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
82
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
83
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
84
|
-
"@deepseek-ai/dsh-sandbox": "^0.1.
|
|
85
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
86
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
87
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
79
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.3-alpha.2",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.3-alpha.2",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.3-alpha.2",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
|
|
83
|
+
"@deepseek-ai/dsh-commands": "^0.1.3-alpha.2",
|
|
84
|
+
"@deepseek-ai/dsh-fs": "^0.1.3-alpha.2",
|
|
85
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.3-alpha.2",
|
|
86
|
+
"@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
|
|
87
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.3-alpha.2",
|
|
88
|
+
"@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
|
|
89
|
+
"@deepseek-ai/dsh-settings": "^0.1.3-alpha.2",
|
|
90
|
+
"@deepseek-ai/dsh-tools": "^0.1.3-alpha.2"
|
|
88
91
|
},
|
|
89
92
|
"peerDependenciesMeta": {
|
|
90
93
|
"@deepseek-ai/cordis": {
|
|
@@ -133,37 +136,37 @@
|
|
|
133
136
|
"devDependencies": {
|
|
134
137
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
135
138
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
136
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
137
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
138
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
139
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
140
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
141
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
142
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
143
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
144
|
-
"@deepseek-ai/dsh-scope": "^0.1.
|
|
145
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
146
|
-
"@deepseek-ai/dsh-user-questions": "^0.1.
|
|
147
|
-
"@deepseek-ai/dsh-client-ui-commands": "^0.1.
|
|
148
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.
|
|
149
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
150
|
-
"@deepseek-ai/dsh-command-compact": "^0.1.
|
|
151
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
152
|
-
"@deepseek-ai/dsh-compaction": "^0.1.
|
|
153
|
-
"@deepseek-ai/dsh-compaction-basic": "^0.1.
|
|
154
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
155
|
-
"@deepseek-ai/dsh-goal": "^0.1.
|
|
156
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
157
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
158
|
-
"@deepseek-ai/dsh-plan-mode": "^0.1.
|
|
159
|
-
"@deepseek-ai/dsh-sandbox": "^0.1.
|
|
160
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
161
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
162
|
-
"@deepseek-ai/dsh-session-stats": "^0.1.
|
|
163
|
-
"@deepseek-ai/dsh-session-title": "^0.1.
|
|
164
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
165
|
-
"@deepseek-ai/dsh-token-meter": "^0.1.
|
|
166
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
139
|
+
"@deepseek-ai/dsh-agent": "^0.1.3-alpha.2",
|
|
140
|
+
"@deepseek-ai/dsh-attachment": "^0.1.3-alpha.2",
|
|
141
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.3-alpha.2",
|
|
142
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.3-alpha.2",
|
|
143
|
+
"@deepseek-ai/dsh-client-store": "^0.1.3-alpha.2",
|
|
144
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.3-alpha.2",
|
|
145
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.3-alpha.2",
|
|
146
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.3-alpha.2",
|
|
147
|
+
"@deepseek-ai/dsh-scope": "^0.1.3-alpha.2",
|
|
148
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.3-alpha.2",
|
|
149
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.3-alpha.2",
|
|
150
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.3-alpha.2",
|
|
151
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.3-alpha.2",
|
|
152
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
|
|
153
|
+
"@deepseek-ai/dsh-command-compact": "^0.1.3-alpha.2",
|
|
154
|
+
"@deepseek-ai/dsh-commands": "^0.1.3-alpha.2",
|
|
155
|
+
"@deepseek-ai/dsh-compaction": "^0.1.3-alpha.2",
|
|
156
|
+
"@deepseek-ai/dsh-compaction-basic": "^0.1.3-alpha.2",
|
|
157
|
+
"@deepseek-ai/dsh-fs": "^0.1.3-alpha.2",
|
|
158
|
+
"@deepseek-ai/dsh-goal": "^0.1.3-alpha.2",
|
|
159
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.3-alpha.2",
|
|
160
|
+
"@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
|
|
161
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.3-alpha.2",
|
|
162
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.3-alpha.2",
|
|
163
|
+
"@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
|
|
164
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.3-alpha.2",
|
|
165
|
+
"@deepseek-ai/dsh-session-stats": "^0.1.3-alpha.2",
|
|
166
|
+
"@deepseek-ai/dsh-session-title": "^0.1.3-alpha.2",
|
|
167
|
+
"@deepseek-ai/dsh-settings": "^0.1.3-alpha.2",
|
|
168
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.3-alpha.2",
|
|
169
|
+
"@deepseek-ai/dsh-tools": "^0.1.3-alpha.2",
|
|
167
170
|
"@types/node": "^24.0.0",
|
|
168
171
|
"@types/react": "^18.3.31",
|
|
169
172
|
"@types/react-dom": "^18.3.7",
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `/dsh-rewind-fix` — orchestration layer.
|
|
3
|
-
*
|
|
4
|
-
* This module owns the COMMAND semantics and the per-session repair pipeline. It
|
|
5
|
-
* intentionally does NOT re-implement the physical codec (`./session-log-io.ts`)
|
|
6
|
-
* or the A/B→C transform (`./rewind-marker-repair.ts`); it wires those against
|
|
7
|
-
* the harness's own session services (read path = `sessionPersistence` +
|
|
8
|
-
* `ctx.sessions`; write path = self-contained 2-frame zstd rewrite + atomic
|
|
9
|
-
* tmp+rename, because the persistence layer is append-only & repairing a log
|
|
10
|
-
* must rewrite non-tail events).
|
|
11
|
-
*
|
|
12
|
-
* Safety model (settled in the design):
|
|
13
|
-
* - Only NON-loaded (closed) sessions are repaired. "Loaded" = the harness has
|
|
14
|
-
* the session's in-memory `this.log`, which is the seq authority — rewriting
|
|
15
|
-
* that session's file would fork it.
|
|
16
|
-
* - Per-session `.lock` (O_EXCL) guards against two tabs/processes fixing the
|
|
17
|
-
* same closed session concurrently.
|
|
18
|
-
* - Each repair is backed up and rolled back on write/verify failure; an
|
|
19
|
-
* interrupted run is idempotent (already-C / marker-free sessions are no-ops).
|
|
20
|
-
* - Repaired sessions have their snapshots cleared via `store.clearSession`.
|
|
21
|
-
*
|
|
22
|
-
* There is no streaming progress channel (`CommandResult` returns once), so the
|
|
23
|
-
* command is "dry wait" like `/compact`: the client is told it takes minutes and
|
|
24
|
-
* a single final summary is returned via `command/done`.
|
|
25
|
-
*
|
|
26
|
-
* {@link runRewindFix} is the DOMAIN core: it returns structured data (not a
|
|
27
|
-
* rendered string) over a narrow deps interface, so it is unit-testable without a
|
|
28
|
-
* real cordis `Context` and stays locale-agnostic. `renderRewindFixReport`
|
|
29
|
-
* renders that data through the plugin's locale translator `t`, and
|
|
30
|
-
* `registerRewindFix` adapts a real `Context` + `SnapshotStore` + renderer and
|
|
31
|
-
* registers the command.
|
|
32
|
-
*/
|
|
33
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
34
|
-
import type { SessionHeader, SessionId } from '@deepseek-ai/dsh-session';
|
|
35
|
-
/** A locale renderer: dictionary key + optional `{name}` params → text. */
|
|
36
|
-
export type RenderFn = (key: string, params?: Record<string, string | number>) => string;
|
|
37
|
-
/** Structural view of the snapshot store the repair needs (`clearSession` only). */
|
|
38
|
-
interface SnapshotStoreLike {
|
|
39
|
-
clearSession(sessionId: string): Promise<unknown>;
|
|
40
|
-
}
|
|
41
|
-
/** The narrow dependency set {@link runRewindFix} needs (injected; unit-testable). */
|
|
42
|
-
export interface RewindFixDeps {
|
|
43
|
-
listSnapshots(signal?: AbortSignal): Promise<Array<{
|
|
44
|
-
header: SessionHeader;
|
|
45
|
-
}>>;
|
|
46
|
-
readRaw(id: SessionId, signal?: AbortSignal): Promise<{
|
|
47
|
-
content: string;
|
|
48
|
-
} | undefined>;
|
|
49
|
-
locate(header: SessionHeader): {
|
|
50
|
-
path: string;
|
|
51
|
-
} | undefined;
|
|
52
|
-
isSessionLoaded(id: SessionId): boolean;
|
|
53
|
-
clearSession(id: string): Promise<unknown>;
|
|
54
|
-
}
|
|
55
|
-
export interface RewindFixOptions {
|
|
56
|
-
/** `--apply` executes the repair; otherwise dry run (no writes). */
|
|
57
|
-
apply: boolean;
|
|
58
|
-
/** The launcher session itself needs repair (guard warning). */
|
|
59
|
-
launcherHasMarkers: boolean;
|
|
60
|
-
signal?: AbortSignal;
|
|
61
|
-
/**
|
|
62
|
-
* Max sessions processed concurrently. Sessions are independent files, so a
|
|
63
|
-
* bounded pool overlaps zstd decode/encode across them (the dominant cost).
|
|
64
|
-
* Default 4; capped by the session count. Higher means more peak memory.
|
|
65
|
-
*/
|
|
66
|
-
concurrency?: number;
|
|
67
|
-
}
|
|
68
|
-
/** Why a session was skipped or failed (drives the localized label). */
|
|
69
|
-
export type OutcomeReason = 'loaded' | 'no-markers' | 'no-artifact' | 'loaded-between' | 'unreadable' | 'repair' | 'locked';
|
|
70
|
-
/** One per-session outcome, carried into the report. */
|
|
71
|
-
export interface SessionOutcome {
|
|
72
|
-
readonly id: string;
|
|
73
|
-
readonly status: 'repaired' | 'skipped' | 'failed';
|
|
74
|
-
readonly a: number;
|
|
75
|
-
readonly b: number;
|
|
76
|
-
readonly c: number;
|
|
77
|
-
/** Number of `/rewind` `args` targets rewired (C→C stale-args repair). */
|
|
78
|
-
readonly staleArgs: number;
|
|
79
|
-
readonly reason?: OutcomeReason;
|
|
80
|
-
readonly error?: string;
|
|
81
|
-
}
|
|
82
|
-
/** Structured result of {@link runRewindFix} (locale-agnostic, unit-testable). */
|
|
83
|
-
export interface RewindFixResult {
|
|
84
|
-
readonly launcherHasMarkers: boolean;
|
|
85
|
-
readonly cancelled: boolean;
|
|
86
|
-
readonly scanned: number;
|
|
87
|
-
readonly durationMs: number;
|
|
88
|
-
readonly apply: boolean;
|
|
89
|
-
readonly sessions: SessionOutcome[];
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Register the `/dsh-rewind-fix` host command. `store` must be the plugin's
|
|
93
|
-
* `SnapshotStore` so repaired sessions get their snapshots cleared; `render` is
|
|
94
|
-
* the plugin's locale translator.
|
|
95
|
-
*/
|
|
96
|
-
export declare function registerRewindFix(ctx: Context, store: SnapshotStoreLike, render: RenderFn): void;
|
|
97
|
-
/**
|
|
98
|
-
* The domain core. Enumerates all persisted sessions, applies the safety rules,
|
|
99
|
-
* repairs each marker-bearing CLOSED session, and returns structured outcome
|
|
100
|
-
* data. It never throws: every per-session failure is recorded and the run
|
|
101
|
-
* continues (idempotent; a rollback restores the original artifact).
|
|
102
|
-
*/
|
|
103
|
-
export declare function runRewindFix(deps: RewindFixDeps, opts: RewindFixOptions): Promise<RewindFixResult>;
|
|
104
|
-
/**
|
|
105
|
-
* Render a {@link RewindFixResult} through the locale translator. The RESULT
|
|
106
|
-
* headline is the FIRST line (so the client card's single-line collapsed
|
|
107
|
-
* summary shows it), followed by the per-session detail lines (shown when the
|
|
108
|
-
* card is expanded).
|
|
109
|
-
*/
|
|
110
|
-
export declare function renderRewindFixReport(result: RewindFixResult, render: RenderFn): string;
|
|
111
|
-
export {};
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure repair core for legacy rewind markers.
|
|
3
|
-
*
|
|
4
|
-
* The `/dsh-rewind-fix` command rewrites the OLD plugin's rewind marker — a
|
|
5
|
-
* bare `assistant/message(turn=N, step=0)` (form A) or a ghost-frame
|
|
6
|
-
* `[step/start][assistant/message][step/end]` inside a closed turn (form B) —
|
|
7
|
-
* into the CURRENT marker shape (form C): a `user/message` whose `source` cites
|
|
8
|
-
* the plugin, whose `surfaceOp`/`sourceEventSeqs` still carry the surface
|
|
9
|
-
* replace, and whose content is the constant `(empty message)` placeholder
|
|
10
|
-
* (never empty, so a strict gateway does not reject it). Form C is what a 0.1.3
|
|
11
|
-
* harness accepts. It ALSO upgrades an older form-C marker whose content was
|
|
12
|
-
* empty to the canonical placeholder.
|
|
13
|
-
*
|
|
14
|
-
* This module is PURE: it only recognizes event shapes and rewrites them. It
|
|
15
|
-
* does zero IO and has no dependency on the harness session services. The
|
|
16
|
-
* session-log IO layer (read/write) and the command orchestration live
|
|
17
|
-
* elsewhere; here we export the transform plus the shared C-marker contract so
|
|
18
|
-
* the running plugin and the repair agree on the exact marker data shape.
|
|
19
|
-
*
|
|
20
|
-
* Correctness contract:
|
|
21
|
-
* - A → C is an in-place retype (seq does NOT shift).
|
|
22
|
-
* - B → C deletes the two ghost `step/start`/`step/end` events AND re-denses
|
|
23
|
-
* every surviving event's `seq`, so the whole log is one global compaction —
|
|
24
|
-
* the same mechanism DSH's own compaction/forward-migration uses.
|
|
25
|
-
* - Every reference (surfaceOp/sourceEventSeqs/data.*) is rewritten through a
|
|
26
|
-
* single `oldSeq → newSeq` map, because deleting frames shifts every later
|
|
27
|
-
* event and a later marker's references may cite an earlier marker's seq.
|
|
28
|
-
* - A marker that survives is retargeted to form C and participates in the
|
|
29
|
-
* renumbering, so references INTO it follow the map too.
|
|
30
|
-
*
|
|
31
|
-
* The transform never mutates its inputs: input events are copied and the
|
|
32
|
-
* mutable reference fields are rebuilt onto fresh objects (a decoded event's
|
|
33
|
-
* `data` is deep-frozen, so mutating a range in place would throw).
|
|
34
|
-
*/
|
|
35
|
-
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
36
|
-
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
37
|
-
/** The rewind-marker source the current plugin writes (form C contract). */
|
|
38
|
-
export declare const REWIND_MARKER_SOURCE: {
|
|
39
|
-
readonly kind: 'plugin';
|
|
40
|
-
readonly plugin: 'dsh-rewind';
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* The rewind-marker message content: always this minimal self-declaring
|
|
44
|
-
* placeholder. It is provider-independent — a strict OpenAI-compatible gateway
|
|
45
|
-
* rejects an empty user message (HTTP 400, Issue #21), and the session log is
|
|
46
|
-
* immutable while the model serving it may change. A constant non-empty
|
|
47
|
-
* placeholder is accepted by every gate and reads as an empty one the model
|
|
48
|
-
* need not act on.
|
|
49
|
-
*/
|
|
50
|
-
export declare const REWIND_MARKER_CONTENT: ContentBlock[];
|
|
51
|
-
/** Whether a form-C marker's content is already the canonical placeholder. */
|
|
52
|
-
export declare function isCanonicalMarkerContent(content: unknown): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Build the form-C marker `data` (the `user/message` payload). When `id` is
|
|
55
|
-
* supplied it is preserved verbatim (the repair keeps the original marker's
|
|
56
|
-
* id, whether nested at `data.message.id` or at `data.id`); when omitted a
|
|
57
|
-
* fresh id is generated so the running plugin can reuse this contract for its
|
|
58
|
-
* live `buildMarker()`.
|
|
59
|
-
*/
|
|
60
|
-
export declare function buildRewindMarkerData(id?: string): Record<string, unknown>;
|
|
61
|
-
/** The surface `replace` op carried by a rewind marker. */
|
|
62
|
-
export interface ReplaceSurfaceOp {
|
|
63
|
-
op: 'replace';
|
|
64
|
-
start: number;
|
|
65
|
-
end: number;
|
|
66
|
-
}
|
|
67
|
-
/** Type guard for the surface `replace` op. */
|
|
68
|
-
export declare function isReplaceSurfaceOp(value: unknown): value is ReplaceSurfaceOp;
|
|
69
|
-
/** Is `event` a legacy rewind marker in either form A or B? */
|
|
70
|
-
export declare function isLegacyRewindMarker(event: Readonly<SessionEvent>): boolean;
|
|
71
|
-
/**
|
|
72
|
-
* Is `event` already a form-C rewind marker (a `user/message`, or a re-typed
|
|
73
|
-
* `assistant/message`, carrying the dsh-rewind plugin source + replace op)? the
|
|
74
|
-
* target shape.
|
|
75
|
-
*/
|
|
76
|
-
export declare function isFormCMarker(event: Readonly<SessionEvent>): boolean;
|
|
77
|
-
/** Result of {@link repairRewindMarkers}. */
|
|
78
|
-
export interface RepairOutput {
|
|
79
|
-
/** The repaired events, densely seq'd, in log order. */
|
|
80
|
-
events: SessionEvent[];
|
|
81
|
-
/** original event seq → new dense seq for every SURVIVING event. */
|
|
82
|
-
mapSeq: ReadonlyMap<number, number>;
|
|
83
|
-
/** Per-form counts observed in the repaired run. */
|
|
84
|
-
stats: {
|
|
85
|
-
a: number;
|
|
86
|
-
b: number;
|
|
87
|
-
c: number;
|
|
88
|
-
removedGhosts: number;
|
|
89
|
-
};
|
|
90
|
-
/** Form-C markers whose content was upgraded to the canonical placeholder. */
|
|
91
|
-
contentUpgrades: number;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Rewrite any number of stacked rewind markers (forms A and B, interleaved)
|
|
95
|
-
* into form C in one global compaction.
|
|
96
|
-
*
|
|
97
|
-
* @param input - the decoded event list, in log order. Not mutated.
|
|
98
|
-
* @returns the repaired event list, the oldSeq→newSeq map, and per-form counts.
|
|
99
|
-
* @throws {Error} when a surviving reference points at a seq that was consumed
|
|
100
|
-
* (a removed ghost frame); failing closed avoids writing a corrupt log.
|
|
101
|
-
*/
|
|
102
|
-
export declare function repairRewindMarkers(input: ReadonlyArray<SessionEvent | unknown>): RepairOutput;
|
|
103
|
-
/** Result of {@link repairStaleArgs}. */
|
|
104
|
-
export interface StaleArgsRepair {
|
|
105
|
-
/** The coherence-repaired events, in log order. */
|
|
106
|
-
readonly events: SessionEvent[];
|
|
107
|
-
/** Number of `/rewind` command `args` targets rewritten. */
|
|
108
|
-
readonly fixed: number;
|
|
109
|
-
}
|
|
110
|
-
/** The rewind-target seq parsed from a `/rewind` command's `args` string. */
|
|
111
|
-
export declare function rewindTargetSeqOfArgs(args: unknown): number | undefined;
|
|
112
|
-
/**
|
|
113
|
-
* C→C coherence repair: after a legacy→C migration renumbered a session's
|
|
114
|
-
* seqs, a `/rewind` command's `args @<seq>` target can remain in the OLD
|
|
115
|
-
* numbering while its (form-C) marker carries the NEW `surfaceOp.start`. The
|
|
116
|
-
* client `hiddenSeqsOf` derives the USER-side hide span from
|
|
117
|
-
* `args @<target>` + `outcome.sourceEventSeq`; when the two diverge it hides
|
|
118
|
-
* the wrong set (the AGENT-side hide reads the marker's `sourceEventSeqs`,
|
|
119
|
-
* which IS correctly remapped). This rewrites each such `@<seq>` to the
|
|
120
|
-
* marker's `surfaceOp.start` — the authoritative post-migration target.
|
|
121
|
-
*
|
|
122
|
-
* Matching follows the same channel `hiddenSeqsOf` uses: a command/run is
|
|
123
|
-
* joined to its marker via the command/done with the same `commandId` (whose
|
|
124
|
-
* `sourceEventSeq` cites the marker seq). It is a no-op for a coherent session
|
|
125
|
-
* (a fresh live rewind, or one already repaired), so it is idempotent and safe
|
|
126
|
-
* to run on every closed session. It never mutates its input: a rewritten
|
|
127
|
-
* command/run is rebuilt onto a fresh event object.
|
|
128
|
-
*/
|
|
129
|
-
export declare function repairStaleArgs(input: readonly SessionEvent[]): StaleArgsRepair;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { type SessionEvent } from '@deepseek-ai/dsh-session';
|
|
2
|
-
/** A complete frame's byte range within a buffer. */
|
|
3
|
-
export interface ZstdFrameRange {
|
|
4
|
-
start: number;
|
|
5
|
-
end: number;
|
|
6
|
-
}
|
|
7
|
-
/** Result of scanning a buffer for complete zstd frames. */
|
|
8
|
-
export interface ZstdFrameScan {
|
|
9
|
-
frames: ZstdFrameRange[];
|
|
10
|
-
/** Offset of an incomplete trailing frame, or undefined when the buffer is whole. */
|
|
11
|
-
tornStart?: number;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Scan a buffer for structurally complete Zstandard frames (mirrors DSH
|
|
15
|
-
* `scanZstdFrames`). A trailing incomplete frame is reported as `tornStart`
|
|
16
|
-
* and omitted from `frames`.
|
|
17
|
-
*/
|
|
18
|
-
export declare function scanZstdFrames(buffer: Buffer): ZstdFrameScan;
|
|
19
|
-
/**
|
|
20
|
-
* Compress one independently decodable, checksummed Zstandard frame (mirrors DSH
|
|
21
|
-
* `compressZstdFrame`). Async so it runs on libuv's threadpool and does not
|
|
22
|
-
* block the event loop, letting a concurrency pool overlap compression across
|
|
23
|
-
* sessions.
|
|
24
|
-
*/
|
|
25
|
-
export declare function compressZstdFrame(input: Buffer | string): Promise<Buffer>;
|
|
26
|
-
/** Decompress one complete Zstandard frame (validates its checksum). Async (threadpool). */
|
|
27
|
-
export declare function decompressZstdFrame(input: Buffer): Promise<Buffer>;
|
|
28
|
-
/** Decode a concatenated multi-frame zstd buffer to plaintext (async, threadpool). Frames are
|
|
29
|
-
* decoded in bounded parallel batches (a large log can have tens of thousands of frames). */
|
|
30
|
-
export declare function decodeZstd(buffer: Buffer): Promise<string>;
|
|
31
|
-
/** One complete session plaintext split into its header line and event body. */
|
|
32
|
-
export interface SplitSession {
|
|
33
|
-
/** The first line (a `type:'session'` record) WITHOUT its trailing newline. */
|
|
34
|
-
headerLine: string;
|
|
35
|
-
/** Every event row, newline-separated, after the header line. */
|
|
36
|
-
body: string;
|
|
37
|
-
}
|
|
38
|
-
/** Split a session plaintext into an independent header line and the event body. */
|
|
39
|
-
export declare function splitSession(plaintext: string): SplitSession;
|
|
40
|
-
/**
|
|
41
|
-
* Decode a JSONL event body into logical {@link SessionEvent}s. Layout-blind:
|
|
42
|
-
* packed chunk rows and per-line rows both decode; range-encoded provenance is
|
|
43
|
-
* expanded. This is the read-side counterpart that consumes the body that
|
|
44
|
-
* `sessionPersistence.readRaw(id)` produced.
|
|
45
|
-
*/
|
|
46
|
-
export declare function decodeEventBody(body: string): SessionEvent[];
|
|
47
|
-
export interface EncodeOptions {
|
|
48
|
-
/** Pack delta-chunk runs into storage rows (lossless, ~60% smaller). Default true. */
|
|
49
|
-
readonly packChunks?: boolean;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Encode a header line + events into a 2-frame Zstandard buffer (mirrors DSH
|
|
53
|
-
* `encodePhysicalJsonl`): the header is its OWN frame, the body its own.
|
|
54
|
-
* @param headerLine - the header JSON text, no trailing newline.
|
|
55
|
-
* @param events - the repaired event list in log order.
|
|
56
|
-
*/
|
|
57
|
-
export declare function encodeSessionLog(headerLine: string, events: readonly SessionEvent[], options?: EncodeOptions): Promise<Buffer>;
|