@wenaixi/dsh-prompt-history 2.2.0 → 2.2.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/CHANGELOG.md +23 -1
- package/README.md +2 -1
- package/README.zh.md +3 -2
- package/lib/client.js +80 -8
- package/lib/types/client/claim-guard.d.ts +20 -0
- package/lib/types/client/feedback.d.ts +1 -1
- package/package.json +3 -2
- package/src/client/InputHistory.tsx +4 -1
- package/src/client/claim-guard.ts +25 -0
- package/src/client/feedback.ts +35 -4
- package/src/client/index.ts +40 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.2.2] - 2026-10-06
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **解除前导命令认领态下行内斜杠技能补全压制**:`/plan` 这类带参命令在按空格后被宿主认领,输入框进入 `claimed` 阶段,其底层 `detectTrigger` 有一行硬编码 `if (guard.tier === "claimed") continue`,把后续所有斜杠一律当成命令参数文本,导致 `/plan /dsh-plugin-dev`、`/plan /browser-harness` 敲不出任何补全(主症状)。插件注入 `inputTriggers` 服务并包装每会话控制器的 `track`:只要光标位于「段前有正文的行内斜杠」,就把守卫降级回 `plain` 让宿主补全管线照常跑。判定逻辑收敛在 `claim-guard.ts` 的 `hasInlineSlash`(纯函数,5 项单测),边界语义:
|
|
12
|
+
- `/plan /ds`、`/plan /dsh-plugin-dev` 命中 → 正常弹补全;
|
|
13
|
+
- `/plan`、`/plan `、`/plan off` 不命中 → 认领令牌与纯文本参数不受影响;
|
|
14
|
+
- 行内斜杠是否是真触发符仍由宿主 `boundaryOk` 裁决,URL、`//` 照旧被抑制。
|
|
15
|
+
- **建议菜单让位选择器增强**:`OPEN_MENU` 扩为 `[role="listbox"], [data-trigger-menu]`,在宿主菜单骨架屏尚未挂载 listbox 的加载窗口期同样让位,消除按键抢占。
|
|
16
|
+
|
|
17
|
+
### 验收
|
|
18
|
+
|
|
19
|
+
隔离实例 `prompt-history-e2e-v3` + 本机 Chrome/CDP,12 项全绿(主症状、六个边界、前导技能与命令菜单未回归、URL 不误触发、双击 Esc 开面板与清空、方向键历史往返、无控制台错误),并在修复前的 `34a60e5` 源码上做对照确认症状可复现(焐热命令目录后 `/plan /ds` 的 listbox 为 0)。
|
|
20
|
+
|
|
21
|
+
## [2.2.1] - 2026-10-06
|
|
22
|
+
|
|
23
|
+
### 修复
|
|
24
|
+
|
|
25
|
+
- **双击 Esc 关闭时 Esc 让位宿主**:`doubleEsc=false` 时连按 Esc 不再被插件消费,宿主自行关闭其浮层/菜单。
|
|
26
|
+
- **宽屏历史面板右侧完整预览**:窗口 ≥1000px 时在列表右侧显示高亮条目的完整内容(最多 6 行,超出显示 `… +N more lines`),对齐 Claude Code `HistorySearchDialog` 的 preview。
|
|
27
|
+
|
|
7
28
|
## [2.2.0] - 2026-10-06
|
|
8
29
|
|
|
9
30
|
### 变更
|
|
@@ -257,7 +278,8 @@
|
|
|
257
278
|
|
|
258
279
|
- 选中即复制(任意选中方式)。
|
|
259
280
|
|
|
260
|
-
[Unreleased]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.2.
|
|
281
|
+
[Unreleased]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.2.1...HEAD
|
|
282
|
+
[2.2.1]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.2.0...v2.2.1
|
|
261
283
|
[2.2.0]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.1.0...v2.2.0
|
|
262
284
|
[2.1.0]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.0.2...v2.1.0
|
|
263
285
|
[2.0.2]: https://github.com/Wenaixi/dsh-prompt-history/compare/v2.0.1...v2.0.2
|
package/README.md
CHANGED
|
@@ -78,8 +78,9 @@ Open **Plugins → `@wenaixi/dsh-prompt-history` → configuration** (persisted
|
|
|
78
78
|
|
|
79
79
|
- **History comes from the session's own message log**: reads the conversation snapshot's user nodes (`user` / `steering`) and appends as they land — strictly consistent with the transcript, persisted with the session, survives page reloads, and needs no configuration or extra storage. With "Cross-session memory" on it seeds from browser localStorage (capped by the max-history setting) and dedupes against the whole ring on every append.
|
|
80
80
|
- **Consecutive duplicates collapse**; browse state resets on session switch.
|
|
81
|
+
- **Inline slash completion survives a claimed command**: the DSH Host suppresses every `/` trigger while a leading command owns the draft (`/plan ` puts the composer in its `claimed` phase), so on a stock Host typing `/plan /browser-harness` shows nothing. This plugin widens that guard back to `plain` whenever the caret sits on an inline slash, which is what makes `/plan /skill-name` suggestions work as you'd expect.
|
|
81
82
|
- Fully localized (中文 / English): settings, toolbar, feedback pills, history list all follow the DSH app locale.
|
|
82
|
-
- The client bundle is ~
|
|
83
|
+
- The client bundle is ~25 KB gzipped and depends only on the official `@deepseek-ai/*` peer packages.
|
|
83
84
|
|
|
84
85
|
## Known limitations
|
|
85
86
|
|
package/README.zh.md
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
- **工具栏**(默认):选区上方出现「复制」「引用」按钮——复制点击才写剪贴板(不刷屏 Win+V);**引用**把选中文本的**完整内容**以简约的 `>` 引用块插入输入框(标准 markdown 引用,发送时渲染为引用块)。
|
|
41
41
|
- **选中即自动复制**:终端风格,选中直接写系统剪贴板。
|
|
42
42
|
6. **鼠标右键直接粘贴** — 在输入框上右键即粘贴剪贴板内容(和 Linux 终端一致,不弹菜单);粘贴走与 Ctrl+V 完全一致的管线(图片与引用 chip 行为一致),execCommand 路径被限制时自动回退到 Clipboard API 手动插入。
|
|
43
|
-
7. **跨会话历史记忆**(设置里可开关,默认关)— 开启后 ↑/↓
|
|
43
|
+
7. **跨会话历史记忆**(设置里可开关,默认关)— 开启后 ↑/↓ 历史在会话间保持,存于浏览器本地(受「历史条数上限」约束,默认 100 条),刷新/切换会话不丢失。
|
|
44
44
|
|
|
45
45
|
纯 UI 行为:不产生会话事件、不改变 agent 循环、不发起模型请求。召回/引用的文本只会进入输入框草稿,只有**你**按 Enter 才会到达模型。
|
|
46
46
|
|
|
@@ -78,8 +78,9 @@ dsh plugin --profile web add @wenaixi/dsh-prompt-history
|
|
|
78
78
|
|
|
79
79
|
- **历史来自会话自身的消息记录**:直接读取会话快照中的用户消息节点(`user` / `steering`),随消息落地实时追加——与聊天记录严格一致,随会话持久化,刷新页面后依然可用,不需要任何配置或额外存储。开启「跨会话记忆」后改从浏览器 localStorage 播种并在每次追加时全环去重。
|
|
80
80
|
- **连续重复自动合并**,浏览状态跟随会话切换自动重置。
|
|
81
|
+
- **认领命令后的行内斜杠补全可用**:宿主在前导命令认领草稿期间(如 `/plan ` 把输入框推进 `claimed` 阶段)会压制所有 `/` 触发,裸宿主上敲 `/plan /browser-harness` 不会弹出任何补全。本插件在光标位于行内斜杠时把该守卫放宽回 `plain`,让 `/plan /技能名` 的补全按预期弹出。
|
|
81
82
|
- **界面国际化**:全部文案(设置、工具栏、提示 pill、历史列表)跟随 DSH 应用语言(中文 / English)。
|
|
82
|
-
- client bundle 压缩后约
|
|
83
|
+
- client bundle 压缩后约 25 KB;依赖全部走官方 `@deepseek-ai/*` peer 依赖。
|
|
83
84
|
|
|
84
85
|
## 已知限制
|
|
85
86
|
|
package/lib/client.js
CHANGED
|
@@ -285,6 +285,7 @@ window.__ModuleLoader__.load({
|
|
|
285
285
|
let historyPanel = null;
|
|
286
286
|
let historyList = null;
|
|
287
287
|
let historyFoot = null;
|
|
288
|
+
let historyPreview = null;
|
|
288
289
|
let historyRefs = null;
|
|
289
290
|
/** Remove the history list panel and detach its outside-click listener (idempotent). */
|
|
290
291
|
function hideHistoryPanel() {
|
|
@@ -295,6 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
295
296
|
}
|
|
296
297
|
historyList = null;
|
|
297
298
|
historyFoot = null;
|
|
299
|
+
historyPreview = null;
|
|
298
300
|
historyRefs = null;
|
|
299
301
|
}
|
|
300
302
|
/** Close the panel when a pointer lands outside both it and the composer. */
|
|
@@ -343,12 +345,16 @@ window.__ModuleLoader__.load({
|
|
|
343
345
|
const ROW_ACTIVE_CSS = "background:var(--dsw-specific-menu-hover);color:var(--dsw-alias-label-primary);border-left-color:var(--dsw-static-blue-500);";
|
|
344
346
|
const AGE_CSS = `flex:none;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;min-width:${AGE_WIDTH}ch;text-align:right;color:var(--dsw-alias-label-tertiary);`;
|
|
345
347
|
const ROW_TEXT_CSS = "min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
348
|
+
/** 宽屏阈值:Claude Code 终端列数 ≥100,web 用像素近似。 */
|
|
349
|
+
const WIDE_MIN_PX = 1e3;
|
|
350
|
+
/** 预览最多行数,对齐 Claude Code PREVIEW_ROWS=6。 */
|
|
351
|
+
const PREVIEW_ROWS = 6;
|
|
346
352
|
/**
|
|
347
353
|
* Open the history list above the composer card.
|
|
348
354
|
*
|
|
349
355
|
* 只做渲染与鼠标交互:键盘(过滤、移动、回填、关闭)在 InputHistory.tsx 里处理,
|
|
350
356
|
* 因为焦点必须留在输入框,宿主 Menu 的方向键行走(依赖真实焦点)用不上。
|
|
351
|
-
*
|
|
357
|
+
* 列表按最新在顶渲染;宽屏(≥1000px)时右侧显示高亮条目的完整内容预览。
|
|
352
358
|
*
|
|
353
359
|
* @param view - 命中文本、相对时间、高亮位置与计数。
|
|
354
360
|
* @param refs - 提交与关闭回调。
|
|
@@ -359,25 +365,34 @@ window.__ModuleLoader__.load({
|
|
|
359
365
|
el.className = HISTORY_CLASS;
|
|
360
366
|
el.style.cssText = PANEL_CSS;
|
|
361
367
|
el.setAttribute("role", "listbox");
|
|
368
|
+
const body = document.createElement("div");
|
|
369
|
+
body.style.cssText = "display:flex;align-items:stretch;";
|
|
362
370
|
const list = document.createElement("div");
|
|
363
|
-
list.style.cssText = "max-height:240px;overflow-y:auto;overscroll-behavior:contain;";
|
|
371
|
+
list.style.cssText = "flex:1 1 0;min-width:0;max-height:240px;overflow-y:auto;overscroll-behavior:contain;";
|
|
364
372
|
list.setAttribute("role", "presentation");
|
|
373
|
+
const preview = document.createElement("div");
|
|
374
|
+
preview.className = "dsh-ph-history-preview";
|
|
375
|
+
preview.style.cssText = "display:none;flex:1 1 0;min-width:0;max-height:240px;overflow-y:auto;padding:7px 12px;border-left:1px solid var(--dsw-alias-border-l2);font:12.5px/1.6 inherit;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;";
|
|
376
|
+
body.append(list, preview);
|
|
365
377
|
const foot = document.createElement("div");
|
|
366
378
|
foot.style.cssText = "padding:5px 12px;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-tertiary);border-top:1px solid var(--dsw-alias-border-l2);";
|
|
367
|
-
el.append(
|
|
379
|
+
el.append(body, foot);
|
|
368
380
|
document.body.appendChild(el);
|
|
369
381
|
historyPanel = el;
|
|
370
382
|
historyList = list;
|
|
371
383
|
historyFoot = foot;
|
|
384
|
+
historyPreview = preview;
|
|
372
385
|
document.addEventListener("pointerdown", onHistoryOutsidePointer, true);
|
|
373
386
|
}
|
|
374
387
|
const el = historyPanel;
|
|
375
388
|
const list = historyList;
|
|
376
389
|
const foot = historyFoot;
|
|
377
|
-
|
|
390
|
+
const preview = historyPreview;
|
|
391
|
+
if (el === null || list === null || foot === null || preview === null) return;
|
|
378
392
|
historyRefs = refs;
|
|
379
393
|
list.replaceChildren();
|
|
380
|
-
view.texts.slice(0, HISTORY_MAX_ROWS)
|
|
394
|
+
const rows = view.texts.slice(0, HISTORY_MAX_ROWS);
|
|
395
|
+
rows.forEach((text, i) => {
|
|
381
396
|
const row = document.createElement("button");
|
|
382
397
|
row.type = "button";
|
|
383
398
|
row.id = `${HISTORY_CLASS}-row-${i}`;
|
|
@@ -404,6 +419,13 @@ window.__ModuleLoader__.load({
|
|
|
404
419
|
if (view.highlight >= 0) el.setAttribute("aria-activedescendant", `${HISTORY_CLASS}-row-${view.highlight}`);
|
|
405
420
|
else el.removeAttribute("aria-activedescendant");
|
|
406
421
|
foot.textContent = view.shown === 0 ? T("history.noMatch") : `${view.shown} / ${view.total} ${T("history.hint")}`;
|
|
422
|
+
preview.style.display = window.innerWidth >= WIDE_MIN_PX ? "block" : "none";
|
|
423
|
+
preview.replaceChildren();
|
|
424
|
+
if (view.highlight >= 0 && view.highlight < rows.length) {
|
|
425
|
+
const lines = (rows[view.highlight] ?? "").split("\n");
|
|
426
|
+
preview.textContent = lines.slice(0, PREVIEW_ROWS).join("\n");
|
|
427
|
+
if (lines.length > PREVIEW_ROWS) preview.append(`\n… +${lines.length - PREVIEW_ROWS} ${T("history.moreLines")}`);
|
|
428
|
+
}
|
|
407
429
|
placeHistoryPanel(el);
|
|
408
430
|
const active = list.querySelector(`#${HISTORY_CLASS}-row-${view.highlight}`);
|
|
409
431
|
if (active instanceof HTMLElement) active.scrollIntoView({ block: "nearest" });
|
|
@@ -877,7 +899,7 @@ window.__ModuleLoader__.load({
|
|
|
877
899
|
lastSet: null
|
|
878
900
|
};
|
|
879
901
|
/** The suggestion menu (slash/at) renders a listbox inside the card while open. */
|
|
880
|
-
const OPEN_MENU = "[role=\"listbox\"]";
|
|
902
|
+
const OPEN_MENU = "[role=\"listbox\"], [data-trigger-menu]";
|
|
881
903
|
/** Pull the ordered node array out of whichever snapshot shape is present. */
|
|
882
904
|
function rawNodesOf(snapshot) {
|
|
883
905
|
const legacy = snapshot.legacy?.nodes;
|
|
@@ -1154,8 +1176,10 @@ window.__ModuleLoader__.load({
|
|
|
1154
1176
|
return;
|
|
1155
1177
|
}
|
|
1156
1178
|
if (e.key !== "Escape") lastEscapeRef.current = 0;
|
|
1157
|
-
else if (!prefs.doubleEsc)
|
|
1158
|
-
|
|
1179
|
+
else if (!prefs.doubleEsc) {
|
|
1180
|
+
lastEscapeRef.current = 0;
|
|
1181
|
+
return;
|
|
1182
|
+
} else {
|
|
1159
1183
|
const now = performance.now();
|
|
1160
1184
|
const doubled = isDoubleEscape(lastEscapeRef.current, now);
|
|
1161
1185
|
if (live.draft !== "") {
|
|
@@ -1927,6 +1951,32 @@ window.__ModuleLoader__.load({
|
|
|
1927
1951
|
"esc.again": "Esc again to clear"
|
|
1928
1952
|
};
|
|
1929
1953
|
//#endregion
|
|
1954
|
+
//#region src/client/claim-guard.ts
|
|
1955
|
+
/**
|
|
1956
|
+
* 认领态行内斜杠判定:宿主触发控制器 claimed 档的放宽开关。
|
|
1957
|
+
*
|
|
1958
|
+
* 与浏览器、cordis 都无关,纯粹是对草稿文本的一处判定,因此单列成纯函数以便直接测试。
|
|
1959
|
+
*/
|
|
1960
|
+
/**
|
|
1961
|
+
* 光标前的草稿里是否存在「行内」斜杠:光标所在的空白分隔段含 "/",且该段之前还有正文。
|
|
1962
|
+
*
|
|
1963
|
+
* 认领命令按宿主约定只能出现在草稿首段(beginCommand 要求 span 之前全是空白),
|
|
1964
|
+
* 所以「段前有正文」正好排除了认领令牌本身:
|
|
1965
|
+
* "/plan /ds" 命中(第二段是行内技能)
|
|
1966
|
+
* "/plan " 不命中(还没有行内段)
|
|
1967
|
+
* "/plan" 不命中(这就是认领令牌,放宽会把它自己再触发一遍)
|
|
1968
|
+
* "/plan off" 不命中(纯文本参数)
|
|
1969
|
+
*
|
|
1970
|
+
* 行内段里的斜杠是否真的是触发符仍由宿主 boundaryOk 裁决(URL、// 等照旧被抑制),
|
|
1971
|
+
* 这里只负责回答「要不要把守卫放宽」,不重复宿主的边界规则。
|
|
1972
|
+
*/
|
|
1973
|
+
function hasInlineSlash(draft, caret) {
|
|
1974
|
+
const before = draft.slice(0, Math.max(0, caret));
|
|
1975
|
+
const start = before.search(/\S[^\s]*$/u);
|
|
1976
|
+
if (start <= 0) return false;
|
|
1977
|
+
return before.slice(start).includes("/");
|
|
1978
|
+
}
|
|
1979
|
+
//#endregion
|
|
1930
1980
|
//#region src/client/index.ts
|
|
1931
1981
|
/** 宿主 Settings 命名空间:等于 Loader 条目 id,与 cordis.patch.yml 的行 id 一致。 */
|
|
1932
1982
|
const HOST_NS = NS;
|
|
@@ -1958,6 +2008,8 @@ window.__ModuleLoader__.load({
|
|
|
1958
2008
|
".dsh-ph-optionHint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);}",
|
|
1959
2009
|
".dsh-ph-foot{display:flex;align-items:center;justify-content:flex-end;gap:16px;margin-top:16px;}"
|
|
1960
2010
|
].join("");
|
|
2011
|
+
/** 已补装 track 补丁的控制器,防止同一会话重复包装。 */
|
|
2012
|
+
const patched = /* @__PURE__ */ new WeakSet();
|
|
1961
2013
|
function apply(ctx) {
|
|
1962
2014
|
const style = document.createElement("style");
|
|
1963
2015
|
style.dataset.pluginCss = "dsh-ph-settings";
|
|
@@ -1988,6 +2040,26 @@ window.__ModuleLoader__.load({
|
|
|
1988
2040
|
}, SettingsCardSlot);
|
|
1989
2041
|
}), "dsh-prompt-history: plugin config card");
|
|
1990
2042
|
});
|
|
2043
|
+
ctx.inject(["inputTriggers"], (raw) => {
|
|
2044
|
+
const service = raw.inputTriggers;
|
|
2045
|
+
const resolve = service?.sessionOf;
|
|
2046
|
+
if (typeof resolve !== "function") return;
|
|
2047
|
+
service.sessionOf = (scope) => {
|
|
2048
|
+
const controller = resolve.call(service, scope);
|
|
2049
|
+
if (controller === void 0 || typeof controller.track !== "function") return controller;
|
|
2050
|
+
if (patched.has(controller)) return controller;
|
|
2051
|
+
patched.add(controller);
|
|
2052
|
+
const hostTrack = controller.track.bind(controller);
|
|
2053
|
+
controller.track = (draft, caret, guard, draftRev) => {
|
|
2054
|
+
if (guard?.tier === "claimed" && hasInlineSlash(draft, caret)) return hostTrack(draft, caret, {
|
|
2055
|
+
...guard,
|
|
2056
|
+
tier: "plain"
|
|
2057
|
+
}, draftRev);
|
|
2058
|
+
return hostTrack(draft, caret, guard, draftRev);
|
|
2059
|
+
};
|
|
2060
|
+
return controller;
|
|
2061
|
+
};
|
|
2062
|
+
});
|
|
1991
2063
|
}
|
|
1992
2064
|
//#endregion
|
|
1993
2065
|
exports.apply = apply;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 认领态行内斜杠判定:宿主触发控制器 claimed 档的放宽开关。
|
|
3
|
+
*
|
|
4
|
+
* 与浏览器、cordis 都无关,纯粹是对草稿文本的一处判定,因此单列成纯函数以便直接测试。
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 光标前的草稿里是否存在「行内」斜杠:光标所在的空白分隔段含 "/",且该段之前还有正文。
|
|
8
|
+
*
|
|
9
|
+
* 认领命令按宿主约定只能出现在草稿首段(beginCommand 要求 span 之前全是空白),
|
|
10
|
+
* 所以「段前有正文」正好排除了认领令牌本身:
|
|
11
|
+
* "/plan /ds" 命中(第二段是行内技能)
|
|
12
|
+
* "/plan " 不命中(还没有行内段)
|
|
13
|
+
* "/plan" 不命中(这就是认领令牌,放宽会把它自己再触发一遍)
|
|
14
|
+
* "/plan off" 不命中(纯文本参数)
|
|
15
|
+
*
|
|
16
|
+
* 行内段里的斜杠是否真的是触发符仍由宿主 boundaryOk 裁决(URL、// 等照旧被抑制),
|
|
17
|
+
* 这里只负责回答「要不要把守卫放宽」,不重复宿主的边界规则。
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasInlineSlash(draft: string, caret: number): boolean;
|
|
20
|
+
//# sourceMappingURL=claim-guard.d.ts.map
|
|
@@ -37,7 +37,7 @@ export declare function hideHistoryPanel(): void;
|
|
|
37
37
|
*
|
|
38
38
|
* 只做渲染与鼠标交互:键盘(过滤、移动、回填、关闭)在 InputHistory.tsx 里处理,
|
|
39
39
|
* 因为焦点必须留在输入框,宿主 Menu 的方向键行走(依赖真实焦点)用不上。
|
|
40
|
-
*
|
|
40
|
+
* 列表按最新在顶渲染;宽屏(≥1000px)时右侧显示高亮条目的完整内容预览。
|
|
41
41
|
*
|
|
42
42
|
* @param view - 命中文本、相对时间、高亮位置与计数。
|
|
43
43
|
* @param refs - 提交与关闭回调。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenaixi/dsh-prompt-history",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Terminal-style prompt history, selection copy/quote and right-click paste for the DSH composer (zh/en).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"@deepseek-ai/dsh-client-locale",
|
|
56
56
|
"@deepseek-ai/dsh-client-ui-plugin-manager",
|
|
57
57
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-primitives"
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-input-trigger"
|
|
59
60
|
],
|
|
60
61
|
"platform": "web"
|
|
61
62
|
}
|
|
@@ -109,7 +109,7 @@ interface PickerState {
|
|
|
109
109
|
const RESET_BROWSE: BrowseState = { step: 0, saved: '', lastSet: null }
|
|
110
110
|
|
|
111
111
|
/** The suggestion menu (slash/at) renders a listbox inside the card while open. */
|
|
112
|
-
const OPEN_MENU = '[role="listbox"]'
|
|
112
|
+
const OPEN_MENU = '[role="listbox"], [data-trigger-menu]'
|
|
113
113
|
|
|
114
114
|
/** DSH ≥ 0.1.2 moved the conversation nodes off the session snapshot onto the
|
|
115
115
|
* chat view (useChat); the lifecycle fields (removed) stay on useSession.
|
|
@@ -459,7 +459,10 @@ export function InputHistory(props: InputHistoryProps) {
|
|
|
459
459
|
if (e.key !== 'Escape') {
|
|
460
460
|
lastEscapeRef.current = 0
|
|
461
461
|
} else if (!prefs.doubleEsc) {
|
|
462
|
+
// 双击 Esc 关闭:Esc 完全交还宿主(不提示、
|
|
463
|
+
// 不消费)。宿主会自行关闭其浮层/菜单。
|
|
462
464
|
lastEscapeRef.current = 0
|
|
465
|
+
return
|
|
463
466
|
} else {
|
|
464
467
|
const now = performance.now()
|
|
465
468
|
const doubled = isDoubleEscape(lastEscapeRef.current, now)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 认领态行内斜杠判定:宿主触发控制器 claimed 档的放宽开关。
|
|
3
|
+
*
|
|
4
|
+
* 与浏览器、cordis 都无关,纯粹是对草稿文本的一处判定,因此单列成纯函数以便直接测试。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 光标前的草稿里是否存在「行内」斜杠:光标所在的空白分隔段含 "/",且该段之前还有正文。
|
|
9
|
+
*
|
|
10
|
+
* 认领命令按宿主约定只能出现在草稿首段(beginCommand 要求 span 之前全是空白),
|
|
11
|
+
* 所以「段前有正文」正好排除了认领令牌本身:
|
|
12
|
+
* "/plan /ds" 命中(第二段是行内技能)
|
|
13
|
+
* "/plan " 不命中(还没有行内段)
|
|
14
|
+
* "/plan" 不命中(这就是认领令牌,放宽会把它自己再触发一遍)
|
|
15
|
+
* "/plan off" 不命中(纯文本参数)
|
|
16
|
+
*
|
|
17
|
+
* 行内段里的斜杠是否真的是触发符仍由宿主 boundaryOk 裁决(URL、// 等照旧被抑制),
|
|
18
|
+
* 这里只负责回答「要不要把守卫放宽」,不重复宿主的边界规则。
|
|
19
|
+
*/
|
|
20
|
+
export function hasInlineSlash(draft: string, caret: number): boolean {
|
|
21
|
+
const before = draft.slice(0, Math.max(0, caret))
|
|
22
|
+
const start = before.search(/\S[^\s]*$/u)
|
|
23
|
+
if (start <= 0) return false
|
|
24
|
+
return before.slice(start).includes('/')
|
|
25
|
+
}
|
package/src/client/feedback.ts
CHANGED
|
@@ -145,6 +145,7 @@ interface HistoryPanelRefs {
|
|
|
145
145
|
let historyPanel: HTMLDivElement | null = null
|
|
146
146
|
let historyList: HTMLDivElement | null = null
|
|
147
147
|
let historyFoot: HTMLDivElement | null = null
|
|
148
|
+
let historyPreview: HTMLDivElement | null = null
|
|
148
149
|
let historyRefs: HistoryPanelRefs | null = null
|
|
149
150
|
|
|
150
151
|
/** Remove the history list panel and detach its outside-click listener (idempotent). */
|
|
@@ -156,6 +157,7 @@ export function hideHistoryPanel(): void {
|
|
|
156
157
|
}
|
|
157
158
|
historyList = null
|
|
158
159
|
historyFoot = null
|
|
160
|
+
historyPreview = null
|
|
159
161
|
historyRefs = null
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -222,12 +224,17 @@ const AGE_CSS = 'flex:none;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,mo
|
|
|
222
224
|
|
|
223
225
|
const ROW_TEXT_CSS = 'min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'
|
|
224
226
|
|
|
227
|
+
/** 宽屏阈值:Claude Code 终端列数 ≥100,web 用像素近似。 */
|
|
228
|
+
const WIDE_MIN_PX = 1000
|
|
229
|
+
/** 预览最多行数,对齐 Claude Code PREVIEW_ROWS=6。 */
|
|
230
|
+
const PREVIEW_ROWS = 6
|
|
231
|
+
|
|
225
232
|
/**
|
|
226
233
|
* Open the history list above the composer card.
|
|
227
234
|
*
|
|
228
235
|
* 只做渲染与鼠标交互:键盘(过滤、移动、回填、关闭)在 InputHistory.tsx 里处理,
|
|
229
236
|
* 因为焦点必须留在输入框,宿主 Menu 的方向键行走(依赖真实焦点)用不上。
|
|
230
|
-
*
|
|
237
|
+
* 列表按最新在顶渲染;宽屏(≥1000px)时右侧显示高亮条目的完整内容预览。
|
|
231
238
|
*
|
|
232
239
|
* @param view - 命中文本、相对时间、高亮位置与计数。
|
|
233
240
|
* @param refs - 提交与关闭回调。
|
|
@@ -238,23 +245,34 @@ export function showHistoryPanel(view: HistoryPanelView, refs: HistoryPanelRefs)
|
|
|
238
245
|
el.className = HISTORY_CLASS
|
|
239
246
|
el.style.cssText = PANEL_CSS
|
|
240
247
|
el.setAttribute('role', 'listbox')
|
|
248
|
+
const body = document.createElement('div')
|
|
249
|
+
body.style.cssText = 'display:flex;align-items:stretch;'
|
|
241
250
|
const list = document.createElement('div')
|
|
242
|
-
list.style.cssText = 'max-height:240px;overflow-y:auto;overscroll-behavior:contain;'
|
|
251
|
+
list.style.cssText = 'flex:1 1 0;min-width:0;max-height:240px;overflow-y:auto;overscroll-behavior:contain;'
|
|
243
252
|
list.setAttribute('role', 'presentation')
|
|
253
|
+
const preview = document.createElement('div')
|
|
254
|
+
preview.className = 'dsh-ph-history-preview'
|
|
255
|
+
preview.style.cssText = 'display:none;flex:1 1 0;min-width:0;max-height:240px;overflow-y:auto;' +
|
|
256
|
+
'padding:7px 12px;border-left:1px solid var(--dsw-alias-border-l2);' +
|
|
257
|
+
'font:12.5px/1.6 inherit;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;' +
|
|
258
|
+
'word-break:break-word;'
|
|
259
|
+
body.append(list, preview)
|
|
244
260
|
const foot = document.createElement('div')
|
|
245
261
|
foot.style.cssText = 'padding:5px 12px;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;' +
|
|
246
262
|
'color:var(--dsw-alias-label-tertiary);border-top:1px solid var(--dsw-alias-border-l2);'
|
|
247
|
-
el.append(
|
|
263
|
+
el.append(body, foot)
|
|
248
264
|
document.body.appendChild(el)
|
|
249
265
|
historyPanel = el
|
|
250
266
|
historyList = list
|
|
251
267
|
historyFoot = foot
|
|
268
|
+
historyPreview = preview
|
|
252
269
|
document.addEventListener('pointerdown', onHistoryOutsidePointer, true)
|
|
253
270
|
}
|
|
254
271
|
const el = historyPanel
|
|
255
272
|
const list = historyList
|
|
256
273
|
const foot = historyFoot
|
|
257
|
-
|
|
274
|
+
const preview = historyPreview
|
|
275
|
+
if (el === null || list === null || foot === null || preview === null) return
|
|
258
276
|
historyRefs = refs
|
|
259
277
|
|
|
260
278
|
list.replaceChildren()
|
|
@@ -288,6 +306,19 @@ export function showHistoryPanel(view: HistoryPanelView, refs: HistoryPanelRefs)
|
|
|
288
306
|
? T('history.noMatch')
|
|
289
307
|
: `${view.shown} / ${view.total} ${T('history.hint')}`
|
|
290
308
|
|
|
309
|
+
// 宽屏右侧预览:显示高亮条目的完整文本,超过 6 行截断 + 「… +N more lines」。
|
|
310
|
+
preview.style.display = window.innerWidth >= WIDE_MIN_PX ? 'block' : 'none'
|
|
311
|
+
preview.replaceChildren()
|
|
312
|
+
if (view.highlight >= 0 && view.highlight < rows.length) {
|
|
313
|
+
const text = rows[view.highlight] ?? ''
|
|
314
|
+
const lines = text.split('\n')
|
|
315
|
+
const shown = lines.slice(0, PREVIEW_ROWS)
|
|
316
|
+
preview.textContent = shown.join('\n')
|
|
317
|
+
if (lines.length > PREVIEW_ROWS) {
|
|
318
|
+
preview.append(`\n… +${lines.length - PREVIEW_ROWS} ${T('history.moreLines')}`)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
291
322
|
placeHistoryPanel(el)
|
|
292
323
|
// 高亮项滚进可视区,但不抢焦点。
|
|
293
324
|
const active = list.querySelector(`#${HISTORY_CLASS}-row-${view.highlight}`)
|
package/src/client/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { SettingsCardController } from './card-controller.ts'
|
|
|
20
20
|
import { NS, en, zh } from './locales.ts'
|
|
21
21
|
import { setTranslator } from './i18n.ts'
|
|
22
22
|
import { bindHostForm } from './prefs.ts'
|
|
23
|
+
import { hasInlineSlash } from './claim-guard.ts'
|
|
23
24
|
|
|
24
25
|
/** 宿主 Settings 命名空间:等于 Loader 条目 id,与 cordis.patch.yml 的行 id 一致。 */
|
|
25
26
|
const HOST_NS = NS
|
|
@@ -56,6 +57,14 @@ const SETTINGS_CSS = [
|
|
|
56
57
|
'.dsh-ph-foot{display:flex;align-items:center;justify-content:flex-end;gap:16px;margin-top:16px;}',
|
|
57
58
|
].join('')
|
|
58
59
|
|
|
60
|
+
/** 宿主每会话一个触发控制器;这里只用到补全管线的 track 入口。 */
|
|
61
|
+
interface TriggerPatch {
|
|
62
|
+
track: (draft: string, caret: number, guard: { tier: string } | null | undefined, draftRev: number) => void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 已补装 track 补丁的控制器,防止同一会话重复包装。 */
|
|
66
|
+
const patched = new WeakSet<object>()
|
|
67
|
+
|
|
59
68
|
export function apply(ctx: ClientContext): void {
|
|
60
69
|
const style = document.createElement('style')
|
|
61
70
|
style.dataset.pluginCss = 'dsh-ph-settings'
|
|
@@ -85,4 +94,34 @@ export function apply(ctx: ClientContext): void {
|
|
|
85
94
|
)
|
|
86
95
|
}), 'dsh-prompt-history: plugin config card')
|
|
87
96
|
})
|
|
88
|
-
|
|
97
|
+
|
|
98
|
+
// 认领态行内斜杠补全:宿主 detectTrigger 在 guard.tier === "claimed" 时硬跳过所有
|
|
99
|
+
// "/"(前导命令已认领草稿,后续斜杠一律按参数文本处理)。这让 "/plan /skill" 这类
|
|
100
|
+
// 命令参数里的技能补全静默失效。这里把守卫放宽:光标位于行内斜杠时降级为 plain,
|
|
101
|
+
// 让宿主补全管线照常跑。前导位置("/plan " 本身)不受影响,仍走原认领语义。
|
|
102
|
+
// ponytail: 依赖 sessionOf().track 这条内部路径,宿主重构触发控制器时静默失效
|
|
103
|
+
// (退回裸宿主行为,不报错);升级 DSH 后重跑隔离实例的 "/plan /ds" 验收即可确认。
|
|
104
|
+
ctx.inject(['inputTriggers'], (raw) => {
|
|
105
|
+
const service = (raw as unknown as Record<string, unknown>).inputTriggers as
|
|
106
|
+
| { sessionOf?: (scope: unknown) => TriggerPatch | undefined }
|
|
107
|
+
| undefined
|
|
108
|
+
const resolve = service?.sessionOf
|
|
109
|
+
if (typeof resolve !== 'function') return
|
|
110
|
+
// 包一层 sessionOf:每个会话控制器首次取到时补装 track 补丁。
|
|
111
|
+
service!.sessionOf = (scope: unknown): TriggerPatch | undefined => {
|
|
112
|
+
const controller = resolve.call(service, scope)
|
|
113
|
+
if (controller === undefined || typeof controller.track !== 'function') return controller
|
|
114
|
+
if (patched.has(controller)) return controller
|
|
115
|
+
patched.add(controller)
|
|
116
|
+
const hostTrack = controller.track.bind(controller)
|
|
117
|
+
controller.track = (draft, caret, guard, draftRev) => {
|
|
118
|
+
// 行内斜杠(光标前最近一个空白之后有 "/")才放宽;无斜杠的前导认领照旧。
|
|
119
|
+
if (guard?.tier === 'claimed' && hasInlineSlash(draft, caret)) {
|
|
120
|
+
return hostTrack(draft, caret, { ...guard, tier: 'plain' }, draftRev)
|
|
121
|
+
}
|
|
122
|
+
return hostTrack(draft, caret, guard, draftRev)
|
|
123
|
+
}
|
|
124
|
+
return controller
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|