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/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`).
@@ -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.
@@ -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、单个浏览器限定;请在实际出问题的地方采集。