dsh-better-display 0.3.7 → 0.3.9
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/CHANGELOG.md +22 -1
- package/DESIGN.md +43 -3
- package/README.en.md +9 -1
- package/README.md +9 -1
- package/docs/dependency-update-policy.md +9 -0
- package/docs/extension-points.md +172 -0
- package/docs/official-rendering-bridge.md +59 -18
- package/docs/openapi.yaml +147 -0
- package/docs/runbooks/blank-reader.md +39 -0
- package/lib/client.js +1629 -586
- package/lib/client.js.map +1 -1
- package/lib/dsh-better-display.js +535 -57
- package/lib/types/client/AssistantNode.d.ts +20 -0
- package/lib/types/client/AssistantNode.d.ts.map +1 -0
- package/lib/types/client/AssistantNode.js +46 -0
- package/lib/types/client/AssistantNode.js.map +1 -0
- package/lib/types/client/Blocks.d.ts.map +1 -1
- package/lib/types/client/Blocks.js +33 -10
- package/lib/types/client/Blocks.js.map +1 -1
- package/lib/types/client/ChoreographedFlow.d.ts.map +1 -1
- package/lib/types/client/ChoreographedFlow.js +2 -0
- package/lib/types/client/ChoreographedFlow.js.map +1 -1
- package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -1
- package/lib/types/client/ClosedProcessSummary.js +3 -1
- package/lib/types/client/ClosedProcessSummary.js.map +1 -1
- package/lib/types/client/McpAppFrame.d.ts.map +1 -1
- package/lib/types/client/McpAppFrame.js +5 -11
- package/lib/types/client/McpAppFrame.js.map +1 -1
- package/lib/types/client/OfficialContent.d.ts +17 -3
- package/lib/types/client/OfficialContent.d.ts.map +1 -1
- package/lib/types/client/OfficialContent.js +33 -7
- package/lib/types/client/OfficialContent.js.map +1 -1
- package/lib/types/client/Reader.d.ts.map +1 -1
- package/lib/types/client/Reader.js +76 -73
- package/lib/types/client/Reader.js.map +1 -1
- package/lib/types/client/SettingsSection.d.ts.map +1 -1
- package/lib/types/client/SettingsSection.js +0 -5
- package/lib/types/client/SettingsSection.js.map +1 -1
- package/lib/types/client/StickyLane.d.ts +12 -0
- package/lib/types/client/StickyLane.d.ts.map +1 -1
- package/lib/types/client/StickyLane.js +78 -2
- package/lib/types/client/StickyLane.js.map +1 -1
- package/lib/types/client/ToolActivity.d.ts.map +1 -1
- package/lib/types/client/ToolActivity.js +10 -5
- package/lib/types/client/ToolActivity.js.map +1 -1
- package/lib/types/client/deliverables.d.ts.map +1 -1
- package/lib/types/client/deliverables.js +2 -0
- package/lib/types/client/deliverables.js.map +1 -1
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +15 -1
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/live-turn.d.ts +17 -5
- package/lib/types/client/live-turn.d.ts.map +1 -1
- package/lib/types/client/live-turn.js +106 -76
- package/lib/types/client/live-turn.js.map +1 -1
- package/lib/types/client/motion.d.ts +26 -2
- package/lib/types/client/motion.d.ts.map +1 -1
- package/lib/types/client/motion.js +144 -10
- package/lib/types/client/motion.js.map +1 -1
- package/lib/types/client/official-slots.d.ts +9 -0
- package/lib/types/client/official-slots.d.ts.map +1 -1
- package/lib/types/client/official-slots.js +22 -4
- package/lib/types/client/official-slots.js.map +1 -1
- package/lib/types/client/open-file.d.ts.map +1 -1
- package/lib/types/client/open-file.js +1 -6
- package/lib/types/client/open-file.js.map +1 -1
- package/lib/types/client/projection.d.ts +15 -2
- package/lib/types/client/projection.d.ts.map +1 -1
- package/lib/types/client/projection.js +16 -4
- package/lib/types/client/projection.js.map +1 -1
- package/lib/types/client/rewind-cut.d.ts +131 -0
- package/lib/types/client/rewind-cut.d.ts.map +1 -0
- package/lib/types/client/rewind-cut.js +321 -0
- package/lib/types/client/rewind-cut.js.map +1 -0
- package/lib/types/client/styles.d.ts +3 -0
- package/lib/types/client/styles.d.ts.map +1 -0
- package/lib/types/client/styles.js +25 -0
- package/lib/types/client/styles.js.map +1 -0
- package/lib/types/client/text-transform.d.ts +93 -0
- package/lib/types/client/text-transform.d.ts.map +1 -0
- package/lib/types/client/text-transform.js +183 -0
- package/lib/types/client/text-transform.js.map +1 -0
- package/lib/types/client/timeline.d.ts.map +1 -1
- package/lib/types/client/timeline.js.map +1 -1
- package/lib/types/client/tool-activity.d.ts +11 -4
- package/lib/types/client/tool-activity.d.ts.map +1 -1
- package/lib/types/client/tool-activity.js +54 -9
- package/lib/types/client/tool-activity.js.map +1 -1
- package/lib/types/client/turn-slots.d.ts +29 -0
- package/lib/types/client/turn-slots.d.ts.map +1 -0
- package/lib/types/client/turn-slots.js +62 -0
- package/lib/types/client/turn-slots.js.map +1 -0
- package/lib/types/client/types.d.ts +18 -0
- package/lib/types/client/types.d.ts.map +1 -1
- package/lib/types/dsh-better-display.d.ts +4 -1
- package/lib/types/dsh-better-display.d.ts.map +1 -1
- package/lib/types/dsh-better-display.js +55 -43
- package/lib/types/dsh-better-display.js.map +1 -1
- package/lib/types/flags.d.ts +7 -0
- package/lib/types/flags.d.ts.map +1 -0
- package/lib/types/flags.js +37 -0
- package/lib/types/flags.js.map +1 -0
- package/lib/types/log.d.ts +17 -0
- package/lib/types/log.d.ts.map +1 -0
- package/lib/types/log.js +122 -0
- package/lib/types/log.js.map +1 -0
- package/lib/types/metrics.d.ts +18 -0
- package/lib/types/metrics.d.ts.map +1 -0
- package/lib/types/metrics.js +35 -0
- package/lib/types/metrics.js.map +1 -0
- package/lib/types/reveal.d.ts +12 -0
- package/lib/types/reveal.d.ts.map +1 -0
- package/lib/types/reveal.js +147 -0
- package/lib/types/reveal.js.map +1 -0
- package/lib/types/workspace-path.d.ts +3 -0
- package/lib/types/workspace-path.d.ts.map +1 -0
- package/lib/types/workspace-path.js +5 -0
- package/lib/types/workspace-path.js.map +1 -0
- package/package.json +36 -5
- package/scripts/changelog.mjs +20 -0
- package/scripts/check-agents-md.mjs +67 -0
- package/scripts/check-bundle-size.mjs +38 -0
- package/scripts/check-dead-flags.mjs +55 -0
- package/scripts/check-todo-debt.mjs +69 -0
- package/scripts/security-audit-report.mjs +92 -0
- package/scripts/verify-commentary-visible.mjs +627 -0
- package/src/client/AssistantNode.tsx +72 -0
- package/src/client/Blocks.tsx +49 -10
- package/src/client/ChoreographedFlow.tsx +3 -1
- package/src/client/ClosedProcessSummary.tsx +3 -1
- package/src/client/McpAppFrame.tsx +5 -13
- package/src/client/OfficialContent.tsx +47 -9
- package/src/client/Reader.module.css +103 -21
- package/src/client/Reader.tsx +83 -88
- package/src/client/SettingsSection.tsx +1 -7
- package/src/client/StickyLane.tsx +81 -2
- package/src/client/ToolActivity.tsx +10 -6
- package/src/client/deliverables.ts +1 -0
- package/src/client/index.tsx +18 -2
- package/src/client/live-turn.ts +100 -69
- package/src/client/motion.tsx +142 -7
- package/src/client/official-slots.tsx +23 -4
- package/src/client/open-file.ts +2 -8
- package/src/client/projection.ts +16 -4
- package/src/client/rewind-cut.ts +340 -0
- package/src/client/styles.ts +22 -0
- package/src/client/text-transform.ts +265 -0
- package/src/client/timeline.ts +0 -2
- package/src/client/tool-activity.ts +67 -9
- package/src/client/turn-slots.ts +80 -0
- package/src/client/types.ts +19 -0
- package/src/dsh-better-display.ts +57 -42
- package/src/flags.ts +40 -0
- package/src/log.ts +137 -0
- package/src/metrics.ts +50 -0
- package/src/reveal.ts +90 -0
- package/src/workspace-path.ts +4 -0
- package/tests/browser-commentary.tsx +191 -0
- package/tests/chat-flow-hook.test.ts +1 -1
- package/tests/cloud-gate.test.ts +1 -1
- package/tests/context-tool-update.test.ts +1 -1
- package/tests/conversation-scroll.test.ts +1 -1
- package/tests/deliverables.test.ts +1 -1
- package/tests/entry.test.ts +1 -1
- package/tests/fixtures/reader-lane-owners.tsx +46 -0
- package/tests/fixtures/reader-official-users.tsx +234 -0
- package/tests/fixtures/reader-theme-overlap.html +80 -0
- package/tests/fixtures/reader-user-bubble.tsx +222 -0
- package/tests/flags.test.ts +66 -0
- package/tests/fold-choreography.test.ts +32 -8
- package/tests/fold-intensity.test.ts +11 -6
- package/tests/footer-precedence.test.ts +9 -1
- package/tests/history-word-regression.test.ts +1 -1
- package/tests/live-turn.test.ts +125 -54
- package/tests/log.test.ts +99 -0
- package/tests/mcp-apps.test.ts +1 -1
- package/tests/message-chrome.test.ts +1 -1
- package/tests/metrics.test.ts +52 -0
- package/tests/official-slots.test.ts +41 -2
- package/tests/open-file.test.ts +1 -1
- package/tests/pending-submission.test.ts +12 -1
- package/tests/pinned-selection.test.ts +16 -0
- package/tests/platform-media.test.ts +1 -1
- package/tests/projection.test.ts +66 -3
- package/tests/reader-glass-css.test.ts +28 -0
- package/tests/reader-official-users.test.ts +91 -0
- package/tests/reader-sticky-surface.test.ts +377 -0
- package/tests/reader-theme-lanes.test.ts +54 -0
- package/tests/reader-theme-overlap.test.ts +151 -0
- package/tests/reader-user-bubble.test.ts +137 -0
- package/tests/reasoning-follow.test.ts +1 -1
- package/tests/retiring-content.test.ts +229 -0
- package/tests/reveal.test.ts +128 -0
- package/tests/rewind-cut.test.ts +1090 -0
- package/tests/settings-copy.test.ts +1 -1
- package/tests/skill-status.test.ts +1 -1
- package/tests/stock-install.test.ts +9 -1
- package/tests/stream-buffer.test.ts +1 -1
- package/tests/styles.test.ts +75 -0
- package/tests/terminal-input-preview.test.ts +113 -0
- package/tests/text-transform.test.ts +272 -0
- package/tests/timeline.test.ts +1 -1
- package/tests/tool-clock.test.ts +1 -1
- package/tests/tool-diff.test.ts +1 -1
- package/tests/waiting-clock.test.ts +1 -1
- package/tests/word-timeline.test.ts +1 -1
- package/tsdown.config.ts +49 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.9 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
- Keep assistant narration visible. Collapsing process hides reasoning, tool calls and process records, and a compact seam between body blocks summarises the folded run (`思考×N · 工具×K · 记录×M`) and expands those rows in place. During a live turn, a later reasoning step folds each earlier reasoning/tool run in its own position, including across narration, without counting narration as `输出×N`. Thanks @Adamaik (#68).
|
|
6
|
+
- An open turn no longer draws that seam when a live-fold cell already summarises the same steps, and the previous body keeps its bottom spacing when the seam is omitted. Closed turns still show the seam. Thanks @sugar-syl (#73).
|
|
7
|
+
- Add a display-only text transform seam (`dsh-better-display.text-transform`) so another plugin can rewrite reasoning and answer text before the reading reveal. The session, the transcript, and the fold stay on the original blocks. Copy still uses the original answer. An appended transform batch keeps the reveal going instead of starting it over. The `dsh-better-display.block` chain owner now also receives `presentation`, `holdFormatting`, and `liveText`. Thanks @lin636 (#62).
|
|
8
|
+
- Honour a third-party `conversation.chat.node` replacement for `assistant-step` when its priority is below the official renderer. The reading view hands that renderer the answer body, so raw HTML cards from plugins such as `dsh-raw-html-v2` render inside Reader. Reasoning, folding, and process motion stay with Reader. That renderer receives the original segment text; the text transform runs only on the Reader markdown fallback (#65).
|
|
9
|
+
- Keep the elapsed-time row and the closed process summary from painting over the answer heading when a third-party theme clears or fades `--dsw-alias-bg-base`, or raises `--dsh-content-font-size`. Sticky bands follow the measured row, stay at least 40px, and each Reader owns its own lane heights. A transparent or 10% token composites over `Canvas` (the page surface), not the text colour (#66).
|
|
10
|
+
- Roll a successful rewind back in the Reader and collapse what leaves (#63, #64, thanks @El1mir). `dsh-rewind-plugin` publishes the command as `rewind-plugin` (older logs say `rewind`). A success with `outcome.sourceEventSeq` and an `@<seq>` target cuts the closed range `[target, marker]`: the target message, everything after it in that range, and the command row itself leave the Reader. A turn's process and tail rows leave only once every content row of that turn is gone, so the stopped-turn notice does not stay behind, and an earlier message in the same turn stays when the target is a later steering line. Preview, candidate probes, a failed or still-running command, a stream tick, and a reconnect do not cut. The user and steering chat seats stay, so the rewind control still shows. Withdrawn turns keep their place and fold for 220ms (height and opacity); the #60 retiring deadline bounds the animation, and reduced motion or a hidden page settles at once. A tool draft from a cut step, or from a call whose tool row was cut, is not put back; a draft that is still streaming on a live step stays. Loading earlier history follows the incoming turn order. Focus inside cut content defers the animated collapse until it leaves, but a page that turns hidden meanwhile drops the content at once. When reduced motion or a hidden page drops cut content that holds focus, focus moves to the nearest surviving turn or the reading column instead of the document; the column draws no focus ring.
|
|
11
|
+
- Reader user bubbles use the host width `min(calc(var(--dsh-chat-content-width, 748px) * 0.702), 82%)`. `.userCluster` was `align-self: flex-end` with no definite width, so it shrank to the text and the host `.userStack` `82%` resolved against that shrink-wrap (#74).
|
|
12
|
+
- Pending submission bubbles use the host 14px/22px font metrics, radius, and pre-wrap, so they no longer jump in size or weight when the official bubble replaces them (#74).
|
|
13
|
+
|
|
14
|
+
## 0.3.8 — 2026-10-13
|
|
15
|
+
|
|
16
|
+
- Fix host entry bundling in tsdown: stop externalising relative host imports and add a portable output build guard to prevent missing bundled modules (`ERR_MODULE_NOT_FOUND ./skill-roots.ts`, #58, #59, #61).
|
|
17
|
+
- Fix Reader layering and tool display: resolve code block and elapsed time element stacking (#47, #50), clean up empty whitespace after collapsing tool calls, and restore terminal command preview (#52, #57).
|
|
18
|
+
- Mirror official user and steering node renderers through slot fallback, restoring compatibility with plugins like `dsh-easyrewrite` without losing custom user bubbles (#42, #56).
|
|
19
|
+
- Preserve standard backdrop-filter blur in Chromium for glass mode, adopting a floating capsule aesthetic for sticky controls (#43).
|
|
20
|
+
- Windows Explorer and Finder reveal fixes: resolve relative workspace paths and safely handle whitespace and special characters without falling back to "This PC" (#60).
|
|
21
|
+
- Re-inject plugin stylesheet automatically if cleared on session switch to prevent blank pages (#60).
|
|
22
|
+
- Add timeout deadline fallback for retiring content animations to ensure completion (#60).
|
|
23
|
+
|
|
24
|
+
## 0.3.7 — 2026-10-08
|
|
4
25
|
|
|
5
26
|
- Restore the official chat seat attributes on Better Display user and steering rows so rewind actions can find durable messages.
|
|
6
27
|
- Render pending inbox steering rows and deduplicate them against observed local submissions.
|
package/DESIGN.md
CHANGED
|
@@ -6,7 +6,7 @@ Keep native DeepSeek Harness process information readable while separating the f
|
|
|
6
6
|
|
|
7
7
|
## Content fidelity
|
|
8
8
|
|
|
9
|
-
- Preserve literal reasoning text, whitespace, source order and block identity.
|
|
9
|
+
- Preserve literal reasoning text, whitespace, source order and block identity. An opt-in display transformer may paint different glyphs; it does not change the stored block. With no transformer installed, the painted text stays literal.
|
|
10
10
|
- Do not infer reasoning from wording, parse private provider logs, rewrite prompts or alter model settings.
|
|
11
11
|
- Preserve native context sources, tool types, summaries, filenames, details and results.
|
|
12
12
|
- Preserve the native conversation, input, model selector and approval system.
|
|
@@ -16,9 +16,21 @@ Keep native DeepSeek Harness process information readable while separating the f
|
|
|
16
16
|
|
|
17
17
|
During a turn, show its real process in chronological order. Starting body output, preparing tool input, finishing one tool, or entering another step is not whole-turn completion.
|
|
18
18
|
|
|
19
|
-
Only a successful public turn-close boundary folds process
|
|
19
|
+
Only a successful public turn-close boundary folds process, leaving the final answer in place. Failures, interruptions, unknown terminal states and approval requests remain visible. A live text selection defers folding until the selection is released. A historical turn can always be reopened.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## Narration is never folded (2026-10-20)
|
|
22
|
+
|
|
23
|
+
Assistant narration — the body segments a step emits between reasoning and tool calls, including text, images and future block kinds — is display content, not process detail. It is ordinary body prose, never annotation chrome. It is always rendered in source order:
|
|
24
|
+
|
|
25
|
+
- Collapsing process hides reasoning cards, tool calls, context rows and other process records; it never hides, truncates or dims narration.
|
|
26
|
+
- Expanding process inserts those records back between the narration segments exactly where they occurred. Narration does not move, duplicate or become an answer card.
|
|
27
|
+
- Narration uses the normal body typography and keeps no left rule of its own. When collapsed process sat between two narration blocks, a compact process-chain summary (1px rail on the shared `--reader-rail` edge + `思考×N · 工具×K` label + chevron, 12px tertiary) occupies the gap; it counts only the rows folded between those blocks and expands them in place when clicked. If a live-fold cell already contains every one of those rows, the seam is omitted. Omitting it does not mark the previous narration with `data-reader-seam-after`, so that body keeps its bottom spacing.
|
|
28
|
+
- A step that carries body blocks no longer advertises process content on that basis; only literal reasoning does.
|
|
29
|
+
- Fold summaries count thinking, tools and records; narration is not counted as `输出×N` and never appears inside a fold box.
|
|
30
|
+
- During a live turn, a later reasoning step collapses every earlier reasoning/tool run in its own position, even across narration blocks; the latest thinking and everything after it stay open, and narration itself is never folded or moved.
|
|
31
|
+
- Auto-fold off, failure, interruption, unknown terminals and selection protection keep their existing behavior.
|
|
32
|
+
|
|
33
|
+
While a turn is still open, a later reasoning step may collapse each earlier reasoning/tool run into its own disclosure at that run's source position. Narration stays outside those disclosures. Body or tool alone never triggers that live fold. A mid-turn user insert or steering message resets the chain, so reasoning after the insert does not fold steps before it. This is presentation-only and does not change the successful-turn final-answer fold. The live-fold cell is the only summary of a run it covers; the between-narration seam is not a second copy of that cell.
|
|
22
34
|
|
|
23
35
|
## Long reasoning
|
|
24
36
|
|
|
@@ -46,6 +58,34 @@ Manual reading, selection and keyboard access take precedence over automatic fol
|
|
|
46
58
|
|
|
47
59
|
Trusted plugins may register `dsh-better-display.block`. Native content remains the fallback. Generative MCP Apps (SEP-1865 / `io.modelcontextprotocol/ui`) are supported via an isolated, sandboxed iframe (`sandbox="allow-scripts allow-forms"`, strictly without `allow-same-origin`) communicating via bidirectional JSON-RPC `postMessage` (`ui/initialize`, `ui/resize`, `ui/update-model-context`, `ui/submit`).
|
|
48
60
|
|
|
61
|
+
### Block chain owner
|
|
62
|
+
|
|
63
|
+
`renderSlotChain('dsh-better-display.block', owner)` passes the session block plus the display inputs the Reader already has. The original fields stay `block`, `streaming`, and `source`. These are added and may be omitted by an older caller:
|
|
64
|
+
|
|
65
|
+
| Field | Meaning |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `presentation.startedAt` | Assistant step time, when the Reader has one. |
|
|
68
|
+
| `presentation.interrupted` | This step was interrupted. |
|
|
69
|
+
| `presentation.liveText` | This block may run the live reveal. |
|
|
70
|
+
| `holdFormatting` | A text selection is holding Markdown formatting in place. |
|
|
71
|
+
| `liveText` | Same boolean as `presentation.liveText`. |
|
|
72
|
+
|
|
73
|
+
A registrant that only reads `block`, `streaming`, and `source` keeps working. Replacing the chain still means that registrant draws the block. The fallback Reader keeps reveal, follow-scroll, and fold. The full shape and a copy-paste plugin are in [docs/extension-points.md](docs/extension-points.md).
|
|
74
|
+
|
|
75
|
+
### Display text transform
|
|
76
|
+
|
|
77
|
+
Service name: `dsh-better-display.text-transform`. Another client plugin lists that name in `inject` and registers a transformer. Kinds are `reasoning` (the thinking card) and `body` (assistant Markdown, and tool prose that uses the same reader). The request is `{ text, streaming, blockKey, kind, source }`. `read` may return a string, `{ text, final }`, or `undefined` to keep the source. `subscribe` tells the reader that a later batch changed what `read` will return.
|
|
78
|
+
|
|
79
|
+
No registrant means the reader never calls `read` and never subscribes. Painted text, reveal, and fold stay as they are.
|
|
80
|
+
|
|
81
|
+
The transformer is display-only. It must not write the session, the transcript, or the block object. Copy answer and copy message read the original blocks. Fold still follows the public turn boundary and the original block list.
|
|
82
|
+
|
|
83
|
+
`final: false` keeps the live reveal open, including after the model has stopped while a batch is still out. A transformer that returns `final: !streaming` leaves the reveal flag unchanged.
|
|
84
|
+
|
|
85
|
+
Each displayed string has its own `transformRevision`. That counter is not the reveal clock. The reveal continues when the new display string extends the previous one, so a translated batch that only appends does not replay the fade from the first character. A string that does not extend the previous one settles in place instead of typing again from empty. Plugins that want the fade to continue should return a string that starts with the previous display string. Do not use `transformRevision` as a React key or as the word-timeline revision.
|
|
86
|
+
|
|
87
|
+
Register during plugin apply, before the reading view paints. A transformer registered mid-turn is picked up on the next reader render.
|
|
88
|
+
|
|
49
89
|
## Host DOM hooks
|
|
50
90
|
|
|
51
91
|
Reader replaces ChatView's message list, not the native conversation chrome. The enclosing `[data-conversation-scroll]` and sticky `[data-composer-seat]` stay host-owned. ChatView marks its column with `data-chat-flow=""`. Third-party skins (maid-atelier, phoebe-atelier, and others) treat a scrollport without that hook as inspect-only and hide the composer. Reader keeps the same empty `data-chat-flow` attribute on its column so those skins still see an interactive conversation. Turns stay on `[data-reader-turn]`; Reader does not emit native `[data-chat-flow-kind]` rows.
|
package/README.en.md
CHANGED
|
@@ -22,7 +22,9 @@ dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.6
|
|
|
22
22
|
|
|
23
23
|
This official CLI command writes only the `web` profile; it cannot modify the Desktop App profile. **pnpm** must be on PATH. For an already-running Web Host, reopen that Host once and reload the page because bundles are read at boot.
|
|
24
24
|
|
|
25
|
-
Adds a **阅读** tab to DeepSeek Harness. While a turn runs you see steps, thinking, and progress. After a successful turn those collapse and the final answer stays. Native Chat / Trajectory, the composer, model picker, tools, and approvals stay. The reading column keeps ChatView's `data-chat-flow` hook so third-party skins that gate the composer on that mark still treat Reader as an interactive conversation.
|
|
25
|
+
Adds a **阅读** tab to DeepSeek Harness. While a turn runs you see steps, thinking, and progress. After a successful turn those collapse and the final answer stays. Native Chat / Trajectory, the composer, model picker, tools, and approvals stay. The reading column keeps ChatView's `data-chat-flow` hook so third-party skins that gate the composer on that mark still treat Reader as an interactive conversation. When another plugin replaces `assistant-step` at a priority below the official renderer (for example `dsh-raw-html-v2` drawing `<div id="vcp-root">` cards), Reader gives it the answer body and keeps reasoning, folding, and process motion. See the [official integration notes](docs/official-rendering-bridge.md).
|
|
26
|
+
|
|
27
|
+
Another plugin can rewrite reasoning and answer text for display only, for example to translate them. The reader still owns the reveal, follow-scroll, and fold. Copy uses the original text, and the session record is unchanged. The contract and a minimal plugin are in [extension points](docs/extension-points.md).
|
|
26
28
|
|
|
27
29
|
A ````mcp-app` fence in the final answer mounts as an interactive card in the reading view, inside `<iframe sandbox="allow-scripts allow-forms">` without `allow-same-origin`. The card can fill the next prompt via JSON-RPC. The skill pack is [`skills/generative-mcpapps/`](skills/generative-mcpapps/). Settings → **Better Display** can preview deliverables in the right Sidebar (system app remains the default), turn on translucent frosted glass (off by default), toggle process auto-folding (On is the default), and reports whether that skill is installed in a harness skill root.
|
|
28
30
|
|
|
@@ -45,11 +47,17 @@ dsh plugin --profile web remove dsh-better-display
|
|
|
45
47
|
|
|
46
48
|
## Develop
|
|
47
49
|
|
|
50
|
+
From a clean clone:
|
|
51
|
+
|
|
48
52
|
```sh
|
|
53
|
+
npm ci
|
|
54
|
+
npm run build
|
|
49
55
|
npm test
|
|
50
56
|
npm run typecheck
|
|
51
57
|
```
|
|
52
58
|
|
|
59
|
+
The harness compat check inside `npm run build` needs ripgrep and a Harness checkout. Optional environment variables are listed in `.env.example` and [AGENTS.md](AGENTS.md). The interactive reading-view path, a blank reader, a missing stylesheet, and a stuck fold are in [docs/runbooks/blank-reader.md](docs/runbooks/blank-reader.md).
|
|
60
|
+
|
|
53
61
|
## License
|
|
54
62
|
|
|
55
63
|
Display and Markdown pieces come from DeepSeek Harness (MIT). Motion is based on [Transitions.dev](https://transitions.dev/). This repo is [MIT](LICENSE).
|
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.6
|
|
|
22
22
|
|
|
23
23
|
这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。本机 PATH 上需要有 **pnpm**。对于已经运行的 Web Host,请重新打开该 Host 一次,再刷新网页;插件 bundle 会在启动时读取。
|
|
24
24
|
|
|
25
|
-
给 DeepSeek Harness 加一个 **阅读** 页签:执行时能看到步骤、思考和进度;整轮成功结束后把过程收起来,留下最终回答。原版「对话 / 轨迹」、输入框、模型选择、工具和审批都还在。阅读列保留宿主 ChatView 的 `data-chat-flow`
|
|
25
|
+
给 DeepSeek Harness 加一个 **阅读** 页签:执行时能看到步骤、思考和进度;整轮成功结束后把过程收起来,留下最终回答。原版「对话 / 轨迹」、输入框、模型选择、工具和审批都还在。阅读列保留宿主 ChatView 的 `data-chat-flow` 钩子,依赖该标记显示输入框的第三方皮肤不会把阅读页当成仅检视视图。若另一个插件用低于官方的 priority 替换了 `assistant-step`(例如 `dsh-raw-html-v2` 渲染 `<div id="vcp-root">` 卡片),阅读页把回答正文交给它,思考、折叠和过程动效仍由阅读页负责。详见 [官方能力接入说明](docs/official-rendering-bridge.md)。
|
|
26
|
+
|
|
27
|
+
另一个插件可以在展示层改写思考和回答(例如翻译),阅读页仍负责浮现、跟随和折叠。复制的是原文,会话记录不变。契约和最小例子见 [扩展点](docs/extension-points.md)。
|
|
26
28
|
|
|
27
29
|
最终回答里的 ````mcp-app` 代码块会在阅读视图里挂成交互卡片,跑在 `<iframe sandbox="allow-scripts allow-forms">` 里,没有 `allow-same-origin`。卡片可以通过 JSON-RPC 把下一轮 prompt 填进输入框。技能包在 [`skills/generative-mcpapps/`](skills/generative-mcpapps/)。设置里的 **Better Display** 可把产物改为右侧栏预览(默认仍用系统应用),打开半透明毛玻璃(默认关),设置过程自动折叠开关(默认开),并检测该技能是否已装进宿主技能目录。
|
|
28
30
|
|
|
@@ -45,11 +47,17 @@ dsh plugin --profile web remove dsh-better-display
|
|
|
45
47
|
|
|
46
48
|
## 开发
|
|
47
49
|
|
|
50
|
+
干净检出后:
|
|
51
|
+
|
|
48
52
|
```sh
|
|
53
|
+
npm ci
|
|
54
|
+
npm run build
|
|
49
55
|
npm test
|
|
50
56
|
npm run typecheck
|
|
51
57
|
```
|
|
52
58
|
|
|
59
|
+
`npm run build` 里的 Harness 兼容检查需要 ripgrep 和一份 Harness 检出。可选环境变量写在 `.env.example` 和 [AGENTS.md](AGENTS.md)。阅读页怎么点开、空白页、样式丢失和折叠停住见 [docs/runbooks/blank-reader.md](docs/runbooks/blank-reader.md)。
|
|
60
|
+
|
|
53
61
|
## 许可
|
|
54
62
|
|
|
55
63
|
展示与 Markdown 部分来自 DeepSeek Harness(MIT)。动效参考 [Transitions.dev](https://transitions.dev/)。本仓库代码 [MIT](LICENSE)。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Dependency update policy
|
|
2
|
+
|
|
3
|
+
Wait 7 days before merging a version bump.
|
|
4
|
+
|
|
5
|
+
`renovate.json` sets `minimumReleaseAge` to `7 days`. Renovate must not treat a release as ready until that age has passed. `.github/dependabot.yml` only schedules weekly npm checks. A weekly pull request is not permission to merge a version on the day it was published. The same 7 day wait applies before merging those bumps.
|
|
6
|
+
|
|
7
|
+
Do not merge a version bump the day it is published, even when CI is green. A lockfile change that only repeats a version which is already 7 days old can merge with the bump it belongs to. It does not skip the wait, and it does not start a new one.
|
|
8
|
+
|
|
9
|
+
This policy does not point at a Dependabot or Renovate pull request. Open one only when the bot actually does.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Extension points
|
|
2
|
+
|
|
3
|
+
Two seams. Both are display-only. Neither one writes the session, the transcript, or the fold input.
|
|
4
|
+
|
|
5
|
+
## `dsh-better-display.text-transform`
|
|
6
|
+
|
|
7
|
+
Cordis service, provided by this plugin's client when it applies. Another client plugin lists the name in `inject` and registers a transformer. With no registrant, the reader does not call `read` and does not subscribe.
|
|
8
|
+
|
|
9
|
+
Kinds:
|
|
10
|
+
|
|
11
|
+
| Kind | What it paints |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `reasoning` | The thinking card (`ReadingReasoning`), before `useStreamingText`. |
|
|
14
|
+
| `body` | Assistant Markdown, and tool prose that uses the same reader (`ReadingMarkdown`). User bubbles are not passed through. |
|
|
15
|
+
|
|
16
|
+
Request:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface DisplayTextRequest {
|
|
20
|
+
text: string;
|
|
21
|
+
streaming: boolean;
|
|
22
|
+
blockKey: string;
|
|
23
|
+
kind: 'reasoning' | 'body';
|
|
24
|
+
source?: 'assistant' | 'user' | 'tool';
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`blockKey` identifies one mounted block. It changes when that block unmounts. It is not a session message id.
|
|
29
|
+
|
|
30
|
+
`read` runs on each paint and returns one of:
|
|
31
|
+
|
|
32
|
+
- `{ text, final }`
|
|
33
|
+
- a string, which is treated as `{ text, final: !streaming }`
|
|
34
|
+
- `undefined`, which keeps the source text
|
|
35
|
+
|
|
36
|
+
`final: false` keeps the live reveal open. That includes the moment after the model has stopped while a translation batch is still outstanding. Return `final: !streaming` to leave the reveal flag as it is today.
|
|
37
|
+
|
|
38
|
+
`subscribe(listener)` is how an async batch tells the reader to call `read` again. It may be invoked once per kind, so it must keep every listener. Skip it for a pure sync transform.
|
|
39
|
+
|
|
40
|
+
Revisions:
|
|
41
|
+
|
|
42
|
+
- `transformRevision` increments when the displayed string or `final` changes. It is not the reveal clock. Do not pass it to the word timeline, and do not use it as a React key. Doing either one settles or remounts the paragraph and the fade starts over.
|
|
43
|
+
- The reveal continues when the new display string starts with the previous display string. Already shown characters keep their birth time. Only the new tail fades in.
|
|
44
|
+
- A string that does not extend the previous one is a replacement. The reader shows that string in place. It does not clear the card and type from the first character.
|
|
45
|
+
|
|
46
|
+
Register during `apply`, before the reading view paints. A transformer added mid-turn is picked up on the next reader render, and that first switch mounts the reveal on the current string.
|
|
47
|
+
|
|
48
|
+
Copy answer and copy message read the original blocks (`originalAnswerText` for the answer). The native 对话 view is untouched, because this seam only runs inside the reader.
|
|
49
|
+
|
|
50
|
+
Fold still uses the original block list and the public turn boundary.
|
|
51
|
+
|
|
52
|
+
A foreign `assistant-step` renderer receives original text. That is a `conversation.chat.node` replacement whose priority is below the official renderer. Reader hands it the segment's original blocks and does not run this transform on that path. Reader markdown, including this transform, is only the fallback when the foreign renderer returns null. Reasoning stays on the reading card either way.
|
|
53
|
+
|
|
54
|
+
Import the types from `dsh-better-display/client`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import type { DisplayTextRequest, TextTransformRegistry } from 'dsh-better-display/client';
|
|
58
|
+
import { TEXT_TRANSFORM_SERVICE } from 'dsh-better-display/client';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`createTextTransformRegistry()` builds a private registry for a unit test. A plugin that wants the reader to paint its text must register on the service the reader provides, not on a registry it constructed itself.
|
|
62
|
+
|
|
63
|
+
### Minimal translation-shaped plugin
|
|
64
|
+
|
|
65
|
+
This one only uppercases. A real translator keeps the same `read` / `subscribe` shape, stores each batch on `blockKey`, and returns a string that extends the previous display string when it wants the fade to continue. It leaves `source === 'tool'` alone unless tool prose should change too.
|
|
66
|
+
|
|
67
|
+
`src/client/index.ts`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
71
|
+
|
|
72
|
+
export const name = 'upper-reasoning-client';
|
|
73
|
+
export const inject = ['dsh-better-display.text-transform'];
|
|
74
|
+
|
|
75
|
+
interface Request {
|
|
76
|
+
text: string;
|
|
77
|
+
streaming: boolean;
|
|
78
|
+
blockKey: string;
|
|
79
|
+
kind: 'reasoning' | 'body';
|
|
80
|
+
source?: 'assistant' | 'user' | 'tool';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function apply(ctx: Context): () => void {
|
|
84
|
+
const seam = ctx.get('dsh-better-display.text-transform') as {
|
|
85
|
+
register(kind: 'reasoning' | 'body' | readonly ('reasoning' | 'body')[], transformer: {
|
|
86
|
+
read(request: Request): { text: string; final: boolean } | undefined;
|
|
87
|
+
}): () => void;
|
|
88
|
+
} | undefined;
|
|
89
|
+
if (!seam) return () => {};
|
|
90
|
+
return seam.register(['reasoning', 'body'], {
|
|
91
|
+
read(request) {
|
|
92
|
+
if (request.source === 'tool') return undefined;
|
|
93
|
+
return { text: request.text.toUpperCase(), final: !request.streaming };
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`package.json`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "dsh-upper-reasoning",
|
|
104
|
+
"private": true,
|
|
105
|
+
"type": "module",
|
|
106
|
+
"dsh": { "client": { "platform": "web" }, "bundle": { "patch": "./cordis.patch.yml" } }
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`cordis.patch.yml`:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- insert:
|
|
114
|
+
- id: dsh-upper-reasoning
|
|
115
|
+
name: dsh-upper-reasoning
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Install this plugin and the local `dsh-better-display` checkout into the same web profile, reopen the Web Host, and reload. The published npm package does not have this seam until a release that includes it.
|
|
119
|
+
|
|
120
|
+
ASCII letters in the thinking card and the final answer render in upper case. Chinese is unchanged by Unicode case, so ask the model to reason in English if you need a visible difference. The fade still grows at the end of the string while the turn streams. Copy answer pastes the original casing. The 对话 tab still shows the original casing. A successful turn still folds the process and leaves the answer.
|
|
121
|
+
|
|
122
|
+
An async batch looks like this:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const cache = new Map<string, { text: string; final: boolean }>();
|
|
126
|
+
const listeners = new Set<() => void>();
|
|
127
|
+
|
|
128
|
+
seam.register(['reasoning', 'body'], {
|
|
129
|
+
read(request) {
|
|
130
|
+
if (request.source === 'tool') return undefined;
|
|
131
|
+
return cache.get(request.blockKey) ?? { text: request.text, final: !request.streaming };
|
|
132
|
+
},
|
|
133
|
+
subscribe(listener) {
|
|
134
|
+
listeners.add(listener);
|
|
135
|
+
return () => { listeners.delete(listener); };
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function publish(blockKey: string, text: string, final: boolean) {
|
|
140
|
+
cache.set(blockKey, { text, final });
|
|
141
|
+
for (const listener of listeners) listener();
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`publish` after each batch. Extend the previous cached string when the fade should continue.
|
|
146
|
+
|
|
147
|
+
## `dsh-better-display.block`
|
|
148
|
+
|
|
149
|
+
Chain slot, `scope: 'session'`. The reader calls `renderSlotChain` once per block. The first selector that returns non-null draws the block. All-null renders the reader fallback, which is the path that keeps reveal, follow-scroll, and fold.
|
|
150
|
+
|
|
151
|
+
Owner:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
interface ReaderBlockOwner {
|
|
155
|
+
block: AssistantBlock;
|
|
156
|
+
streaming: boolean;
|
|
157
|
+
source: 'assistant' | 'user' | 'tool';
|
|
158
|
+
presentation?: {
|
|
159
|
+
startedAt?: number;
|
|
160
|
+
interrupted?: boolean;
|
|
161
|
+
liveText?: boolean;
|
|
162
|
+
};
|
|
163
|
+
holdFormatting?: boolean;
|
|
164
|
+
liveText?: boolean;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`presentation`, `holdFormatting`, and `liveText` are what the reader already computed. They used to stop at the fallback. A plugin that replaces the chain can read them. A plugin that only reads `block`, `streaming`, and `source` does not need to change.
|
|
169
|
+
|
|
170
|
+
Selectors stay pure. Logging the owner from a selector is a temporary check, not the contract.
|
|
171
|
+
|
|
172
|
+
Replacing the chain drops the reader fallback for that block, including the text transform above. A translation plugin that wants the reader's motion should use the text-transform service and leave this chain alone.
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
## 代码归属
|
|
12
12
|
|
|
13
|
-
| 模块
|
|
14
|
-
|
|
|
15
|
-
| `official-slots.tsx`
|
|
16
|
-
| `OfficialContent.tsx`
|
|
17
|
-
| `Reader.tsx`、`ToolActivity.tsx` | 原有阅读布局、过程组织、摘要和交互。工具详情里的控件采用官方形式。
|
|
18
|
-
| `platform-media.ts`
|
|
19
|
-
| `compat/harness-rc2.json`
|
|
13
|
+
| 模块 | 负责什么 |
|
|
14
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `official-slots.tsx` | 用公开注册表 API 将官方注册信息映射到 Reader 的独立插槽;保留注入、locale、store、selector 和子插槽。监听贡献增删,逐条挂载和释放。 |
|
|
16
|
+
| `OfficialContent.tsx` | 将回答身份、工具、节点和产物上下文传给官方控件。这里只组合,不处理业务请求。 |
|
|
17
|
+
| `Reader.tsx`、`ToolActivity.tsx` | 原有阅读布局、过程组织、摘要和交互。工具详情里的控件采用官方形式。 |
|
|
18
|
+
| `platform-media.ts` | RC2 的本地媒体 URL 约定;读取授权仍由 Host 文件 API 负责。 |
|
|
19
|
+
| `compat/harness-rc2.json` | 已验收的官方接口、被自有展示覆盖的实现和注册清单基线。 |
|
|
20
20
|
|
|
21
21
|
## 为什么有插槽映射
|
|
22
22
|
|
|
@@ -28,15 +28,17 @@ RC2 的一个子插槽只能有一个声明者,只有声明它的组件能获
|
|
|
28
28
|
|
|
29
29
|
## 这版覆盖的能力
|
|
30
30
|
|
|
31
|
-
| 能力
|
|
32
|
-
|
|
|
33
|
-
| 回答操作、反馈
|
|
34
|
-
| 工具详情
|
|
35
|
-
| 显式 `present` 产物
|
|
36
|
-
| 已修改文件快捷操作
|
|
37
|
-
| 文件行号和正文文件链接
|
|
38
|
-
| 本地 Markdown 图片
|
|
39
|
-
|
|
|
31
|
+
| 能力 | 处理方式 |
|
|
32
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| 回答操作、反馈 | 官方 `conversation.chat.assistant-actions`,使用持久化 messageId;中断的临时片段不伪造身份。 |
|
|
34
|
+
| 工具详情 | 官方 `tool.call.toolview`,包括 memo、注入状态、翻译和递归子插槽;未注册的工具保留原有结果预览。 |
|
|
35
|
+
| 显式 `present` 产物 | 官方 turnTail 中的文件卡片和菜单,保留官方状态和打开操作。 |
|
|
36
|
+
| 已修改文件快捷操作 | 保持原 Reader 的复制、定位和打开模式;仅从 Reader 的官方 tail 展示参数中排除重复的 produced chips,不改变官方源数据。未知匹配结构原样呈现。 |
|
|
37
|
+
| 文件行号和正文文件链接 | 保留工具传来的行号;正文优先采用官方 file-mention 服务,现有路径匹配作为补充。 |
|
|
38
|
+
| 本地 Markdown 图片 | 和 RC2 相同的同源文件 API,加载失败保留替代文本,危险协议不直接渲染。 |
|
|
39
|
+
| 用户与 steering 消息 | 镜像 `conversation.chat.node` 的 keyed renderer,保留 `sessionId`、turn hook 和 Reader 跳转属性;未注册对应 key 时通过插槽 `fallback` 呈现 Reader 气泡。renderer 主动返回 `null` 时不补画气泡。 |
|
|
40
|
+
| 回答正文上的第三方替换渲染器 | 只镜像 priority 低于官方 `assistant-step`(priority 0)的注册。正文段交给该渲染器,`groupPart` 为 `response`,blocks 只有这一段。思考、折叠、动效和回答外框仍由 Reader 绘制。没有这类注册时正文仍走 Reader Markdown。 |
|
|
41
|
+
| 命令、新节点类型 | 接入官方节点渲染及其子插槽;无可用渲染器时保留记录并提供“在对话中查看”。 |
|
|
40
42
|
|
|
41
43
|
阅读布局、折叠、逐字动效、阅读速度、默认设置保持现版。移除了未被使用的全局 `body.dataset.readerFolding` 写入。
|
|
42
44
|
|
|
@@ -60,10 +62,49 @@ npm run test:auto-fold
|
|
|
60
62
|
|
|
61
63
|
## rewind 座位兼容
|
|
62
64
|
|
|
63
|
-
Reader 的 user 与 steering 行会同时发布 `data-chat-anchor-key`、`data-chat-flow-key`、`data-chat-node-key` 和 `data-chat-flow-kind`,这些属性是 rewind 用来定位 durable 回退操作的官方 chat 座位契约。排队中的 inbox steering 也会渲染为 `data-pending-steering` 行,并按非文本内容块数量发布附件计数;本地 pending submission 与已落 durable 节点通过 source.rpcId
|
|
65
|
+
Reader 的 user 与 steering 行会同时发布 `data-chat-anchor-key`、`data-chat-flow-key`、`data-chat-node-key` 和 `data-chat-flow-kind`,这些属性是 rewind 用来定位 durable 回退操作的官方 chat 座位契约。排队中的 inbox steering 也会渲染为 `data-pending-steering` 行,并按非文本内容块数量发布附件计数;本地 pending submission 与已落 durable 节点通过 source.rpcId 去重。
|
|
66
|
+
|
|
67
|
+
座位属性只解决「回退按钮出现在哪一行」,不解决「回退之后屏幕上还剩什么」。`dsh-rewind-plugin` 是 DOM 层实现:它从命令节点算出被遮蔽的 anchor seq,再对每个带 `data-chat-anchor-key` 的元素设 `display:none`。Reader 自渲染转录(turn 区块、answer、工具行),除 user/steering 外不发这个属性,所以 DOM 隐藏只够遮住气泡,被撤回的回答、工具行和 `/rewind` 命令行本身仍留在屏幕上——回退看起来只是被记录成一条命令,而不是把对话滚回发送该消息之前。
|
|
68
|
+
|
|
69
|
+
因此 Reader 自己执行同一份契约,见 `src/client/rewind-cut.ts`:从命令名属于 `{'rewind-plugin', 'rewind'}`(插件注册的命令 id 是前者,会话日志记录的是后者)且 `outcome.kind === 'success'` 的命令节点取 `outcome.sourceEventSeq`(替换型标记的日志 seq)与 `args` 里的 `@<seq>` 目标,合成闭区间 `[target, marker]`,落在区间内的 chat 行(含命令行自身)一律不渲染。三个要点:
|
|
70
|
+
|
|
71
|
+
- 回退的替换型标记 `user/message` 不是 append 事件,因此**不会**成为 chat 节点;命令节点的 `outcome.sourceEventSeq` 是客户端唯一的遮蔽信号。不要解析 `outcome.text`。
|
|
72
|
+
- 区间按「真正重叠/相接才合并」处理,不做 `[min,max]` 塌缩,否则两次回退之间的新内容会被一并吞掉。目标消息自身也在区间内:回退恢复的是发送该消息**之前**的状态。
|
|
73
|
+
- 行位置不是整数:官方 ChatView 会把合成行锚定在真实 seq 的分数偏移上(`CHAT_SYNTHETIC_SEQ_OFFSETS`:`turn-process` 在 `controlAnchorSeq - 0.1`、`turn-tail` 在 `seq + 0.1`、被中断的回答在 `seq - 0.9`)。判定用这个真实位置(`anchorSeqOf` 接受有限非负数),区间是闭区间 `[target, marker]`,**不要**放到下一个整数。下一回合的 `turn-process` 就锚在 `startSeq - 0.1`,落在 `[marker, marker + 1)` 里。
|
|
74
|
+
- `preview` 和 `__candidates` 不产生区间,哪怕 outcome 带了 marker。目标晚于 marker 的命令也不产生区间。失败或尚未成功的命令不产生区间,所以流式输出、重连、单纯重绘都不会被当成回退。
|
|
75
|
+
- 合成行故意锚在所属 turn 的事件区间之外。只有该 turn 的内容行全部落入区间(或已被宿主标成 hidden)时,才撤掉它的 `turn-process` 和 `turn-tail`。目标之前、同一 turn 里的用户消息和回答留着,过程摘要也留着。
|
|
76
|
+
|
|
77
|
+
最后一条也是订阅面:回退只是就地更新既有 command 节点,`snapshot.order` 与 `snapshot.nodes` 的引用都不变,针对其一的 selector 永不触发。Reader 因此订阅 `visibleChatSignature`(可见 key 拼成的字符串),让 chat hook 的 `Object.is` 比较能够发现变化。被撤回的行随即进入 `RetiringTurns`(`src/client/motion.tsx`)的退场通道:它按原索引保留该 turn,用 220ms 的 height/opacity 收起动画播完再卸载,行下方的内容是滑上去而不是瞬移。
|
|
78
|
+
|
|
79
|
+
纯图片目标现在也能撤回:这条路径只依赖命令节点的 seq,不再依赖插件对消息文本的匹配。收起动画在减少动态效果或页面隐藏时直接落到回退后的内容。`tests/rewind-cut.test.ts` 固定了上述规则、Reader 的接线和「流式 / 重绘 / 重连不误伤」;`tests/reader-official-users.test.ts` 仍覆盖座位与 easyrewrite 集成。
|
|
80
|
+
|
|
81
|
+
`dsh-easyrewrite@2.6.0` 在 `conversation.chat.node` 注册 `key: 'user', priority: -1` 的 `UserBubbleView`;撤回按钮是该组件内的 `data-dsh-easyrewrite="recall-key"`。chat 座位属性本身不会创建这个按钮,Reader 还需镜像此注册。它的快捷键使用 `data-chat-flow-kind="user"`,待定撤回的后续行隐藏使用 `data-chat-flow` 和 `data-chat-anchor-key`。
|
|
82
|
+
|
|
83
|
+
`tests/reader-official-users.test.ts` 在固定的 headless Chromium 中运行真实 Reader 与官方 registry/renderer,覆盖 user/steering 镜像、唯一气泡与锚点、session/turn 注入、替换、卸载、空 key 回退和 renderer 主动隐藏。可将已下载的 npm 包传入,额外验证 easyrewrite 的真实气泡、撤回按钮和点击后的确认胶囊:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
DSH_EASYREWRITE_CLIENT=/path/to/package/lib/client.js \
|
|
87
|
+
node --import tsx/esm --test tests/reader-official-users.test.ts
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
该 fixture 不执行撤回提交。完整验收仍需在独立测试会话中打开 Reader,确认每条 user 消息只有一个气泡与撤回按钮;点击撤回并确认后检查输入框回填、取消恢复、发送后的真实历史回退,再切换到原对话检查一致性。另需用 steering 消息和历史跳转检查补充消息与锚点。
|
|
91
|
+
|
|
92
|
+
## 第三方 assistant-step 替换渲染器
|
|
93
|
+
|
|
94
|
+
Reader 自己画 `assistant-step`。思考卡片、过程折叠、逐字动效和回答栏留在阅读页,所以官方 priority 0 的 `AssistantNodeView` 不会再挂进阅读座位。挂进去的话,正文会被官方组件整段换掉。
|
|
95
|
+
|
|
96
|
+
Keyed 槽位里 priority 更小的注册胜出。priority 小于 0 的 `assistant-step` 注册就是在替换官方渲染器。`dsh-raw-html-v2@0.8.0` 用 priority `-10` 注册 `VcpSlotGuard`,遍历 `node.data.blocks`,把 text 块切成 Markdown 和 `<div id="vcp-root">` 卡片。阅读页只镜像这种替换,座位仍是 `dsh-better-display.official.nodes/conversation.chat.node`。`tool-call`、`turn-tail`、`turn-process` 继续不镜像。
|
|
97
|
+
|
|
98
|
+
每个正文段单独交给胜出的替换渲染器。传过去的 node 保留原来的身份、`status` 和 `step`,`data.blocks` 只有这一段的正文块,并带 `groupPart: 'response'`,和宿主把思考与回答拆开的方式一致。思考段仍走阅读页的 `ReasoningCard`。折叠、吸顶、复制和回答外框也不交给替换渲染器。
|
|
99
|
+
|
|
100
|
+
没有这类注册时,`renderSlot` 的 fallback 仍是阅读页自己的 Markdown。替换渲染器返回 `null` 时不补画第二份正文。它卸下之后,下一次 reconcile 回到 Markdown。
|
|
101
|
+
|
|
102
|
+
这里不解析某一种卡片协议,也不改会话数据。原始 HTML、代码围栏和流式切段仍由那个插件自己的渲染器处理。阅读页保证的是:这个渲染器能在回答正文里运行。启用替换渲染器时,正文不再走阅读页的逐字动效;思考和折叠动效保持不变。替换渲染器若自己再画一份「已中断」,会和阅读页的「已停止」叠在一起,这是该渲染器的展示,不另做裁剪。
|
|
103
|
+
|
|
104
|
+
`tests/official-slots.test.ts` 锁定「priority 低于 0 才镜像,官方 priority 与 tool-call 不镜像」。`tests/fixtures/reader-official-users.tsx` 在真实 Reader 里渲染一条带思考和 `<div id="vcp-root">` 的 assistant-step:替换渲染器收到 `groupPart: 'response'` 和正文块,思考文本留在 `data-reader-text-kind="reasoning"`,回答栏里不再出现 Markdown 代码块。
|
|
64
105
|
|
|
65
106
|
## 后续仍需完成
|
|
66
107
|
|
|
67
|
-
Markdown 定制仍保留逐字动效和 MCP 展示钩子,官方公共组件还没有对应的注入接口。上下文行、部分过程展示和工具摘要也仍有自有实现。本分支先建立官方能力的完整运行路径,后续按视觉基线逐模块收敛这些副本,并拆分 Reader
|
|
108
|
+
Markdown 定制仍保留逐字动效和 MCP 展示钩子,官方公共组件还没有对应的注入接口。上下文行、部分过程展示和工具摘要也仍有自有实现。本分支先建立官方能力的完整运行路径,后续按视觉基线逐模块收敛这些副本,并拆分 Reader 的阅读策略与动画执行。不能据此承诺所有未来版本零维护。上面的 assistant-step 替换渲染器是已承诺的共存方式;除此之外不承诺任意第三方插件的视觉兼容。
|
|
68
109
|
|
|
69
110
|
已有客户端通过 HMR 更新时,无需新增 Host 或改写会话数据;应先核对实际安装来源、构建产物和同页行为。正式版本保留 Git 标签及发布包,便于按版本安装和回退。
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: dsh-better-display
|
|
4
|
+
version: 0.3.9
|
|
5
|
+
description: >
|
|
6
|
+
Exact routes registered by apply() in src/dsh-better-display.ts.
|
|
7
|
+
POST /better-display/reveal is createRevealHandler in src/reveal.ts.
|
|
8
|
+
GET /better-display/skill-status returns toPublicSkillStatus.
|
|
9
|
+
The host webServer mounts these paths. The handlers do not read an
|
|
10
|
+
Authorization header or any other credential.
|
|
11
|
+
servers:
|
|
12
|
+
- url: /
|
|
13
|
+
description: DeepSeek Harness host after this plugin has loaded.
|
|
14
|
+
paths:
|
|
15
|
+
/better-display/reveal:
|
|
16
|
+
post:
|
|
17
|
+
operationId: revealInFileManager
|
|
18
|
+
summary: Reveal a workspace file in the OS file manager
|
|
19
|
+
description: >
|
|
20
|
+
Any method other than POST returns 405 with an empty body and no
|
|
21
|
+
Content-Type. The JSON body is read to completion, then parsed.
|
|
22
|
+
An absolute path is used as-is and does not call the workspace root
|
|
23
|
+
lookup. A relative path must resolve to exactly one existing file
|
|
24
|
+
under the session cwd or the matching workspace registry roots.
|
|
25
|
+
Drive-relative and root-relative Windows paths are rejected.
|
|
26
|
+
On success the host spawns open -R, explorer.exe /select, or xdg-open.
|
|
27
|
+
requestBody:
|
|
28
|
+
required: true
|
|
29
|
+
content:
|
|
30
|
+
application/json:
|
|
31
|
+
schema:
|
|
32
|
+
type: object
|
|
33
|
+
required: [path]
|
|
34
|
+
properties:
|
|
35
|
+
path:
|
|
36
|
+
type: string
|
|
37
|
+
description: Trimmed. Empty or whitespace-only is 400.
|
|
38
|
+
sessionId:
|
|
39
|
+
type: string
|
|
40
|
+
description: Optional. Used only when path is not absolute.
|
|
41
|
+
examples:
|
|
42
|
+
relative:
|
|
43
|
+
value:
|
|
44
|
+
path: docs/report.md
|
|
45
|
+
sessionId: target
|
|
46
|
+
responses:
|
|
47
|
+
'200':
|
|
48
|
+
description: The file manager was spawned. Body is JSON.
|
|
49
|
+
content:
|
|
50
|
+
application/json:
|
|
51
|
+
schema:
|
|
52
|
+
type: object
|
|
53
|
+
required: [ok]
|
|
54
|
+
additionalProperties: false
|
|
55
|
+
properties:
|
|
56
|
+
ok:
|
|
57
|
+
type: boolean
|
|
58
|
+
enum: [true]
|
|
59
|
+
'400':
|
|
60
|
+
description: >
|
|
61
|
+
JSON.parse failed, path was empty, the path was not
|
|
62
|
+
workspace-relative, no root contained the file, or more than one
|
|
63
|
+
root contained it. error is String(err), so it includes the
|
|
64
|
+
Error prefix.
|
|
65
|
+
content:
|
|
66
|
+
application/json:
|
|
67
|
+
schema:
|
|
68
|
+
$ref: '#/components/schemas/ErrorBody'
|
|
69
|
+
'405':
|
|
70
|
+
description: req.method was not POST. Empty body. Content-Type is not set.
|
|
71
|
+
/better-display/skill-status:
|
|
72
|
+
get:
|
|
73
|
+
operationId: getGenerativeMcpappsSkillStatus
|
|
74
|
+
summary: Public generative-mcpapps install status
|
|
75
|
+
description: >
|
|
76
|
+
Any method other than GET returns 405 with an empty body and no
|
|
77
|
+
Content-Type. The optional cwd query selects which project roots are
|
|
78
|
+
scanned. The JSON body is toPublicSkillStatus, which always replaces
|
|
79
|
+
roots with the two conventional relative paths and drops packPath
|
|
80
|
+
and any host home absolute path.
|
|
81
|
+
parameters:
|
|
82
|
+
- in: query
|
|
83
|
+
name: cwd
|
|
84
|
+
required: false
|
|
85
|
+
schema:
|
|
86
|
+
type: string
|
|
87
|
+
description: Project directory passed to scanGenerativeMcpappsStatus. Omitted when the settings probe has no session cwd.
|
|
88
|
+
responses:
|
|
89
|
+
'200':
|
|
90
|
+
description: Public skill status. This body has no ok field.
|
|
91
|
+
content:
|
|
92
|
+
application/json:
|
|
93
|
+
schema:
|
|
94
|
+
$ref: '#/components/schemas/PublicSkillStatus'
|
|
95
|
+
'405':
|
|
96
|
+
description: req.method was not GET. Empty body. Content-Type is not set.
|
|
97
|
+
'500':
|
|
98
|
+
description: scanGenerativeMcpappsStatus threw. error is String(err).
|
|
99
|
+
content:
|
|
100
|
+
application/json:
|
|
101
|
+
schema:
|
|
102
|
+
$ref: '#/components/schemas/ErrorBody'
|
|
103
|
+
components:
|
|
104
|
+
schemas:
|
|
105
|
+
ErrorBody:
|
|
106
|
+
type: object
|
|
107
|
+
required: [ok, error]
|
|
108
|
+
additionalProperties: false
|
|
109
|
+
properties:
|
|
110
|
+
ok:
|
|
111
|
+
type: boolean
|
|
112
|
+
enum: [false]
|
|
113
|
+
error:
|
|
114
|
+
type: string
|
|
115
|
+
PublicSkillRoot:
|
|
116
|
+
type: object
|
|
117
|
+
required: [source, path]
|
|
118
|
+
additionalProperties: false
|
|
119
|
+
properties:
|
|
120
|
+
source:
|
|
121
|
+
type: string
|
|
122
|
+
enum: [conventional]
|
|
123
|
+
path:
|
|
124
|
+
type: string
|
|
125
|
+
enum: ['.dsh/skills', '.agents/skills']
|
|
126
|
+
PublicSkillStatus:
|
|
127
|
+
type: object
|
|
128
|
+
required: [name, installed, via, roots]
|
|
129
|
+
additionalProperties: false
|
|
130
|
+
properties:
|
|
131
|
+
name:
|
|
132
|
+
type: string
|
|
133
|
+
enum: [generative-mcpapps]
|
|
134
|
+
installed:
|
|
135
|
+
type: boolean
|
|
136
|
+
via:
|
|
137
|
+
type: string
|
|
138
|
+
nullable: true
|
|
139
|
+
enum: ['skills.list', 'skill-root', null]
|
|
140
|
+
description: skills.list when the host skills registry lists the skill, skill-root when a scanned root has it, otherwise null.
|
|
141
|
+
roots:
|
|
142
|
+
type: array
|
|
143
|
+
minItems: 2
|
|
144
|
+
maxItems: 2
|
|
145
|
+
items:
|
|
146
|
+
$ref: '#/components/schemas/PublicSkillRoot'
|
|
147
|
+
description: Always .dsh/skills then .agents/skills, source conventional. Not the scanned absolute roots.
|