dsh-tree-task-flow 0.3.13 → 0.3.15
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/README.md +157 -91
- package/client.js +629 -512
- package/config.example.yml +13 -6
- package/cordis.patch.yml +5 -3
- package/lib/commands.js +91 -18
- package/lib/http.js +228 -47
- package/lib/index.js +24 -16
- package/lib/interrupt.js +18 -0
- package/lib/notes.js +173 -0
- package/lib/pause.js +84 -15
- package/lib/plan.js +48 -0
- package/lib/prompt.js +20 -4
- package/lib/region.js +375 -58
- package/lib/render.js +311 -0
- package/lib/requirement.js +914 -0
- package/lib/settings.js +53 -0
- package/lib/store.js +91 -7
- package/lib/tools.js +602 -111
- package/package.json +2 -2
package/lib/settings.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 界面上的开关:**哪些键可以改**,以及怎么把它们并进配置。
|
|
3
|
+
*
|
|
4
|
+
* 单独成文件是为了让"可改的键"只有一份定义:`lib/index.js` 开机时用它把落盘的
|
|
5
|
+
* 选择并进配置,`lib/http.js` 收到面板的请求时用它校验键名。两处各写一份,
|
|
6
|
+
* 迟早会出现"界面能点、开机不认"或者反过来的错位。
|
|
7
|
+
*
|
|
8
|
+
* 优先级:**界面改过的(落盘)> profile 里的 config > DEFAULT_CONFIG**。
|
|
9
|
+
* 界面上改完还被 profile 顶回去,人会以为开关坏了。
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-tree-task-flow/settings
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 面板可以改的键。
|
|
16
|
+
*
|
|
17
|
+
* 只收布尔开关:它们都是"要不要让插件自己动起来"这类事,改了立刻能看出效果,
|
|
18
|
+
* 也不需要额外解释。`maxAutoRounds` / `interruptDelaysMs` 这类带数值的旋钮
|
|
19
|
+
* 留在 profile 里——界面上给一个数字要配输入框与校验,不值当。
|
|
20
|
+
*/
|
|
21
|
+
export const SETTING_KEYS = ["autoContinue", "interruptRecovery"];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 从一个对象里挑出认识的开关,且值必须是布尔。
|
|
25
|
+
*
|
|
26
|
+
* 落盘的 `settings.json` 可能被人手改坏、也可能是旧版本留下的键,
|
|
27
|
+
* 所以这里**只认名单上的键**,其余一概忽略;值不是布尔的也忽略,
|
|
28
|
+
* 免得一个 `"false"` 字符串把 `cfg.autoContinue !== true` 那种判断带偏。
|
|
29
|
+
*
|
|
30
|
+
* @param value - 待挑选的对象,通常是落盘的设置。
|
|
31
|
+
* @returns 只含合法键值的新对象;没有合法的就是 `{}`。
|
|
32
|
+
*/
|
|
33
|
+
export function pickSettings(value) {
|
|
34
|
+
const picked = {};
|
|
35
|
+
if (value === null || typeof value !== "object") return picked;
|
|
36
|
+
for (const key of SETTING_KEYS) {
|
|
37
|
+
if (typeof value[key] === "boolean") picked[key] = value[key];
|
|
38
|
+
}
|
|
39
|
+
return picked;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 把当前生效的开关压成面板要的形状。
|
|
44
|
+
*
|
|
45
|
+
* @param cfg - 合并后的配置。
|
|
46
|
+
* @returns `{ autoContinue, interruptRecovery }`,缺的按 false 报。
|
|
47
|
+
*/
|
|
48
|
+
export function settingsSnapshot(cfg) {
|
|
49
|
+
return {
|
|
50
|
+
autoContinue: cfg?.autoContinue === true,
|
|
51
|
+
interruptRecovery: cfg?.interruptRecovery === true,
|
|
52
|
+
};
|
|
53
|
+
}
|
package/lib/store.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 三类状态,都**以 `sessionId` 为键**:
|
|
5
5
|
*
|
|
6
|
-
* - `plans/<sessionId>.json`
|
|
6
|
+
* - `plans/<sessionId>.json` 计划树(需求都在里面的 `goals` 里)
|
|
7
|
+
* - `notes/<sessionId>.json` 笔记,按会话一份
|
|
7
8
|
* - `surfaces/<sessionId>.json` 三层的折叠游标(只给界面看,不参与折叠)
|
|
8
9
|
* - `held/<sessionId>.json` 「被按住」:暂停 / 停止,**要跨重启保留**
|
|
9
10
|
* - `revive/<sessionId>.json` 意外中断的拉起重试链,**要跨重启保留**
|
|
11
|
+
* - `settings.json` 界面上的开关,**跟会话无关**,整个状态根一份
|
|
10
12
|
*
|
|
11
13
|
* 计划属于建立它的那个会话。放到会话无关的位置,任何一个会话的计划都会被
|
|
12
14
|
* 所有会话的 `agent/pre-step` 读到——那等于把不相干的节点指派塞进别人的上下文,
|
|
@@ -27,6 +29,8 @@
|
|
|
27
29
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
28
30
|
import { homedir } from "node:os";
|
|
29
31
|
import { dirname, join } from "node:path";
|
|
32
|
+
import { normalizeNotes } from "./notes.js";
|
|
33
|
+
import { normalizeRequirement } from "./requirement.js";
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* 没有显式配置时的状态根目录。
|
|
@@ -59,19 +63,29 @@ function writeJson(file, value) {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/**
|
|
62
|
-
* 老结构(整棵树只有一个 `goal` 字段)读进来时升级成 `goals`
|
|
66
|
+
* 老结构(整棵树只有一个 `goal` 字段)读进来时升级成 `goals` 数组,
|
|
67
|
+
* 再给每条记录补上需求那一层缺的字段。
|
|
63
68
|
*
|
|
64
|
-
*
|
|
69
|
+
* 计划文件是插件自己的持久状态,换个版本不该让既有的树失联——所以**读的时候补,
|
|
70
|
+
* 不迁移磁盘上的老文件**:老会话的计划文件保持原样,缺什么字段就在内存里补什么,
|
|
71
|
+
* 下一次写入自然带上(见 `requirement.normalizeRequirement`)。
|
|
65
72
|
*
|
|
66
73
|
* @param plan - 磁盘上读到的对象,可能是 null。
|
|
67
74
|
* @returns 归一化后的计划树。
|
|
68
75
|
*/
|
|
69
76
|
function normalizePlan(plan) {
|
|
70
77
|
if (plan === null || typeof plan !== "object") return plan;
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
let upgraded = plan;
|
|
79
|
+
if (!Array.isArray(plan.goals)) {
|
|
80
|
+
if (plan.goal === undefined || plan.goal === null) {
|
|
81
|
+
upgraded = { ...plan, goals: [] };
|
|
82
|
+
} else {
|
|
83
|
+
const { goal, ...rest } = plan;
|
|
84
|
+
upgraded = { ...rest, version: 2, goals: [goal] };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const goal of upgraded.goals) normalizeRequirement(goal);
|
|
88
|
+
return upgraded;
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/**
|
|
@@ -89,6 +103,12 @@ export function createStore(root) {
|
|
|
89
103
|
/** 意外中断重试链的文件路径。 */
|
|
90
104
|
const reviveFile = (sessionId) => file("revive", `${sessionId}.json`);
|
|
91
105
|
|
|
106
|
+
/** 设置文件的路径(与会话无关,整个状态根一份)。 */
|
|
107
|
+
const settingsFile = () => file("settings.json");
|
|
108
|
+
|
|
109
|
+
/** 笔记文件的路径(按会话一份)。 */
|
|
110
|
+
const notesFile = (sessionId) => file("notes", `${sessionId}.json`);
|
|
111
|
+
|
|
92
112
|
/** 读「被按住」状态;读不到或文件坏了,一律按"没被按住"处理。 */
|
|
93
113
|
function readHeldState(sessionId) {
|
|
94
114
|
if (!sessionId) return { paused: false, stopped: false, at: null };
|
|
@@ -176,6 +196,70 @@ export function createStore(root) {
|
|
|
176
196
|
writeJson(file("surfaces", `${sessionId}.json`), value);
|
|
177
197
|
},
|
|
178
198
|
|
|
199
|
+
// ---- 设置:**与会话无关**,整个状态根一份 ----
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 读界面上的开关。
|
|
203
|
+
*
|
|
204
|
+
* 为什么落盘:开关是"这台机器上的偏好",重启一次就忘掉等于没有这个开关。
|
|
205
|
+
* 它在 `apply()` 里盖在 profile 的 config 之上——**人在界面上的选择优先**,
|
|
206
|
+
* 否则界面改完、profile 一改又被顶回去,人会以为开关坏了。
|
|
207
|
+
*
|
|
208
|
+
* 只认自己认识的键:文件里多出别的字段一律忽略,坏文件当空。
|
|
209
|
+
*
|
|
210
|
+
* @returns `{ autoContinue?, interruptRecovery?, at? }`;没改过就是 `{}`。
|
|
211
|
+
*/
|
|
212
|
+
readSettings() {
|
|
213
|
+
const value = readJson(settingsFile(), null);
|
|
214
|
+
if (value === null || typeof value !== "object") return {};
|
|
215
|
+
return value;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 改几个开关,其余保持磁盘上的原样(读改写,与 `patchHeld` 同一个道理:
|
|
220
|
+
* 两个开关分属两条路径,谁都不能整份覆盖)。
|
|
221
|
+
*
|
|
222
|
+
* @param patch - `{ autoContinue?, interruptRecovery? }`;没给的键不动。
|
|
223
|
+
* @returns 写入后的完整设置对象。
|
|
224
|
+
*/
|
|
225
|
+
patchSettings(patch) {
|
|
226
|
+
const current = readJson(settingsFile(), null);
|
|
227
|
+
const next = current !== null && typeof current === "object" ? { ...current } : {};
|
|
228
|
+
for (const [key, value] of Object.entries(patch ?? {})) {
|
|
229
|
+
if (typeof value === "boolean") next[key] = value;
|
|
230
|
+
}
|
|
231
|
+
next.at = Date.now();
|
|
232
|
+
writeJson(settingsFile(), next);
|
|
233
|
+
return next;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// ---- 笔记:**按会话**,一个会话一份 ----
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 读这个会话的笔记。
|
|
240
|
+
*
|
|
241
|
+
* 笔记跟着会话走:它记的是"这次工作的上下文里必须一直记着的几句话",
|
|
242
|
+
* 换一个会话就等于换了一份工作,旧笔记跟过去只会变成噪声。
|
|
243
|
+
*
|
|
244
|
+
* @param sessionId - 会话 id。
|
|
245
|
+
* @returns `{ version, items, updatedAt }`;没有笔记时 `items` 是空数组。
|
|
246
|
+
*/
|
|
247
|
+
readNotes(sessionId) {
|
|
248
|
+
if (!sessionId) return normalizeNotes(null);
|
|
249
|
+
return normalizeNotes(readJson(notesFile(sessionId), null));
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 整份写回笔记表。调用方(`lib/notes.js` 的 `upsertNote`)已经改好了内容。
|
|
254
|
+
*
|
|
255
|
+
* @param sessionId - 会话 id。
|
|
256
|
+
* @param state - 完整笔记表。
|
|
257
|
+
*/
|
|
258
|
+
writeNotes(sessionId, state) {
|
|
259
|
+
if (!sessionId) return;
|
|
260
|
+
writeJson(notesFile(sessionId), state);
|
|
261
|
+
},
|
|
262
|
+
|
|
179
263
|
// ---- 「被按住」:暂停 / 停止。**要跨重启保留**,所以落盘 ----
|
|
180
264
|
|
|
181
265
|
/**
|