dsh-plain-talk 0.0.0-stage → 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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.en.md +147 -0
- package/README.md +152 -3
- package/cordis.patch.yml +5 -0
- package/dsh/check.js +250 -0
- package/dsh/index.js +473 -0
- package/dsh/rules.js +44 -0
- package/dsh/vocab.js +215 -0
- package/icon.svg +6 -0
- package/lib/client.js +155 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +56 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 0.1.1
|
|
2
|
+
|
|
3
|
+
- README 重写:只保留用户需要的信息,删除 npm 发布与收录流程说明。
|
|
4
|
+
- 输出对比改为左右并列表格,场景重排为 A / B / C。
|
|
5
|
+
- 示例代号改为中性占位符(EXP-7、DEV-1K、run-3),规则与自检提示同步。
|
|
6
|
+
- README 顶部加入 banner。
|
|
7
|
+
|
|
8
|
+
# 0.1.0
|
|
9
|
+
|
|
10
|
+
- 输入框「说人话」开关:按会话记录,状态写入 `$DSH_HOME/plain-talk.json`,重启后保留。
|
|
11
|
+
- 系统提示词区段:结论先行、默认 ≤12 行 / 300 字、禁回指、引用必须贴原文、代号首现解释、语言跟随用户。
|
|
12
|
+
- `plain_talk_check`:发送前自检,报回指、内部信息、只给指针的引用、未解释代号、长度与列表问题。
|
|
13
|
+
- `plain_talk_vocab`:读取与维护用户词汇清单,分常识 / 专业 / 项目 / 内部代号 / 待归类五类。
|
|
14
|
+
- 「调研 X」两阶段学习:第一次回复把 X 当作未知,回合结束后才写入词汇清单。
|
|
15
|
+
- `PLAIN_TALK_DEFAULT=off` 可让新会话默认关闭,用于基准测试或按需开启的用法。
|
|
16
|
+
- 不写 session 事件;只写两个 sidecar JSON 文件。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lizilong1993
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# dsh-plain-talk
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/lizilong1993/dsh-plain-talk/main/assets/banner.jpg" alt="dsh-plain-talk — 说人话" width="100%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>English</strong> · <a href="README.md">中文</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-263146?style=flat-square"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/dsh-plain-talk"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plain-talk?style=flat-square"></a>
|
|
14
|
+
<img alt="dsh-plugin" src="https://img.shields.io/badge/dsh--plugin-ready-478CBF?style=flat-square&logo=deepseek&logoColor=white">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
A reply-shaping plugin for DeepSeek Harness: one composer switch. When it is on, replies lead with the conclusion, stay under 12 lines by default, paste the text they cite, gloss an opaque code the first time it appears, and stop re-explaining words you already use.
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
- **Conclusion first.** The first line is the answer or the verdict. No restating the question, no "let me take a look".
|
|
22
|
+
- **Short by default.** ≤12 lines / 300 characters unless you ask for depth.
|
|
23
|
+
- **Standalone.** No "as mentioned above" / "the previous step"; codes such as `EXP-7`, `DEV-1K`, `run-3` get a one-line gloss at first use.
|
|
24
|
+
- **Citations you can read.** A `file:line` reference always comes with the quoted lines.
|
|
25
|
+
- **User vocabulary.** Words you have used, project names and common terms count as known; "research X" still explains X on the first reply, then remembers it.
|
|
26
|
+
|
|
27
|
+
## Results
|
|
28
|
+
|
|
29
|
+
Environment: DeepSeek Harness `desktop` profile running headless, model `cmc/deepseek/deepseek-v4.1-flash` (high), 2026-11-04. Same prompt, same workspace, only the switch changes; the vocabulary is cleared before each arm.
|
|
30
|
+
|
|
31
|
+
### Summary
|
|
32
|
+
|
|
33
|
+
| Scenario | Metric | Off | On | Change |
|
|
34
|
+
|---|---|---|---|---|
|
|
35
|
+
| A. Completion task (4 files + verification) | final reply chars | 144 | 189 | +45 (adds a verdict and a self-check step) |
|
|
36
|
+
| | output tokens | 2,333 | 1,344 | **−42%** |
|
|
37
|
+
| | tool calls | 12 | 11 | −1 |
|
|
38
|
+
| | elapsed | 131 s | 107 s | −24 s |
|
|
39
|
+
| B. Explanation | final reply chars | 410 | 350 | **−15%** |
|
|
40
|
+
| | output tokens | 732 | 326 | **−55%** |
|
|
41
|
+
| | cross-turn references | 2 | 0 | **−2** |
|
|
42
|
+
| C. Report rewrite (turn a draft into a user-facing report) | final reply chars | 558 | 496 | **−11%** |
|
|
43
|
+
| | output tokens | 2,781 | 3,066 | +10% (one extra self-check round) |
|
|
44
|
+
| | unexplained codes | 6 | 0 | **−6** |
|
|
45
|
+
| | pointer-only citations | 1 | 0 | **−1** |
|
|
46
|
+
|
|
47
|
+
The rules section costs about +589 input tokens per step (single-step, zero-tool B run: 260,351 → 260,940).
|
|
48
|
+
|
|
49
|
+
### A. Completion task
|
|
50
|
+
|
|
51
|
+
| Aspect | Off | On |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| First line | 任务完成,产物都在 `bench-demo/` 目录: | 已达标:`bench-demo` 下四个文件已就位,运行脚本输出 `ABC`。 |
|
|
54
|
+
| Artifacts | `a.txt`, `b.txt`, `c.txt` hold A, B, C; `check.mjs` reads them in order and prints the joined result | Each file holds exactly one line; `check.mjs` reads, joins and prints, measured output `ABC` |
|
|
55
|
+
| Reader check | Run `node check.mjs`; output `ABC`, exit code 0. | Run `node check.mjs` in `bench-demo`; expect one line `ABC`. |
|
|
56
|
+
| Chars / output tokens | 144 / 2,333 | 189 / 1,344 |
|
|
57
|
+
|
|
58
|
+
### B. Explanation
|
|
59
|
+
|
|
60
|
+
| Aspect | Off | On |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Paragraph 1 | 多数人以为这只是"上下文窗口不够长",其实窗口够长也会忘。一是截断与压缩……二是注意力是软检索……三是位置编码在超长距离上外推能力下降。 | 简单说,模型没有"记忆",它每次回答只能看到当前上下文窗口里的全部 token;窗口塞满后,早先的对话要么被截断、要么被压缩成摘要。 |
|
|
63
|
+
| Paragraph 2 | 再往深一层,是训练目标没给"长期记忆"发工资。预训练是逐 token 预测,几乎不惩罚"忘了前面说过什么"…… | 另一层原因是训练时的分布。模型主要在短文本上学习长距离依赖,真到几万 token 的跨度,关联能力本来就弱…… |
|
|
64
|
+
| Cross-turn references | 2 | 0 |
|
|
65
|
+
| Chars / output tokens | 410 / 732 | 350 / 326 |
|
|
66
|
+
|
|
67
|
+
### C. Report rewrite
|
|
68
|
+
|
|
69
|
+
The draft contains codes, cross-turn wording and a pointer-only citation. Project codes are replaced with neutral placeholders in this example.
|
|
70
|
+
|
|
71
|
+
| Aspect | Off | On |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Verdict line | 结论:EXP-7(新加的实例校验模块)这次不合并。加了它的 CAND 定位精度 61.2% → 63.8%……没到 3 个点的门槛。 | 结论:EXP-7(给推理流程加"实例验证"一步的实验)没达到合并门槛,不合并。 |
|
|
74
|
+
| Codes | EXP-7 and CAND glossed; AUROC, pp, round-2 left bare | EXP-7, DEV-1K, run-3, BASE, CAND, round-2 all glossed in place |
|
|
75
|
+
| Citation | "see reports/exp7/summary.md:42", no quoted content | says the file is not in the repository and drops the citation |
|
|
76
|
+
| Next step | several options in one paragraph | one action |
|
|
77
|
+
| Chars / output tokens | 558 / 2,781 | 496 / 3,066 |
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
Requires a DeepSeek Harness build with the `conversation.input.left` slot (0.2.x line).
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
dsh plugin --profile desktop add dsh-plain-talk
|
|
85
|
+
dsh plugin --profile desktop add github:lizilong1993/dsh-plain-talk
|
|
86
|
+
dsh plugin --profile desktop add ./dsh-plain-talk
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Restart DSH afterwards.
|
|
90
|
+
|
|
91
|
+
## Use
|
|
92
|
+
|
|
93
|
+
1. Open any session: a speech-bubble icon appears next to the permission selector in the composer.
|
|
94
|
+
2. It starts on (blue). Click it to turn it off (grey) for that session only.
|
|
95
|
+
3. State lives in `$DSH_HOME/plain-talk.json` and survives restarts.
|
|
96
|
+
4. Want new sessions to start off? Set `PLAIN_TALK_DEFAULT=off` where DSH launches.
|
|
97
|
+
|
|
98
|
+
## Vocabulary
|
|
99
|
+
|
|
100
|
+
`$DSH_HOME/plain-talk-vocab.json`, shared across sessions.
|
|
101
|
+
|
|
102
|
+
| Category | Meaning | Examples |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| common | built in, never explained | arXiv, DOI, API, GPU |
|
|
105
|
+
| domain | field terms you have used | diffusion model, AUROC |
|
|
106
|
+
| project | repositories, products, workspace names | AcmeService_V2, DataHub_V3 |
|
|
107
|
+
| internal | experiment/task/commit codes | EXP-7, DEV-1K, run-3 |
|
|
108
|
+
| unclassified | recorded, not classified yet | other detected codes |
|
|
109
|
+
|
|
110
|
+
"Research X" is two-phase: X is marked pending, the first reply still explains it, and the turn end records it. Common terms such as arXiv are unaffected.
|
|
111
|
+
|
|
112
|
+
Maintain it with `plain_talk_vocab` (`list` / `add` / `remove` / `classify`) or by editing the JSON.
|
|
113
|
+
|
|
114
|
+
## Self-check tool
|
|
115
|
+
|
|
116
|
+
`plain_talk_check` takes a draft and reports:
|
|
117
|
+
|
|
118
|
+
- errors: cross-turn wording, session/subagent/tool names, pointer-only citations;
|
|
119
|
+
- warnings: unexplained codes, over 12 lines / 300 characters, lists over 5, preamble, closing offer, English-only reply to a Chinese user.
|
|
120
|
+
|
|
121
|
+
The section asks the model to run it for completion reports and replies over 12 lines.
|
|
122
|
+
|
|
123
|
+
## How it works
|
|
124
|
+
|
|
125
|
+
- Host (`dsh/index.js`) registers one `systemPrompt` section (`plain-talk`, order 55) whose text function returns the rules only while that session's switch is on.
|
|
126
|
+
- Browser half (`lib/client.js`) registers into `conversation.input.left` and renders a 24×24 button.
|
|
127
|
+
- The switch is read and written over the loopback route `/plain-talk/state` (127.0.0.1 / ::1 only, 8 KB body limit).
|
|
128
|
+
- Switch and vocabulary are atomic JSON sidecar files; no session events are written.
|
|
129
|
+
|
|
130
|
+
## Data
|
|
131
|
+
|
|
132
|
+
Only `$DSH_HOME/plain-talk.json` and `$DSH_HOME/plain-talk-vocab.json` are written. No network, no upload, no credential access. A corrupt state file reads as "no choice recorded".
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
Environment variable `PLAIN_TALK_DEFAULT` (`on` / `off`, default `on`).
|
|
137
|
+
|
|
138
|
+
## Notes
|
|
139
|
+
|
|
140
|
+
- The button position depends on `conversation.input.left`; a host redesign may move it beside other left-side controls.
|
|
141
|
+
- Rules are prompt constraints; `plain_talk_check` warns but never blocks a send.
|
|
142
|
+
- Vocabulary is shared per user, not per project; code detection is heuristic.
|
|
143
|
+
- With the mode on, each request carries about 590 extra input tokens.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT — see [LICENSE](./LICENSE).
|
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# dsh-plain-talk(说人话)
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/lizilong1993/dsh-plain-talk/main/assets/banner.jpg" alt="dsh-plain-talk — 说人话" width="100%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="README.en.md">English</a> · <strong>中文</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-263146?style=flat-square"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/dsh-plain-talk"><img alt="npm" src="https://img.shields.io/npm/v/dsh-plain-talk?style=flat-square"></a>
|
|
14
|
+
<img alt="dsh-plugin" src="https://img.shields.io/badge/dsh--plugin-ready-478CBF?style=flat-square&logo=deepseek&logoColor=white">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
DeepSeek Harness 的回复优化插件:输入框里一个「说人话」开关。打开后,回复先给结论、默认不超过 12 行、引用直接贴原文、代号第一次出现时用一句人话解释,并记住你已经认识的词。
|
|
18
|
+
|
|
19
|
+
## 它做什么
|
|
20
|
+
|
|
21
|
+
- **结论先行**:第一行就是答案或判定,不复述问题,不写"我来看看"。
|
|
22
|
+
- **默认短**:≤12 行 / 300 字;你要求展开时才放开深度。
|
|
23
|
+
- **自包含**:不用"上述 / 前面 / 上一轮";代号(EXP-7、DEV-1K、run-3)第一次出现写成 `EXP-7(一句话说明)`。
|
|
24
|
+
- **引用可读**:给 `文件:行号` 的同时贴出那几行原文。
|
|
25
|
+
- **词汇清单**:你说过的词、项目名、常见术语默认已知;"调研 X"的第一轮仍会解释 X,回复交付后才记住。
|
|
26
|
+
|
|
27
|
+
## 效果
|
|
28
|
+
|
|
29
|
+
测试环境:DeepSeek Harness `desktop` profile、headless 运行、模型 `cmc/deepseek/deepseek-v4.1-flash`(high),2026-11-04。同一提示词、同一工作区,只切换开关;两侧开始前都清空词汇清单。
|
|
30
|
+
|
|
31
|
+
### 汇总
|
|
32
|
+
|
|
33
|
+
| 场景 | 指标 | 关闭 | 开启 | 变化 |
|
|
34
|
+
|---|---|---|---|---|
|
|
35
|
+
| A 完成类任务(建 4 个文件并验证) | 最终回复字数 | 144 | 189 | +45(多"达标"和"你自己确认") |
|
|
36
|
+
| | 输出 token | 2,333 | 1,344 | **−42%** |
|
|
37
|
+
| | 工具调用 | 12 | 11 | −1 |
|
|
38
|
+
| | 耗时 | 131 s | 107 s | −24 s |
|
|
39
|
+
| B 解释类(为什么模型会忘事) | 最终回复字数 | 410 | 350 | **−15%** |
|
|
40
|
+
| | 输出 token | 732 | 326 | **−55%** |
|
|
41
|
+
| | 回指次数 | 2 | 0 | **−2** |
|
|
42
|
+
| C 报告整理(把草稿改成给用户的汇报) | 最终回复字数 | 558 | 496 | **−11%** |
|
|
43
|
+
| | 输出 token | 2,781 | 3,066 | +10%(多一次自检) |
|
|
44
|
+
| | 未解释代号 | 6 | 0 | **−6** |
|
|
45
|
+
| | 只给指针的引用 | 1 | 0 | **−1** |
|
|
46
|
+
|
|
47
|
+
规则段每步约 +589 input token(C 组单步、零工具:260,351 → 260,940)。
|
|
48
|
+
|
|
49
|
+
### A 完成类:同一任务,两侧输出
|
|
50
|
+
|
|
51
|
+
| 对比点 | 关闭 | 开启 |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| 首行 | 任务完成,产物都在 `bench-demo/` 目录: | 已达标:`bench-demo` 下四个文件已就位,运行脚本输出 `ABC`。 |
|
|
54
|
+
| 产物说明 | `a.txt`、`b.txt`、`c.txt` 内容分别是 A、B、C;`check.mjs` 按 a→b→c 顺序读取,去掉换行后拼接输出 | `a.txt`、`b.txt`、`c.txt` 各只有一行 `A`、`B`、`C`;`check.mjs` 按 a → b → c 读取、拼接并打印,实测输出:`ABC` |
|
|
55
|
+
| 读者确认 | 运行 `node check.mjs`,输出 `ABC`,退出码 0。 | 你自己确认:在 `bench-demo` 目录执行 `node check.mjs`,应看到一行 `ABC`。 |
|
|
56
|
+
| 字数 / 输出 token | 144 / 2,333 | 189 / 1,344 |
|
|
57
|
+
|
|
58
|
+
### B 解释类:同一问题,两侧输出
|
|
59
|
+
|
|
60
|
+
| 对比点 | 关闭 | 开启 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| 第一段 | 多数人以为这只是"上下文窗口不够长",其实窗口够长也会忘。一是截断与压缩……二是注意力是软检索……三是位置编码在超长距离上外推能力下降。 | 简单说,模型没有"记忆",它每次回答只能看到当前上下文窗口里的全部 token;窗口塞满后,早先的对话要么被截断、要么被压缩成摘要。 |
|
|
63
|
+
| 第二段 | 再往深一层,是训练目标没给"长期记忆"发工资。预训练是逐 token 预测,几乎不惩罚"忘了前面说过什么"…… | 另一层原因是训练时的分布。模型主要在短文本上学习长距离依赖,真到几万 token 的跨度,关联能力本来就弱…… |
|
|
64
|
+
| 回指 | 2 处("前面说过"等) | 0 |
|
|
65
|
+
| 字数 / 输出 token | 410 / 732 | 350 / 326 |
|
|
66
|
+
|
|
67
|
+
### C 报告整理:同一份草稿,两侧整理结果
|
|
68
|
+
|
|
69
|
+
草稿里放了代号、回指和只给指针的引用。示例中的项目代号已替换为中性占位符。
|
|
70
|
+
|
|
71
|
+
| 对比点 | 关闭 | 开启 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| 结论句 | 结论:EXP-7(新加的实例校验模块)这次不合并。加了它的 CAND 定位精度 61.2% → 63.8%,涨 2.6 个百分点,没到 3 个点的门槛。 | 结论:EXP-7(给推理流程加"实例验证"一步的实验)没达到合并门槛,不合并。 |
|
|
74
|
+
| 代号处理 | EXP-7、CAND 有解释;AUROC、pp、round-2 仍是裸代号 | EXP-7、DEV-1K、run-3、BASE、CAND、round-2 全部就地解释 |
|
|
75
|
+
| 引用 | 只写"见 reports/exp7/summary.md:42",没有原文 | 说明该文件不在仓库里,这条不引用 |
|
|
76
|
+
| 下一步 | 一段话里给了多个选项 | 只给一个动作 |
|
|
77
|
+
| 字数 / 输出 token | 558 / 2,781 | 496 / 3,066 |
|
|
78
|
+
|
|
79
|
+
## 安装
|
|
80
|
+
|
|
81
|
+
需要带 `conversation.input.left` 插槽的 DeepSeek Harness(0.2.x 线)。
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
# npm(推荐,预构建)
|
|
85
|
+
dsh plugin --profile desktop add dsh-plain-talk
|
|
86
|
+
|
|
87
|
+
# GitHub
|
|
88
|
+
dsh plugin --profile desktop add github:lizilong1993/dsh-plain-talk
|
|
89
|
+
|
|
90
|
+
# 本地目录
|
|
91
|
+
dsh plugin --profile desktop add ./dsh-plain-talk
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
装完重启 DSH。
|
|
95
|
+
|
|
96
|
+
## 使用
|
|
97
|
+
|
|
98
|
+
1. 打开任意会话,输入框权限选择器右侧会多一个气泡图标。
|
|
99
|
+
2. 默认开启(蓝色);点一下变灰,只关掉当前会话。
|
|
100
|
+
3. 状态写在 `$DSH_HOME/plain-talk.json`,重启后保持。
|
|
101
|
+
4. 想让新会话默认关闭:启动环境里设 `PLAIN_TALK_DEFAULT=off`。
|
|
102
|
+
|
|
103
|
+
## 词汇清单
|
|
104
|
+
|
|
105
|
+
`$DSH_HOME/plain-talk-vocab.json`,跨会话共享。
|
|
106
|
+
|
|
107
|
+
| 类别 | 含义 | 例子 |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| 常识词汇 | 内置,不解释 | arXiv、DOI、API、GPU |
|
|
110
|
+
| 专业词汇 | 你用过的领域词 | 扩散模型、AUROC |
|
|
111
|
+
| 项目 / 产品名 | 仓库、产品、工作区名 | AcmeService_V2、DataHub_V3 |
|
|
112
|
+
| 内部代号 | 实验号、任务号、提交号 | EXP-7、DEV-1K、run-3 |
|
|
113
|
+
| 待归类 | 已记录、未分类 | 其他识别到的代号 |
|
|
114
|
+
|
|
115
|
+
"调研 X"分两步:X 先记为待确认,第一轮回复照常解释;回复交付后写入词表。常识词(如 arXiv)不受影响。
|
|
116
|
+
|
|
117
|
+
维护用 `plain_talk_vocab`:`list` / `add` / `remove` / `classify`;也可以直接改 JSON。
|
|
118
|
+
|
|
119
|
+
## 自检工具
|
|
120
|
+
|
|
121
|
+
`plain_talk_check` 接收一条草稿,返回问题清单:
|
|
122
|
+
|
|
123
|
+
- 必须改:回指词、会话号 / 子代理 / 工具名、只给指针没有原文。
|
|
124
|
+
- 建议改:未解释代号、超过 12 行 / 300 字、列表超过 5 条、开头寒暄、结尾征询、中文用户却整段英文。
|
|
125
|
+
|
|
126
|
+
规则区段要求模型在完成类回复或超过 12 行的回复发送前调用它。
|
|
127
|
+
|
|
128
|
+
## 工作原理
|
|
129
|
+
|
|
130
|
+
- 宿主端(`dsh/index.js`)注册一个系统提示词段落(`plain-talk`,order 55),只有当前会话开关打开时返回规则正文。
|
|
131
|
+
- 浏览器端(`lib/client.js`)注册进 `conversation.input.left`,渲染 24×24 气泡按钮。
|
|
132
|
+
- 开关经 loopback 路由 `/plain-talk/state` 读写(仅 127.0.0.1 / ::1,body ≤8 KB)。
|
|
133
|
+
- 状态与词表都是 JSON sidecar,原子写;不写 session 事件。
|
|
134
|
+
|
|
135
|
+
## 数据与权限
|
|
136
|
+
|
|
137
|
+
只写 `$DSH_HOME/plain-talk.json` 和 `$DSH_HOME/plain-talk-vocab.json`。不联网、不上传、不读凭据;状态文件损坏时按"无记录"处理。
|
|
138
|
+
|
|
139
|
+
## 配置
|
|
140
|
+
|
|
141
|
+
只有环境变量 `PLAIN_TALK_DEFAULT`(`on` / `off`,默认 `on`)。
|
|
142
|
+
|
|
143
|
+
## 注意
|
|
144
|
+
|
|
145
|
+
- 按钮位置依赖 `conversation.input.left`,宿主改版后可能移到其它左侧控件旁。
|
|
146
|
+
- 规则是提示词约束,`plain_talk_check` 只提醒、不拦截发送。
|
|
147
|
+
- 词表按用户共享,不按项目隔离;代号识别是启发式判断。
|
|
148
|
+
- 默认开启时每个请求多约 590 input token。
|
|
149
|
+
|
|
150
|
+
## 许可
|
|
151
|
+
|
|
152
|
+
MIT —— 见 [LICENSE](./LICENSE)。
|
package/cordis.patch.yml
ADDED
package/dsh/check.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Reply checker for dsh-plain-talk.
|
|
2
|
+
//
|
|
3
|
+
// This is deliberately a checker, not a gate: it reports what a reader would
|
|
4
|
+
// stumble on, with the exact replacement form. The model decides what to keep.
|
|
5
|
+
//
|
|
6
|
+
// Three principles learned from real sessions in this workspace:
|
|
7
|
+
// - Common terms and project names are not errors. Only opaque codes the
|
|
8
|
+
// reader has never seen (EXP-7, DEV-1K, run-3) need a first-use gloss.
|
|
9
|
+
// - A pointer is not a citation: `file.py:42` alone sends the reader away.
|
|
10
|
+
// - Cross-turn wording ("上述", "上一轮") is the main reason a report cannot
|
|
11
|
+
// be read on its own.
|
|
12
|
+
|
|
13
|
+
const ALLOW = new Set([
|
|
14
|
+
'api', 'json', 'jsonl', 'yaml', 'yml', 'html', 'css', 'http', 'https', 'url', 'uri',
|
|
15
|
+
'cli', 'ui', 'ux', 'gpu', 'cpu', 'ram', 'ssd', 'hdd', 'ai', 'llm', 'sdk', 'ide',
|
|
16
|
+
'csv', 'tsv', 'ts', 'js', 'py', 'sh', 'md', 'txt', 'png', 'jpg', 'jpeg', 'svg',
|
|
17
|
+
'gif', 'webp', 'sql', 'xml', 'toml', 'npm', 'pnpm', 'git', 'ssh', 'dns', 'tcp',
|
|
18
|
+
'udp', 'tls', 'ssl', 'jwt', 'oauth', 'cors', 'orm', 'crud', 'rest', 'grpc',
|
|
19
|
+
'wasm', 'dom', 'smtp', 'imap', 'vpn', 'utf8', 'utf-8', 'utf16', 'utf-16',
|
|
20
|
+
'sha1', 'sha256', 'sha-256', 'sha512', 'sha-512', 'md5', 'x86', 'x64', 'arm64',
|
|
21
|
+
'mp3', 'mp4', 'pdf', 'h264', 'hevc', 'ipv4', 'ipv6', 'k8s', 'i18n', 'l10n',
|
|
22
|
+
'2d', '3d', '4k', 'v1', 'v2', 'v3', 'v4', 'ci', 'pr', 'id', 'ok', 'vs', 'iso',
|
|
23
|
+
'doi', 'arxiv', 'p50', 'p95', 'p99', '2x', '4x', '8x', '16x', 'gpt', 'llm',
|
|
24
|
+
'linux', 'windows', 'macos', 'node', 'react', 'github', 'python', 'javascript',
|
|
25
|
+
'typescript', 'docker', 'sqlite', 'postgres', 'redis',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const MODEL_FAMILY = /^(?:gpt|claude|gemini|llama|qwen|deepseek|mistral|phi|o1|o3|o4|glm|kimi|mimo|hy)\d*[-.]?\d*/i
|
|
29
|
+
|
|
30
|
+
const BACKREF_ZH = [
|
|
31
|
+
'上述', '上面', '前述', '前文', '前面提到', '前面说', '之前提到', '之前说',
|
|
32
|
+
'此前', '上一步', '上一轮', '前一轮', '刚才', '该方案', '该项', '该任务',
|
|
33
|
+
'前者', '后者', '如上', '见上文', '先前', '那次', '前面', '之前',
|
|
34
|
+
]
|
|
35
|
+
const BACKREF_EN = [
|
|
36
|
+
'as mentioned', 'aforementioned', 'as discussed', 'the above', 'previous step',
|
|
37
|
+
'earlier', 'previously', 'the former', 'the latter',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
const PLUMBING = [
|
|
41
|
+
/\bsession[-_][0-9a-z-]{6,}/i,
|
|
42
|
+
/子代理|子会话|父代理|父会话|\bsubagent\b|\bparent agent\b|report sent to/i,
|
|
43
|
+
/\b(?:plain_talk_check|run_code|ops_bash|ops_grep|todo_write)\b/,
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const PREAMBLE = /^(好的|好|当然|没问题|收到|明白|我来|让我|让我先|我们先|首先,我|先看一[下看])/
|
|
47
|
+
const CLOSER = /(需要我|要不要|如果你愿意|希望(这|以上)|随时告诉我|还需要我|要我继续)/
|
|
48
|
+
|
|
49
|
+
const FILE_EXT = 'py|js|mjs|cjs|ts|tsx|json|jsonl|md|ya?ml|toml|sh|ps1|bat|csv|tsv|txt|ipynb|log|html|css|tex|bib|pdf|zip|sql|db|pt|pth|ckpt|npy|npz|parquet|docx|xlsx|pptx'
|
|
50
|
+
|
|
51
|
+
const IDENT_PATTERNS = [
|
|
52
|
+
['file', new RegExp(`(?:[A-Za-z]:[\\\\/]|\\/)?[A-Za-z0-9_.-]+(?:[\\\\/][A-Za-z0-9_.-]+)*\\.(?:${FILE_EXT})\\b`, 'gi')],
|
|
53
|
+
['hash', /\b[0-9a-f]{7,40}\b/gi],
|
|
54
|
+
['snake', /\b[A-Za-z][A-Za-z0-9]*(?:_[A-Za-z0-9]+)+\b/g],
|
|
55
|
+
['camel', /\b[a-z]+[A-Z][A-Za-z0-9]*\b|\b(?:[A-Z][a-z0-9]+){2,}[A-Za-z0-9]*\b|\b[A-Z][a-z]+[A-Z]{2,}[A-Za-z0-9]*\b/g],
|
|
56
|
+
['acronym', /\b[A-Z]{2,8}\b/g],
|
|
57
|
+
['code', /\b[A-Za-z]{1,10}[-_.]?\d{1,6}[A-Za-z]?\b|\b\d{1,4}[A-Za-z]{1,6}\b/g],
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
export const LIMITS = {
|
|
61
|
+
boxChars: 300,
|
|
62
|
+
boxLines: 12,
|
|
63
|
+
listRun: 5,
|
|
64
|
+
identifierWarns: 8,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cjkCount = (s) => (s.match(/[\u4e00-\u9fff]/g) ?? []).length
|
|
68
|
+
const latinCount = (s) => (s.match(/[A-Za-z]/g) ?? []).length
|
|
69
|
+
const len = (s) => [...s].length
|
|
70
|
+
|
|
71
|
+
function stripCode(text) {
|
|
72
|
+
return text
|
|
73
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
74
|
+
.replace(/`[^`\n]*`/g, ' ')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isCommonTerm(term) {
|
|
78
|
+
const text = String(term ?? '').trim()
|
|
79
|
+
if (!text) return false
|
|
80
|
+
return ALLOW.has(text.toLowerCase()) || MODEL_FAMILY.test(text)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function scanIdentifiers(input) { const text = stripCode(String(input ?? ''))
|
|
84
|
+
const found = []
|
|
85
|
+
for (const [kind, re] of IDENT_PATTERNS) {
|
|
86
|
+
re.lastIndex = 0
|
|
87
|
+
let m
|
|
88
|
+
while ((m = re.exec(text))) {
|
|
89
|
+
const raw = m[0].replace(/[.,;:]+$/, '')
|
|
90
|
+
if (!raw) continue
|
|
91
|
+
found.push({ kind, token: raw, start: m.index, end: m.index + raw.length })
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
found.sort((a, b) => a.start - b.start || (b.end - b.start) - (a.end - a.start))
|
|
95
|
+
const accepted = []
|
|
96
|
+
for (const item of found) {
|
|
97
|
+
if (accepted.some((a) => item.start < a.end && item.end > a.start)) continue
|
|
98
|
+
accepted.push(item)
|
|
99
|
+
}
|
|
100
|
+
return accepted
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isAllowed(token, known) {
|
|
104
|
+
const lower = token.toLowerCase()
|
|
105
|
+
if (ALLOW.has(lower)) return true
|
|
106
|
+
if (MODEL_FAMILY.test(token)) return true
|
|
107
|
+
// Durations and multipliers ("10m", "3s", "4x") are measurements, not names.
|
|
108
|
+
if (/^\d+(?:ms|s|m|h|d|w|k|m|b|x)$/i.test(token)) return true
|
|
109
|
+
// Product-style names (AcmeService_V2, DataHub_V3) are project vocabulary the
|
|
110
|
+
// reader already holds; a lower-case internal key (tier_reaudit) is not.
|
|
111
|
+
if (/^[A-Za-z0-9]+(?:_[A-Za-z0-9]+)+$/.test(token) && (token.match(/[A-Z]/g) ?? []).length >= 2) return true
|
|
112
|
+
if (known && (known.has(token) || known.has(lower))) return true
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function explainedAfter(text, end) {
|
|
117
|
+
const after = text.slice(end, end + 60)
|
|
118
|
+
const m = /^\s*[((]([^))]{4,})[))]/.exec(after)
|
|
119
|
+
if (!m) return false
|
|
120
|
+
return cjkCount(m[1]) >= 3 || latinCount(m[1]) >= 8
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function knownFrom(text, known) {
|
|
124
|
+
return { text, known }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Analyze one reply draft.
|
|
129
|
+
*
|
|
130
|
+
* @param draft - the reply text the model is about to send.
|
|
131
|
+
* @param options - `knownTokens` (project/user vocabulary) and `language`
|
|
132
|
+
* (`zh` when the user writes Chinese).
|
|
133
|
+
* @returns issues plus the measurements the README benchmark reports.
|
|
134
|
+
*/
|
|
135
|
+
export function analyze(draft, options = {}) {
|
|
136
|
+
const text = String(draft ?? '')
|
|
137
|
+
const known = new Set((options.knownTokens ?? []).map((t) => String(t)))
|
|
138
|
+
const knownLower = new Set([...known].map((t) => t.toLowerCase()))
|
|
139
|
+
const language = options.language ?? 'unknown'
|
|
140
|
+
const issues = []
|
|
141
|
+
|
|
142
|
+
const add = (severity, rule, message, fix) => issues.push({ severity, rule, message, fix })
|
|
143
|
+
|
|
144
|
+
const narrative = stripCode(text)
|
|
145
|
+
|
|
146
|
+
// 1. Cross-turn wording.
|
|
147
|
+
const backrefs = [
|
|
148
|
+
...BACKREF_ZH.filter((w) => narrative.includes(w)),
|
|
149
|
+
...BACKREF_EN.filter((w) => narrative.toLowerCase().includes(w)),
|
|
150
|
+
]
|
|
151
|
+
if (backrefs.length) {
|
|
152
|
+
add('error', 'backref', `出现回指词:${backrefs.slice(0, 5).join('、')}。`, '把指代的东西重新写一遍,读者没有前文可翻。')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. Internal plumbing.
|
|
156
|
+
const plumbing = PLUMBING.find((re) => re.test(narrative))
|
|
157
|
+
if (plumbing) {
|
|
158
|
+
add('error', 'internal', '出现会话号、子代理或工具名等内部信息。', '删掉;只写用户能看见的对象。')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 3. Pointer-only citations.
|
|
162
|
+
const pointers = []
|
|
163
|
+
const pointerRe = new RegExp(`(?:[A-Za-z]:[\\\\/]|/)?[\\w.-]+(?:[\\\\/][\\w.-]+)*\\.(?:${FILE_EXT}):\\d+`, 'gi')
|
|
164
|
+
pointerRe.lastIndex = 0
|
|
165
|
+
let pm
|
|
166
|
+
while ((pm = pointerRe.exec(text))) pointers.push(pm[0])
|
|
167
|
+
if (pointers.length) {
|
|
168
|
+
const unquoted = pointers.filter((pointer) => {
|
|
169
|
+
const at = text.indexOf(pointer)
|
|
170
|
+
const window = text.slice(Math.max(0, at - 220), at + pointer.length + 220)
|
|
171
|
+
return !/「[^」]{4,}」|"[^"]{4,}"|『[^』]{4,}』|`[^`]{4,}`|```/.test(window)
|
|
172
|
+
})
|
|
173
|
+
if (unquoted.length) {
|
|
174
|
+
add('error', 'citation', `只有位置没有原文:${unquoted.slice(0, 3).join('、')}。`, '把那几行原文贴出来,再标位置。')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 4. Unexplained opaque identifiers.
|
|
179
|
+
const seen = new Set()
|
|
180
|
+
const unknown = []
|
|
181
|
+
for (const token of scanIdentifiers(narrative)) {
|
|
182
|
+
if (seen.has(token.token)) continue
|
|
183
|
+
seen.add(token.token)
|
|
184
|
+
if (isAllowed(token.token, known) || isAllowed(token.token, knownLower)) continue
|
|
185
|
+
if (explainedAfter(narrative, token.end)) continue
|
|
186
|
+
unknown.push(token.token)
|
|
187
|
+
}
|
|
188
|
+
if (unknown.length) {
|
|
189
|
+
add(
|
|
190
|
+
'warn',
|
|
191
|
+
'identifier',
|
|
192
|
+
`有 ${unknown.length} 个代号没有说明:${unknown.slice(0, 6).join('、')}${unknown.length > 6 ? ' 等' : ''}。`,
|
|
193
|
+
'第一次出现写成「EXP-7(一句话说明它是什么)」;用户在本对话用过的词和项目名不用解释。',
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 5. Shape.
|
|
198
|
+
const lines = text.split('\n').filter((l) => l.trim())
|
|
199
|
+
const cjk = cjkCount(narrative)
|
|
200
|
+
if (cjk > LIMITS.boxChars || lines.length > LIMITS.boxLines) {
|
|
201
|
+
add('warn', 'length', `回复有 ${cjk} 个汉字、${lines.length} 行,超过默认的 ${LIMITS.boxChars} 字 / ${LIMITS.boxLines} 行。`, '删掉读者不决策所需的内容;用户要细节时再展开。')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let run = 0
|
|
205
|
+
let maxRun = 0
|
|
206
|
+
for (const line of text.split('\n')) {
|
|
207
|
+
if (/^\s*(?:[-*+]|\d+[.)])\s+/.test(line)) { run++; maxRun = Math.max(maxRun, run) } else if (!line.trim()) { run = 0 }
|
|
208
|
+
}
|
|
209
|
+
if (maxRun > LIMITS.listRun) {
|
|
210
|
+
add('warn', 'list', `有一段列表 ${maxRun} 条,超过 5 条。`, '合并或只留最要紧的五条。')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const first = lines[0] ?? ''
|
|
214
|
+
if (PREAMBLE.test(first)) {
|
|
215
|
+
add('warn', 'preamble', `开头是「${first.slice(0, 20)}」,属于寒暄或过程宣告。`, '第一行直接给结论。')
|
|
216
|
+
}
|
|
217
|
+
const tail = text.slice(-180)
|
|
218
|
+
const closer = CLOSER.exec(tail)
|
|
219
|
+
if (closer) {
|
|
220
|
+
add('warn', 'closer', `结尾出现「${closer[0]}」,在征求额外任务。`, '删掉;需要下一步就只给一个具体动作。')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 6. Language mismatch.
|
|
224
|
+
const total = cjk + latinCount(narrative)
|
|
225
|
+
const cjkRatio = total === 0 ? 0 : cjk / total
|
|
226
|
+
if (language === 'zh' && cjkRatio < 0.2 && latinCount(narrative) > 40) {
|
|
227
|
+
add('warn', 'language', '用户用中文提问,但这条回复几乎全是英文。', '用中文写;命令、路径、报错原文保持原样。')
|
|
228
|
+
}
|
|
229
|
+
const hasNext = /下一步|接下来要|接下来请|你可以先|建议你先|Next step/i.test(text)
|
|
230
|
+
if (!hasNext && (cjk > 200 || lines.length > 10)) {
|
|
231
|
+
add('warn', 'next', '回复较长,但没有给出一个明确的下一步。', '结尾补一个用户现在就能做的事;没有就写"没有待办"。')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
ok: !issues.some((i) => i.severity === 'error'),
|
|
236
|
+
issues,
|
|
237
|
+
stats: {
|
|
238
|
+
chars: text.length,
|
|
239
|
+
cjk,
|
|
240
|
+
lines: lines.length,
|
|
241
|
+
cjkRatio: Number(cjkRatio.toFixed(3)),
|
|
242
|
+
identifiers: unknown.length,
|
|
243
|
+
backrefs: backrefs.length,
|
|
244
|
+
maxListRun: maxRun,
|
|
245
|
+
hasNext,
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export { cjkCount, latinCount, len }
|