dsh-edit-turn 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,9 @@ DSH 的会话日志是 append-only 的事件流:说错的提示词、问偏的
25
25
  - **默认零上下文污染** —— 替换事件的载体是一个**空的 `system/message`**。官方格式文档里空的后置 system 节点是「dormant,不投影成任何消息」,所以回退后模型看到的上下文,和「对话真的停在那一点」完全一致,不会多出任何标记文本。
26
26
  - **轮边界安全** —— 遮蔽窗口右端固定为日志最后一个 surface 节点,左端固定为目标消息节点,因此助手消息(内含 tool_use)与它产生的 tool/result 永远一起走,**不可能留下悬空的调用/结果对**。
27
27
  - **重跑走官方准入路径** —— `ctx.sessionController.prompt()` 是唯一的口径:它会自己 resume 冷会话,并恰好开一个新轮次。
28
+ - **一次点击即执行** —— 保存后不再有二次确认。编辑器本身已经是用户主动打开的动作,面板里也写明了保存会丢弃哪些内容;想恢复两步确认可在 profile 里一行开启(`confirm: true`)。
28
29
  - **中英双语 UI**,跟随 DSH 当前语言。
30
+ - **皮肤友好** —— 编辑器面板自带不透明表面(`--dshet-panel`)而不是借用主题的表面色变量。皮肤的本意就是让表面半透明、把插画透出来,而它只会给**自己的**元素补可读背景,插件类名不在其中;借用皮肤变量的面板会变成全透明,文字直接压在插画上。暗色分支走官方属性 `body[data-ds-dark-theme]`(与 `dsh-client-ui-theme` 及多个官方 UI 包一致),并用 `backdrop-filter` 与皮肤融合。
29
31
  - **可配置载体**:万一某个 DSH 版本对空 system 节点处理不同,一行配置即可切回短标记载体。
30
32
  - **宿主路由只限本机回环**,并校验 `Host` 与 `Origin`。
31
33
 
@@ -46,9 +48,8 @@ dsh plugin --profile web add /path/to/dsh-edit-turn
46
48
  ### 使用
47
49
 
48
50
  1. 把鼠标移到你想改的那条**用户消息**上,点右侧的编辑图标(铅笔)。
49
- 2. 消息下方展开编辑器,原文已预填。改完点「保存并重跑」。
50
- 3. 出现确认步骤(默认开启):确认后会从模型上下文中移除这条消息之后的一切,并重新跑这一轮。
51
- 4. 编辑器与该轮之后的转录行一起消失,新提示词与新回复出现在下方。
51
+ 2. 消息下方展开编辑器,原文已预填。改完点「保存并重跑」——**一次点击即执行**:这条消息之后的一切从模型上下文中移除,并立刻重新跑这一轮。
52
+ 3. 编辑器与该轮之后的转录行一起消失,新提示词与新回复出现在下方。
52
53
 
53
54
  编辑器里如果提示「这条消息包含图片或文件附件」,说明改写只保留文字,附件会被丢弃。
54
55
 
@@ -68,7 +69,7 @@ dsh plugin --profile web add /path/to/dsh-edit-turn
68
69
  |---|---|---|
69
70
  | `carrier` | `system/message` | 替换事件的载体类型。`system/message` = 空节点,不投影成模型消息;`user/message` = 短标记文本,会作为一条用户消息进入上下文(等价插件在生产中用的是这种形状,作为兜底)。 |
70
71
  | `markerText` | 一段说明文字 | `carrier: user/message` 时的载体文本。 |
71
- | `confirm` | `true` | 客户端保存后是否先要求一次确认。 |
72
+ | `confirm` | `false` | 客户端保存后是否先要求一次确认。默认**关闭**(一次点击即执行:编辑器本身已是用户主动打开的动作,面板里也写明了保存的后果);设为 `true` 可恢复两步确认。 |
72
73
 
73
74
  ### 工作原理
74
75
 
@@ -110,7 +111,7 @@ await ctx.sessionController.prompt({ requestId, sessionId, mode: 'queue', conten
110
111
  |---|---|---|
111
112
  | 官方 append 契约(真实校验器,进程内) | `npm run verify:contract` | 46 项通过:替换事件被接受、派生历史真的收缩、日志 append-only、工具结果与调用同进同退、空 system 载体不产生模型消息、**连续两次回退都被接受** |
112
113
  | 纯逻辑 + 宿主集成 + 客户端 DOM 行为(真 HTTP、真校验器、桩服务、DOM 桩) | `npm test` | 63 项通过 |
113
- | 客户端半部静态检查(注册、i18n 完整性、样式、版本三处同步、线协议) | `npm run verify:client` | 全部通过 |
114
+ | 客户端半部静态检查(注册、i18n 完整性、样式、**皮肤可读性**、版本三处同步、线协议) | `npm run verify:client` | 全部通过 |
114
115
  | 实机前端产物校验(运行中的 DSH 是否在下发当前代码) | `npm run verify:live -- --token-file ~/path/to/dsh.log` | 全部通过 |
115
116
  | 真实 profile 安装 / 补丁合成 / 启动 / 工具契约 / 路由守卫 | `npm run verify:profile` | 全部通过 |
116
117
 
@@ -181,7 +182,9 @@ This plugin adds it:
181
182
  - **Zero context pollution by default.** The replacement carrier is an **empty `system/message`**. The official format documents empty later system nodes as dormant, projecting to no message, so the context after an edit is exactly what it would be had the conversation really stopped there - no marker text is added.
182
183
  - **Turn-boundary safe.** The shadow window always ends at the last surface node and always opens at the addressed message, so an assistant message (which carries its own tool_use blocks) and the tool/result it produced are shadowed together. A dangling call/result pair is impossible.
183
184
  - **Official re-run.** `ctx.sessionController.prompt()` is the only prompt admission path; it resumes a cold Session itself and opens exactly one new turn.
185
+ - **One click applies** - no second confirmation. The editor is already an explicit action the user opened, and the panel states what saving discards; a profile can restore the two-step flow with `confirm: true`.
184
186
  - **Bilingual UI** that follows the current DSH locale.
187
+ - **Skin-friendly** - the editor paints its own opaque surface (`--dshet-panel`) instead of borrowing the theme's surface colours. A skin exists to make surfaces translucent so its artwork shows through, and it only compensates for *its own* elements; a plugin's class names are not on that list, so a panel that borrows those variables can end up fully transparent with text sitting straight on the art. The dark branch uses the official `body[data-ds-dark-theme]` hook (the same one `dsh-client-ui-theme` and several official UI packages use) and blends in with `backdrop-filter`.
185
188
  - **Configurable carrier**: if some DSH release treats empty system nodes differently, one config line restores the marker-text carrier.
186
189
  - **Loopback-only host routes** with `Host` and `Origin` validation.
187
190
 
@@ -202,9 +205,8 @@ Restart the DSH process that serves that profile to pick it up.
202
205
  ### Usage
203
206
 
204
207
  1. Hover the **user message** you want to change and click the pencil action.
205
- 2. An editor opens below it with the original text pre-filled. Click "Save and re-run".
206
- 3. A confirmation step appears (on by default). Confirming removes everything after this message from the model context and re-runs the turn.
207
- 4. The editor disappears together with the discarded rows; the new prompt and its reply appear below.
208
+ 2. An editor opens below it with the original text pre-filled. Click "Save and re-run" - **one click applies**: everything after this message leaves the model context and the turn runs again immediately.
209
+ 3. The editor disappears together with the discarded rows; the new prompt and its reply appear below.
208
210
 
209
211
  If the editor warns that the message carries attachments, the rewrite keeps the
210
212
  text only and drops them.
@@ -223,7 +225,7 @@ text only and drops them.
223
225
  |---|---|---|
224
226
  | `carrier` | `system/message` | Event type carrying the replacement. `system/message` is an empty dormant node that projects to no model message. `user/message` is a short marker that does enter the context; an equivalent plugin runs that shape in production, so it is the fallback. |
225
227
  | `markerText` | an explanatory line | Carrier text when `carrier: user/message`. |
226
- | `confirm` | `true` | Whether the client asks for confirmation before applying. |
228
+ | `confirm` | `false` | Whether the client asks for a second confirmation before applying. Off by default - one click applies, because the editor is already an explicit action the user opened and the panel states what saving discards. Set it to `true` to restore the two-step flow. |
227
229
 
228
230
  ### How it works
229
231
 
@@ -276,7 +278,7 @@ Verified against DSH `0.1.6-alpha.2`, entirely **without model calls**:
276
278
  |---|---|---|
277
279
  | Official append contract against the real validator, in process | `npm run verify:contract` | 46 checks pass: the replacement is accepted, the derived history really shrinks, the log stays append-only, a tool result leaves with its call, the empty system carrier adds no model message, **two consecutive rollbacks are both accepted** |
278
280
  | Pure logic, host integration and browser-half DOM behaviour (real HTTP, real validator, stubbed services, DOM stub) | `npm test` | 63 tests pass |
279
- | Browser-half static checks (registration, i18n completeness, styles, three-way version sync, wire contract) | `npm run verify:client` | all pass |
281
+ | Browser-half static checks (registration, i18n completeness, styles, skin legibility, three-way version sync, wire contract) | `npm run verify:client` | all pass |
280
282
  | Live client artifact (is the running DSH serving the current code?) | `npm run verify:live -- --token-file ~/path/to/dsh.log` | all pass |
281
283
  | Real profile: install, patch composition, boot, tool contract, route guards | `npm run verify:profile` | all pass |
282
284
 
package/lib/client.js CHANGED
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
30
30
  const ROUTE_PREFIX = '/dsh-edit-turn'
31
31
 
32
32
  /** Keep in sync with package.json and lib/index.js. */
33
- const PLUGIN_VERSION = '0.1.0'
33
+ const PLUGIN_VERSION = '0.1.1'
34
34
 
35
35
  // --- copy -----------------------------------------------------------------
36
36
 
@@ -100,10 +100,24 @@ window.__ModuleLoader__.load({
100
100
 
101
101
  // --- style ----------------------------------------------------------------
102
102
 
103
+ // A skin is free to make the theme's surface colours translucent - that is
104
+ // the point of one: the artwork shows through, and the skin compensates by
105
+ // giving *its own* elements a readable background and a text halo. A plugin's
106
+ // classes are not in that list, so anything painted with --dsw-alias-bg-*
107
+ // can end up with no background at all and become unreadable over the art.
108
+ // (Seen in practice: a skin sets --dsw-alias-bg-base to a fully transparent
109
+ // colour at scrim 0, and --dsw-alias-bg-layer-1 to a ~50% tint.)
110
+ //
111
+ // So this panel paints its own opaque surface and switches on the official
112
+ // dark-theme hook `body[data-ds-dark-theme]`, which the theme and the official
113
+ // UI packages use. Theme variables are still used for *colours* (hue,
114
+ // semantic tones) - those survive a skin; it is alpha that does not.
103
115
  const CSS = [
104
- '.dshet-action{width:28px;height:28px;padding:6px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dsw-alias-label-tertiary,#8a8f98);cursor:pointer;transition:background-color .12s,color .12s}',
105
- '.dshet-action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,inherit)}',
106
- '.dshet-action:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
116
+ ':root{--dshet-panel:rgba(255,255,255,.96);--dshet-field:rgba(244,247,252,.98);--dshet-chip:rgba(255,255,255,.82);--dshet-line:rgba(16,24,40,.16);--dshet-ink:#0f1524;--dshet-ink-dim:#4b5872;--dshet-hover:rgba(16,24,40,.07);--dshet-shadow:0 12px 32px rgba(9,18,40,.22);--dshet-accent:#4d6bfe;--dshet-on-accent:#fff}',
117
+ 'body[data-ds-dark-theme]{--dshet-panel:rgba(22,28,44,.96);--dshet-field:rgba(14,19,33,.98);--dshet-chip:rgba(22,28,44,.82);--dshet-line:rgba(255,255,255,.18);--dshet-ink:#eef2f8;--dshet-ink-dim:#a6b1c6;--dshet-hover:rgba(255,255,255,.11);--dshet-shadow:0 12px 32px rgba(0,0,0,.5)}',
118
+ '.dshet-action{width:28px;height:28px;padding:6px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:28px;background:transparent;color:var(--dshet-ink-dim);cursor:pointer;transition:background-color .12s,color .12s}',
119
+ '.dshet-action:hover:not(:disabled){background:var(--dshet-chip);box-shadow:inset 0 0 0 1px var(--dshet-line);color:var(--dshet-ink)}',
120
+ '.dshet-action:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
107
121
  '.dshet-action:disabled{cursor:default;opacity:.4}',
108
122
  '.dshet-action svg{width:15px;height:15px}',
109
123
  '.dshet-action-host{display:inline-flex;align-items:center;justify-content:center}',
@@ -112,23 +126,23 @@ window.__ModuleLoader__.load({
112
126
  '.dshet-row:hover .dshet-floating,.dshet-floating:focus-within{opacity:1}',
113
127
  '.dshet-collapsing{overflow:hidden;transition:height .2s ease,opacity .14s ease,margin .2s ease,padding .2s ease}',
114
128
  '[data-dshet-hidden="1"]{display:none!important}',
115
- '.dshet-editor{margin:8px 0 4px;padding:12px;border:1px solid var(--dsw-alias-border-secondary,rgba(127,127,127,.28));border-radius:12px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 2px 10px rgba(0,0,0,.06)}',
116
- '.dshet-editor-title{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}',
117
- '.dshet-editor textarea{width:100%;min-height:84px;max-height:46vh;resize:vertical;box-sizing:border-box;padding:10px 12px;border:1px solid var(--dsw-alias-border-secondary,rgba(127,127,127,.32));border-radius:8px;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:14px;line-height:22px}',
118
- '.dshet-editor textarea:focus{outline:none;border-color:var(--dsw-alias-button-primary-fill,#4d6bfe)}',
119
- '.dshet-editor textarea:disabled{opacity:.6}',
120
- '.dshet-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px;line-height:19px}',
121
- '.dshet-warn{margin:8px 0 0;color:var(--dsw-alias-state-warning-primary,#c47b12);font-size:12px;line-height:19px}',
122
- '.dshet-error{margin:8px 0 0;color:var(--dsw-alias-state-error-primary,#d54941);font-size:12px;line-height:19px}',
129
+ '.dshet-editor{margin:8px 0 4px;padding:12px;border:1px solid var(--dshet-line);border-radius:12px;background:var(--dshet-panel);backdrop-filter:blur(18px) saturate(1.2);box-shadow:var(--dshet-shadow);color:var(--dshet-ink)}',
130
+ '.dshet-editor-title{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px;font-size:13px;font-weight:600;color:var(--dshet-ink)}',
131
+ '.dshet-editor textarea{width:100%;min-height:84px;max-height:46vh;resize:vertical;box-sizing:border-box;padding:10px 12px;border:1px solid var(--dshet-line);border-radius:8px;background:var(--dshet-field);color:var(--dshet-ink);font:inherit;font-size:14px;line-height:22px}',
132
+ '.dshet-editor textarea::placeholder{color:var(--dshet-ink-dim)}',
133
+ '.dshet-editor textarea:focus{outline:none;border-color:var(--dsw-alias-button-primary-fill,var(--dshet-accent))}',
134
+ '.dshet-editor textarea:disabled{opacity:.65}',
135
+ '.dshet-note{margin:8px 0 0;color:var(--dshet-ink-dim);font-size:12px;line-height:19px}',
136
+ '.dshet-warn{margin:8px 0 0;color:var(--dsw-alias-state-warning-primary,#a2650a);font-size:12px;line-height:19px}',
137
+ '.dshet-error{margin:8px 0 0;color:var(--dsw-alias-state-error-primary,#c93a31);font-size:12px;line-height:19px}',
123
138
  '.dshet-footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:12px}',
124
- '.dshet-btn{padding:6px 14px;border:1px solid var(--dsw-alias-border-secondary,rgba(127,127,127,.32));border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:background-color .12s,border-color .12s}',
125
- '.dshet-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1))}',
126
- '.dshet-btn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4d6bfe);outline-offset:2px}',
139
+ '.dshet-btn{padding:6px 14px;border:1px solid var(--dshet-line);border-radius:8px;background:var(--dshet-field);color:var(--dshet-ink);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:background-color .12s,border-color .12s}',
140
+ '.dshet-btn:hover:not(:disabled){background:var(--dshet-hover)}',
141
+ '.dshet-btn:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,var(--dshet-accent));outline-offset:2px}',
127
142
  '.dshet-btn:disabled{cursor:default;opacity:.5}',
128
- '.dshet-btn-primary{border-color:transparent;background:var(--dsw-alias-button-primary-fill,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}',
129
- '.dshet-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-fill,#4d6bfe);filter:brightness(1.08)}',
130
- '.dshet-notice{margin:4px 0;padding:6px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.08));color:var(--dsw-alias-state-error-primary,#d54941);font-size:12px;line-height:19px}',
131
- '.dshet-spacer{flex:1 1 auto}',
143
+ '.dshet-btn-primary{border-color:var(--dsw-alias-button-primary-fill,var(--dshet-accent));background:var(--dsw-alias-button-primary-fill,var(--dshet-accent));color:var(--dsw-alias-label-primary-foreground,var(--dshet-on-accent))}',
144
+ '.dshet-btn-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-fill,var(--dshet-accent));filter:brightness(1.08)}',
145
+ '.dshet-notice{margin:4px 0;padding:6px 10px;border:1px solid var(--dshet-line);border-radius:8px;background:var(--dshet-panel);backdrop-filter:blur(18px) saturate(1.2);box-shadow:var(--dshet-shadow);color:var(--dsw-alias-state-error-primary,#c93a31);font-size:12px;line-height:19px}',
132
146
  '@media (prefers-reduced-motion:reduce){.dshet-collapsing{transition:none}.dshet-floating{transition:none}}',
133
147
  ].join('')
134
148
 
@@ -196,7 +210,9 @@ window.__ModuleLoader__.load({
196
210
  surfaceReady: false,
197
211
  loaded: false,
198
212
  loadError: false,
199
- confirmStep: true,
213
+ // Matches the host's default: one click applies. Only an explicit
214
+ // `confirm: true` in the profile turns the second step back on.
215
+ confirmStep: false,
200
216
  revision: 0,
201
217
  })
202
218
  }
package/lib/index.js CHANGED
@@ -48,7 +48,7 @@ export const PLUGIN_ID = 'dsh-edit-turn'
48
48
  export const name = PLUGIN_ID
49
49
 
50
50
  /** Keep in sync with package.json and lib/client.js. */
51
- export const PLUGIN_VERSION = '0.1.0'
51
+ export const PLUGIN_VERSION = '0.1.1'
52
52
 
53
53
  const ROUTE_PREFIX = '/dsh-edit-turn'
54
54
  const SESSION_ID_RE = /^(session-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
@@ -68,7 +68,12 @@ const DEFAULT_MARKER = '[dsh-edit-turn] The user revised an earlier message. Eve
68
68
  /** Deployment policy for the rollback. */
69
69
  export const Config = z.object({
70
70
  markerText: z.string().default(DEFAULT_MARKER),
71
- confirm: z.boolean().default(true),
71
+ /**
72
+ * Whether the client asks for a second confirmation before applying. Off by
73
+ * default: the editor is already an explicit action the user opened, and the
74
+ * panel states what saving does. Set it to `true` to require the extra step.
75
+ */
76
+ confirm: z.boolean().default(false),
72
77
  })
73
78
 
74
79
  // ---------------------------------------------------------------------------
@@ -832,7 +837,7 @@ export function apply(ctx, config) {
832
837
  ? config.markerText
833
838
  : DEFAULT_MARKER,
834
839
  carrier: config && config.carrier === 'user/message' ? 'user/message' : 'system/message',
835
- confirm: config && typeof config.confirm === 'boolean' ? config.confirm : true,
840
+ confirm: config && typeof config.confirm === 'boolean' ? config.confirm : false,
836
841
  }
837
842
 
838
843
  const registerRoutes = (webServer, fiber) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-edit-turn",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Edit any user turn in DeepSeek Harness and re-run it: an inline editor on the message row rolls the conversation back to just before that turn through the official surface-replace contract, then re-prompts the agent with the revised text. The append-only session log is never rewritten.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",