@wenaixi/dsh-prompt-history 2.2.1 → 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 +14 -0
- package/README.md +2 -1
- package/README.zh.md +3 -2
- package/lib/client.js +49 -1
- package/lib/types/client/claim-guard.d.ts +20 -0
- package/package.json +3 -2
- package/src/client/InputHistory.tsx +1 -1
- package/src/client/claim-guard.ts +25 -0
- package/src/client/index.ts +40 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
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
|
+
|
|
7
21
|
## [2.2.1] - 2026-10-06
|
|
8
22
|
|
|
9
23
|
### 修复
|
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
|
@@ -899,7 +899,7 @@ window.__ModuleLoader__.load({
|
|
|
899
899
|
lastSet: null
|
|
900
900
|
};
|
|
901
901
|
/** The suggestion menu (slash/at) renders a listbox inside the card while open. */
|
|
902
|
-
const OPEN_MENU = "[role=\"listbox\"]";
|
|
902
|
+
const OPEN_MENU = "[role=\"listbox\"], [data-trigger-menu]";
|
|
903
903
|
/** Pull the ordered node array out of whichever snapshot shape is present. */
|
|
904
904
|
function rawNodesOf(snapshot) {
|
|
905
905
|
const legacy = snapshot.legacy?.nodes;
|
|
@@ -1951,6 +1951,32 @@ window.__ModuleLoader__.load({
|
|
|
1951
1951
|
"esc.again": "Esc again to clear"
|
|
1952
1952
|
};
|
|
1953
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
|
|
1954
1980
|
//#region src/client/index.ts
|
|
1955
1981
|
/** 宿主 Settings 命名空间:等于 Loader 条目 id,与 cordis.patch.yml 的行 id 一致。 */
|
|
1956
1982
|
const HOST_NS = NS;
|
|
@@ -1982,6 +2008,8 @@ window.__ModuleLoader__.load({
|
|
|
1982
2008
|
".dsh-ph-optionHint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);}",
|
|
1983
2009
|
".dsh-ph-foot{display:flex;align-items:center;justify-content:flex-end;gap:16px;margin-top:16px;}"
|
|
1984
2010
|
].join("");
|
|
2011
|
+
/** 已补装 track 补丁的控制器,防止同一会话重复包装。 */
|
|
2012
|
+
const patched = /* @__PURE__ */ new WeakSet();
|
|
1985
2013
|
function apply(ctx) {
|
|
1986
2014
|
const style = document.createElement("style");
|
|
1987
2015
|
style.dataset.pluginCss = "dsh-ph-settings";
|
|
@@ -2012,6 +2040,26 @@ window.__ModuleLoader__.load({
|
|
|
2012
2040
|
}, SettingsCardSlot);
|
|
2013
2041
|
}), "dsh-prompt-history: plugin config card");
|
|
2014
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
|
+
});
|
|
2015
2063
|
}
|
|
2016
2064
|
//#endregion
|
|
2017
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
|
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.
|
|
@@ -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/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
|
+
}
|