dsh-rewind-plugin 0.15.0 → 0.15.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.md +1 -1
- package/SECURITY.md +1 -1
- package/lib/client.js +47 -8
- package/lib/index.js +57 -50
- package/lib/types/client/hidden.d.ts +31 -5
- package/lib/types/client/popover.d.ts +1 -1
- package/lib/types/index.d.ts +6 -0
- package/lib/types/locales.d.ts +3 -7
- 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/README.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Docs
|
|
2
|
-
|
|
3
|
-
Documentation lives under `docs/`, organized by concern. Everything here is
|
|
4
|
-
maintainer-facing unless the purpose column says otherwise. This file is the
|
|
5
|
-
index/navigation entry point.
|
|
6
|
-
|
|
7
|
-
## Index
|
|
8
|
-
|
|
9
|
-
| Path | Purpose | Audience |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| `architecture.md` | Module layering, rewind/checkpoint pipelines, compatibility strategy, roadmap | maintainers |
|
|
12
|
-
| `format.md` | Durable on-disk format spec (checkpoint entries + restore journals) | maintainers |
|
|
13
|
-
| `harness-reference.md` | DeepSeek Harness interface reference + plugin source layout | maintainers |
|
|
14
|
-
| `snapshot-auto-cleanup.md` | Global snapshot auto-cleanup policy and command (`.zh` mirror) | users / maintainers |
|
|
15
|
-
| `rewind-fix.md` | Historical guide for the removed `/dsh-rewind-fix` (`.zh` mirror) | users / maintainers |
|
|
16
|
-
| `contract/client-contract.md` | Rewind visibility contract for third-party DOM plugins (`.zh` mirror) | integrators |
|
|
17
|
-
| `compat/audit.md` | Compatibility audit: verified surfaces, recorded findings, probe matrix | maintainers |
|
|
18
|
-
| `compat/tracking-boundary.md` | Which files a rewind restores: the tracking boundary (`.zh` mirror) | users / maintainers |
|
|
19
|
-
| `compat/diagnostics.md` | Browser diagnostics (anomaly alerts; verbose switch gates the startup identity line) (`.zh` mirror) | users / maintainers |
|
|
20
|
-
| `release/release.md` | Release workflow & DSH peer-version alignment (`.zh` mirror) | maintainers |
|
|
21
|
-
|
|
22
|
-
Repo-root docs outside `docs/`: `SECURITY.md` (security model) and
|
|
23
|
-
`CONTRIBUTING.md` (contribution guide).
|
|
24
|
-
|
|
25
|
-
## Conventions
|
|
26
|
-
|
|
27
|
-
- Bilingual docs use the `.md` / `.zh.md` file split (e.g. `contract/client-contract.md`
|
|
28
|
-
+ `contract/client-contract.zh.md`); the root pair is the exception — `README.md` is
|
|
29
|
-
Chinese, `README.en.md` English.
|
|
30
|
-
- `compat/audit.md` is the single source of truth for compatibility conclusions;
|
|
31
|
-
other docs link to it instead of restating them.
|
|
32
|
-
- Cross-links are relative so the whole `docs/` directory stays relocatable — and it
|
|
33
|
-
ships intact in the npm tarball via the `docs` entry in `files` (`package.json`).
|
package/docs/architecture.md
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
How dsh-rewind is built: module layering, the rewind pipeline, the checkpoint
|
|
4
|
-
pipeline, the compatibility strategy, and the roadmap. The module map lives in
|
|
5
|
-
`AGENTS.md`; this document adds the wiring between the modules. The durable
|
|
6
|
-
on-disk format is specified separately in
|
|
7
|
-
`docs/format.md`, the security model in `SECURITY.md`.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
One thing: rewind a conversation **in place** to any earlier user message —
|
|
12
|
-
never forking a session or switching windows — with an optional Claude-Code-
|
|
13
|
-
style workspace file restore (`both` mode). No network, no git operations, no
|
|
14
|
-
credentials access.
|
|
15
|
-
|
|
16
|
-
## Module layering
|
|
17
|
-
|
|
18
|
-
The module map is the one in `AGENTS.md` (`Layout at a glance`); this document
|
|
19
|
-
adds the wiring between the modules and does not repeat it.
|
|
20
|
-
|
|
21
|
-
Two dependency rules keep the design testable:
|
|
22
|
-
|
|
23
|
-
1. **`rewind.ts` is pure** — no I/O, no `Session` dependency; everything
|
|
24
|
-
derives from the event log + ordered surface, so the whole planning layer
|
|
25
|
-
is unit-testable without a host.
|
|
26
|
-
2. **`snapshot.ts` is host-independent** — it talks to the disk through plain
|
|
27
|
-
`node:fs` plus injected seams (`DiskProbe`, `DeleteFile`, a test-only
|
|
28
|
-
`crash` hook), so the store is testable without the harness.
|
|
29
|
-
|
|
30
|
-
The client never reads the DOM to derive rewind state; it consumes the host's
|
|
31
|
-
machine channels (see [Compatibility strategy](#compatibility-strategy)).
|
|
32
|
-
|
|
33
|
-
## Rewind pipeline
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
↶ button / /rewind @<seq> both
|
|
37
|
-
→ handleRewind: parseRewindTarget + planRewind (target must be a
|
|
38
|
-
user/message currently on the surface)
|
|
39
|
-
→ agent.cancel({ keepInbox: true }) if running; waitForAgentIdle
|
|
40
|
-
→ dropPendingSteering (next-step inbox only; queued messages untouched)
|
|
41
|
-
→ append the rewind marker = a user/message with surfaceOp
|
|
42
|
-
{ op: 'replace', startSeq, endSeq } over every surface node after the
|
|
43
|
-
target (+ sourceEventSeqs = shadowed seqs) [a single event]
|
|
44
|
-
→ if mode 'both': store.restoreAfter(targetSeq) + syncRestoreObservations
|
|
45
|
-
→ result text carries machine tokens (impact=<n>, restore:/delete: lines)
|
|
46
|
-
→ client: hides withdrawn rows (data-dsh-rewind-hidden), refills composer
|
|
47
|
-
with the target message's text
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Key invariants:
|
|
51
|
-
|
|
52
|
-
- **The log is append-only.** The marker is the *only* mutation: it cuts the
|
|
53
|
-
model-visible surface, never the raw history (search/export still see it).
|
|
54
|
-
- **The marker content is a constant `(empty message)` placeholder.** It carries
|
|
55
|
-
the dsh-rewind plugin source and is a `user/message`, the only surface type
|
|
56
|
-
that can cite the shadowed seqs (`sourceEventSeqs`) — `assistant/message` can
|
|
57
|
-
no longer carry them (v2). It derives to itself, so it stays as a present
|
|
58
|
-
user turn at the surface tail. The content is provider-independent and never
|
|
59
|
-
empty: the session log is immutable but the model serving a session may
|
|
60
|
-
change later, and a strict OpenAI-compatible gateway rejects an empty user
|
|
61
|
-
message (HTTP 400, Issue #21).
|
|
62
|
-
- **No ghost step frame is needed**: the token-meter step machine ignores
|
|
63
|
-
`user/message`, and the session invariant imposes no open-turn requirement
|
|
64
|
-
on it, so the marker is appended while idle, outside any turn, as one event.
|
|
65
|
-
- **Restore is reconciled against the live disk** (`planRestore`), so repeated
|
|
66
|
-
rewinds are idempotent and a rewind whose target state already matches is a
|
|
67
|
-
no-op. A path whose directory no longer resolves to its commit-time location
|
|
68
|
-
is skipped and reported, never written through (see `SECURITY.md`).
|
|
69
|
-
|
|
70
|
-
### Marker format history
|
|
71
|
-
|
|
72
|
-
The rewind marker is written as form C (a `user/message` with a
|
|
73
|
-
`surfaceOp.replace` over the shadowed range, `sourceEventSeqs`, and the
|
|
74
|
-
constant `(empty message)` content). Earlier
|
|
75
|
-
plugin versions wrote shapes a newer harness no longer accepts:
|
|
76
|
-
|
|
77
|
-
- **form A** — a bare `assistant/message(turn=N, step=0)` with no frame.
|
|
78
|
-
- **form B** — a ghost turn frame: `[step/start][assistant/message][step/end]`
|
|
79
|
-
inside a closed turn.
|
|
80
|
-
|
|
81
|
-
A/B became unreadable once v2 reserved surface `replace` to a node that cites
|
|
82
|
-
`sourceEventSeqs` (`assistant/message` can no longer carry them). The
|
|
83
|
-
`/dsh-rewind-fix` command rewrote form A/B in closed sessions to form C so a
|
|
84
|
-
newer harness accepts the log — it was a **temporary migration tool** (kept on
|
|
85
|
-
the `0.9.x` line) and is **removed in the `0.10.x` line**.
|
|
86
|
-
|
|
87
|
-
## Checkpoint pipeline (Claude Code before-backup model)
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
tools/execute captureBefore: for write / edit, stage a raw byte copy of
|
|
91
|
-
the file's BEFORE state into the store's .pending/
|
|
92
|
-
(node:fs copyFile, never through a string);
|
|
93
|
-
subagent sessions are NOT tracked and get no rewind
|
|
94
|
-
surface at all (Claude Code alignment — the Harness
|
|
95
|
-
refuses generic Session RPCs for a subagent-owned
|
|
96
|
-
identity, so /rewind cannot execute there).
|
|
97
|
-
tools/post-execute commitEntry: anchor = latest user/message seq; skip
|
|
98
|
-
failed calls; publish the staged bytes as the entry's
|
|
99
|
-
sidecar and write the metadata beside them — including
|
|
100
|
-
where the directory resolved (`realpath`), the location
|
|
101
|
-
pin a restore re-checks.
|
|
102
|
-
session/event user/message boundary: reconcileTracked re-reads every
|
|
103
|
-
(user/message) tracked file and records a new before-backup for any
|
|
104
|
-
whose disk state changed since last seen — external
|
|
105
|
-
edits/deletions enter the record this way.
|
|
106
|
-
prune keeps the newest 100 anchor groups per session, storing
|
|
107
|
-
identical before-content as in-place links that are
|
|
108
|
-
materialized before their group is dropped, never dropping
|
|
109
|
-
a group a non-terminal restore journal still references,
|
|
110
|
-
and recycles terminal restore journals.
|
|
111
|
-
pruneStale cross-session auto-cleanup (default off): whole
|
|
112
|
-
long-inactive session dirs past the cutoff are removed;
|
|
113
|
-
the active session is never targeted (a subagent's tool
|
|
114
|
-
result never triggers the sweep — it owns no dir and would
|
|
115
|
-
claim that exemption).
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Compatibility strategy
|
|
119
|
-
|
|
120
|
-
- **Peer ranges as one tuple per DSH line**: npm's prerelease rules require the
|
|
121
|
-
peer range to share the host's `[major, minor, patch]` tuple, so a new DSH
|
|
122
|
-
tuple replaces the peer tuple (the single-line model) and `package.json` is
|
|
123
|
-
the only place the current tuple is written down; `scripts/check-dsh-version.mjs`
|
|
124
|
-
flags when a new tuple arrives. See `docs/release/release.md`.
|
|
125
|
-
- **Test-driven investigation**: `tests/compat-invariants.test.ts` /
|
|
126
|
-
`compat-interop.test.ts` / `compat-gaps.test.ts` probe harness behavior and pin findings in
|
|
127
|
-
`docs/compat/audit.md`; `scripts/verify-host.mjs` runs a real end-to-end
|
|
128
|
-
rewind + `/compact` chain (the full check suite).
|
|
129
|
-
- **Stable machine channels for third parties**: `dsh-rewind-plugin/client`
|
|
130
|
-
exports the pure hidden-span computation; withdrawn rows carry
|
|
131
|
-
`data-dsh-rewind-hidden`; both are semver-protected
|
|
132
|
-
(`docs/contract/client-contract.md`). DOM coupling is minimized to a small
|
|
133
|
-
set of marked rows plus a structurally-typed slot registration.
|
|
134
|
-
|
|
135
|
-
## Roadmap
|
|
136
|
-
|
|
137
|
-
Ideas under consideration, not commitments:
|
|
138
|
-
|
|
139
|
-
- **Multi-process identity/lock**: the current in-flight guard is
|
|
140
|
-
per-process; a cross-process exclusive lock (like the change-ledger
|
|
141
|
-
competitors) would cover multiple host processes on one worktree.
|
|
142
|
-
- **Locale expansion**: client/host copy is zh/en today; the copy layer
|
|
143
|
-
(`src/locales.ts`, `src/client/locales.ts`) is already keyed for more.
|
|
144
|
-
- **Composer re-send polish**: the target text is already refilled after a
|
|
145
|
-
rewind; a first-class "edit and re-send" affordance is a small extension.
|
package/docs/compat/audit.md
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# Compatibility audit (compat-audit)
|
|
2
|
-
|
|
3
|
-
> Method: **the tests are the audit**. The probes in `tests/compat-invariants.test.ts`,
|
|
4
|
-
> `tests/compat-interop.test.ts`, `tests/compat-gaps.test.ts`,
|
|
5
|
-
> `tests/compat-tool-updates.test.ts` and
|
|
6
|
-
> `scripts/verify-host.mjs` drive the plugin's real execution paths through the DSH
|
|
7
|
-
> subsystems' **real consumer paths** (real `@deepseek-ai/*` packages) and assert
|
|
8
|
-
> compatibility invariants. A probe failure is a finding; it enters the
|
|
9
|
-
> fix/pin/record loop.
|
|
10
|
-
>
|
|
11
|
-
> Targeted version: npm `@deepseek-ai/*@0.2.0-rc.2` (the peers declare the matching range).
|
|
12
|
-
> Source reference: the upstream [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
13
|
-
>
|
|
14
|
-
> Version alignment: one peer tuple per DSH line, declaring only what was verified — the
|
|
15
|
-
> model, the update steps, and the `check-dsh-version.mjs` signal live in
|
|
16
|
-
> `docs/release/release.md`. The floor moved when a release changed interfaces inside the
|
|
17
|
-
> tuple: `0.1.6-alpha.2` removed the client session-ownership and queue-mirror APIs, and
|
|
18
|
-
> `0.1.7-alpha.1` moved the session writer to format v4 (producer-owned message sources,
|
|
19
|
-
> tool-role results, `developer/message`) and replaced the settings namespace registry with
|
|
20
|
-
> each entry's volatile `Config`, so the peer floor and both plugin seams moved with them.
|
|
21
|
-
> The plugin targets a single DSH version line; compatibility with earlier lines is not kept.
|
|
22
|
-
|
|
23
|
-
### Single channel
|
|
24
|
-
|
|
25
|
-
The plugin targets one DSH channel. Each seam below reads the targeted
|
|
26
|
-
version's shape only (no `Session.events` / `<textarea>` / face-`chat`
|
|
27
|
-
legacy branch).
|
|
28
|
-
|
|
29
|
-
| Seam | Implementation at the targeted version |
|
|
30
|
-
| --- | --- |
|
|
31
|
-
| Host session log | `session.snapshotEvents()` |
|
|
32
|
-
| Client chat snapshot | `uiConversation` `chat` view (`chatSnapshotOf`) |
|
|
33
|
-
| Client composer refill | `conversation.input.setDraft` facade |
|
|
34
|
-
| Client settings card | entry form via `ctx.configForms.get(entryId)` (`plugins.bundle.config`) |
|
|
35
|
-
| Client seat-button DOM | structural locate of the copy-`<button>` container (`actionsContainerOf`) |
|
|
36
|
-
|
|
37
|
-
## Definition of "fully compatible" (invariants)
|
|
38
|
-
|
|
39
|
-
| Invariant | Meaning | Probe location |
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| I1 log replayability | A rewound log passes token-meter replay and `Session.create` (the resume-preflight validation) without throwing | `compat-invariants` I1, `verify-host` 12/13 |
|
|
42
|
-
| I2 surface consistency | After a cut, the surface has no duplicate nodes, every node exists in the log, the withdrawn target never returns to the surface, and `deriveMessages()` is legal | `compat-invariants` I2 |
|
|
43
|
-
| I3 step/turn structure | Client turn-tail ordering, unique `step/start` (real turns), no ghost turns; the `user/message` rewind marker adds no step frame | `compat-invariants` I3, `helpers.assertTurnTailOrdering` |
|
|
44
|
-
| I4 fold-service safety | stats / title / goal / projection fold a marker-bearing log without throwing, with predictable values | `compat-invariants` I4 |
|
|
45
|
-
| I5 compact interop | A tool-call orphaned by a cancelled turn is pair-balanced once shadowed by a rewind; a rewind across a compaction checkpoint is explicitly refused; a rewind-then-compact transaction stays legal | `compat-interop` I5, `verify-host` 12/14 |
|
|
46
|
-
| I6 tool pipeline | before-snapshot capture/commit/restore is correct (existing `snapshot.test.ts` + `verify-host` 4–8) for the tracked tools `write` / `edit` — `str_replace_editor` is an optional DSH package that stopped being a default tool in DSH 0.1.3, so it is not tracked; cancellation timing never hangs | `verify-host` 4–9 |
|
|
47
|
-
| I7 client ordering | A log carrying tool turns and rewind markers (a single `user/message` replace) satisfies the client builder ordering | `compat-interop` I7 |
|
|
48
|
-
| I8 runtime safety | `rewind`/`compact` combinations never leave a dangling step/turn frame | `verify-host` 13 |
|
|
49
|
-
| I9 tool-update projection | `0.1.7-rc.2` folds mid-conversation tool additions from the whole log while a rewind cuts only the surface: after a withdrawal no `tool-addition`/`tool-removal` block reaches the provider and the current declarations stay complete | `compat-tool-updates` I9 |
|
|
50
|
-
|
|
51
|
-
## Verified-compatible surfaces (probes pass)
|
|
52
|
-
|
|
53
|
-
- **compaction transactions**: `toolPairingBalancedBefore/After` stays balanced after a marker cut; the real `/compact` command (`command-compact` + `compaction-basic`, stub summarizer) can land `compaction/start…end` on top of a rewind marker and stay replayable; `/compact` is a legal no-op on a small surface.
|
|
54
|
-
- **tool-update history (`0.1.7-rc.2`)**: `Session.toolHistory()` folds the full append-only log, so a withdrawn `tool-addition` stays recorded after a rewind; `projectToolUpdates` drops every update whose message left the request and falls back to the complete current declarations. Pin: `tests/compat-tool-updates.test.ts` (on-surface projection, then the post-rewind fallback).
|
|
55
|
-
- **session-stats**: the `user/message` marker adds no step (the step count stays at the real turns' steps), no phantom turn.
|
|
56
|
-
- **session-title / goal fold**: a marker does not disturb `foldSessionTitle` / `foldGoal`.
|
|
57
|
-
- **plan-mode**: the marker is a turn-less `user/message` (no phantom turn); a rewind never touches the log-only `plan/mode` state (plan mode stays active; the user leaves it with `/plan off`) and the log stays replayable (`compat-invariants` I1/I3 marker + `plan/mode` probe, `verify-host`).
|
|
58
|
-
- **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
|
|
59
|
-
- **settings card (`0.1.7-alpha.1`)**: the card declares `configForms` as a module-level inject (the web profile always composes `ui-settings`), stages through the harness's own `SettingsFormModel` + `SettingsForm`, and registers into `plugins.bundle.config` keyed by the bundle package name; it reads that shared per-entry form's snapshot and writes through its revision-fenced `mutate`, disposing the model's subscription with the fiber. The host half declares the bundle's page policy (`configure({ auto: false }, ctx.fiber)` inside a `ctx.inject(['settings'])` child, per the dsh-settings README), opting the entry out of a client-built schema page. Pins: `verify:host` (`the bundle declares its own config page`), `tests/client-settings-card.test.ts` (staged plan, refusal, subscription release), `tests/client-lifecycle.test.ts`.
|
|
60
|
-
- **client session ownership (`0.1.6-alpha.2`)**: `SessionListState.current` / `currentAddress` are gone; main-view ownership is read from the row's `retainedBy.mainView` label — what `ui-workspace` retains the open session with. Pin: `tests/client-refill.test.ts`.
|
|
61
|
-
- **client pending input (`0.1.6-alpha.2`)**: `SessionSnapshot.queue` and the host `queue-mirror` are gone; pending steering rows come from the session's own `inbox` projection, and only USER-sourced `next-step` rows are retractable (the removed mapping was `source.kind === 'user' ? 'steering' : 'context'`). The rendered bubble also stays the client's own submission echo until the durable node arrives (`ChatView.pendingInputs` merges the Host occurrence into that echo by `rpcId`; `QueueDock` mirrors only `next-turn`), so the occurrence lands with NO DOM mutation — the portal refresh therefore follows the `inbox` face's own `subscribe` beside the DOM observer. An attachment-bearing row is identified by its message-attachment block (`data-message-attachments`) and matched by block count, not by rendered text (the block renders a file name or an image's loading label, which no inbox row reproduces); a missing attribute degrades to "no button". Pins: `tests/pending.test.ts`, `tests/client-retract.test.ts`, `tests/client-portal-refresh.test.ts`, `tests/client-dom.test.ts`.
|
|
62
|
-
- **client settings slot (`0.1.6-alpha.2`)**: `settings.plugin.item` is gone; the configuration form registers into `plugins.bundle.config`, keyed by the bundle package name. Pin: `tests/client-contract.test.ts`.
|
|
63
|
-
- **`agent/created` lifecycle guard**: the session-format reconcile runs on the alpha line's `agent/created` (fire-and-forget); pin: `verify-host` 4e dispatches it.
|
|
64
|
-
- **marker vs `/compact`**: the checkpoint shape is unchanged on this line — a `user/message` replace (`surfaceOp {replace, startSeq, endSeq}` + `sourceEventSeqs`); `assistant/message` still cannot carry `sourceEventSeqs`.
|
|
65
|
-
- **`image/offload` projection**: the alpha line's first `SessionMessageProjection` changes derived content without touching surface node membership; candidate listing and target resolution tolerate it. Pin: `tests/image-offload-projection.test.ts`.
|
|
66
|
-
- **session-cwd**: the fs tools no longer canonicalize a parent-traversing cwd, so `src/session-cwd.ts` returns `header.cwd` verbatim. Pin: `tests/session-cwd.test.ts`.
|
|
67
|
-
- **startup admission (`0.1.7-rc.1`)**: the gate reads the manifest's own `@deepseek-ai/dsh-*` peers and requires each to satisfy the runtime with `includePrerelease`; it runs for a profile bundle and again for every row its patch inserts, and a denial skips the bundle or disables the row. This plugin's declarations all satisfy the targeted version above, so it is admitted with no exemption — a skipped bundle on a later line is a peer mismatch, not a load failure.
|
|
68
|
-
|
|
69
|
-
## Known behavior boundaries (deterministic differences, non-crash, documented)
|
|
70
|
-
|
|
71
|
-
- **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; the `user/message` marker is folded as a present user turn (it adds no step). This is the intended "fold the full log" semantics, pinned by probe.
|
|
72
|
-
- **token-meter usage anchor stays stable** (G3): the `user/message` marker carries no usage, but because it is not an `assistant/message`, the baseline anchor does not drop to a heuristic estimate — it stays `usage` across a rewind. Pinned by `compat-gaps` G3.
|
|
73
|
-
- **marker content is the constant `(empty message)` placeholder**: never empty, because the session log is immutable but the model serving a session may change later — a strict OpenAI-compatible gateway rejects an empty user message (HTTP 400, Issue #21). The marker is a small visible user turn in derived history. Pin: `verify-host` (`marker is a user/message with the dsh-rewind producer source and the (empty message) placeholder`).
|
|
74
|
-
- **marker shape (v4)**: the replace `surfaceOp` is `{ op: 'replace', startSeq, endSeq }` (renamed from `start`/`end` in session-format v3); the dsh-rewind source is the single producer-owned kind `{ kind: 'dsh-rewind' }`, declared by merging `MessageSourceMap` (v4 removed the catch-all `plugin` kind and rejects it on write). Two older shapes stay readable, because a stored marker is immutable: the released v3 wrapper `{ kind: 'plugin', plugin: 'dsh-rewind' }`, and the `plugin:dsh-rewind` form the v3→v4 conversion gives a producer its rename table does not know. The `user/message` + `sourceEventSeqs` shape and the `(empty message)` content are unchanged.
|
|
75
|
-
- **Withdrawn content stays searchable/exportable**: session-query full-text and `/export` read the raw log; a rewind cuts only the surface, so withdrawn messages remain (declared in the README).
|
|
76
|
-
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
77
|
-
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
|
78
|
-
- **Attachment files left after a message is shadowed**: attachment storage is not cleaned with the surface (`dsh-attachment-local` not installed, not automatically verified).
|
|
79
|
-
- **Rewind leaves plan mode untouched**: `/plan text` is two independent actions (enter plan mode + steer the message). Rewinding the message undoes only the message — the log-only `plan/mode` state stays active, and the user leaves plan mode with `/plan off`, which still commits after a rewind (the marker creates no open turn). Pin: `verify-host` plan checks (`plan rewind leaves plan mode active`, `/plan off after rewind turns plan mode off`), `tests/hidden.test.ts` `messageTextAt`.
|
|
80
|
-
- **Bundle card metadata is declared by the plugin, not by the manager (`0.1.7-alpha.1`)**: the manager still titles a bundle with `shortName(pkg.name)` and its raw `package.json` description when nothing else is declared, but it now reads plugin-owned resources first — `locale/<lang>.json` (`meta.title`/`meta.description`) through the package exports map, and an `icon` file relative to the manifest. This plugin declares both, so its card shows an icon and copy that follows the active language; only the fallback description remains mixed-language. Pin: `tests/package-layout.test.ts` (icon path/media/size and both dictionaries).
|
|
81
|
-
- **Synchronous Session history reads are deprecated** on this line (`snapshotEvents` / `eventAt` / `ownEvents`): existing calls may remain, new calls are prohibited. The plugin keeps its existing reads (turn anchor + candidate listing); the migration path is a `ctx.sessionProjections` projection unit or the async paged history read this line has not shipped yet — not a plugin-side index.
|
|
82
|
-
- **A peer version range is never enforced at runtime**, and a physical copy nearer than the host still wins (upstream profile-resolution lookup order: "a peer version range is not an additional resolver filter"; "a nearer physical candidate wins"). So the `@deepseek-ai/schemastery` peer floor *is* the availability guarantee for `.volatile()` (#40): a plugin directory or linked checkout left holding an older copy (e.g. the pre-3.18.2 devDependency) shadows the host's 3.18.4 and fails the top-level `Config` evaluation before the plugin loads. Floor pinned at `^3.18.4`.
|
|
83
|
-
|
|
84
|
-
## Upstream (harness) issues and the plugin's no-compensation stance
|
|
85
|
-
|
|
86
|
-
The plugin treats these as harness-side defects it does not compensate for. Each entry records
|
|
87
|
-
the harness issue, its current status, and the plugin's stance, so a future maintainer does not
|
|
88
|
-
"fix the wrong direction."
|
|
89
|
-
|
|
90
|
-
### RU-I18N (resolved on the 0.1.7 line): host command copy follows the user's language
|
|
91
|
-
|
|
92
|
-
> **Status**: the `locale` settings section is gone — the locale host half only calls
|
|
93
|
-
> `settings.configure({ auto: false })`, and the preference lives in the `locale` entry's own
|
|
94
|
-
> Config. The plugin reads that value through the settings service's descriptor read
|
|
95
|
-
> (`readHostLocale` in `src/index.ts` calls `settings.describe({ redactSecrets: true })`, the
|
|
96
|
-
> route the settings UI and the client's own locale runtime take) at mount — which is what the
|
|
97
|
-
> once-registered command descriptions carry — and again before each command renders, so the
|
|
98
|
-
> output follows a language switch without a remount. The read is best-effort: a missing
|
|
99
|
-
> method, a missing entry, or a throwing read stays on the neutral English default.
|
|
100
|
-
>
|
|
101
|
-
> **Client-side command-description i18n is still first-party-only**: DSH localizes host
|
|
102
|
-
> command descriptions through the client `locale` binding (`ui-commands`), but the
|
|
103
|
-
> description keys come from a **closed allowlist** (`HOST_FACES`: compact, export, feedback,
|
|
104
|
-
> goal, permission, plan). A command outside that set — every third-party plugin — is passed
|
|
105
|
-
> through verbatim, and no host command *output* is localized by the harness for anyone. This
|
|
106
|
-
> plugin's host dictionary is what carries both, in the language read above.
|
|
107
|
-
>
|
|
108
|
-
> **Plugin stance**: no compensation; the seam is the descriptor read above. The wrong direction
|
|
109
|
-
> is the earlier one — reading `settings.get('locale').preference` once inside an
|
|
110
|
-
> `ctx.inject(['settings'])` callback and assuming cordis ordered the locale section before it
|
|
111
|
-
> (it does not; the descriptors were baked in the English default, defect #28).
|
|
112
|
-
|
|
113
|
-
### R-OPENSTEP (rewind part resolved): an unclosed `step` in the log breaks token-meter replay
|
|
114
|
-
|
|
115
|
-
> **Status**: resolved upstream — DSH auto-closes crash-left step/turn/tool boundaries at load
|
|
116
|
-
> (`interruptedTurnClosers`, consumed by the agent-loop's `resumeWith` cold read). A leftover
|
|
117
|
-
> unclosed step can still come from a third-party plugin appending `step/start` through the public
|
|
118
|
-
> `Session.append`, or from a hand-edited log; when it does, replay rejects later step activity, so
|
|
119
|
-
> `/compact` fails and automatic compaction silently stays disabled. Rewind's part: the marker is a
|
|
120
|
-
> `user/message` and appends no `step/start`, so the rewind amplifier is closed.
|
|
121
|
-
>
|
|
122
|
-
> **Plugin stance**: no guard. A `hasOpenStep` + `planRewind` pre-refusal was implemented and
|
|
123
|
-
> misjudged on **real session logs** (normal rewinds refused, GUI verification broken), so it was
|
|
124
|
-
> reverted — do not re-attempt a plugin-side pre-refusal. The fix direction is upstream (token-meter
|
|
125
|
-
> recovery for unclosed steps), and the residual risk is accepted: that log is already abnormal.
|
|
126
|
-
|
|
127
|
-
### R-PROMPTCARD (resolved on the 0.1.7 line): the repeated `System prompt` card is filtered out of chat
|
|
128
|
-
|
|
129
|
-
> **Status**: `isVisibleChatNode`
|
|
130
|
-
> (`packages/client/ui-chat/src/client/contract/chat-visibility.ts`) drops `system-prompt`
|
|
131
|
-
> nodes — and the `permission` command — from the visible chat rows before the row is built,
|
|
132
|
-
> so a rewind on this line adds no prompt row at all; the durable events and the trajectory
|
|
133
|
-
> inspection keep them. #22, #31 and the (now removed) repeated `System prompt` card known issue
|
|
134
|
-
> no longer reproduce.
|
|
135
|
-
>
|
|
136
|
-
> **Plugin stance**: still no compensation. The DOM row-hiding seam (`hiddenSeqsOf`) covers
|
|
137
|
-
> withdrawn messages, where hiding is essential, not cosmetic.
|
|
138
|
-
|
|
139
|
-
### R-PANELKEYS: the panel keyboard stays the plugin's own
|
|
140
|
-
|
|
141
|
-
> **Situation**: on Web, Escape, Tab, the four arrows, and Enter without Alt are
|
|
142
|
-
> platform-reserved. The predicate is exported (`bindingIssue` from
|
|
143
|
-
> `@deepseek-ai/dsh-client-shortcuts/protocol`), but a reserved binding is still refused, so
|
|
144
|
-
> neither a plugin nor a user may bind them. `Menu`
|
|
145
|
-
> is the dropdown primitive, while the harness's command-option panel (`ui-commands`'
|
|
146
|
-
> popupSelect) renders `MenuSurface` plus its own controller with `role="listbox"`/
|
|
147
|
-
> `role="option"`. `ui-commands`' `settle()` also calls `focusComposer()` unconditionally
|
|
148
|
-
> after `await onSelect(...)`; its only early return requires replacing the popup binding,
|
|
149
|
-
> which needs the internal `segment` that business code cannot reach.
|
|
150
|
-
>
|
|
151
|
-
> **Decision**: the mode-selection and retract panels keep their own capture-phase
|
|
152
|
-
> Escape/↑/↓ handling — focus-independent, the shape the official command panel uses — and
|
|
153
|
-
> keep `role="dialog"`. The plugin neither renders `Menu` nor compensates the composer focus
|
|
154
|
-
> restore. Capture ownership is pinned by `tests/client-popover.test.ts`; the ghost-row walk
|
|
155
|
-
> exclusion is **not** pinned, and on the typed `/rewind` path Enter still needs a row to hold
|
|
156
|
-
> focus after the shell's focus restore.
|
|
157
|
-
|
|
158
|
-
## Uncovered boundaries (need an additional e2e layer; non-blocking)
|
|
159
|
-
|
|
160
|
-
- Real LLM streaming and auto title generation (L2 stubbed).
|
|
161
|
-
- Real SQLite index lifetime (`dsh-session-query-sqlite` not installed; native deps).
|
|
162
|
-
- Actual browser rendering replay (the client contract's logic layer is covered by `client-contract.test.ts`).
|
|
163
|
-
- Real JSONL persistence round-trip (`dsh-session-persistence-jsonl` depends on native `koffi`; the round-trip validates the harness's own zstd/JSON codec, the plugin is not party to it, not worth the cost, not installed).
|
|
164
|
-
- `session-reference`'s `SessionReferenceResolver.prepare` needs a full session-query service; its data base (current-surface projection) is already covered by the G1 probe (`foldSurface`).
|
|
165
|
-
- telemetry pipeline (`dsh-session-telemetry-otel`) and the attachment provider (`dsh-attachment-local`).
|
|
166
|
-
- Running workflow/jobs cancelled by a rewind: the tool contract requires observing `exec.signal` and settling (`packages/core/tools/src/index.ts`); a rewind triggers the harness's standard cancel, not plugin-specific — statically confirmed, real workflows untested.
|
|
167
|
-
|
|
168
|
-
Statuses and stances for the named findings live in the sections above; G1 (surface classification
|
|
169
|
-
via `foldSurface`), G2 (projection checkpoint) and G3 (the confirmed-non-defect usage anchor on
|
|
170
|
-
token-meter) all pass in `compat-gaps.test.ts`.
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Browser diagnostics
|
|
2
|
-
|
|
3
|
-
[简体中文](diagnostics.zh.md)
|
|
4
|
-
|
|
5
|
-
Every browser-side diagnostic this plugin emits is routed through one small,
|
|
6
|
-
level-filtered channel, so an anomaly surfaces under a single console filter.
|
|
7
|
-
The channel is deliberately narrow: it records only genuine internal anomalies
|
|
8
|
-
the plugin itself detects as off-contract — **not** the normal-path facts the
|
|
9
|
-
user already sees on screen, which carry no attribution.
|
|
10
|
-
|
|
11
|
-
## Always-on anomaly alerts
|
|
12
|
-
|
|
13
|
-
`error` and `warn` are always printed (no switch, no configuration) so a fault
|
|
14
|
-
surfaces even for a user who never configured anything:
|
|
15
|
-
|
|
16
|
-
| Level | Meaning |
|
|
17
|
-
| --- | --- |
|
|
18
|
-
| `error` | Unexpected/breaking failure (e.g. the settings card fails to register) |
|
|
19
|
-
| `warn` | Recoverable anomaly guard (e.g. a rewind command/refill throws, a waited-on outcome never settles, an unmet session binding) |
|
|
20
|
-
|
|
21
|
-
These cases are rare, cheap, and off-contract. Normal-path detail (which
|
|
22
|
-
branch a refill took, whether a write matched, which rows a rewind cut) is
|
|
23
|
-
deliberately **not** logged: it re-states what the user already sees and adds
|
|
24
|
-
nothing to attribution.
|
|
25
|
-
|
|
26
|
-
## Scopes
|
|
27
|
-
|
|
28
|
-
Diagnostics are printed as `[dsh-rewind:<scope>] ...`. The scope names the
|
|
29
|
-
subsystem an anomaly belongs to, so a report can be captured with a single
|
|
30
|
-
console filter:
|
|
31
|
-
|
|
32
|
-
| Scope | What an anomaly here means |
|
|
33
|
-
| --- | --- |
|
|
34
|
-
| `boot` | Startup identity — `loaded v<version> (build <hash>)`, gated by the verbose switch (see below). Confirms the running bundle matches a fix. |
|
|
35
|
-
| `refill` | The composer refill after a rewind (command/wait/refill throws, a rejected or unmatched command, an outcome that never settles) |
|
|
36
|
-
| `preview` | The `/rewind preview` impact probe behind the mode popover (command rejected/unmatched/threw, an outcome that never settles) |
|
|
37
|
-
| `portals` | Per-message button mount issues (e.g. no session binding) |
|
|
38
|
-
| `retract` | The pending-steering retract path (the remove lost the race, or the remove threw) |
|
|
39
|
-
| `settings` | The snapshot-cleanup settings card |
|
|
40
|
-
|
|
41
|
-
## Verbose switch
|
|
42
|
-
|
|
43
|
-
The `info`/`debug` levels are gated by `localStorage['dsh-rewind.debug']`, read
|
|
44
|
-
once per call and filtered by namespace. Two gated lines remain: the `boot`
|
|
45
|
-
startup identity line and the `retract` lost-race line. Both are deliberately
|
|
46
|
-
**off** by default (a normal user's console stays clean, and neither is an
|
|
47
|
-
anomaly), so a reporter enables the scope to see them:
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
// Just the startup identity line.
|
|
51
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind:boot'
|
|
52
|
-
|
|
53
|
-
// Or everything (avoids needing an exact scope).
|
|
54
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Reload (`F5`) after setting it. To switch it off:
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
delete localStorage['dsh-rewind.debug']
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The `error`/`warn` anomaly alerts are **not** gated by this switch — they are
|
|
64
|
-
always printed.
|
|
65
|
-
|
|
66
|
-
## Capturing a report
|
|
67
|
-
|
|
68
|
-
1. Reproduce once on the affected page.
|
|
69
|
-
2. In DevTools, filter the Console for `dsh-rewind` and copy the output
|
|
70
|
-
(with the plugin version and the DSH/kernel version).
|
|
71
|
-
|
|
72
|
-
The `error`/`warn` anomaly alerts require no setup — they always print. To also
|
|
73
|
-
capture the startup identity line (`boot`) — useful for ruling out a stale
|
|
74
|
-
bundle / un-restarted host — enable the verbose switch first (see above), then
|
|
75
|
-
reload.
|
|
76
|
-
|
|
77
|
-
## Notes
|
|
78
|
-
|
|
79
|
-
- This diagnostic surface is an aid for maintainers and cooperating reporters,
|
|
80
|
-
**not** a stable public interface; its exact keys and output may change
|
|
81
|
-
without notice.
|
|
82
|
-
- It is scoped to one browser origin and one browser; capture where the problem
|
|
83
|
-
actually occurs.
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# 浏览器诊断
|
|
2
|
-
|
|
3
|
-
[English](diagnostics.md)
|
|
4
|
-
|
|
5
|
-
本插件在浏览器端输出的所有诊断信息都经由同一条分级通道,因此异常可在单个控制台过滤器下浮现。该通道**刻意收窄**:只记录插件自身检测到的、偏离预期契约的真实内部异常——**不记录**用户早已在屏幕上看到、且无归因价值的普通路径事实。
|
|
6
|
-
|
|
7
|
-
## 常开的异常告警
|
|
8
|
-
|
|
9
|
-
`error` 与 `warn` **总是打印**(无需开关、无需配置),即使从未配置的用户也能看到故障:
|
|
10
|
-
|
|
11
|
-
| 级别 | 含义 |
|
|
12
|
-
| --- | --- |
|
|
13
|
-
| `error` | 意外/破坏性故障(如设置卡片注册失败) |
|
|
14
|
-
| `warn` | 可恢复的异常护栏(如回退命令/回填抛出异常、等待的结果始终未落定、会话绑定缺失) |
|
|
15
|
-
|
|
16
|
-
这些情形少见、廉价、且偏离契约。普通路径的细节(回填走了哪个分支、写入是否匹配、回退切断哪些行)则**刻意不记录**:它们重复用户已经看到的现象,对归因毫无增量。
|
|
17
|
-
|
|
18
|
-
## 作用域
|
|
19
|
-
|
|
20
|
-
诊断以 `[dsh-rewind:<scope>] ...` 形式打印。`scope` 标明异常所属子系统,因此可用单个控制台过滤器抓取报告:
|
|
21
|
-
|
|
22
|
-
| scope | 这里的异常含义 |
|
|
23
|
-
| --- | --- |
|
|
24
|
-
| `boot` | 启动身份行——`loaded v<version> (build <hash>)`,由详细输出开关控制(见下)。用于确认运行中的 bundle 是否匹配某个修复。 |
|
|
25
|
-
| `refill` | 回退后的输入框回填(命令/等待/回填抛出异常、命令被拒绝或未匹配、等待的结果始终未落定) |
|
|
26
|
-
| `preview` | 模式气泡背后的 `/rewind preview` 影响探测(命令被拒绝/未匹配/抛出异常、等待的结果始终未落定) |
|
|
27
|
-
| `portals` | 每条消息按钮的挂载问题(如无会话绑定) |
|
|
28
|
-
| `retract` | 插话撤回路径(remove 竞争失败,或 remove 抛错) |
|
|
29
|
-
| `settings` | 快照清理设置卡片 |
|
|
30
|
-
|
|
31
|
-
## 详细输出开关
|
|
32
|
-
|
|
33
|
-
`info`/`debug` 级别受 `localStorage['dsh-rewind.debug']` 控制,每次调用读取并按命名空间过滤。仅剩两条受控行:`boot` 启动身份行与 `retract` 竞争失败行。两者**默认关闭**(普通用户控制台保持干净,且都不是异常),排查者需先开启对应作用域才能看到:
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
// 仅启动身份行。
|
|
37
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind:boot'
|
|
38
|
-
|
|
39
|
-
// 或全部开启(省去精确 scope)。
|
|
40
|
-
localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
设置后刷新(`F5`)。关闭:
|
|
44
|
-
|
|
45
|
-
```js
|
|
46
|
-
delete localStorage['dsh-rewind.debug']
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`error`/`warn` 异常告警**不受**该开关控制——它们总是打印。
|
|
50
|
-
|
|
51
|
-
## 采集一段报告
|
|
52
|
-
|
|
53
|
-
1. 在出问题的页面上复现一次。
|
|
54
|
-
2. 在 DevTools 里把 Console 按 `dsh-rewind` 过滤,复制输出(连同插件版本、DSH/内核版本)。
|
|
55
|
-
|
|
56
|
-
`error`/`warn` 异常告警无需任何设置——它们总是打印。若要同时采集启动身份行(`boot`,用于排除「旧 bundle / 宿主未重启」),请先开启详细输出开关(见上),再刷新。
|
|
57
|
-
|
|
58
|
-
## 备注
|
|
59
|
-
|
|
60
|
-
- 该诊断表面是维护者与配合排查者使用的工具,**不是**稳定公开接口;其具体键与输出可能不经通知而改变。
|
|
61
|
-
- 它受单个浏览器 origin、单个浏览器限定;请在实际出问题的地方采集。
|