clearai-dsh 0.1.1 → 0.1.3
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 +31 -0
- package/README.md +26 -23
- package/README.zh-CN.md +26 -23
- package/bin/clearai.mjs +128 -12
- package/package.json +6 -2
- package/presets/clearai/plugins/clearai-kernel.js +103 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
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
|
+
## [0.1.3] — 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`npx clearai-dsh install` — one command, and the only prerequisite left is DSH's own.** The published package has always carried an install-side tool, but it only *diagnosed*: `doctor`, `root-yaml`, `seed`, `unseed`. The installer that could actually place the package lived in `tools/install-native.mjs`, which is not in the published files — so a stranger had nothing to run but `dsh plugin … add`, a command whose first word assumes a `dsh` that an `npx`-launched harness never puts on `PATH`. The new `install` verb resolves the CLI (a `dsh` on `PATH`, else `npx --yes @deepseek-ai/dsh`), installs into the profile, and then reads the composed config back to show that the `clearai-host` row really landed. `--dist` / `--tarball` / `--spec` point it at a local build instead of the registry, which is what the lifecycle check now exercises.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **The install instructions no longer teach a mechanism we do not own.** They handed the reader a `corepack enable` line as the way to get pnpm. Corepack is a version *router*, not an install: its 186-byte shim fetches a pnpm on first use, and corepack 0.34 — the one Node 24 ships — launches pnpm by looking for `bin/pnpm.cjs`, which pnpm 11 and later no longer provide. It can therefore fetch a version it is unable to run, and its shims can shadow a pnpm that already worked. The docs now name the requirement (a `pnpm` on `PATH`, which is DSH's rather than ours) and leave the choice of how to satisfy it to the reader.
|
|
14
|
+
- `install` does not bootstrap a profile or hand-reconcile one. The CLI initializes a profile the first time it is used for one (`initialized profile web at …`), and a second implementation of the host's reconcile step is exactly the duplication this project rejects. Passing a shipped profile name to `--from-default-profile` is an error in the CLI (`profile "web" is shipped and cannot be a custom profile target`), so the verb does not offer that flag at all.
|
|
15
|
+
- `install` stops when `pnpm` is missing instead of degrading: pnpm is DSH's prerequisite, not this plugin's. The degraded, pnpm-less path stays in `tools/install-native.mjs`, where it exists for one-shot E2E homes and labels itself as degraded.
|
|
16
|
+
- **`doctor` asks the composed config through a `dsh` on `PATH` first**, falling back to `npx --no-install`. It previously always went through npx, so a machine that had the CLI on `PATH` could still be told the composition could not be determined.
|
|
17
|
+
- **The lifecycle check had a gate that could never open.** Its byte-for-byte comparison included `INVENTORY.txt` — the build's own file manifest, which is not in `files` and is therefore never present in a pnpm-installed copy — so that assertion was red on every run, and because the lifecycle check is not part of CI, nobody saw it. It also drove every compose query through `npx --no-install`, which returns an empty string when npx cannot run: two positive assertions failed while the negative one ("the row is gone") passed on that empty output. Both are fixed — the CLI is resolved from `PATH` first, and the comparison ignores the build manifest — and the check now exercises the shipped `install` verb too (28 checks).
|
|
18
|
+
|
|
19
|
+
## [0.1.2] — 2026-09-12
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **A gate could be impossible to open.** The plan review is a real gate — only a person's approval writes the authorisation mark, and the kernel refuses to start work without it. But the review card was raised **only** when a plan was created, so after a person chose *revise first, then resubmit*, the model revised the plan and there was **no entry point left** to present it again. The plan stayed unauthorised while the kernel correctly refused to work: a mechanism turned into a dead end.
|
|
24
|
+
|
|
25
|
+
Now `AmendPlan` and `RefinePlan` present an unauthorised plan again automatically, and the new **`RequestPlanReview`** tool is an explicit entry point for the model or a person to re-present it. Only approval writes the mark; every other outcome still writes nothing.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `RequestPlanReview` — re-present the current plan for review without changing anything. Returns `already_confirmed` when the plan is already authorised.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **The continuation window no longer shows a machine id.** The native goal chip displayed `ClearAI 续跑窗口 · 目标 g-…` — a mechanism word plus an id, on a surface the platform renders for people. It now reads as a sentence about the work (`继续做完:<what you asked for>`). The window's identity is no longer the text: ownership is tracked in the ledger, and a change of wording goes through `goals.edit`, which **does not touch the round budget**, so revising a goal still cannot refresh it.
|
|
34
|
+
|
|
5
35
|
## [0.1.1] — 2026-09-12
|
|
6
36
|
|
|
7
37
|
### Fixed
|
|
@@ -33,5 +63,6 @@ The first release: ClearAI as a native DSH plugin.
|
|
|
33
63
|
- **Restart `dsh web` after installing**: both halves are cached in the running process, so a browser refresh is not enough.
|
|
34
64
|
- Known gaps — what is deliberately not implemented, and what has only been verified to a stated depth — are listed in [`docs/known-gaps.md`](docs/known-gaps.md).
|
|
35
65
|
|
|
66
|
+
[0.1.2]: https://github.com/Clearailhc/clearai-dsh/releases/tag/v0.1.2
|
|
36
67
|
[0.1.1]: https://github.com/Clearailhc/clearai-dsh/releases/tag/v0.1.1
|
|
37
68
|
[0.1.0]: https://github.com/Clearailhc/clearai-dsh/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -22,6 +22,32 @@ A language model can produce a plausible answer in seconds. ClearAI is about wha
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
One command, and it needs nothing but Node:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx clearai-dsh install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It resolves the DSH CLI (from your `PATH`, or through `npx`), installs the plugin into your `web` profile, and reads the composed config back so you are not taking "success" on faith. Underneath it is the host's own install, so this is the same command: `dsh plugin --profile web add clearai-dsh`.
|
|
34
|
+
|
|
35
|
+
**Restart `dsh web` after that** (`npx @deepseek-ai/dsh web`). Both halves of the plugin are cached inside the running process, so refreshing the browser is not enough. Then open a session and pick **ClearAI** in the preset picker.
|
|
36
|
+
|
|
37
|
+
If it stops because **pnpm is not on your `PATH`**: DSH manages a profile by driving pnpm, so it needs one. Install it with `npm install -g pnpm`, or your system package manager. Prefer that to `corepack enable`, which installs a version *router* rather than pnpm, and the corepack shipped with current Node can fetch a pnpm it is unable to launch.
|
|
38
|
+
|
|
39
|
+
From a checkout (development, not the install path):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test # kernel / host / brain / client / ontology suites
|
|
43
|
+
node tools/build-package.mjs # assemble dist/ from source
|
|
44
|
+
node tools/verify-package.mjs # rebuild and compare byte-for-byte
|
|
45
|
+
node tools/verify-clean-install.mjs # install into an empty DSH_HOME through the real CLI
|
|
46
|
+
node docs/diagrams/build.mjs # regenerate the loop diagram (needs google-chrome)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`dist/` is generated and never committed. See [DSH integration](docs/dsh-integration.md).
|
|
50
|
+
|
|
25
51
|
## Why this is not just another agent loop
|
|
26
52
|
|
|
27
53
|
Most agent loops track one thing: whether the task is done. The Epistemic Loop also tracks **how a conclusion came to be trusted**:
|
|
@@ -73,29 +99,6 @@ The plugin contributes three surfaces on top of stock DSH: a **deliverables** vi
|
|
|
73
99
|
|
|
74
100
|

|
|
75
101
|
|
|
76
|
-
## Install
|
|
77
|
-
|
|
78
|
-
Requires **Node ≥ 22** and **`pnpm` on `PATH`** — `dsh plugin …` is a pnpm forwarder, so without pnpm the profile cannot be managed at all:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
corepack enable --install-directory ~/.local/bin # if you do not have pnpm yet
|
|
82
|
-
dsh plugin --profile web add clearai-dsh
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**Restart `dsh web` afterwards.** Both halves of the plugin are cached inside the running process, so refreshing the browser is not enough. Then open a session and pick **ClearAI** in the preset picker.
|
|
86
|
-
|
|
87
|
-
From a checkout:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
npm test # kernel / host / brain / client / ontology suites
|
|
91
|
-
node tools/build-package.mjs # assemble dist/ from source
|
|
92
|
-
node tools/verify-package.mjs # rebuild and compare byte-for-byte
|
|
93
|
-
node tools/verify-clean-install.mjs # install into an empty DSH_HOME through the real CLI
|
|
94
|
-
node docs/diagrams/build.mjs # regenerate the loop diagram (needs google-chrome)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
`dist/` is generated and never committed. See [DSH integration](docs/dsh-integration.md).
|
|
98
|
-
|
|
99
102
|
## Where it lands in DSH
|
|
100
103
|
|
|
101
104
|
ClearAI adds an epistemic layer on the DSH **composition surface** — one host package, one agent preset, one client module. The DSH engine is not modified.
|
package/README.zh-CN.md
CHANGED
|
@@ -22,6 +22,32 @@ ClearAI 是一个**原生 DSH 插件**,把认识论循环带进 DeepSeek Harne
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
一条命令,除了 Node 什么都不需要:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx clearai-dsh install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
它会自己找到 DSH CLI(PATH 上有就用,没有就走 npx),把插件装进你的 `web` profile,再把组合读回来核一眼 —— 不用凭一句「成功」相信它。底下就是宿主自己的安装动作,所以两者等价:`dsh plugin --profile web add clearai-dsh`。
|
|
34
|
+
|
|
35
|
+
**装完要重启 `dsh web`**(`npx @deepseek-ai/dsh web`)。插件的两半都在运行中的进程里按模块 URL 缓存,只刷新浏览器不够。然后新建会话,在预设选择器里选 **ClearAI**。
|
|
36
|
+
|
|
37
|
+
如果它因为 **PATH 上没有 pnpm** 而停下:DSH 管理 profile 就是靠 pnpm,所以需要一个。用 `npm install -g pnpm` 装,或用你的系统包管理器。**别用 `corepack enable` 抄近路**——它装的是一个版本**转发器**而不是 pnpm,而当前 Node 自带的那份 corepack 可能下载一个它自己启动不了的 pnpm。
|
|
38
|
+
|
|
39
|
+
从仓库开发(这是开发路径,不是安装路径):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test # 内核 / 宿主 / 外脑 / 客户端 / 本体 五份套件
|
|
43
|
+
node tools/build-package.mjs # 由源装配 dist/
|
|
44
|
+
node tools/verify-package.mjs # 现场重建并逐字节比对
|
|
45
|
+
node tools/verify-clean-install.mjs # 空 DSH_HOME + 真 CLI 装一遍(16 条断言)
|
|
46
|
+
node docs/diagrams/build.mjs # 重画循环主图(需 google-chrome)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`dist/` 是生成物,不进版本库。见 [DSH 集成](docs/dsh-integration.zh-CN.md)。
|
|
50
|
+
|
|
25
51
|
## 为什么它不只是又一个 agent loop
|
|
26
52
|
|
|
27
53
|
多数 agent loop 只跟踪一件事:任务做完没有。认识论循环还跟踪**一个结论凭什么被信任**:
|
|
@@ -73,29 +99,6 @@ ClearAI **不**声称实现递归自我改进。它提供的是自我改进系
|
|
|
73
99
|
|
|
74
100
|

|
|
75
101
|
|
|
76
|
-
## 安装
|
|
77
|
-
|
|
78
|
-
需要 **Node ≥ 22** 和 **`pnpm` 在 PATH 上** —— `dsh plugin …` 是 pnpm 的一层转发器,没有 pnpm 就管不了 profile:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
corepack enable --install-directory ~/.local/bin # 还没有 pnpm 就先装它
|
|
82
|
-
dsh plugin --profile web add clearai-dsh
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**装完要重启 `dsh web`。** 插件的两半都在运行中的进程里按模块 URL 缓存,只刷新浏览器不够。然后新建会话,在预设选择器里选 **ClearAI**。
|
|
86
|
-
|
|
87
|
-
从仓库开发:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
npm test # 内核 / 宿主 / 外脑 / 客户端 / 本体 五份套件
|
|
91
|
-
node tools/build-package.mjs # 由源装配 dist/
|
|
92
|
-
node tools/verify-package.mjs # 现场重建并逐字节比对
|
|
93
|
-
node tools/verify-clean-install.mjs # 空 DSH_HOME + 真 CLI 装一遍(16 条断言)
|
|
94
|
-
node docs/diagrams/build.mjs # 重画循环主图(需 google-chrome)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
`dist/` 是生成物,不进版本库。见 [DSH 集成](docs/dsh-integration.zh-CN.md)。
|
|
98
|
-
|
|
99
102
|
## 它落在 DSH 的哪一层
|
|
100
103
|
|
|
101
104
|
ClearAI 把认识论层加在 DSH 的**组合面**上——一个宿主包、一个 agent 预设、一个客户端模块,**DSH 引擎一行都没改**。
|
package/bin/clearai.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* clearai-dsh
|
|
3
|
+
* clearai-dsh 的安装侧工具。**只有一个动词会动 profile(`install`),而它动的方式是把活交给宿主**。
|
|
4
4
|
*
|
|
5
5
|
* 分包的三样东西落在三个平面:
|
|
6
6
|
* · 宿主半(`lib/host.js`)—— 由包自带的 `cordis.patch.yml` 在 profile 层插一行,`dsh plugin add` 自动生效;
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* · agent 预设(`presets/clearai/`)—— **名册(roster)只从 root 目录扫**,包没法直接声明,
|
|
9
9
|
* 所以要么把 root 指进包里,要么把它播种到用户根。这一件就是本工具存在的理由。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* 处理方式(前四个默认只**打印**不改;默认动词仍是 `doctor` —— 一个安装侧工具不该在
|
|
12
|
+
* 你没说要装的时候动你的部署):
|
|
12
13
|
* doctor 看现状:包在哪、预设在哪、名册能不能看见它、探测到的 dsh / profile 是什么
|
|
14
|
+
* install 把包装进 profile —— `dsh plugin --profile <p> add <spec>` 的一层**前置解析**
|
|
15
|
+
* (读者不必知道 profile 叫什么、CLI 从哪来、包名怎么写),装完给读数与下一步
|
|
13
16
|
* root-yaml 打印**可以直接粘进 profile 的 `cordis.patch.yml`** 的那一行(路径已算成绝对路径)
|
|
14
17
|
* seed 把预设**播种**到用户根 `~/.dsh/.agent-presets/<id>`(带哈希记账:改过的不覆盖)
|
|
15
18
|
* unseed 撤掉播种:只删「我们播的、且没被改过」的文件;用户 fork 的 id 一律不碰
|
|
@@ -19,10 +22,10 @@
|
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
import { createHash } from 'node:crypto'
|
|
22
|
-
import {
|
|
25
|
+
import { spawnSync } from 'node:child_process'
|
|
23
26
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
24
27
|
import { homedir } from 'node:os'
|
|
25
|
-
import { dirname, join, relative, resolve } from 'node:path'
|
|
28
|
+
import { delimiter, dirname, join, relative, resolve } from 'node:path'
|
|
26
29
|
import { fileURLToPath } from 'node:url'
|
|
27
30
|
|
|
28
31
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
@@ -85,6 +88,44 @@ function rosterRoots() {
|
|
|
85
88
|
return [join(DSH_HOME, '.agent-presets')]
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
/**
|
|
92
|
+
* 在 PATH 上找一个可执行文件,找到就返回全路径。
|
|
93
|
+
*
|
|
94
|
+
* 为什么不用 `bash -lc 'command -v pnpm'`(tools/install-native.mjs 的写法):
|
|
95
|
+
* 这里要能在 Windows 上跑,而 `command -v` 是 shell 内建;另外**只查文件、不执行它** ——
|
|
96
|
+
* 执行一个 pnpm 可能是别的东西(比如 corepack 的转发器)在跑,那会引出网络与副作用。
|
|
97
|
+
*/
|
|
98
|
+
function findOnPath(name) {
|
|
99
|
+
const exts = process.platform === 'win32' ? ['.cmd', '.exe', '.bat'] : ['']
|
|
100
|
+
for (const dir of (process.env.PATH ?? '').split(delimiter)) {
|
|
101
|
+
if (dir === '') continue
|
|
102
|
+
for (const ext of exts) {
|
|
103
|
+
const candidate = join(dir, `${name}${ext}`)
|
|
104
|
+
try {
|
|
105
|
+
if (statSync(candidate).isFile()) return candidate
|
|
106
|
+
} catch {
|
|
107
|
+
/* 这个目录里没有 */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 问组合:宿主行到底有没有真的进组合。
|
|
116
|
+
*
|
|
117
|
+
* 优先用 PATH 上**真正的** `dsh`(以前一律走 `npx --no-install`,于是 PATH 上有 CLI 的机器
|
|
118
|
+
* 也会报「问不到」);没有 CLI 才退回 npx,且仍然 `--no-install` —— 诊断动作不该顺手下载东西。
|
|
119
|
+
*/
|
|
120
|
+
function composeQuery(profileName, env) {
|
|
121
|
+
const onPath = findOnPath('dsh')
|
|
122
|
+
const args = ['--profile', profileName, '--dump-config']
|
|
123
|
+
const run = onPath === null
|
|
124
|
+
? spawnSync('npx', ['--no-install', '@deepseek-ai/dsh', ...args], { encoding: 'utf8', env, timeout: 120000, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
125
|
+
: spawnSync(onPath, args, { encoding: 'utf8', env, timeout: 120000, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
126
|
+
return run.status === 0 ? String(run.stdout ?? '') : null
|
|
127
|
+
}
|
|
128
|
+
|
|
88
129
|
function doctor() {
|
|
89
130
|
const rows = []
|
|
90
131
|
rows.push(`包目录 ${PKG_DIR}`)
|
|
@@ -103,13 +144,7 @@ function doctor() {
|
|
|
103
144
|
* **包的补丁层**,不在 profile 的 cordis.patch.yml 里 —— 读文件的写法会误报「还没挂」)。
|
|
104
145
|
* 事实在组合里;问不到就如实说问不到,不猜。
|
|
105
146
|
*/
|
|
106
|
-
const composed = (
|
|
107
|
-
try {
|
|
108
|
-
return execFileSync('npx', ['--no-install', '@deepseek-ai/dsh', '--profile', profile, '--dump-config'], { encoding: 'utf8', env: { ...process.env, DSH_HOME }, timeout: 120000, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
109
|
-
} catch {
|
|
110
|
-
return null
|
|
111
|
-
}
|
|
112
|
-
})()
|
|
147
|
+
const composed = composeQuery(profile, { ...process.env, DSH_HOME })
|
|
113
148
|
if (composed === null) {
|
|
114
149
|
rows.push('组合 (问不到:dsh 不可用或超时 —— 下面两条无法判定)')
|
|
115
150
|
} else {
|
|
@@ -125,6 +160,82 @@ function doctor() {
|
|
|
125
160
|
console.log('\n提示:doctor 只读;它不会替你改 profile,也不会替你播种。')
|
|
126
161
|
}
|
|
127
162
|
|
|
163
|
+
/**
|
|
164
|
+
* install —— 把包装进一个 profile。
|
|
165
|
+
*
|
|
166
|
+
* 真正干活的**永远是宿主的 CLI**(`dsh plugin --profile <p> add <spec>`):它自己会初始化
|
|
167
|
+
* profile、在 profile 目录里跑 pnpm、再对账 `dsh.profile.bundles`。这里只解析三件事 ——
|
|
168
|
+
* 装什么、装到哪、谁来跑 —— 于是读者不必先读过别的文档。
|
|
169
|
+
*
|
|
170
|
+
* 两条纪律:
|
|
171
|
+
* · **不偷偷降级**。没有 pnpm 就停下来把话说清楚:pnpm 是 DSH 的前置,不是本插件的。
|
|
172
|
+
* 在 profile 里手工摆文件等于把宿主的 reconcile 抄成第二份实现,而它一旦与宿主漂移,
|
|
173
|
+
* 坏的是用户的部署。(降级只保留在 `tools/install-native.mjs` —— 那是一次性 DSH_HOME 上的
|
|
174
|
+
* E2E 需要,而且它如实标注自己是降级。)
|
|
175
|
+
* · **装完给读数**。不靠一句「成功」交差:再问一次组合,看宿主行是不是真的进去了。
|
|
176
|
+
*/
|
|
177
|
+
function install() {
|
|
178
|
+
const home = resolve(value('home', DSH_HOME))
|
|
179
|
+
const env = { ...process.env, DSH_HOME: home }
|
|
180
|
+
const profileDir = join(home, 'profiles', profile)
|
|
181
|
+
const manifest = JSON.parse(readFileSync(join(PKG_DIR, 'package.json'), 'utf8'))
|
|
182
|
+
const explicit = value('spec', null)
|
|
183
|
+
const dist = value('dist', null)
|
|
184
|
+
const tarball = value('tarball', null)
|
|
185
|
+
/**
|
|
186
|
+
* 缺省装 **registry 上的这一版**:profile 从此真正拥有它(能升级、能卸载),也不依赖
|
|
187
|
+
* npx 缓存还在。`--dist` / `--tarball` / `--spec` 是给开发与 E2E 用的另一条入口。
|
|
188
|
+
*/
|
|
189
|
+
const spec = explicit ?? (tarball !== null ? resolve(tarball) : dist !== null ? `file:${resolve(dist)}` : `${manifest.name}@${manifest.version}`)
|
|
190
|
+
const source = explicit !== null ? '你给的 spec' : tarball !== null ? '本地 tarball' : dist !== null ? '本地目录' : 'registry'
|
|
191
|
+
/** PATH 上有 `dsh` 就用它;没有就用 npx 取官方 CLI(`--yes`:一键安装不该卡在一个确认提示上)。 */
|
|
192
|
+
const dshPath = findOnPath('dsh')
|
|
193
|
+
const route = dshPath === null ? 'npx --yes @deepseek-ai/dsh(PATH 上没有 dsh)' : dshPath
|
|
194
|
+
/**
|
|
195
|
+
* 子进程**继承 stdio**:安装进度、以及 CLI 那句 `initialized profile …` 都如实流到用户眼前;
|
|
196
|
+
* 失败时他看到的也是真实报错,而不是我截出来的尾巴。
|
|
197
|
+
*/
|
|
198
|
+
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 }))
|
|
199
|
+
|
|
200
|
+
console.log(`【安装】${manifest.name}@${manifest.version}`)
|
|
201
|
+
console.log(` 装什么 ${spec}(${source})`)
|
|
202
|
+
console.log(` 装到哪 ${profileDir}`)
|
|
203
|
+
console.log(` 谁来跑 ${route}`)
|
|
204
|
+
|
|
205
|
+
const pnpmPath = findOnPath('pnpm')
|
|
206
|
+
if (pnpmPath === null) {
|
|
207
|
+
console.error('\n✗ PATH 上没有 pnpm,而 DSH 管理一个 profile 就是靠它:`dsh plugin …` 把参数转发给 pnpm。')
|
|
208
|
+
console.error(' 装一个再来:npm install -g pnpm(或用系统包管理器,如 brew install pnpm)。')
|
|
209
|
+
console.error(' 别用 corepack enable 抄近路 —— 它装的是版本**转发器**而不是 pnpm,而当前 Node 自带的')
|
|
210
|
+
console.error(' 那份 corepack 可能下载一个它自己启动不了的 pnpm。')
|
|
211
|
+
console.error(' 这里刻意不手工改 profile:那等于把宿主的 reconcile 抄成第二份实现,与宿主漂移时坏的是你的部署。')
|
|
212
|
+
process.exit(1)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* **刻意不做 profile bootstrap**:CLI 第一次用到某个 profile 时自己就会初始化它
|
|
217
|
+
* (实测输出 `dsh: initialized profile web at …`),那本来就是宿主的不变量,抄一遍只会
|
|
218
|
+
* 多一个会漂移的实现。顺带说:`--from-default-profile` 只接受**自定义目标**,把 shipped
|
|
219
|
+
* 名字传给它会被 CLI 直接拒 —— `profile "web" is shipped and cannot be a custom profile
|
|
220
|
+
* target`。所以这里连那个开关都不提供。
|
|
221
|
+
*/
|
|
222
|
+
const added = dsh(['plugin', '--profile', profile, 'add', spec])
|
|
223
|
+
if (added.status !== 0) {
|
|
224
|
+
console.error('\n✗ 安装失败:见上面的输出。')
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const composed = composeQuery(profile, env)
|
|
229
|
+
if (composed === null) {
|
|
230
|
+
console.log(' 宿主行 (问不到组合:CLI 不可用或超时 —— 装没装进去,从这里确认不了;用 doctor 再看)')
|
|
231
|
+
} else {
|
|
232
|
+
const inCompose = /^- id: clearai-host$/m.test(composed)
|
|
233
|
+
console.log(` 宿主行 ${inCompose ? '在组合里 ✓' : '**不在组合里** —— 装是装上了,但组合里没看到它(用 doctor 查)'}`)
|
|
234
|
+
}
|
|
235
|
+
console.log('\n 下一步 重启 dsh web(两半都在进程里按模块 URL 缓存,只刷新浏览器不够),然后在预设选择器里选 ClearAI。')
|
|
236
|
+
console.log(` 卸载 dsh plugin --profile ${profile} remove ${manifest.name}(同样可以冠 npx)`)
|
|
237
|
+
}
|
|
238
|
+
|
|
128
239
|
function rootYaml() {
|
|
129
240
|
const line = [
|
|
130
241
|
'# ClearAI 预设的 root(由 clearai-dsh 的 bin 打印,路径已算成绝对路径)',
|
|
@@ -214,11 +325,16 @@ function unseed() {
|
|
|
214
325
|
}
|
|
215
326
|
|
|
216
327
|
if (command === 'doctor') doctor()
|
|
328
|
+
else if (command === 'install') install()
|
|
217
329
|
else if (command === 'root-yaml') rootYaml()
|
|
218
330
|
else if (command === 'seed') seed()
|
|
219
331
|
else if (command === 'unseed') unseed()
|
|
220
332
|
else if (command === 'version' || flag('version')) console.log(JSON.parse(readFileSync(join(PKG_DIR, 'package.json'), 'utf8')).version)
|
|
221
333
|
else {
|
|
222
|
-
console.error(
|
|
334
|
+
console.error(
|
|
335
|
+
`unknown command: ${command}\n` +
|
|
336
|
+
'用法:clearai-dsh [doctor|install|root-yaml|seed|unseed] [--profile web] [--home <dir>]\n' +
|
|
337
|
+
' install 还可以:--dist <dir> | --tarball <tgz> | --spec <spec>',
|
|
338
|
+
)
|
|
223
339
|
process.exit(2)
|
|
224
340
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clearai-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "ClearAI: The Epistemic Loop, native to DSH.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Clearailhc/clearai-dsh.git"
|
|
11
|
+
},
|
|
8
12
|
"main": "./lib/host.js",
|
|
9
13
|
"exports": {
|
|
10
14
|
".": "./lib/host.js",
|
|
@@ -13,7 +17,7 @@
|
|
|
13
17
|
"./package.json": "./package.json"
|
|
14
18
|
},
|
|
15
19
|
"bin": {
|
|
16
|
-
"clearai-dsh": "
|
|
20
|
+
"clearai-dsh": "bin/clearai.mjs"
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
19
23
|
"lib",
|
|
@@ -473,7 +473,7 @@ export const CONFIG_KEYS = [
|
|
|
473
473
|
*/
|
|
474
474
|
export const MECHANISM_TOOLS = {
|
|
475
475
|
goal: ['SetGoal', 'CloseGoal'],
|
|
476
|
-
plan: ['CreatePlan', 'CheckPlan', 'AmendPlan', 'RefinePlan', 'VoidPlanStep', 'ClosePlan', 'AdvancePlan'],
|
|
476
|
+
plan: ['CreatePlan', 'CheckPlan', 'RequestPlanReview', 'AmendPlan', 'RefinePlan', 'VoidPlanStep', 'ClosePlan', 'AdvancePlan'],
|
|
477
477
|
worldline: ['ForkPlan', 'AdvanceWorldline', 'ConvergeFork', 'WorldlineStatus', 'AwaitWorldlines', 'AbandonFork'],
|
|
478
478
|
scout: ['SpawnScout', 'MapScouts'],
|
|
479
479
|
/** 外脑:写侧两件(读侧全走宿主原生的技能目录与 `skill` 工具)。 */
|
|
@@ -1948,14 +1948,31 @@ export function apply(ctx, config = {}) {
|
|
|
1948
1948
|
* 两处的字面值由两侧的测试各钉一遍(内核这一份 + 客户端那一份),漂移会当场红。
|
|
1949
1949
|
*/
|
|
1950
1950
|
/** 窗口身份里**不再写档位**(§34):「多问我 / 自己跑」不是用户的配置,是运行时状态。 */
|
|
1951
|
+
/** 一句话摘要:压平空白、超长截断(平台上那句给人看的话用它;内核里没有客户端的 `brief`)。 */
|
|
1952
|
+
function clip(text, max) {
|
|
1953
|
+
const flat = String(text ?? '').replace(/\s+/g, ' ').trim()
|
|
1954
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`
|
|
1955
|
+
}
|
|
1951
1956
|
function windowTarget(state, derived) {
|
|
1952
1957
|
const goal = state.goal
|
|
1953
|
-
if (goal !== null && goal.status === 'open') return
|
|
1954
|
-
if (derived.activePlan !== null) return
|
|
1955
|
-
return '
|
|
1958
|
+
if (goal !== null && goal.status === 'open') return `继续做完:${clip(goal.claim, 26)}`
|
|
1959
|
+
if (derived.activePlan !== null) return `继续推进:${clip(derived.activePlan.brief ?? derived.activePlan.id, 26)}`
|
|
1960
|
+
return '继续把手上这一步做完'
|
|
1956
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* 窗口上那句**给人看的话**(§17.1 的第二次修正,2026-09-12 实测)。
|
|
1964
|
+
*
|
|
1965
|
+
* 原来这里是 `ClearAI 续跑窗口 · 目标 g-mtyirobr2y2l`:一个机制词加一串机器 id。
|
|
1966
|
+
* 而这句是**印在平台面板上、给人看**的 —— 人该看到"在做什么",不是我们在内部怎么称呼它。
|
|
1967
|
+
* (同一屏上还并排着原生那句「进行中的目标」,机制词叠机制词。)
|
|
1968
|
+
*
|
|
1969
|
+
* 换成人话之后,身份就不能再靠"文本相等"判了(目标一改口径文本就变),于是:
|
|
1970
|
+
* · **归属**仍看我们账上记的那一枚(`before.goal === current.id`)与"这句还是不是我们写的";
|
|
1971
|
+
* · 文本变了走 **`goals.edit`** —— 改这句话**不动轮数**,所以反复修订刷不出预算;
|
|
1972
|
+
* · 只有额度(`maxGoalRounds`)变了才换窗口(那是授权变了)。
|
|
1973
|
+
*/
|
|
1957
1974
|
function windowObjective(target) {
|
|
1958
|
-
return
|
|
1975
|
+
return target
|
|
1959
1976
|
}
|
|
1960
1977
|
|
|
1961
1978
|
function continuationView(agent) {
|
|
@@ -2038,10 +2055,19 @@ export function apply(ctx, config = {}) {
|
|
|
2038
2055
|
* 是我们的 ⇒ 身份可以换;不是我们的 ⇒ 以人为准,一个字都不动(§17.2)。
|
|
2039
2056
|
*/
|
|
2040
2057
|
const ours = before !== null && before.label !== null && before.label !== undefined && before.label === current.objective
|
|
2041
|
-
|
|
2058
|
+
// 额度变了 = 授权变了 ⇒ 换一枚新窗口(清 + 建,按宿主契约)。
|
|
2059
|
+
if (ours && current.maxGoalRounds !== maxGoalRounds) {
|
|
2042
2060
|
goals.clear(agent, { id: current.id, revision: current.revision })
|
|
2043
2061
|
const view = goals.create(agent, { objective, maxGoalRounds })
|
|
2044
|
-
return said(`\n续跑窗口已换新(
|
|
2062
|
+
return said(`\n续跑窗口已换新(额度变成 ${windowLabel(view.maxGoalRounds)})。`, armed(view.id, view.objective))
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* 台上那句话该更新了(服务对象换了口径 / 换了对象)⇒ **改这句话,不换窗口**。
|
|
2066
|
+
* `edit` 不动轮数,所以"反复修订刷新预算"这条捷径不成立。
|
|
2067
|
+
*/
|
|
2068
|
+
if (ours && current.objective !== objective) {
|
|
2069
|
+
goals.edit(agent, { id: current.id, revision: current.revision }, { objective })
|
|
2070
|
+
return said('', armed(current.id, objective))
|
|
2045
2071
|
}
|
|
2046
2072
|
if (before !== null && before.state === 'armed' && before.goal === current.id) return ''
|
|
2047
2073
|
// 账没跟上(旧会话升级上来、或上一次落账没发出去):补一条,不碰平台对象。
|
|
@@ -2251,6 +2277,37 @@ export function apply(ctx, config = {}) {
|
|
|
2251
2277
|
}
|
|
2252
2278
|
}
|
|
2253
2279
|
|
|
2280
|
+
/**
|
|
2281
|
+
* 请人审阅**已经立起来的**计划,并按结果落授权记号。
|
|
2282
|
+
*
|
|
2283
|
+
* 为什么必须有这条路:`CreatePlan` 会请人审阅,但**改完不会再请** —— 而审阅卡上那句
|
|
2284
|
+
* 「改完再呈一次」正是我们承诺的。2026-09-12 实测的死胡同:人在审阅里选了「先改再交」,
|
|
2285
|
+
* 模型照意见改了计划,然后**没有任何入口**能再呈一次 ⇒ 计划永远停在未授权,
|
|
2286
|
+
* 而内核又如实拒绝开工。**打不开的门比没有门更糟**:它把机制变成死胡同。
|
|
2287
|
+
*
|
|
2288
|
+
* 尺子与 CreatePlan 完全一样:只有 approved 才落记号,其余三种结局一个字都不落。
|
|
2289
|
+
*/
|
|
2290
|
+
async function reviewExistingPlan(plan, exec, mutations, stepsOverride) {
|
|
2291
|
+
const steps = stepsOverride ?? plan.steps
|
|
2292
|
+
const review = await requestPlanReview(
|
|
2293
|
+
exec.agent,
|
|
2294
|
+
renderPlanForReview(
|
|
2295
|
+
plan.id,
|
|
2296
|
+
plan.brief ?? '',
|
|
2297
|
+
null,
|
|
2298
|
+
steps.map((step) => ({ id: step.id, do: step.do, artifacts: step.artifacts ?? [], done_criteria: step.done_criteria })),
|
|
2299
|
+
),
|
|
2300
|
+
exec.signal,
|
|
2301
|
+
)
|
|
2302
|
+
if (review.outcome === 'approved') {
|
|
2303
|
+
mutations.push({ t: 'plan/confirmed', plan: plan.id, by: 'user', at: new Date().toISOString() })
|
|
2304
|
+
return { confirmed: true, note: '\n人在审阅里**批准**了这份计划——授权记号已落账,可以开工。' }
|
|
2305
|
+
}
|
|
2306
|
+
if (review.outcome === 'declined') return { confirmed: false, note: `\n人又一次选择**先改再交**${review.note === '' ? '' : `,他的意见:${review.note}`}——仍未授权,按意见再改。` }
|
|
2307
|
+
if (review.outcome === 'cancelled') return { confirmed: false, note: '\n人把审阅撤下、改为先说话:仍未授权,等他的下一步指令。' }
|
|
2308
|
+
return { confirmed: false, note: `\n(这份计划还没有得到人的授权:${review.note}。**不要开工**——如实停下等人。)` }
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2254
2311
|
/** 给人审阅的计划正文(markdown)。原生审阅界面渲染它,所以它得是人读得懂的一份计划。 */
|
|
2255
2312
|
function renderPlanForReview(planId, brief, goal, steps) {
|
|
2256
2313
|
const lines = [`# 计划:${typeof brief === 'string' && brief.trim() !== '' ? brief.trim() : planId}`]
|
|
@@ -2481,7 +2538,8 @@ export function apply(ctx, config = {}) {
|
|
|
2481
2538
|
`${isRevision ? `目标已修订到 rev${revision}` : `目标已立(${goalId})`},登记 ${hypotheses.length} 条假设。${scoutNote}` +
|
|
2482
2539
|
applyContinuationPolicy(exec.agent, state, hostService.derive(sessionId), {
|
|
2483
2540
|
goalOpen: true,
|
|
2484
|
-
|
|
2541
|
+
// 平台上那句话(给人看)用**目标的主张**说,不写 id;身份与额度由账上那枚窗口负责。
|
|
2542
|
+
target: `继续做完:${clip(String(args.claim ?? ''), 26)}`,
|
|
2485
2543
|
mutations,
|
|
2486
2544
|
}),
|
|
2487
2545
|
})
|
|
@@ -2779,12 +2837,14 @@ export function apply(ctx, config = {}) {
|
|
|
2779
2837
|
const problem = validateSteps([args.step])
|
|
2780
2838
|
if (problem !== null) return fail('invalid_step', problem)
|
|
2781
2839
|
if (plan.steps.some((step) => step.id === args.step.id)) return fail('duplicate_step', `步骤 id 已存在:${args.step.id}`)
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2840
|
+
const amended = { id: args.step.id, do: args.step.do, artifacts: args.step.artifacts ?? [], done_criteria: args.step.done_criteria, tests: args.step.tests ?? null }
|
|
2841
|
+
mutations.push({ t: 'plan/amended', plan: plan.id, step: amended })
|
|
2842
|
+
/**
|
|
2843
|
+
* **没授权的计划:改完再呈一次**(审阅卡上承诺的就是这句)。
|
|
2844
|
+
* 已经授权的计划不再打扰人 —— 补一步不是重新立约。
|
|
2845
|
+
*/
|
|
2846
|
+
const again = plan.confirmed_at === null ? await reviewExistingPlan(plan, exec, mutations, [...plan.steps, amended]) : { note: '' }
|
|
2847
|
+
return done({ ok: true, code: 'plan_amended', progress_changed: false, message: `已补一步 ${args.step.id}(进度不变)。${again.note}` })
|
|
2788
2848
|
},
|
|
2789
2849
|
})
|
|
2790
2850
|
|
|
@@ -2813,7 +2873,35 @@ export function apply(ctx, config = {}) {
|
|
|
2813
2873
|
const selfRef = SELF_REFERENCE.find(([pattern]) => pattern.test(criteria))
|
|
2814
2874
|
if (selfRef !== undefined) return fail('criteria_self_reference', selfRef[1])
|
|
2815
2875
|
mutations.push({ t: 'plan/refined', plan: plan.id, step: step.id, old_criteria: step.done_criteria, new_criteria: criteria, reason: args.reason ?? null })
|
|
2816
|
-
|
|
2876
|
+
const refinedSteps = plan.steps.map((item) => (item.id === step.id ? { ...item, done_criteria: criteria } : item))
|
|
2877
|
+
const againRefined = plan.confirmed_at === null ? await reviewExistingPlan(plan, exec, mutations, refinedSteps) : { note: '' }
|
|
2878
|
+
return done({ ok: true, code: 'plan_refined', progress_changed: false, message: `步骤 ${step.id} 的判据已精化(进度不变,旧判据留痕)。${againRefined.note}` })
|
|
2879
|
+
},
|
|
2880
|
+
})
|
|
2881
|
+
|
|
2882
|
+
defineTool({
|
|
2883
|
+
name: 'RequestPlanReview',
|
|
2884
|
+
description:
|
|
2885
|
+
'把**当前这份计划**再呈给人审阅一次(不改任何东西)。计划还没获授权、而你已经按人的意见改完时用它——审阅卡不会自己回来,必须有人再呈一次。人批准 ⇒ 授权记号落账,可以开工;否则一个字都不落,如实停下。已经授权的计划不必再问。',
|
|
2886
|
+
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
|
2887
|
+
output: CARD_OUTPUT,
|
|
2888
|
+
async execute(_args, exec) {
|
|
2889
|
+
const call = open(exec)
|
|
2890
|
+
if (call.ok !== true) return call.response
|
|
2891
|
+
const { hostService, sessionId, state, mutations } = call
|
|
2892
|
+
const done = finish(hostService, sessionId, mutations)
|
|
2893
|
+
const plan = activePlanOf(state)
|
|
2894
|
+
if (plan === null) return fail('no_active_plan', '没有活动计划。')
|
|
2895
|
+
if (plan.confirmed_at !== null) {
|
|
2896
|
+
return done({ ok: true, code: 'already_confirmed', progress_changed: false, message: `计划 ${plan.id} 已经获授权(${plan.confirmed_by ?? 'user'}),不必再问。` })
|
|
2897
|
+
}
|
|
2898
|
+
const review = await reviewExistingPlan(plan, exec, mutations)
|
|
2899
|
+
return done({
|
|
2900
|
+
ok: true,
|
|
2901
|
+
code: review.confirmed ? 'plan_confirmed' : 'plan_review_pending',
|
|
2902
|
+
progress_changed: false,
|
|
2903
|
+
message: review.confirmed ? `计划 ${plan.id} 已获授权。${review.note}` : `计划 ${plan.id} 仍未授权。${review.note}`,
|
|
2904
|
+
})
|
|
2817
2905
|
},
|
|
2818
2906
|
})
|
|
2819
2907
|
|