dsh-rewind-plugin 0.15.0-beta.2 → 0.15.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.md +1 -1
- package/lib/client.js +1 -1
- package/package.json +38 -40
- package/CONTRIBUTING.md +0 -73
- package/assets/screenshots/add-plugin.png +0 -0
- package/assets/screenshots/cleanup-setting.png +0 -0
- package/assets/screenshots/impact-list.png +0 -0
- package/assets/screenshots/mode-popover.png +0 -0
- package/assets/screenshots/rewind-button.png +0 -0
- package/assets/screenshots/rewind-candidates.png +0 -0
- package/docs/README.md +0 -33
- package/docs/architecture.md +0 -145
- package/docs/compat/audit.md +0 -170
- package/docs/compat/diagnostics.md +0 -83
- package/docs/compat/diagnostics.zh.md +0 -61
- package/docs/compat/tracking-boundary.md +0 -217
- package/docs/compat/tracking-boundary.zh.md +0 -131
- package/docs/contract/client-contract.md +0 -58
- package/docs/contract/client-contract.zh.md +0 -54
- package/docs/format.md +0 -271
- package/docs/harness-reference.md +0 -75
- package/docs/release/release.md +0 -136
- package/docs/release/release.zh.md +0 -124
- package/docs/rewind-fix.md +0 -89
- package/docs/rewind-fix.zh.md +0 -89
- package/docs/snapshot-auto-cleanup.md +0 -78
- package/docs/snapshot-auto-cleanup.zh.md +0 -48
package/docs/format.md
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
# Durable format
|
|
2
|
-
|
|
3
|
-
The on-disk format of the dsh-rewind checkpoint store, pinned as a spec. The
|
|
4
|
-
implementation is `src/snapshot.ts`; this document is the reference for what
|
|
5
|
-
readers may rely on and what a future incompatible change must do. If the code
|
|
6
|
-
and this spec disagree, the code wins and this spec is a bug.
|
|
7
|
-
|
|
8
|
-
## State root
|
|
9
|
-
|
|
10
|
-
The store root defaults to `<dsh home>/rewind-snapshots/` — the dsh data
|
|
11
|
-
directory (`~/.dsh/rewind-snapshots/` when `DSH_HOME` is unset) — overridable in
|
|
12
|
-
order by the `snapshotDir` plugin config, then the `DSH_REWIND_SNAPSHOT_DIR`
|
|
13
|
-
environment variable. It is a sibling of the workspace, never a subtree of it.
|
|
14
|
-
Deleting the root only removes file backups; the store rebuilds from scratch.
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
<root>/
|
|
18
|
-
└── <sessionId>/ # safeSessionId(sessionId)
|
|
19
|
-
├── store # store-format marker ("2")
|
|
20
|
-
├── format # DSH session-format marker (session.header.version)
|
|
21
|
-
├── <anchorSeq>/ # decimal seq of the anchoring user/message
|
|
22
|
-
│ ├── <base>.json # one committed before-backup (metadata)
|
|
23
|
-
│ └── <base>.before # its raw byte sidecar (the before content)
|
|
24
|
-
├── .pending/ # captures staged but not yet committed
|
|
25
|
-
│ └── <base>.before
|
|
26
|
-
├── rescue/<opId>/<n>.before # one pre-restore ("rescue") copy per op
|
|
27
|
-
└── journal-<opId>.json # one restore-op journal
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
- `sessionId` is sanitized to `[a-zA-Z0-9._-]`; the bare values `.` and `..`
|
|
31
|
-
are replaced (`safeSessionId`) so a hostile id cannot traverse out of the
|
|
32
|
-
root.
|
|
33
|
-
- `callId` is sanitized to `[a-zA-Z0-9._-]` (`safeFileId`) and extended with an
|
|
34
|
-
8-hex digest of the unsanitized id — `<base>` is
|
|
35
|
-
`<safeFileId(callId)>-<sha256(callId)[0..8]>`, so two call ids that sanitize
|
|
36
|
-
to the same name (e.g. `a:b` / `a_b`) cannot collide. Readers never infer a
|
|
37
|
-
name: every reference names its file, so pre-digest (released v1) names keep
|
|
38
|
-
resolving.
|
|
39
|
-
- `<anchorSeq>` is a decimal integer; directories with non-integer names are
|
|
40
|
-
ignored by readers.
|
|
41
|
-
- Checkpoint entries are read from those numeric directories only
|
|
42
|
-
(`<anchorSeq>/<base>.json`). Neither marker is `.json`, and a stray `.json` in
|
|
43
|
-
the session root is ignored.
|
|
44
|
-
- Journal files are recognized by the `journal-` prefix (current format) or the
|
|
45
|
-
released `restore-journal-` prefix (read-only compatibility).
|
|
46
|
-
- `store` is the session's store-format marker (a decimal version, written
|
|
47
|
-
atomically); a missing marker means the released v1 string format. `format` is
|
|
48
|
-
the DSH **session**-format marker the snapshots were anchored under (see
|
|
49
|
-
`SECURITY.md`) — the two are independent.
|
|
50
|
-
|
|
51
|
-
## Checkpoint entry
|
|
52
|
-
|
|
53
|
-
One JSON file per before-backup, named `<base>.json` next to its `<base>.before`
|
|
54
|
-
sidecar:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
interface CheckpointEntryJson { // one `<base>.json`
|
|
58
|
-
store: 2 // on-disk store format (absent/1 = the released v1 string format)
|
|
59
|
-
callId: string // the tool call that mutated the file
|
|
60
|
-
file: string // resolved display path (absolute)
|
|
61
|
-
parent?: string // realpath of the file's directory at commit time (the location pin)
|
|
62
|
-
blob: string | null // sibling sidecar file name; null = the file was created
|
|
63
|
-
size: number // byte length of the sidecar (0 when `blob` is null)
|
|
64
|
-
mode?: number // permission bits, applied when content is restored
|
|
65
|
-
lossy?: true // recorded from v1 content that had already lost bytes
|
|
66
|
-
time: number // epoch ms, strictly increasing within a store instance
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
A dedup link (see below) carries `callId`, `file`, `ref`, `time` and an
|
|
71
|
-
optional `parent` instead of `blob` and `size`.
|
|
72
|
-
|
|
73
|
-
Semantics:
|
|
74
|
-
|
|
75
|
-
- **`blob` is the pre-edit state**: a string names the sidecar holding the exact
|
|
76
|
-
before bytes (copied raw, never through a JS string, so binary and non-UTF-8
|
|
77
|
-
files round-trip byte-exactly); `null` means the call created the file — valid
|
|
78
|
-
only with `size: 0` and never with `lossy`. The name is an invariant: it must
|
|
79
|
-
equal the entry's own file name minus `.json` plus `.before`.
|
|
80
|
-
- **`anchorSeq` is the parent directory**, deliberately not a field of the
|
|
81
|
-
entry: rewinding to message N applies every entry anchored at or after N (the
|
|
82
|
-
boundary is inclusive).
|
|
83
|
-
- **`parent` is the location pin**: the `realpath` of `dirname(file)` at commit
|
|
84
|
-
time. A restore refuses the path when its directory no longer resolves there.
|
|
85
|
-
Only the path's FINAL component is checked for links (`lstat().nlink > 1` or a
|
|
86
|
-
symlink; see `SECURITY.md`), so a repointed ancestor directory would otherwise
|
|
87
|
-
redirect the write — or the unlink of a recorded creation — outside the
|
|
88
|
-
recorded location. Absent means "no pin" and falls back to the final-component
|
|
89
|
-
check alone.
|
|
90
|
-
- **`time` is the ordering key within an anchor group**: it is monotonic per
|
|
91
|
-
store instance (bumped past the previous commit), so same-millisecond
|
|
92
|
-
commits stay capture-ordered and a re-read always picks the same "earliest"
|
|
93
|
-
entry per path.
|
|
94
|
-
- **`mode` never decides an action** (a mode-only difference is a no-op); it is
|
|
95
|
-
applied, best-effort, only when content is written back.
|
|
96
|
-
- **`lossy` marks a record that had already lost bytes** (v1 content that was
|
|
97
|
-
decoded lossily, or a materialized link to one): it is comparable but never
|
|
98
|
-
written back, so a lossy record can never overwrite a live file.
|
|
99
|
-
- Synthetic re-check entries (external edits/deletions seen at a user-message
|
|
100
|
-
boundary) use `callId = recheck-<anchorSeq>-<sha256(path) first 8 hex>`.
|
|
101
|
-
|
|
102
|
-
### Dedup link entry
|
|
103
|
-
|
|
104
|
-
A tracked file that records the same `before` content as its immediately-prior
|
|
105
|
-
entry for that path is stored as a **link** instead of a second byte copy: the
|
|
106
|
-
entry carries a `ref` (the `<anchorSeq>/<base>.json` of that prior entry) and
|
|
107
|
-
omits `blob`/`size`, so identical content is never duplicated across entries. A
|
|
108
|
-
reader resolves the `ref` back to the terminal real snapshot; `blob: null` (i.e.
|
|
109
|
-
a recorded creation) still means "the file was created". A `ref` is validated as
|
|
110
|
-
a single-level, `<digits>/<file>.json` relative reference (no traversal) so a
|
|
111
|
-
corrupt or hostile ref cannot escape the store root when followed; because refs
|
|
112
|
-
name the actual file, a link may point at a released-v1 entry. Because links
|
|
113
|
-
reference prior entries, `prune` materializes a surviving link whose `ref` lands
|
|
114
|
-
on a group it is about to drop before deleting that group, so no kept link is
|
|
115
|
-
left dangling; the materialized entry keeps the bytes and the link's own
|
|
116
|
-
location pin, but not the referent's `mode` (a link records no permissions of
|
|
117
|
-
its own).
|
|
118
|
-
|
|
119
|
-
## Restore journal
|
|
120
|
-
|
|
121
|
-
One JSON file per restore operation, written **before any mutation** and
|
|
122
|
-
updated as the pass applies:
|
|
123
|
-
|
|
124
|
-
```ts
|
|
125
|
-
type ByteRef = { blob: string } | { text: string } // session-relative byte file, or inline text
|
|
126
|
-
|
|
127
|
-
interface RestoreJournal {
|
|
128
|
-
version: 2
|
|
129
|
-
id: string // `op-<base36 ms>-<random>`; file name suffix
|
|
130
|
-
sessionId: string
|
|
131
|
-
targetSeq: number // rewind target the restore belongs to
|
|
132
|
-
startedAt: number // epoch ms
|
|
133
|
-
finishedAt?: number // set on a terminal state
|
|
134
|
-
state: 'running' | 'rollback-running' | 'completed' | 'rolled-back' | 'recovery-required'
|
|
135
|
-
actions: RestoreJournalAction[]
|
|
136
|
-
rollbackError?: string // set when a rollback pass failed partway
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
interface RestoreJournalAction {
|
|
140
|
-
path: string
|
|
141
|
-
action: 'restore' | 'delete' // restore = write the content back; delete = unlink
|
|
142
|
-
before: ByteRef | null // target content for restore; null for delete
|
|
143
|
-
rescue: ByteRef | null // pre-restore disk state; null = file was absent
|
|
144
|
-
mode?: number // recorded target permissions
|
|
145
|
-
rescueMode?: number // recorded pre-restore permissions
|
|
146
|
-
parent?: string // checkpoint-time location pin (see the entry)
|
|
147
|
-
rescueError?: string // set when the rescue capture failed (rollback skips it)
|
|
148
|
-
done: boolean // true once the action's fs op completed and was marked
|
|
149
|
-
failed?: string // per-action failure message (the pass never aborts)
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
A `{blob}` reference is safe and session-relative: `<anchorSeq>/<base>.before`
|
|
154
|
-
(an entry sidecar) or `rescue/<opId>/<n>.before` (a rescue copy), validated on
|
|
155
|
-
both write and read so a corrupt or hostile journal can never point a restore or
|
|
156
|
-
a rollback outside the store. `{text}` refs carry content that has no sidecar:
|
|
157
|
-
released-v1 entry content, or a legacy journal's inline strings.
|
|
158
|
-
|
|
159
|
-
States: `running` and `rollback-running` are non-terminal; `completed` /
|
|
160
|
-
`rolled-back` are terminal. Reconciliation *reports* a still non-terminal op as
|
|
161
|
-
`interrupted` (or `recovery-required` when the journal is corrupt or a rollback
|
|
162
|
-
could not complete), while the journal itself stays `running` /
|
|
163
|
-
`rollback-running`. A journal read back from a legacy file (`restore-journal-`
|
|
164
|
-
prefix, `version: 1`, inline strings) is rewritten IN PLACE as `version: 2` once
|
|
165
|
-
a redo or rollback pass completes, so the same op never leaves two divergent
|
|
166
|
-
versions on disk.
|
|
167
|
-
|
|
168
|
-
## Write guarantees
|
|
169
|
-
|
|
170
|
-
- **Atomicity**: every JSON write serializes to a sibling `<target>.tmp` and
|
|
171
|
-
renames over the target. A crash between the two steps leaves only the temp
|
|
172
|
-
file — never a readable half-written target — and readers ignore temp files
|
|
173
|
-
(they do not end in `.json`). The next write of the same target overwrites
|
|
174
|
-
a leftover temp.
|
|
175
|
-
- **Bytes before metadata**: a checkpoint commit places the sidecar first
|
|
176
|
-
(a staged capture is `rename`d out of `.pending/`; other sources are written
|
|
177
|
-
temp-then-rename) and only then publishes the entry JSON, so a crash can
|
|
178
|
-
leave an unreferenced sidecar but never an entry whose bytes are missing.
|
|
179
|
-
A sidecar that is missing or shorter than `size` is a per-file failure, never
|
|
180
|
-
a silent "the file was created".
|
|
181
|
-
- **Pinned location**: every record this build writes — entry, link and journal
|
|
182
|
-
action — carries where the tracked file's directory resolved at commit time
|
|
183
|
-
(the `parent` pin, best-effort), and a restore re-checks it before touching the
|
|
184
|
-
path — the initial pass, a post-restart `continueRestore` and a
|
|
185
|
-
`rollbackRestore` alike (the journal carries the pin). A directory that
|
|
186
|
-
resolves elsewhere is refused and reported, so a restore can never write or
|
|
187
|
-
unlink outside the recorded location; a record with no pin (released-v1 data,
|
|
188
|
-
or a commit whose parent could not be resolved) falls back to the
|
|
189
|
-
final-component link check alone. A parent chain that is GONE is still
|
|
190
|
-
recreated — the plugin restores files whose directory was deleted — but only
|
|
191
|
-
while its nearest surviving ancestor resolves inside the pin. A stable
|
|
192
|
-
symlinked ancestor is never refused: both sides of the comparison are
|
|
193
|
-
`realpath`s.
|
|
194
|
-
- **Journal before mutation**: the rescue state of every planned path is
|
|
195
|
-
captured as a raw byte copy and the intent journal — references only —
|
|
196
|
-
persisted atomically BEFORE the first fs mutation; each action is marked
|
|
197
|
-
`done` as it is applied.
|
|
198
|
-
- **Disk is truth**: after a restart, reconciliation compares the real disk
|
|
199
|
-
against each action's goal (the restore target for `running` journals, the
|
|
200
|
-
rescue state for `rollback-running` / `recovery-required` ones). A path
|
|
201
|
-
whose disk already matches is marked done without being touched.
|
|
202
|
-
- **Bounded storage**: `prune` keeps the newest 100 anchor groups per session
|
|
203
|
-
(`MAX_ANCHOR_GROUPS`), materializing any surviving dedup link that references
|
|
204
|
-
a group being dropped before deleting whole anchor directories; it also
|
|
205
|
-
recycles terminal journals (`completed` / `rolled-back`) together with their
|
|
206
|
-
`rescue/<opId>/` bytes, and collects `.pending/` captures older than 24 h that
|
|
207
|
-
no commit consumed. A group a non-terminal journal still references is pinned
|
|
208
|
-
— evicting it would make "continue finishes the interrupted op" impossible —
|
|
209
|
-
so the effective window may exceed `keep` until that op is resolved.
|
|
210
|
-
Non-terminal and corrupt journals are always kept. Across sessions,
|
|
211
|
-
`pruneStale` removes whole long-inactive session directories whose newest
|
|
212
|
-
member stamp is older than a configurable idle cutoff (default off), so the
|
|
213
|
-
store root does not grow without bound either.
|
|
214
|
-
|
|
215
|
-
## Validation and failure policy
|
|
216
|
-
|
|
217
|
-
- **Entries**: a missing, malformed, or self-contradictory entry is read as
|
|
218
|
-
`undefined` (silently skipped) — losing one backup, never the recovery path.
|
|
219
|
-
Contradictions are never guessed at: a `blob: null` with `size !== 0` or with
|
|
220
|
-
`lossy`, a `blob` that is not this entry's sidecar name, and a non-string
|
|
221
|
-
`before` in a v1 record are all corruption, and guessing an entry's kind is
|
|
222
|
-
how a restore turns into a delete. An absent or malformed `parent` is not
|
|
223
|
-
corruption: it means "no pin", so the entry falls back to the released
|
|
224
|
-
final-component rule. Two cases are *not* silent skips: an entry
|
|
225
|
-
whose `store` is newer than this build fails the whole operation closed (see
|
|
226
|
-
Versioning), and a record whose sidecar is missing or too short is a per-file
|
|
227
|
-
failure that the restore reports — never a delete of the live file.
|
|
228
|
-
- **Journals**: a corrupt or schema-invalid journal **fails loud** —
|
|
229
|
-
`reconcileRestores` reports it as `recovery-required` and never drops it,
|
|
230
|
-
because dropping it would silently erase the interrupted restore's recovery
|
|
231
|
-
record. A `ref` with a traversal, absolute, or unknown-root segment counts as
|
|
232
|
-
corrupt.
|
|
233
|
-
- **Journal IO**: best-effort by design — if a journal cannot be written, the
|
|
234
|
-
restore proceeds with pre-journal semantics (crash safety degrades,
|
|
235
|
-
behavior does not).
|
|
236
|
-
|
|
237
|
-
## Versioning policy
|
|
238
|
-
|
|
239
|
-
The format is versioned twice: a session-level `store` marker (the value is the
|
|
240
|
-
current `CURRENT_STORE_VERSION`, 2) and a self-describing field on every record
|
|
241
|
-
(`store` on entries, `version` on journals). A missing `store` marker (or `1`)
|
|
242
|
-
means the released v1 string format, which is still read but never migrated. A
|
|
243
|
-
`store` **above** the current version fails the whole operation closed — no file
|
|
244
|
-
restore and no new entry written into a store a newer build owns (an explicit
|
|
245
|
-
clear or the age-based sweep is not version-gated) — while the conversation
|
|
246
|
-
rewind itself keeps working (it does not depend on snapshots). A journal whose
|
|
247
|
-
`version` is present but neither 1 nor 2 is corrupt and is reported
|
|
248
|
-
`recovery-required` (see Validation).
|
|
249
|
-
|
|
250
|
-
Because compatibility is not safe in both directions, the byte format reuses
|
|
251
|
-
none of v1's path/state keys (`path`, `anchorSeq`, `before`) and does not carry
|
|
252
|
-
`anchorSeq` (it equals the parent directory). A released v1 build therefore
|
|
253
|
-
rejects each such entry instead of reading it as "the file was created", so a
|
|
254
|
-
downgrade cannot delete workspace files; that field-name contract is pinned by
|
|
255
|
-
`tests/downgrade-safety.test.ts`. Compatibility means reading old data, not
|
|
256
|
-
repairing it: bytes the v1 build had already lost cannot be recovered.
|
|
257
|
-
|
|
258
|
-
A future incompatible format should raise the marker/version (readers already
|
|
259
|
-
fail closed above their own version) or move the state root and ship an explicit
|
|
260
|
-
migration tool. Old-format data is never silently re-interpreted.
|
|
261
|
-
|
|
262
|
-
## Cleanup policy persistence
|
|
263
|
-
|
|
264
|
-
The snapshot auto-cleanup policy (the `autoCleanupEnabled` switch and the
|
|
265
|
-
`autoCleanupMaxAgeDays` idle cutoff) is no longer a file: it is this plugin
|
|
266
|
-
entry's own live configuration (both fields declared `.volatile()`, validated by
|
|
267
|
-
the entry's schemastery `Config` schema, defaults on the schema). The policy was
|
|
268
|
-
previously persisted in a legacy `<dsh home>/snapshot-cleanup.json` file; that
|
|
269
|
-
file store and its one-time startup migration have been removed. The last-sweep
|
|
270
|
-
clock stays in its own `<dsh home>/snapshot-cleanup-last-sweep.json` state file,
|
|
271
|
-
which is unchanged.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# DeepSeek Harness interface reference
|
|
2
|
-
|
|
3
|
-
> Maintainer doc: the harness subsystems this plugin depends on, and the key
|
|
4
|
-
> source files behind each interface. Official repo:
|
|
5
|
-
> [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
6
|
-
|
|
7
|
-
## Subsystem docs (`docs/subsystems/`)
|
|
8
|
-
|
|
9
|
-
- [session.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/session.md) — `Session` / `SessionStore` / event model (`Session.append`, `surfaceOp`, `sourceEventSeqs`)
|
|
10
|
-
- [core.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/core.md) — `Agent` (`status`, `session`) and other core types
|
|
11
|
-
- [commands.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/commands.md) — command registration (`ctx.commands.register`, `CommandInvocation`, `CommandResult`)
|
|
12
|
-
- [tools.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/tools.md) — tool execution seam (`tools/pre-execute` / `tools/post-execute`, `ToolExecution`)
|
|
13
|
-
- [session-query.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/session-query.md) — session query / `foldSurface` read-only interfaces
|
|
14
|
-
- [token-meter.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/token-meter.md) — replay-aware token meter (`TokenMeter.measure`, surface pricing)
|
|
15
|
-
- [compaction.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/compaction.md) — compaction seam / `compaction/*` events / `BasicCompactionEngine` (`summarize()` hook)
|
|
16
|
-
- [session-projection.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/session-projection.md) — projection registry (`SessionProjectionRegistry.snapshot/checkpoint`)
|
|
17
|
-
- [session-stats](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/session/session-stats) — whole-log stats projection (folds ALL events; rewind does not roll back counts)
|
|
18
|
-
- [session-title.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/session-title.md) — durable title state (`foldSessionTitle`)
|
|
19
|
-
- [goal.md](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/subsystems/goal.md) — goal fold (`foldGoal`) and round admission
|
|
20
|
-
|
|
21
|
-
Compatibility probes against these subsystems (test-driven investigation, the
|
|
22
|
-
`compat-invariants` / `compat-interop` vitest suites + the `verify-host` real
|
|
23
|
-
`/compact` chain): [compat/audit.md](compat/audit.md).
|
|
24
|
-
|
|
25
|
-
Also in the harness repo's `docs/`: `persistence-catalog.md` (full
|
|
26
|
-
`SessionEventMap`), `tool-catalog.md` (tool inventory), `config-catalog.md`
|
|
27
|
-
(configuration inventory).
|
|
28
|
-
|
|
29
|
-
## Key source (`packages/`)
|
|
30
|
-
|
|
31
|
-
| Interface | File |
|
|
32
|
-
|---|---|
|
|
33
|
-
| `Session.append`, surface validation | [packages/core/session/src/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/session/src/index.ts) |
|
|
34
|
-
| `foldSurface`, replacement rules | [packages/core/session/src/surface.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/session/src/surface.ts) |
|
|
35
|
-
| `SessionEventMap`, `SurfaceOp` | [packages/core/session/src/types.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/session/src/types.ts) |
|
|
36
|
-
| `createUserMessage`, `MessageSource` | [packages/llm/llm/src/message.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/llm/llm/src/message.ts) |
|
|
37
|
-
| `CommandDefinition`, `CommandInvocation` | [packages/interaction/commands/src/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/interaction/commands/src/index.ts) |
|
|
38
|
-
| `Agent` (`status` / `session`) | [packages/core/agent/src/runtime-types.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/agent/src/runtime-types.ts) |
|
|
39
|
-
| `tools/pre-execute` / `execute` / `post-execute` | [packages/core/tools/src/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/tools/src/index.ts) |
|
|
40
|
-
| Client DOM anchors (`data-chat-flow-kind` / `data-chat-anchor-key`) | [packages/client/ui-chat/src/client/chat/ChatNodeSeat.tsx](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-chat/src/client/chat/ChatNodeSeat.tsx) |
|
|
41
|
-
| User bubble rendering | [packages/client/ui-chat/src/client/chat/MessageItem.tsx](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-chat/src/client/chat/MessageItem.tsx) |
|
|
42
|
-
| Client `SessionFace` (`command` / `cancel`) | [packages/api/session-controller/src/client/contract/session.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/api/session-controller/src/client/contract/session.ts) |
|
|
43
|
-
| Client pending interaction (SessionPendingInteraction) | [packages/client/ui-session/src/client/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-session/src/client/index.ts) |
|
|
44
|
-
|
|
45
|
-
## Plugin source layout
|
|
46
|
-
|
|
47
|
-
The module map below duplicates `AGENTS.md` (`Layout at a glance`), which is
|
|
48
|
-
the canonical source; this block only adds the finer-grained client-side files
|
|
49
|
-
and packaging entries.
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
src/client/index.ts client plugin: /rewind command decoration + per-message ↶ button portals;
|
|
53
|
-
re-exports the client contract (hiddenSeqsOf / targetSeqOfArgs / HiddenChat)
|
|
54
|
-
src/client/popover.ts mode-selection popover (both-mode impact confirm)
|
|
55
|
-
src/client/hidden.ts withdrawn-span computation (hiddenSeqsOf), pure
|
|
56
|
-
src/client/candidates.ts rewind candidate listing (rewindCandidatesOf), pure
|
|
57
|
-
src/client/pending.ts pending-steering bubble ↔ inbox next-step (user-sourced) matching, pure
|
|
58
|
-
src/client/portals.tsx per-user-message ↶ button portals (React bridge into the actions row)
|
|
59
|
-
src/client/settings-card.tsx snapshot-cleanup form on the bundle page (plugins.bundle.config)
|
|
60
|
-
src/client/locales.ts zh / en copy (LocaleNamespaceMap)
|
|
61
|
-
src/client/styles.ts injected styles (dsh design tokens)
|
|
62
|
-
src/client/build-info.ts client build identity (__DSH_REWIND_VERSION__ / __DSH_REWIND_BUILD__)
|
|
63
|
-
src/client/log.ts client logger (namespaced, dsh-rewind.debug-gated)
|
|
64
|
-
scripts/build.mjs esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
|
|
65
|
-
scripts/check-dsh-version.mjs DSH peer-tuple check (latest dist-tag vs peers)
|
|
66
|
-
scripts/update-badge.mjs regenerate the tests badge (CI only)
|
|
67
|
-
scripts/verify-host.mjs end-to-end host verification (full check suite)
|
|
68
|
-
scripts/session-decode.mjs / session-encode.mjs `.jsonl.zstd` codec (test fixtures)
|
|
69
|
-
tests/ vitest suites (rewind / snapshot / hidden / session-cwd / integration)
|
|
70
|
-
docs/ maintainer docs: contract/, compat/, release/ subdirectories
|
|
71
|
-
assets/screenshots/ UI screenshots
|
|
72
|
-
cordis.patch.yml bundle patch (mounts the dual-face plugin row)
|
|
73
|
-
package.json dsh.bundle + dsh.client manifests, optional peerDependencies
|
|
74
|
-
```
|
|
75
|
-
|
package/docs/release/release.md
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
# Release
|
|
2
|
-
|
|
3
|
-
[简体中文](release.zh.md)
|
|
4
|
-
|
|
5
|
-
## First release (manual, one-time)
|
|
6
|
-
|
|
7
|
-
Trusted Publisher can only be configured once the package exists, so the first
|
|
8
|
-
version is published locally:
|
|
9
|
-
|
|
10
|
-
```sh
|
|
11
|
-
npm login
|
|
12
|
-
npm publish --access public
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
- If prompted for `EOTP`: complete the browser auth link the CLI prints, or
|
|
16
|
-
retry with a 6-digit code — `npm publish --otp=<code>`.
|
|
17
|
-
- The first version carries no provenance (local path) — acceptable; every CI
|
|
18
|
-
release after that publishes with Sigstore/SLSA provenance automatically.
|
|
19
|
-
|
|
20
|
-
## Configure Trusted Publisher (npmjs.com, one-time)
|
|
21
|
-
|
|
22
|
-
Open `https://www.npmjs.com/package/dsh-rewind-plugin` → package **settings** →
|
|
23
|
-
**Trusted Publisher**:
|
|
24
|
-
|
|
25
|
-
| Field | Value |
|
|
26
|
-
| --- | --- |
|
|
27
|
-
| Provider | GitHub Actions |
|
|
28
|
-
| Organization or user | `SiriLee` |
|
|
29
|
-
| Repository | `dsh-rewind` (the GitHub repo, not the npm name) |
|
|
30
|
-
| Workflow filename | `publish.yml` |
|
|
31
|
-
| Environment | empty |
|
|
32
|
-
| Allowed actions | `npm publish` |
|
|
33
|
-
|
|
34
|
-
## Subsequent releases (CI, automatic)
|
|
35
|
-
|
|
36
|
-
The release workflow is **version-driven**: it derives the npm dist-tag from the
|
|
37
|
-
version in `package.json`, independent of branch. A stable version publishes to
|
|
38
|
-
`latest`; a pre-release publishes to the dist-tag named by its pre-release
|
|
39
|
-
identifier (e.g. `0.9.0-alpha.1` → `alpha`, `0.9.0-rc.1` → `rc`). The dist-tag is
|
|
40
|
-
never passed by hand.
|
|
41
|
-
|
|
42
|
-
The line being released determines the branch and the bump:
|
|
43
|
-
|
|
44
|
-
| Release | Branch | Bump | dist-tag |
|
|
45
|
-
| --- | --- | --- | --- |
|
|
46
|
-
| Stable patch (current line) | `release/<line>.x` | `npm version patch` | `latest` |
|
|
47
|
-
| Pre-release (next line) | `main` | `npm version prerelease --preid=alpha` | `alpha` |
|
|
48
|
-
| Stable (next line) | `main` | `npm version <next>` | `latest` |
|
|
49
|
-
|
|
50
|
-
Each release is `git push <branch>` followed by `git push <branch> --tags`.
|
|
51
|
-
|
|
52
|
-
- The release commit is **`chore: release vX.Y.Z`**; the **lightweight `vX.Y.Z`
|
|
53
|
-
tag** sits on it. Do **not** create tag/release first with `gh release create
|
|
54
|
-
<tag>` (it tags the remote `main` head and breaks the tag/version match).
|
|
55
|
-
- **Before bumping, manually confirm there is no newer DSH version the plugin
|
|
56
|
-
has not been verified against** (a pre-release can ship in DSH Desktop
|
|
57
|
-
without being on npm; see docs/compat/audit.md).
|
|
58
|
-
- The workflow verifies the tag matches `package.json`, runs typecheck + tests +
|
|
59
|
-
a full build + artifact verification, publishes with `--provenance` (Sigstore)
|
|
60
|
-
to the version-derived dist-tag, and creates a GitHub Release (a pre-release
|
|
61
|
-
is created as a GitHub pre-release, not `latest`). It is **idempotent** — an
|
|
62
|
-
already published version is skipped.
|
|
63
|
-
- CI (`.github/workflows/ci.yml`) runs `npm run check` — typecheck + tests +
|
|
64
|
-
build + artifact verification + a `npm pack --dry-run` — on every push / PR
|
|
65
|
-
across both Node engines boundary versions; the tarball layout is guarded by
|
|
66
|
-
`tests/package-layout.test.ts`.
|
|
67
|
-
- The GitHub Release body is auto-created with `--generate-notes` as a
|
|
68
|
-
**placeholder** (`--latest` / `--prerelease` per version). After the publish
|
|
69
|
-
run succeeds, overwrite the body by hand in the repo's bilingual style
|
|
70
|
-
(Chinese first, then English) — never keep the auto text as the final note.
|
|
71
|
-
|
|
72
|
-
## DSH version alignment (single peer tuple)
|
|
73
|
-
|
|
74
|
-
DSH is still in rc; npm's prerelease matching rules require a peer range to
|
|
75
|
-
share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
|
|
76
|
-
uses one peer tuple per DSH line (e.g. `^0.2.0-rc.1`). The range is
|
|
77
|
-
conservative: it declares only what was verified, and guarantees nothing
|
|
78
|
-
outside it.
|
|
79
|
-
|
|
80
|
-
- **When to update**: when the verified range changes — a new DSH tuple, or a
|
|
81
|
-
deliberate narrowing (e.g. dropping the internal `alpha` series); a release
|
|
82
|
-
already inside the range changes nothing **unless it changes interfaces inside
|
|
83
|
-
the tuple** (`0.1.6-alpha.2` removed the client session-ownership and
|
|
84
|
-
queue-mirror APIs, so the floor moved with it). All `@deepseek-ai/*` packages
|
|
85
|
-
release together; `npm view @deepseek-ai/dsh dist-tags` is the signal.
|
|
86
|
-
- **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
|
|
87
|
-
compares the `latest` dist-tag version against the tuple the peers cover
|
|
88
|
-
(exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
|
|
89
|
-
pre-release published under another tag or bundled without
|
|
90
|
-
going to npm is a manual pre-release check — see the "Before bumping" step above.
|
|
91
|
-
- **Update steps**: point every `@deepseek-ai/dsh-*` peer and devDependency at
|
|
92
|
-
the verified range → `npm install` → `npm run check` → release.
|
|
93
|
-
- **After DSH goes final**: final releases are not bound by the prerelease
|
|
94
|
-
tuple rule, so the peers can converge to a single stable range (e.g.
|
|
95
|
-
`^0.1.x`); this section can then be deleted.
|
|
96
|
-
- **Declared minimum (`engines.dsh`)**: alongside the peer tuple, each
|
|
97
|
-
release declares the DSH runtime floor under `engines.dsh` (e.g.
|
|
98
|
-
`>=0.2.0-rc.1`). No runtime consumer reads it; its form and its alignment with
|
|
99
|
-
the peer tuple are pinned by `tests/package-layout.test.ts`. Bump it in
|
|
100
|
-
the same release that changes the peer range; never leave code raised while
|
|
101
|
-
the declared floor stays behind. Only the `>=X.Y.Z[-pre]` form is used.
|
|
102
|
-
- **The peers are enforced at startup (`0.1.7-rc.1`)**: DSH checks a profile
|
|
103
|
-
bundle's own `@deepseek-ai/dsh-*` peers against the running version and skips
|
|
104
|
-
the whole bundle when one does not satisfy it, so a peer range that is merely
|
|
105
|
-
stale now costs the plugin its load instead of a warning. See
|
|
106
|
-
`docs/compat/audit.md`.
|
|
107
|
-
|
|
108
|
-
## Versioned-line release model
|
|
109
|
-
|
|
110
|
-
**One release targets one DSH version line.** The plugin's own version is
|
|
111
|
-
independent of the host; a release declares its DSH line through the peer
|
|
112
|
-
constraint (a single companion tuple), never through the plugin version.
|
|
113
|
-
|
|
114
|
-
The DSH line a release targets is the peer constraint, and its npm dist-tag is
|
|
115
|
-
derived from the version (see above), so this model does not track the plugin's
|
|
116
|
-
own version number.
|
|
117
|
-
|
|
118
|
-
**Versioning.** A DSH version-line break is a MAJOR bump (incompatible with the
|
|
119
|
-
prior DSH line). Within a line, MINOR/PATCH remain compatible.
|
|
120
|
-
|
|
121
|
-
**Branching (trunk-based).** `main` is the single integration and release line
|
|
122
|
-
and is always releasable. The currently-shipped stable is cut into a short-lived
|
|
123
|
-
`release/<version>.x` maintenance branch from its release commit; that branch
|
|
124
|
-
receives backported fixes while `main` advances to the next line. The prior
|
|
125
|
-
(broad-compat) line is frozen as a tag, with no branch.
|
|
126
|
-
|
|
127
|
-
**Support window / EOL.** A DSH line is supported within a declared window. By
|
|
128
|
-
default the window runs until the next DSH line ships as `latest`; after that
|
|
129
|
-
the line is EOL, frozen, and receives no further patches.
|
|
130
|
-
|
|
131
|
-
**Bug-fix flow (forward-fix then backport).** A fix affecting multiple supported
|
|
132
|
-
lines is applied on `main` first, then backported to each still-supported
|
|
133
|
-
release branch. A fix specific to one line is applied only on that line.
|
|
134
|
-
|
|
135
|
-
The single-line model uses one peer tuple per release (see DSH version
|
|
136
|
-
alignment above); the OR-union multi-line practice it replaced is not used.
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# 发布流程
|
|
2
|
-
|
|
3
|
-
[English](release.md)
|
|
4
|
-
|
|
5
|
-
## 首次发布(手动,一次性)
|
|
6
|
-
|
|
7
|
-
Trusted Publisher 要求**包已存在**才能配置,因此首个版本走本地发布:
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm login
|
|
11
|
-
npm publish --access public
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
- 若提示 `EOTP`(一次性密码):按 CLI 输出的浏览器认证链接完成认证,或用
|
|
15
|
-
authenticator 的 6 位码 `npm publish --otp=<code>` 重试。
|
|
16
|
-
- 首个版本无 provenance(本地路径),合规;之后每次 CI 发布自动带
|
|
17
|
-
Sigstore/SLSA provenance。
|
|
18
|
-
|
|
19
|
-
## 配置 Trusted Publisher(npmjs.com,一次性)
|
|
20
|
-
|
|
21
|
-
打开 `https://www.npmjs.com/package/dsh-rewind-plugin` → 包右上角 **settings**
|
|
22
|
-
→ **Trusted Publisher**:
|
|
23
|
-
|
|
24
|
-
| 字段 | 值 |
|
|
25
|
-
| --- | --- |
|
|
26
|
-
| Provider | GitHub Actions |
|
|
27
|
-
| Organization or user | `SiriLee` |
|
|
28
|
-
| Repository | `dsh-rewind`(GitHub 仓库名,与 npm 包名可不同) |
|
|
29
|
-
| Workflow filename | `publish.yml` |
|
|
30
|
-
| Environment | 留空 |
|
|
31
|
-
| Allowed actions | `npm publish` |
|
|
32
|
-
|
|
33
|
-
## 后续发布(CI 自动)
|
|
34
|
-
|
|
35
|
-
发布 workflow 为**版本驱动**:npm dist-tag 由 `package.json` 中的版本号决定、与分支无关。
|
|
36
|
-
稳定版发 `latest`;pre-release 发到与其 pre-release 标识符同名的 dist-tag
|
|
37
|
-
(如 `0.9.0-alpha.1` → `alpha`、`0.9.0-rc.1` → `rc`)。dist-tag 无需手传。
|
|
38
|
-
|
|
39
|
-
按要发布的版本线选择分支与版本步进:
|
|
40
|
-
|
|
41
|
-
| 发布类型 | 分支 | 版本步进 | dist-tag |
|
|
42
|
-
| --- | --- | --- | --- |
|
|
43
|
-
| 当前线稳定补丁 | `release/<line>.x` | `npm version patch` | `latest` |
|
|
44
|
-
| 下一线 pre-release | `main` | `npm version prerelease --preid=alpha` | `alpha` |
|
|
45
|
-
| 下一线正式版 | `main` | `npm version <next>` | `latest` |
|
|
46
|
-
|
|
47
|
-
每次发布为 `git push <分支>`,再 `git push <分支> --tags`。
|
|
48
|
-
|
|
49
|
-
- 发布提交为 **`chore: release vX.Y.Z`**,**轻量 `vX.Y.Z` tag** 落在该提交上。
|
|
50
|
-
**不要**用 `gh release create <tag>` 事先建 tag/release(会打到远端 `main`
|
|
51
|
-
HEAD,导致 tag/版本校验失败)。
|
|
52
|
-
- **升版前手动确认**:确认无插件未针对其验证过的更新 DSH 版本(pre-release
|
|
53
|
-
可能只随 Desktop 捆绑、而不发到 npm;见 docs/compat/audit.md)。
|
|
54
|
-
- workflow 校验 tag 与 `package.json` 版本一致,跑 typecheck + 测试 + 完整
|
|
55
|
-
构建 + 产物验证,以 `--provenance`(Sigstore)发布到版本推导出的 dist-tag,
|
|
56
|
-
并创建 GitHub Release(pre-release 会建成 GitHub pre-release,而非 `latest`)。
|
|
57
|
-
**幂等**——已发布的版本会跳过。
|
|
58
|
-
- CI(`.github/workflows/ci.yml`)在每次 push / PR 跑 `npm run check`——
|
|
59
|
-
typecheck + 测试 + 构建 + 产物验证 + `npm pack --dry-run`,且覆盖
|
|
60
|
-
engines 两个边界版本;tarball 布局由 `tests/package-layout.test.ts` 守护。
|
|
61
|
-
- GitHub Release 正文由 workflow 以 `--generate-notes` 自动生成,仅是**占位**
|
|
62
|
-
(按版本取 `--latest` / `--prerelease`)。发布运行成功后,请用**手写双语**
|
|
63
|
-
正文(中文在前、英文在后)覆盖——切勿把自动文本当作最终发布说明。
|
|
64
|
-
|
|
65
|
-
## DSH 版本适配(单一 peer 元组)
|
|
66
|
-
|
|
67
|
-
DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿主版本
|
|
68
|
-
**同 `[major, minor, patch]` 元组**才能匹配。因此 peerDependencies 采用
|
|
69
|
-
**每一条 DSH 线一个 peer 元组**(如 `^0.1.2-rc.1`);范围是保守的,只声明实际验证过的内容。
|
|
70
|
-
|
|
71
|
-
- **何时需要更新**:**已验证范围**变化时——DSH 发新元组,或主动收窄(例如不再
|
|
72
|
-
声明内测的 DSH `alpha` 系列);落在已声明范围内的发布不改变任何东西,**除非它在
|
|
73
|
-
同一元组内改变了接口**(`0.1.6-alpha.2` 删除了客户端会话所有权与 queue 镜像
|
|
74
|
-
API,下限随之移动)。DSH 所有包同版本发布;信号是
|
|
75
|
-
`npm view @deepseek-ai/dsh dist-tags`。
|
|
76
|
-
- **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
|
|
77
|
-
dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
|
|
78
|
-
`latest` tag**;发布在其它 tag 的 pre-release 走**手动发布前检查**
|
|
79
|
-
——见上文"升版前手动确认"。
|
|
80
|
-
- **更新步骤**:把每个 `@deepseek-ai/dsh-*` peer 与 devDependency 指向已验证
|
|
81
|
-
范围 → `npm install` → `npm run check` → 发版。
|
|
82
|
-
- **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
|
|
83
|
-
限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
|
|
84
|
-
- **声明的最低运行时(`engines.dsh`)**:与 peer 元组一起,每个发布在
|
|
85
|
-
`engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`)。当前线上还没有
|
|
86
|
-
任何读取方,因此它只是前置声明、尚未被强制执行。**与 peer 范围持平的同一发布里
|
|
87
|
-
一并 bump**;不可只升代码、声明下限停留在旧值。仅使用 `>=X.Y.Z[-pre]` 形式。
|
|
88
|
-
- **peer 在启动时会被强制执行(`0.1.7-rc.1`)**:DSH 会拿 bundle 自身的
|
|
89
|
-
`@deepseek-ai/dsh-*` peer 与运行版本比对,只要有一个不满足就**整包跳过**,
|
|
90
|
-
因此过期的 peer 范围不再是"仅告警",而是直接导致插件不加载。见
|
|
91
|
-
`docs/compat/audit.md`。
|
|
92
|
-
|
|
93
|
-
## 发布版本线模型
|
|
94
|
-
|
|
95
|
-
**一个发布对准一条 DSH 版本线。** 插件自身版本号与宿主解耦;某发布所对准的
|
|
96
|
-
DSH 线由 peer 约束(单一 companion 元组)声明,而非插件版本号。
|
|
97
|
-
|
|
98
|
-
| 插件版本 | DSH 线 | 角色(示例) |
|
|
99
|
-
| --- | --- | --- |
|
|
100
|
-
| `0.7.x` | `0.1.1` + `0.1.2`(广兼容) | 冻结 / EOL |
|
|
101
|
-
| `0.8.x` | `0.1.2-rc.1`(单线) | 当前线 |
|
|
102
|
-
| `0.9.x` | `0.1.3`(单线) | 后续线 |
|
|
103
|
-
|
|
104
|
-
各行仅为示例——某个发布对准的 DSH 线由 peer 约束声明,其 npm dist-tag 按版本号
|
|
105
|
-
派生(见上文),因此本模型不跟随插件自身版本号变化。
|
|
106
|
-
|
|
107
|
-
**版本号。** DSH 版本线破坏性变更 = MAJOR 升版(与更早 DSH 线向后不兼容)。
|
|
108
|
-
同一线内 MINOR/PATCH 保持向后兼容。
|
|
109
|
-
|
|
110
|
-
**分支(主干开发)。** `main` 是唯一集成与发布线,始终可发版。当前已发布
|
|
111
|
-
稳定版从发布提交点切出短命 `release/<版本>.x` 维护分支;该分支承接
|
|
112
|
-
backport 修复,同时 `main` 前进到下一线。更早的(广兼容)线冻结成 tag,无分支。
|
|
113
|
-
|
|
114
|
-
**dist-tag 路由。** 发布 workflow 按版本号取 npm dist-tag:稳定版发 `latest`;
|
|
115
|
-
pre-release 发到与其 pre-release 标识符同名的 dist-tag(`0.9.0-alpha.1` →
|
|
116
|
-
`alpha`、`0.9.0-rc.1` → `rc`)。pre-release 不占据 `latest`。
|
|
117
|
-
|
|
118
|
-
**支持窗口 / EOL。** 一条 DSH 线仅在声明的窗口内受支持。默认窗口到下一
|
|
119
|
-
DSH 线作为 `latest` 发布为止;此后该线 EOL、冻结、不再发补丁。
|
|
120
|
-
|
|
121
|
-
**Bug 修复流程(先向前修,再回迁)。** 跨多条支持线的修复,先在 `main` 上
|
|
122
|
-
修复,再回迁到各仍受支持的 release 分支。仅特定线的修复,只在对应线修复。
|
|
123
|
-
|
|
124
|
-
单线模型每个发布使用单一 peer 元组(见上文「DSH 版本适配」);其取代的 OR 并集多线做法已不使用。
|