clearai-dsh 0.2.0 → 0.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 +38 -2
- package/README.md +31 -3
- package/README.zh-CN.md +32 -4
- package/bin/clearai.mjs +171 -42
- package/lib/client.js +563 -117
- package/lib/domain-language.js +43 -25
- package/lib/fold.js +710 -6
- package/lib/host.js +41 -1
- package/package.json +14 -3
- package/presets/clearai/agent.cordis.yml +7 -0
- package/presets/clearai/plugins/clearai-kernel.js +122 -8
- package/presets/clearai/plugins/prompts.js +18 -3
- package/presets/clearai/preset.yml +3 -3
- package/presets/clearai/skills/clearai-loop/SKILL.md +16 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## [0.2.2] — 2026-09-18
|
|
6
|
+
|
|
7
|
+
**装的时候不再吓人。** 0.2.1 的 `npx clearai-dsh install` 会打出一串 peer 警告(react / graphology-types …),读起来像装坏了——而它们一个字都不影响运行。这一版把安装面收窄到运行时真正需要的那一个依赖,并让安装侧 CLI 按系统语言出话。
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **安装面只剩一个运行时依赖。** `@xyflow/react` / `graphology` / `graphology-layout-forceatlas2` / `docx` 挪进 `devDependencies`:前三个只在构建期打 vendor(`lib/client.js` 里是**内联**的,装机后不解析 npm),`docx` 只给营销 docx 脚本用。于是 profile 里不再多装一批包,也不会再有那些注定填不上的 peer 警告——React 由 **DSH 宿主**提供(客户端半 `require('react')` 是问宿主拿的),`graphology-types` 只是类型包。运行时唯一保留的是 `zod`(宿主半 `lib/host.js` 真的 `from 'zod'`)。
|
|
12
|
+
- **安装侧 CLI 跟系统语言走。** `doctor` / `install` / `root-yaml` / `seed` / `unseed` 的每一句都在中英两份文案表里(并排放在一处,改的时候不会只改一边);判据是 `--lang zh|en` > `CLEARAI_LANG` > `LC_ALL` / `LC_MESSAGES` / `LANG` > ICU 的默认 locale,`C` / `POSIX` 当「没有语言信息」按英文处理。0.2.1 之前是无论系统是什么都说中文。
|
|
13
|
+
|
|
14
|
+
## [0.2.1] — 2026-09-18
|
|
15
|
+
|
|
16
|
+
**知识任务是循环的原生行为,不是另一个模式。** 本体、实体、认识论早就在,但普通研究的最短路径仍然是「检索 → 总结 → 写报告」——要建本体得用户先想起来说一句。这一版修的是**接线缺口**:把知识任务的判据做成结构的(目标还开着 + 带着登记过的假设),系统自己进知识模式;并把图从手写 SVG 换成 React Flow,给了它一个真正的全屏工作区。
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **知识模式(分诊)**:判据是**结构的**——目标还开着,而且它带着登记过的命题。立约(`SetGoal`)本身就是模型已经做出的承诺;普通问答从不立约,于是从不进这一档(**零成本契约**)。词面启发式猜错了没人能复核,结构判据可以。
|
|
21
|
+
- **知识预检**:把「已知」自动送到模型面前——只读、有界、**词面命中不猜语义**;每条读数说得出它来自哪条事实。
|
|
22
|
+
- **缺口读数**:从已有事实算出还缺什么形态,每条指得出一个能补的动作。真跑的反直觉结论:改变行为的其实是**缺口的可见性**,不是门——所以两者都留(可见性让它想做,门不让它绕过)。
|
|
23
|
+
- **知识门**:结案之前、派评估者之前拦住**没有形态的核心结论**——纯散文不许升格。
|
|
24
|
+
- **知识 Inspector**:点节点或边 → 定义 / 关系 / 断言 / 证据链 / 登记与修订史;「按此筛选」是详情里的**显式动作**,不猜你点它的意思。新增宿主只读路由 `/api/clearai/inspector` 与行为测试。
|
|
25
|
+
- **图谱工作区**:图带可展开成全屏工作区,布局是力导向(知识图谱的原生形状);渲染交给 React Flow(`@xyflow/react` 12,vendor 行随构建走,与 `dist/` 同一条纪律)。
|
|
26
|
+
- **哲学 P6**:「本体生长是循环的原生行为,不是另一个模式」——写进[循环哲学](docs/loop-philosophy.zh-CN.md)与[认识论循环](docs/epistemic-loop.zh-CN.md)(中英)。
|
|
27
|
+
- **素材工具** `tools/panel-shots.mjs`:折一场真会话 → 挂**真组件** → 真 Chrome 截图(与 `tools/graph-shots.mjs` 同一条口径)——面板截图从此可复现,不用人去界面里手点。
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **图 DTO 统一**:图带与工作区共用同一份投影,判据只有一处(`fold` → `view()` → `graphProjection()`);「同一份账本 ⇒ 同一张图」是投影的性质。
|
|
32
|
+
- **预设描述**收敛成一句中英并排:「利用认识论循环构建可信本体。Build a trustworthy ontology through the epistemic loop.」——名册只有这两行元数据,宿主不会替我们本地化,所以只能自己写死。
|
|
33
|
+
- **README 的「安装」改为「安装与使用」**:写明怎么在模式选择器里切到 `ClearAI`(默认标准模式不挂认识论循环),并把本体图 / 图带 / Inspector 的真机截图放进去。
|
|
34
|
+
- 营销物料换掉全部陈旧面板截图:知乎稿与 docx、小红书 9 张卡片改用真机会话的投影(统计同步为 15 套件 1618 条断言 · 29 个意图工具 · 6808 行内核 · 50 种事件分支)。
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
6
37
|
|
|
7
|
-
|
|
38
|
+
- **命题身份在修订时被重新签发**:同一句话在 id 空间里躺着两份读数(一份「已支持」、一份「未触及」),真会话的卡实测 4 条主张显示成 6~8 行。现在主张原文不变就复用原 id,换了主张才发新 id;并补上 `hypothesis/superseded` 一直缺席的生产者。
|
|
39
|
+
- **节点拖不动**(两次):受控 `nodes` 没接 `onNodesChange`;拖动键写成了对象,`[object Object]` 查不到。
|
|
40
|
+
- **React Flow 是 forwardRef 对象,不是函数**——守卫把合法组件判成「没装上」;并改为同作用域注入,不再依赖运行时模块行。
|
|
41
|
+
- **图带收不到 sessionId**;时间戳不再显示 1970。
|
|
42
|
+
- **工作区读面有主人**:子会话结构上写不进词汇 / 事实货架。
|
|
43
|
+
- 力导向布局的四个真机缺陷(评估者独立复核后逐条修掉)。
|
|
8
44
|
|
|
9
45
|
## [0.2.0] — 2026-09-17
|
|
10
46
|
|
package/README.md
CHANGED
|
@@ -59,13 +59,28 @@ ClearAI does **not** claim recursive self-improvement. It provides the epistemic
|
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
## Install
|
|
62
|
+
## Install and use
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
npx clearai-dsh install
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
The installer's output follows your system language (`--lang zh|en` overrides it, `doctor` / `seed` / `unseed` take the same flag). Its only runtime dependency is `zod`; the graph stack is bundled into the client half at build time.
|
|
69
|
+
|
|
70
|
+
Restart `dsh web` afterwards (`npx @deepseek-ai/dsh web`), then **create a session and switch to the `ClearAI` mode in the picker at the top**:
|
|
71
|
+
|
|
72
|
+
1. Open `dsh web` and click "New session";
|
|
73
|
+
2. Click the current mode name at the top (default: **Standard mode**) to open the preset list;
|
|
74
|
+
3. Pick **ClearAI** — its card reads "利用认识论循环构建可信本体。Build a trustworthy ontology through the epistemic loop.";
|
|
75
|
+
4. Just ask your question. Ordinary Q&A runs as usual; once you set a goal and register hypotheses, the system enters knowledge mode by itself: known facts come to you, gaps stay visible, and conclusions earn their place.
|
|
76
|
+
|
|
77
|
+
<picture>
|
|
78
|
+
<img src="docs/shots/zh/jepa-ontology.png" alt="The knowledge graph in ClearAI mode" width="820">
|
|
79
|
+
</picture>
|
|
80
|
+
|
|
81
|
+
*The ontology graph in ClearAI mode — this real session grew 21 concepts and 9 predicates; the same ledger always yields the same picture. (UI shown is Chinese.)*
|
|
82
|
+
|
|
83
|
+
If pnpm is not on PATH: `npm install -g pnpm` (do not `corepack enable` — it installs a version forwarder that may download a pnpm it cannot launch).
|
|
69
84
|
|
|
70
85
|
From the repository:
|
|
71
86
|
|
|
@@ -84,7 +99,19 @@ node docs/diagrams/build-hero.mjs # redraw the product hero (needs google-chro
|
|
|
84
99
|
|
|
85
100
|
The middle column has two switchable views: **Deliverables** and **Ontology**. The right sidebar: **Worldlines** and **External Brain**.
|
|
86
101
|
|
|
87
|
-
**Ontology** — this view is your knowledge home. At the top, a **graph band**: the ontology graph (what your domain looks like) and the entity graph (what you have actually verified) toggle with one click; clicking a
|
|
102
|
+
**Ontology** — this view is your knowledge home. At the top, a **graph band**: the ontology graph (what your domain looks like) and the entity graph (what you have actually verified) toggle with one click; clicking a node or edge opens the **knowledge inspector** (definition / relations / assertions / evidence chain / history), and "filter by this" is an explicit action inside the detail view. Below that, the **ontology shelf**: established entries, each with assertion chips (click to see what the term means), boundary, and support level; contradictions surface automatically. The vocabulary maintenance block sits collapsed at the bottom — it auto-expands when a language exists before any sentence does.
|
|
103
|
+
|
|
104
|
+
<picture>
|
|
105
|
+
<img src="docs/shots/zh/jepa-band.png" alt="The graph band: ontology graph and entity graph" width="820">
|
|
106
|
+
</picture>
|
|
107
|
+
|
|
108
|
+
*The band — the ontology graph and the entity graph share one deterministic projection, so the same ledger always yields the same picture (captured from a real session: 21 concepts, 9 predicates).*
|
|
109
|
+
|
|
110
|
+
<picture>
|
|
111
|
+
<img src="docs/shots/zh/jepa-inspector.png" alt="Knowledge inspector: definition, relations, assertions, evidence chain" width="820">
|
|
112
|
+
</picture>
|
|
113
|
+
|
|
114
|
+
*Open any node or edge: definition, relations, assertions, evidence chain, registration and revision history, all in one place. (UI shown is Chinese.)*
|
|
88
115
|
|
|
89
116
|
**Worldlines** — when two routes genuinely disagree, they run as separate branches with their own readings; the losing one stays on record, and adoption is a human press.
|
|
90
117
|
|
|
@@ -107,6 +134,7 @@ The middle column has two switchable views: **Deliverables** and **Ontology**. T
|
|
|
107
134
|
- [Positioning](docs/positioning.md) · [Domain ontology design](docs/domain-ontology.md)
|
|
108
135
|
- [Epistemic loop](docs/epistemic-loop.md) · [Verification ontology](docs/verification-loop.md) · [Loop philosophy](docs/loop-philosophy.md)
|
|
109
136
|
- [Design principles](docs/design-principles.md) · [Soul map](docs/soul-map.md) · [Glossary](docs/glossary.md)
|
|
137
|
+
- [Knowledge-native loop ledger](docs/optimization/knowledge-native-loop.zh-CN.md) (this round: triage / preflight / knowledge gate / graph / inspector; zh-CN)
|
|
110
138
|
- [Development plan](docs/optimization/domain-ontology-plan.md) (with real-run evidence from the Hengtong project)
|
|
111
139
|
- [Known gaps](docs/known-gaps.md) · [Authority map](docs/authority-map.md) · [Release verification](docs/release-verification.md)
|
|
112
140
|
|
package/README.zh-CN.md
CHANGED
|
@@ -44,7 +44,7 @@ ClearAI 是一个**本体发现与探索平台**,核心由两个概念支撑
|
|
|
44
44
|
| 完成 | 模型宣布完成 | 系统按交付的证据算出来 |
|
|
45
45
|
| 裁决 | 谁做的谁说了算 | 分离——超过一定等级,做的人不能判自己 |
|
|
46
46
|
| 失败 | 删掉、重来、忘掉 | 留下:被推翻的命题是结果,不是噪声 |
|
|
47
|
-
| 沉淀 | 一段聊天记录 |
|
|
47
|
+
| 沉淀 | 一段聊天记录 | **一个本体**:本体中的每个关系都有来源和验证记录 |
|
|
48
48
|
|
|
49
49
|
<picture>
|
|
50
50
|
<source media="(prefers-color-scheme: dark)" srcset="docs/diagrams/epistemic-loop-hero-dark.zh-CN.png">
|
|
@@ -59,13 +59,28 @@ ClearAI **不**声称递归自我改进。它提供的是自我改进系统所
|
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## 安装与使用
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
npx clearai-dsh install
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
安装侧的输出**跟系统语言走**(`--lang zh|en` 可覆盖;`doctor` / `seed` / `unseed` 同样认这个开关)。运行时依赖只有 `zod`——图谱那套栈在构建期就打进客户端半了。
|
|
69
|
+
|
|
70
|
+
装完重启 `dsh web`(`npx @deepseek-ai/dsh web`),然后**新建会话,在顶部的模式选择器里切换到 `ClearAI`**:
|
|
71
|
+
|
|
72
|
+
1. 打开 `dsh web`,点「新建会话」;
|
|
73
|
+
2. 点顶部当前的模式名(默认是「标准模式」),展开预设列表;
|
|
74
|
+
3. 选 **ClearAI**——卡片描述就一句:「利用认识论循环构建可信本体。Build a trustworthy ontology through the epistemic loop.」;
|
|
75
|
+
4. 像平常一样提问。普通问答照常走;一旦你立了目标并登记假设,系统自动进入知识模式:预检把已知送上来,缺口如实读出来,结论过证据才准入。
|
|
76
|
+
|
|
77
|
+
<picture>
|
|
78
|
+
<img src="docs/shots/zh/jepa-ontology.png" alt="ClearAI 模式下的知识图谱" width="820">
|
|
79
|
+
</picture>
|
|
80
|
+
|
|
81
|
+
*ClearAI 模式下的本体图——这场真实会话长出 21 个概念、9 条谓词;同一份账本永远得到同一张图。*
|
|
82
|
+
|
|
83
|
+
如果 PATH 上没有 pnpm:`npm install -g pnpm`(别用 `corepack enable`——它装的是版本转发器,可能下载一个自己启动不了的 pnpm)。
|
|
69
84
|
|
|
70
85
|
从仓库开发:
|
|
71
86
|
|
|
@@ -84,7 +99,19 @@ node docs/diagrams/build-hero.mjs # 重画产品主图(需 google-chrome)
|
|
|
84
99
|
|
|
85
100
|
中栏两格可切:**产物**与**本体**。右栏:**世界树**与**外脑**。
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
**本体**——这一格是你的知识主场。顶部是**图带**:本体图(你的领域长什么样)与实体图(已经验证出了什么)一键切换;点节点或边打开**知识 Inspector**(定义 / 关系 / 断言 / 证据链 / 历史),「按此过滤」是详情里的显式动作。下面是**本体货架**:已确立的条目,每条带断言芯片(点开看这个词什么意思)、边界与等级;互相矛盾的自动亮出来。词汇维护区收在最底下——语言先于句子时它自动展开。
|
|
103
|
+
|
|
104
|
+
<picture>
|
|
105
|
+
<img src="docs/shots/zh/jepa-band.png" alt="本体图带:本体图与实体图切换" width="820">
|
|
106
|
+
</picture>
|
|
107
|
+
|
|
108
|
+
*图带——本体图与实体图共用同一份确定性投影,同一份账本永远得到同一张图;素材来自一场真实会话(21 概念 · 9 谓词)。*
|
|
109
|
+
|
|
110
|
+
<picture>
|
|
111
|
+
<img src="docs/shots/zh/jepa-inspector.png" alt="知识 Inspector:定义、关系、断言、证据链" width="820">
|
|
112
|
+
</picture>
|
|
113
|
+
|
|
114
|
+
*点开任一节点或边:定义、关系、断言、证据链、登记与修订史,一处读完。*
|
|
88
115
|
|
|
89
116
|
**世界树**——两条路线真的分歧时,各自独立跑、各自带读数;落选的那条留在记录里,采纳是人按的那一下。
|
|
90
117
|
|
|
@@ -107,6 +134,7 @@ node docs/diagrams/build-hero.mjs # 重画产品主图(需 google-chrome)
|
|
|
107
134
|
- [定位](docs/positioning.zh-CN.md) · [领域本体设计](docs/domain-ontology.zh-CN.md)
|
|
108
135
|
- [认识论循环](docs/epistemic-loop.zh-CN.md) · [验证本体](docs/verification-loop.zh-CN.md) · [循环哲学](docs/loop-philosophy.zh-CN.md)
|
|
109
136
|
- [设计原则](docs/design-principles.zh-CN.md) · [灵魂映射](docs/soul-map.zh-CN.md) · [术语表](docs/glossary.zh-CN.md)
|
|
137
|
+
- [知识原生循环执行账本](docs/optimization/knowledge-native-loop.zh-CN.md)(本轮:分诊 / 预检 / 知识门 / 图 / Inspector)
|
|
110
138
|
- [开发计划](docs/optimization/domain-ontology-plan.zh-CN.md)(含亨通真跑读数)
|
|
111
139
|
- [已知缺口](docs/known-gaps.zh-CN.md) · [权威归属](docs/authority-map.zh-CN.md) · [发布验收](docs/release-verification.zh-CN.md)
|
|
112
140
|
|
package/bin/clearai.mjs
CHANGED
|
@@ -44,6 +44,143 @@ const value = (name, fallback) => {
|
|
|
44
44
|
}
|
|
45
45
|
const profile = value('profile', 'web')
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 语言:**跟系统走,不猜**。
|
|
49
|
+
*
|
|
50
|
+
* 判据顺序:`--lang zh|en` > `CLEARAI_LANG` > `LC_ALL` / `LC_MESSAGES` / `LANG` > ICU 的默认 locale。
|
|
51
|
+
* `C` / `POSIX` 是「没有语言信息」,按英文处理(它们是 CI 与最小容器的默认值,不代表中文)。
|
|
52
|
+
* 为什么认不出来时说英文:这是发给陌生人的第一条命令输出,不该假设对方读中文。
|
|
53
|
+
* 两边的文案**并排放在一张表里**——同一句话的两个版本挨着,改的时候不会只改一边。
|
|
54
|
+
*/
|
|
55
|
+
const lang = (() => {
|
|
56
|
+
const candidates = [value('lang', null), process.env.CLEARAI_LANG, process.env.LC_ALL, process.env.LC_MESSAGES, process.env.LANG, Intl.DateTimeFormat().resolvedOptions().locale]
|
|
57
|
+
for (const candidate of candidates) {
|
|
58
|
+
if (typeof candidate !== 'string' || candidate === '') continue
|
|
59
|
+
const tag = candidate.toLowerCase()
|
|
60
|
+
if (tag.startsWith('zh')) return 'zh'
|
|
61
|
+
if (tag === 'c' || tag === 'posix' || tag.startsWith('en')) return 'en'
|
|
62
|
+
}
|
|
63
|
+
return 'en'
|
|
64
|
+
})()
|
|
65
|
+
|
|
66
|
+
const TEXT = {
|
|
67
|
+
zh: {
|
|
68
|
+
pkgDir: '包目录 ',
|
|
69
|
+
presetSrc: '预设源 ',
|
|
70
|
+
presetOk: '(agent.cordis.yml ✓)',
|
|
71
|
+
presetMissing: '(缺 agent.cordis.yml ✗)',
|
|
72
|
+
dshHome: 'DSH_HOME ',
|
|
73
|
+
userRoot: '用户根 ',
|
|
74
|
+
rosterVisible: '✓ 名册看得见',
|
|
75
|
+
rosterAbsent: '✗ 还没有(用 seed 播种,或把 root-yaml 那一行粘进 profile)',
|
|
76
|
+
shadowCopy: ' ⚠️ 影子副本 {path} 与包里的那份**不一致**,而它被包的 root 遮住、永远不会被读到(自检却会优先读它)。删掉它,或用它来承载你自己的改动并换一个 id。',
|
|
77
|
+
profileRow: 'profile ',
|
|
78
|
+
profileAbsent: '(不存在:先跑一次 dsh --profile {profile})',
|
|
79
|
+
composeRow: '组合 ',
|
|
80
|
+
composeUnreachable: '(问不到:dsh 不可用或超时 —— 下面两条无法判定)',
|
|
81
|
+
hostRow: '宿主行 ',
|
|
82
|
+
hostIn: '在组合里 ✓',
|
|
83
|
+
hostOut: '不在组合里(先 dsh plugin --profile {profile} add <本包>)',
|
|
84
|
+
rosterRootRow: '名册 root ',
|
|
85
|
+
rosterNoRoots: '组合里没看到 roots(这个部署可能不挂名册)',
|
|
86
|
+
rosterExpr: '{expr}(dump 打的是表达式原文,求值在装载时)',
|
|
87
|
+
seedLedgerRow: '播种记账 ',
|
|
88
|
+
ledgerEmpty: '(空)',
|
|
89
|
+
ledgerCount: '{count} 个条目 · {path}',
|
|
90
|
+
doctorHint: '\n提示:doctor 只读;它不会替你改 profile,也不会替你播种。',
|
|
91
|
+
installHeader: '【安装】{name}@{version}',
|
|
92
|
+
installWhat: '装什么 ',
|
|
93
|
+
installWhere: '装到哪 ',
|
|
94
|
+
installWho: '谁来跑 ',
|
|
95
|
+
hostRowShort: '宿主行 ',
|
|
96
|
+
sourceSpec: '你给的 spec',
|
|
97
|
+
sourceTarball: '本地 tarball',
|
|
98
|
+
sourceDir: '本地目录',
|
|
99
|
+
sourceRegistry: 'registry',
|
|
100
|
+
routeNpx: 'npx --yes @deepseek-ai/dsh(PATH 上没有 dsh)',
|
|
101
|
+
pnpmMissing: '\n✗ PATH 上没有 pnpm,而 DSH 管理一个 profile 就是靠它:`dsh plugin …` 把参数转发给 pnpm。\n 装一个再来:npm install -g pnpm(或用系统包管理器,如 brew install pnpm)。\n 别用 corepack enable 抄近路 —— 它装的是版本**转发器**而不是 pnpm,而当前 Node 自带的\n 那份 corepack 可能下载一个它自己启动不了的 pnpm。\n 这里刻意不手工改 profile:那等于把宿主的 reconcile 抄成第二份实现,与宿主漂移时坏的是你的部署。',
|
|
102
|
+
installFailed: '\n✗ 安装失败:见上面的输出。',
|
|
103
|
+
hostUnknown: '(问不到组合:CLI 不可用或超时 —— 装没装进去,从这里确认不了;用 doctor 再看)',
|
|
104
|
+
hostAbsent: '**不在组合里** —— 装是装上了,但组合里没看到它(用 doctor 查)',
|
|
105
|
+
nextStep: '\n 下一步 重启 dsh web(两半都在进程里按模块 URL 缓存,只刷新浏览器不够),然后在预设选择器里选 ClearAI。',
|
|
106
|
+
uninstall: ' 卸载 dsh plugin --profile {profile} remove {name}(同样可以冠 npx)',
|
|
107
|
+
rootYamlComment1: '# ClearAI 预设的 root(由 clearai-dsh 的 bin 打印,路径已算成绝对路径)',
|
|
108
|
+
rootYamlComment2: ' # ⚠️ 补丁层会**替换整份 config**:下面这些键必须与你部署里那份一致,否则会丢。',
|
|
109
|
+
rootYamlComment3: ' # 先 `dsh --profile <p> --dump-config | grep -A 20 agent-presets` 看一眼当前值再粘。',
|
|
110
|
+
noPresetInPackage: '✗ 包里没有预设:{path}',
|
|
111
|
+
seededTo: ' 播种到 {dest}',
|
|
112
|
+
seedCounts: ' 新增 {seeded} · 刷新 {refreshed}',
|
|
113
|
+
seedDrifted: ' · **你改过、没覆盖** {count}({list})',
|
|
114
|
+
seedNext: ' 下一步:重启 dsh(或刷新页面)后,预设选择器里应出现「ClearAI」。',
|
|
115
|
+
noSeedLedger: ' 没有播种记账:什么都不做(不知道哪些是我们播的,就不删)。',
|
|
116
|
+
unseedRemoved: ' 删掉 {count} 个「我们播的、没被改过」的文件',
|
|
117
|
+
unseedKept: ' **保留** {count} 个你改过的文件(它们现在是你的):{list}',
|
|
118
|
+
unknownCommand: 'unknown command: {command}',
|
|
119
|
+
usage: '用法:clearai-dsh [doctor|install|root-yaml|seed|unseed] [--profile web] [--home <dir>] [--lang zh|en]\n install 还可以:--dist <dir> | --tarball <tgz> | --spec <spec>',
|
|
120
|
+
},
|
|
121
|
+
en: {
|
|
122
|
+
pkgDir: 'package ',
|
|
123
|
+
presetSrc: 'preset src ',
|
|
124
|
+
presetOk: '(agent.cordis.yml ✓)',
|
|
125
|
+
presetMissing: '(no agent.cordis.yml ✗)',
|
|
126
|
+
dshHome: 'DSH_HOME ',
|
|
127
|
+
userRoot: 'user root ',
|
|
128
|
+
rosterVisible: '✓ visible to the roster',
|
|
129
|
+
rosterAbsent: '✗ not there yet (seed it, or paste the root-yaml line into the profile)',
|
|
130
|
+
shadowCopy: ' ⚠️ shadow copy {path} differs from the copy in the package, and the package root shadows it — it will never be read (while the self-check reads it first). Delete it, or keep your changes there under a different id.',
|
|
131
|
+
profileRow: 'profile ',
|
|
132
|
+
profileAbsent: '(does not exist yet: run dsh --profile {profile} once)',
|
|
133
|
+
composeRow: 'composition ',
|
|
134
|
+
composeUnreachable: '(could not ask: dsh unavailable or timed out — the next two rows cannot be decided)',
|
|
135
|
+
hostRow: 'host row ',
|
|
136
|
+
hostIn: 'is in the composition ✓',
|
|
137
|
+
hostOut: 'is NOT in the composition (run dsh plugin --profile {profile} add <this package>)',
|
|
138
|
+
rosterRootRow: 'roster root ',
|
|
139
|
+
rosterNoRoots: 'no roots in the composition (this deployment may not mount the roster)',
|
|
140
|
+
rosterExpr: '{expr}(the dump prints the expression verbatim; it is evaluated at load time)',
|
|
141
|
+
seedLedgerRow: 'seed ledger ',
|
|
142
|
+
ledgerEmpty: '(empty)',
|
|
143
|
+
ledgerCount: '{count} entries · {path}',
|
|
144
|
+
doctorHint: '\ndoctor only reads. It will not change your profile and will not seed anything for you.',
|
|
145
|
+
installHeader: '[install] {name}@{version}',
|
|
146
|
+
installWhat: 'what ',
|
|
147
|
+
installWhere: 'where ',
|
|
148
|
+
installWho: 'who runs it ',
|
|
149
|
+
hostRowShort: 'host row ',
|
|
150
|
+
sourceSpec: 'the spec you gave',
|
|
151
|
+
sourceTarball: 'local tarball',
|
|
152
|
+
sourceDir: 'local directory',
|
|
153
|
+
sourceRegistry: 'registry',
|
|
154
|
+
routeNpx: 'npx --yes @deepseek-ai/dsh (no dsh on PATH)',
|
|
155
|
+
pnpmMissing: '\n✗ pnpm is not on PATH, and it is how DSH manages a profile: `dsh plugin …` forwards to it.\n Install one and come back: npm install -g pnpm (or your package manager, e.g. brew install pnpm).\n Do not take the corepack enable shortcut — that installs a version *forwarder*, not pnpm, and the\n corepack bundled with current Node may fetch a pnpm it is unable to launch.\n This verb deliberately does not edit the profile by hand: that would be a second implementation of the host\'s reconcile, and yours is the deployment that breaks when it drifts.',
|
|
156
|
+
installFailed: '\n✗ install failed: see the output above.',
|
|
157
|
+
hostUnknown: '(could not ask the composition: CLI unavailable or timed out — whether it landed cannot be decided here; run doctor)',
|
|
158
|
+
hostAbsent: '**NOT in the composition** — it installed, but the composition does not show it (run doctor)',
|
|
159
|
+
nextStep: '\n next restart dsh web (both halves are cached in the running process by module URL, so a browser refresh is not enough), then pick ClearAI in the preset picker.',
|
|
160
|
+
uninstall: ' uninstall dsh plugin --profile {profile} remove {name}(npx works too)',
|
|
161
|
+
rootYamlComment1: '# ClearAI preset root (printed by clearai-dsh; the path is already absolute)',
|
|
162
|
+
rootYamlComment2: ' # ⚠️ a patch layer **replaces the whole config**: these keys must match the ones in your deployment, or they are lost.',
|
|
163
|
+
rootYamlComment3: ' # Run `dsh --profile <p> --dump-config | grep -A 20 agent-presets` first and paste over the current values.',
|
|
164
|
+
noPresetInPackage: '✗ no preset in the package: {path}',
|
|
165
|
+
seededTo: ' seeded to {dest}',
|
|
166
|
+
seedCounts: ' added {seeded} · refreshed {refreshed}',
|
|
167
|
+
seedDrifted: ' · **kept your edits** {count}({list})',
|
|
168
|
+
seedNext: ' next: restart dsh (or refresh the page) — ClearAI should now appear in the preset picker.',
|
|
169
|
+
noSeedLedger: ' nothing was seeded: doing nothing (without the ledger there is no way to tell which files are ours, so nothing is deleted).',
|
|
170
|
+
unseedRemoved: ' removed {count} file(s) we seeded and you did not edit',
|
|
171
|
+
unseedKept: ' **kept** {count} file(s) you edited (they are yours now): {list}',
|
|
172
|
+
unknownCommand: 'unknown command: {command}',
|
|
173
|
+
usage: 'usage: clearai-dsh [doctor|install|root-yaml|seed|unseed] [--profile web] [--home <dir>] [--lang zh|en]\n install also takes: --dist <dir> | --tarball <tgz> | --spec <spec>',
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** `{name}` 是占位符;需要拼计数与列表的句子写成函数(见 doctor / seed / unseed)。 */
|
|
178
|
+
function t(key, params) {
|
|
179
|
+
const text = TEXT[lang][key] ?? TEXT.zh[key]
|
|
180
|
+
if (text === undefined) throw new Error(`missing text: ${key}`)
|
|
181
|
+
return typeof text === 'function' ? text(params ?? {}) : text.replace(/\{(\w+)\}/g, (_, name) => String(params?.[name] ?? ''))
|
|
182
|
+
}
|
|
183
|
+
|
|
47
184
|
/** 递归列出文件(相对路径 → sha256 前 16 位)。只为记账,不为校验完整性。 */
|
|
48
185
|
function hashTree(root) {
|
|
49
186
|
const out = {}
|
|
@@ -128,14 +265,14 @@ function composeQuery(profileName, env) {
|
|
|
128
265
|
|
|
129
266
|
function doctor() {
|
|
130
267
|
const rows = []
|
|
131
|
-
rows.push(
|
|
132
|
-
rows.push(
|
|
133
|
-
rows.push(
|
|
268
|
+
rows.push(`${t('pkgDir')}${PKG_DIR}`)
|
|
269
|
+
rows.push(`${t('presetSrc')}${PRESET_SRC}${existsSync(join(PRESET_SRC, 'agent.cordis.yml')) ? t('presetOk') : t('presetMissing')}`)
|
|
270
|
+
rows.push(`${t('dshHome')}${DSH_HOME}`)
|
|
134
271
|
const roots = rosterRoots()
|
|
135
272
|
for (const root of roots) {
|
|
136
273
|
const dir = join(root, PRESET_ID)
|
|
137
274
|
const present = existsSync(join(dir, 'agent.cordis.yml'))
|
|
138
|
-
rows.push(
|
|
275
|
+
rows.push(`${t('userRoot')}${dir} ${present ? t('rosterVisible') : t('rosterAbsent')}`)
|
|
139
276
|
/**
|
|
140
277
|
* 用户根里那份**会不会被包的 root 遮住**。
|
|
141
278
|
*
|
|
@@ -152,10 +289,10 @@ function doctor() {
|
|
|
152
289
|
} catch {
|
|
153
290
|
same = false
|
|
154
291
|
}
|
|
155
|
-
if (!same) rows.push(
|
|
292
|
+
if (!same) rows.push(t('shadowCopy', { path: theirs }))
|
|
156
293
|
}
|
|
157
294
|
const profileDir = join(DSH_HOME, 'profiles', profile)
|
|
158
|
-
rows.push(
|
|
295
|
+
rows.push(`${t('profileRow')}${profileDir}${existsSync(profileDir) ? '' : t('profileAbsent', { profile })}`)
|
|
159
296
|
/**
|
|
160
297
|
* 宿主行与名册 root **问组合**,不问某个文件(2026-09-11 修:装出来的形态下这两行来自
|
|
161
298
|
* **包的补丁层**,不在 profile 的 cordis.patch.yml 里 —— 读文件的写法会误报「还没挂」)。
|
|
@@ -163,18 +300,18 @@ function doctor() {
|
|
|
163
300
|
*/
|
|
164
301
|
const composed = composeQuery(profile, { ...process.env, DSH_HOME })
|
|
165
302
|
if (composed === null) {
|
|
166
|
-
rows.push('
|
|
303
|
+
rows.push(`${t('composeRow')}${t('composeUnreachable')}`)
|
|
167
304
|
} else {
|
|
168
305
|
const rowIds = [...composed.matchAll(/^- id: (\S+)$/gm)].map((match) => match[1])
|
|
169
|
-
rows.push(
|
|
306
|
+
rows.push(`${t('hostRow')}${rowIds.includes('clearai-host') ? t('hostIn') : t('hostOut', { profile })}`)
|
|
170
307
|
const roster = /- id: agent-presets[\s\S]{0,600}?\n\s*- path:[^\n]*/.exec(composed)
|
|
171
308
|
const expr = roster === null ? null : roster[0].split('\n').pop().trim()
|
|
172
|
-
rows.push(
|
|
309
|
+
rows.push(`${t('rosterRootRow')}${expr === null ? t('rosterNoRoots') : t('rosterExpr', { expr })}`)
|
|
173
310
|
}
|
|
174
311
|
const ledger = readLedger()
|
|
175
|
-
rows.push(
|
|
312
|
+
rows.push(`${t('seedLedgerRow')}${Object.keys(ledger).length === 0 ? t('ledgerEmpty') : t('ledgerCount', { count: Object.keys(ledger).length, path: LEDGER })}`)
|
|
176
313
|
for (const row of rows) console.log(` ${row}`)
|
|
177
|
-
console.log('
|
|
314
|
+
console.log(t('doctorHint'))
|
|
178
315
|
}
|
|
179
316
|
|
|
180
317
|
/**
|
|
@@ -204,28 +341,24 @@ function install() {
|
|
|
204
341
|
* npx 缓存还在。`--dist` / `--tarball` / `--spec` 是给开发与 E2E 用的另一条入口。
|
|
205
342
|
*/
|
|
206
343
|
const spec = explicit ?? (tarball !== null ? resolve(tarball) : dist !== null ? `file:${resolve(dist)}` : `${manifest.name}@${manifest.version}`)
|
|
207
|
-
const source = explicit !== null ? '
|
|
344
|
+
const source = explicit !== null ? t('sourceSpec') : tarball !== null ? t('sourceTarball') : dist !== null ? t('sourceDir') : t('sourceRegistry')
|
|
208
345
|
/** PATH 上有 `dsh` 就用它;没有就用 npx 取官方 CLI(`--yes`:一键安装不该卡在一个确认提示上)。 */
|
|
209
346
|
const dshPath = findOnPath('dsh')
|
|
210
|
-
const route = dshPath === null ? '
|
|
347
|
+
const route = dshPath === null ? t('routeNpx') : dshPath
|
|
211
348
|
/**
|
|
212
349
|
* 子进程**继承 stdio**:安装进度、以及 CLI 那句 `initialized profile …` 都如实流到用户眼前;
|
|
213
350
|
* 失败时他看到的也是真实报错,而不是我截出来的尾巴。
|
|
214
351
|
*/
|
|
215
352
|
const dsh = (args) => (dshPath === null ? spawnSync('npx', ['--yes', '@deepseek-ai/dsh', ...args], { env, stdio: 'inherit', timeout: 900000 }) : spawnSync(dshPath, args, { env, stdio: 'inherit', timeout: 900000 }))
|
|
216
353
|
|
|
217
|
-
console.log(
|
|
218
|
-
console.log(`
|
|
219
|
-
console.log(`
|
|
220
|
-
console.log(`
|
|
354
|
+
console.log(t('installHeader', { name: manifest.name, version: manifest.version }))
|
|
355
|
+
console.log(` ${t('installWhat')}${spec}(${source})`)
|
|
356
|
+
console.log(` ${t('installWhere')}${profileDir}`)
|
|
357
|
+
console.log(` ${t('installWho')}${route}`)
|
|
221
358
|
|
|
222
359
|
const pnpmPath = findOnPath('pnpm')
|
|
223
360
|
if (pnpmPath === null) {
|
|
224
|
-
console.error('
|
|
225
|
-
console.error(' 装一个再来:npm install -g pnpm(或用系统包管理器,如 brew install pnpm)。')
|
|
226
|
-
console.error(' 别用 corepack enable 抄近路 —— 它装的是版本**转发器**而不是 pnpm,而当前 Node 自带的')
|
|
227
|
-
console.error(' 那份 corepack 可能下载一个它自己启动不了的 pnpm。')
|
|
228
|
-
console.error(' 这里刻意不手工改 profile:那等于把宿主的 reconcile 抄成第二份实现,与宿主漂移时坏的是你的部署。')
|
|
361
|
+
console.error(t('pnpmMissing'))
|
|
229
362
|
process.exit(1)
|
|
230
363
|
}
|
|
231
364
|
|
|
@@ -238,28 +371,28 @@ function install() {
|
|
|
238
371
|
*/
|
|
239
372
|
const added = dsh(['plugin', '--profile', profile, 'add', spec])
|
|
240
373
|
if (added.status !== 0) {
|
|
241
|
-
console.error('
|
|
374
|
+
console.error(t('installFailed'))
|
|
242
375
|
process.exit(1)
|
|
243
376
|
}
|
|
244
377
|
|
|
245
378
|
const composed = composeQuery(profile, env)
|
|
246
379
|
if (composed === null) {
|
|
247
|
-
console.log(
|
|
380
|
+
console.log(` ${t('hostRowShort')}${t('hostUnknown')}`)
|
|
248
381
|
} else {
|
|
249
382
|
const inCompose = /^- id: clearai-host$/m.test(composed)
|
|
250
|
-
console.log(`
|
|
383
|
+
console.log(` ${t('hostRowShort')}${inCompose ? t('hostIn') : t('hostAbsent')}`)
|
|
251
384
|
}
|
|
252
|
-
console.log(
|
|
253
|
-
console.log(
|
|
385
|
+
console.log(t('nextStep'))
|
|
386
|
+
console.log(t('uninstall', { profile, name: manifest.name }))
|
|
254
387
|
}
|
|
255
388
|
|
|
256
389
|
function rootYaml() {
|
|
257
390
|
const line = [
|
|
258
|
-
'
|
|
391
|
+
t('rootYamlComment1'),
|
|
259
392
|
'- id: \'@deepseek-ai/dsh-agent-presets\'',
|
|
260
393
|
' config:',
|
|
261
|
-
'
|
|
262
|
-
'
|
|
394
|
+
t('rootYamlComment2'),
|
|
395
|
+
t('rootYamlComment3'),
|
|
263
396
|
' default: standard',
|
|
264
397
|
' includeShippedRoot: true',
|
|
265
398
|
' includeUserRoot: true',
|
|
@@ -272,7 +405,7 @@ function rootYaml() {
|
|
|
272
405
|
|
|
273
406
|
function seed() {
|
|
274
407
|
if (!existsSync(join(PRESET_SRC, 'agent.cordis.yml'))) {
|
|
275
|
-
console.error(
|
|
408
|
+
console.error(t('noPresetInPackage', { path: PRESET_SRC }))
|
|
276
409
|
process.exit(1)
|
|
277
410
|
}
|
|
278
411
|
const root = value('root', rosterRoots()[0])
|
|
@@ -303,16 +436,16 @@ function seed() {
|
|
|
303
436
|
}
|
|
304
437
|
before[PRESET_ID] = { files: next, at: new Date().toISOString(), dest }
|
|
305
438
|
writeLedger(before)
|
|
306
|
-
console.log(
|
|
307
|
-
console.log(
|
|
308
|
-
console.log(
|
|
439
|
+
console.log(t('seededTo', { dest }))
|
|
440
|
+
console.log(`${t('seedCounts', { seeded: seeded.length, refreshed: refreshed.length })}${drifted.length === 0 ? '' : t('seedDrifted', { count: drifted.length, list: drifted.slice(0, 5).join(lang === 'zh' ? '、' : ', ') })}`)
|
|
441
|
+
console.log(t('seedNext'))
|
|
309
442
|
}
|
|
310
443
|
|
|
311
444
|
function unseed() {
|
|
312
445
|
const ledger = readLedger()
|
|
313
446
|
const entry = ledger[PRESET_ID]
|
|
314
447
|
if (entry === undefined) {
|
|
315
|
-
console.log(
|
|
448
|
+
console.log(t('noSeedLedger'))
|
|
316
449
|
return
|
|
317
450
|
}
|
|
318
451
|
const dest = entry.dest ?? join(rosterRoots()[0], PRESET_ID)
|
|
@@ -331,8 +464,8 @@ function unseed() {
|
|
|
331
464
|
}
|
|
332
465
|
delete ledger[PRESET_ID]
|
|
333
466
|
writeLedger(ledger)
|
|
334
|
-
console.log(
|
|
335
|
-
if (kept.length > 0) console.log(
|
|
467
|
+
console.log(t('unseedRemoved', { count: removed.length }))
|
|
468
|
+
if (kept.length > 0) console.log(t('unseedKept', { count: kept.length, list: kept.slice(0, 5).join(lang === 'zh' ? '、' : ', ') }))
|
|
336
469
|
// 空目录收掉(只收我们知道的那些;非空绝不动)
|
|
337
470
|
try {
|
|
338
471
|
if (existsSync(dest) && readdirSync(dest).length === 0) rmSync(dest, { recursive: true })
|
|
@@ -348,10 +481,6 @@ else if (command === 'seed') seed()
|
|
|
348
481
|
else if (command === 'unseed') unseed()
|
|
349
482
|
else if (command === 'version' || flag('version')) console.log(JSON.parse(readFileSync(join(PKG_DIR, 'package.json'), 'utf8')).version)
|
|
350
483
|
else {
|
|
351
|
-
console.error(
|
|
352
|
-
`unknown command: ${command}\n` +
|
|
353
|
-
'用法:clearai-dsh [doctor|install|root-yaml|seed|unseed] [--profile web] [--home <dir>]\n' +
|
|
354
|
-
' install 还可以:--dist <dir> | --tarball <tgz> | --spec <spec>',
|
|
355
|
-
)
|
|
484
|
+
console.error(`${t('unknownCommand', { command })}\n${t('usage')}`)
|
|
356
485
|
process.exit(2)
|
|
357
486
|
}
|