dsh-tree-task-flow 0.3.12 → 0.3.14
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 +153 -30
- package/client.js +212 -10
- package/config.example.yml +17 -3
- package/cordis.patch.yml +11 -5
- package/lib/auto-continue.js +32 -1
- package/lib/commands.js +46 -5
- package/lib/compaction.js +2 -2
- package/lib/http.js +52 -6
- package/lib/index.js +45 -6
- package/lib/interrupt.js +559 -0
- package/lib/pause.js +40 -15
- package/lib/plan.js +144 -30
- package/lib/region.js +65 -15
- package/lib/settings.js +53 -0
- package/lib/store.js +89 -0
- package/lib/tools.js +102 -17
- package/package.json +1 -1
package/cordis.patch.yml
CHANGED
|
@@ -11,11 +11,17 @@
|
|
|
11
11
|
# ⚠️ 同 id 的 config 是**整体替换**,不是逐字段深合并——覆盖时要把需要的
|
|
12
12
|
# 字段一次写全。可覆盖的字段见 DEFAULT_CONFIG:
|
|
13
13
|
#
|
|
14
|
-
# enabled
|
|
15
|
-
# autoContinue
|
|
16
|
-
# maxAutoRounds
|
|
17
|
-
#
|
|
18
|
-
#
|
|
14
|
+
# enabled 总开关 默认 true(写 false 关掉)
|
|
15
|
+
# autoContinue 是否允许自动续行 默认 false(设置面板里能改)
|
|
16
|
+
# maxAutoRounds 连续自动续行轮次上限 默认 5
|
|
17
|
+
# interruptRecovery 意外中断是否自动拉起 默认 true(设置面板里能改)
|
|
18
|
+
# interruptDelaysMs 网络类中断拉起时刻 默认 null(= 1/5/10/30 分钟)
|
|
19
|
+
# promptOrder 工具说明段顺序 默认 1800
|
|
20
|
+
# rootDir 插件状态目录 默认 $DSH_HOME/dsh-task-tree
|
|
21
|
+
#
|
|
22
|
+
# 两个布尔开关(autoContinue / interruptRecovery)平时不用在这里改:
|
|
23
|
+
# **DSH 设置面板**的导航里有一页叫「树形任务流」,点一下就行——**改完立刻生效、
|
|
24
|
+
# 并且盖过这里写的值**(设置面板 > profile config > DEFAULT_CONFIG)。
|
|
19
25
|
- insert:
|
|
20
26
|
- id: task-tree
|
|
21
27
|
name: 'dsh-tree-task-flow'
|
package/lib/auto-continue.js
CHANGED
|
@@ -43,12 +43,43 @@ export function registerAutoContinue(ctx, { store, cfg }) {
|
|
|
43
43
|
return stopped.has(sessionId) || store?.readHeld?.(sessionId)?.stopped === true;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* 用户按下停止:**从落盘的 `turn/end` 认,不能从 `agent/turn-stopping` 认。**
|
|
48
|
+
*
|
|
49
|
+
* `agent/turn-stopping` 只在"这一轮本来就要正常收尾"时才发——`dsh-agent-loop` 里
|
|
50
|
+
* 唯一一处 dispatch 在 `turn()` 的 try 里,紧挨着前面那句 `signal.throwIfAborted()`。
|
|
51
|
+
* 用户按停止会让信号 abort、从别处抛出,catch 里直接记下
|
|
52
|
+
* `turn/end reason.kind = "aborted"` 并重新抛出,那条 dispatch 根本走不到。
|
|
53
|
+
* 也就是说 `payload.signal.aborted` 在那里几乎永远是 `false`——拿它当闸门等于没闸门:
|
|
54
|
+
* 会话被停掉之后 `agent/status` 一转 `idle`,续行立刻又把它拉起来。
|
|
55
|
+
*
|
|
56
|
+
* 可靠的事实是**落盘的 `turn/end`**:中止一定会在 `finally` 里被记成
|
|
57
|
+
* `reason: { kind: "aborted", reason: { kind: "user" } }`(cause 为 `user` 就是人按的,
|
|
58
|
+
* 另有 `parent` / `hook` / `disposed`)。而且它发生在驱动循环 `setPhase(idle)`
|
|
59
|
+
* 之前——`agent/status` 是之后才发的,所以这个标记一定赶得上那次判断。
|
|
60
|
+
*
|
|
61
|
+
* 中止就是中止:不管是谁按的,插件都不该自作主张把这一轮重新拉起来。
|
|
62
|
+
*/
|
|
63
|
+
ctx.on("session/event", (session, event) => {
|
|
64
|
+
if (event?.type !== "turn/end") return;
|
|
65
|
+
if (event.data?.reason?.kind !== "aborted") return;
|
|
66
|
+
const sessionId = session?.id;
|
|
67
|
+
if (sessionId === undefined) return;
|
|
68
|
+
stopped.add(sessionId);
|
|
69
|
+
// 落盘:重启后条目上仍看得到「继续」,也仍然压得住自动续行。
|
|
70
|
+
store?.patchHeld?.(sessionId, { stopped: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 次要路径,留着兜底:如果中止恰好发生在 `agent/turn-stopping` 的 dispatch 过程中
|
|
75
|
+
* (信号在那一刻才变 abort),上面那条 `turn/end` 判据同样会兜住它;这里只是让
|
|
76
|
+
* 标记更早一点落下。**不要把它当成主判据**,原因见上。
|
|
77
|
+
*/
|
|
46
78
|
ctx.on("agent/turn-stopping", (payload) => {
|
|
47
79
|
const sessionId = payload?.agent?.session?.id;
|
|
48
80
|
if (sessionId === undefined) return;
|
|
49
81
|
if (payload?.signal?.aborted === true) {
|
|
50
82
|
stopped.add(sessionId);
|
|
51
|
-
// 落盘:重启后条目上仍看得到「继续」,也仍然压得住自动续行。
|
|
52
83
|
store?.patchHeld?.(sessionId, { stopped: true });
|
|
53
84
|
}
|
|
54
85
|
});
|
package/lib/commands.js
CHANGED
|
@@ -8,13 +8,25 @@
|
|
|
8
8
|
* @module dsh-tree-task-flow/commands
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
activePath,
|
|
13
|
+
checkpointOf,
|
|
14
|
+
complete,
|
|
15
|
+
deleteNode,
|
|
16
|
+
dropNode,
|
|
17
|
+
findNode,
|
|
18
|
+
goalsOf,
|
|
19
|
+
levelLabel,
|
|
20
|
+
renderGoal,
|
|
21
|
+
renderTree,
|
|
22
|
+
} from "./plan.js";
|
|
12
23
|
|
|
13
24
|
/** 用法说明,status 和 help 都会带上。 */
|
|
14
25
|
const USAGE = [
|
|
15
26
|
"/task-tree 查看本会话的任务树与检查点状态",
|
|
16
27
|
"/task-tree done <id> <结果> 提交某个节点的结果并完成它",
|
|
17
|
-
"/task-tree drop <id>
|
|
28
|
+
"/task-tree drop <id> 丢弃某个节点:留着可查,标记为不做了",
|
|
29
|
+
"/task-tree delete <id> 删除某个节点:连子节点一起从树里摘掉,不看状态",
|
|
18
30
|
"/task-tree pause 暂停:当前工具跑完后停住,不再往下推进",
|
|
19
31
|
"/task-tree resume 继续:从停住的地方接着跑,不插消息",
|
|
20
32
|
"/task-tree reset 清掉本会话的计划树",
|
|
@@ -59,8 +71,9 @@ export function registerCommands(ctx, { store, cfg, pause = null, enabled = fals
|
|
|
59
71
|
|
|
60
72
|
commands.register({
|
|
61
73
|
name: "task-tree",
|
|
62
|
-
description:
|
|
63
|
-
|
|
74
|
+
description:
|
|
75
|
+
"查看与管理任务树:status / done <id> <结果> / drop <id> / delete <id> / pause / resume / reset",
|
|
76
|
+
input: { hint: "status | done <id> <结果> | drop <id> | delete <id> | pause | resume | reset" },
|
|
64
77
|
handler: (invocation) => {
|
|
65
78
|
const sessionId = invocation?.agent?.session?.id;
|
|
66
79
|
if (sessionId === undefined) {
|
|
@@ -151,6 +164,34 @@ export function registerCommands(ctx, { store, cfg, pause = null, enabled = fals
|
|
|
151
164
|
};
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
if (verb === "delete") {
|
|
168
|
+
const id = rest[0];
|
|
169
|
+
if (id === undefined) {
|
|
170
|
+
return { kind: "error", text: `用法:/task-tree delete <节点 id>\n\n${USAGE}` };
|
|
171
|
+
}
|
|
172
|
+
const plan = store.readPlan(sessionId);
|
|
173
|
+
if (plan === null) return { kind: "error", text: "本会话还没有计划。" };
|
|
174
|
+
// 摘掉之后就查不到"这次删的是哪个目标"了,先记一份。
|
|
175
|
+
const touched = findNode(plan, id);
|
|
176
|
+
const result = deleteNode(plan, id);
|
|
177
|
+
if (!result.ok) return { kind: "error", text: `没有删除:${result.reason}` };
|
|
178
|
+
store.writePlan(sessionId, plan);
|
|
179
|
+
|
|
180
|
+
const lines = [`已删除:${result.removed.join("、")}`, ""];
|
|
181
|
+
if (touched !== null && touched.level !== "goal") {
|
|
182
|
+
// 删的是任务或子任务:它所在的那个目标还在,只给它一个目标的树。
|
|
183
|
+
lines.push(renderGoal(touched.goal, plan, true));
|
|
184
|
+
} else {
|
|
185
|
+
const restGoal = goalsOf(plan).length;
|
|
186
|
+
lines.push(
|
|
187
|
+
restGoal === 0
|
|
188
|
+
? "计划树现在空了。"
|
|
189
|
+
: `这个会话还剩 ${restGoal} 个目标,要看全部用 /task-tree status。`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return { kind: "success", text: lines.join("\n") };
|
|
193
|
+
}
|
|
194
|
+
|
|
154
195
|
if (verb !== "status") {
|
|
155
196
|
return { kind: "error", text: `不认识的子命令:${verb}\n\n${USAGE}` };
|
|
156
197
|
}
|
|
@@ -161,7 +202,7 @@ export function registerCommands(ctx, { store, cfg, pause = null, enabled = fals
|
|
|
161
202
|
if (enabled !== true) {
|
|
162
203
|
lines.push("", DISABLED_NOTE);
|
|
163
204
|
} else if (plan === null) {
|
|
164
|
-
lines.push("", "本会话还没有计划。用
|
|
205
|
+
lines.push("", "本会话还没有计划。用 tree_task_plan 建立目标与任务。");
|
|
165
206
|
} else {
|
|
166
207
|
// 检查点优先于"当前节点":那一刻要推进的不是某个子任务,
|
|
167
208
|
// 而是"这一层到底做完了没有"这个判断。
|
package/lib/compaction.js
CHANGED
|
@@ -67,8 +67,8 @@ const NOTICE_TEXT =
|
|
|
67
67
|
"tree_task消息:上下文刚被压缩过,计划树没有被压缩。用 tree_task_status 取回当前进度、" +
|
|
68
68
|
"各节点已提交的 result,以及下一步该推进哪个节点。";
|
|
69
69
|
|
|
70
|
-
/**
|
|
71
|
-
const NOTICE_LINE = "
|
|
70
|
+
/** 界面上的一行摘要(**不含统一前缀**——前缀由 `pluginSource` 加)。 */
|
|
71
|
+
const NOTICE_LINE = "上下文被压缩,计划树仍在";
|
|
72
72
|
|
|
73
73
|
/** 提示消息的编号,保证同一毫秒内连发两条也不撞 id。 */
|
|
74
74
|
let noticeSeq = 0;
|
package/lib/http.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - **暂停状态**:条目上的暂停/继续按钮写的就是这个。暂停挂的是
|
|
15
15
|
* `agent/pre-step`(见 `pause.js`),不插消息、不改计划,所以它不属于计划树,
|
|
16
16
|
* 单独回传。
|
|
17
|
+
* - **设置**:面板上那两个开关(见 `settings.js`)。它跟会话无关,读的时候顺手带上,
|
|
18
|
+
* 改的时候写盘并**就地改活配置**,所以改完立刻生效,不用重启。
|
|
17
19
|
*
|
|
18
20
|
* 条目上的按钮都写计划树,所以写完要把**新的**活动路径、结果与检查点一并回传,
|
|
19
21
|
* 免得条目还要再发一次请求才知道自己按出了什么效果。
|
|
@@ -21,7 +23,8 @@
|
|
|
21
23
|
* @module dsh-tree-task-flow/http
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
|
-
import { activePath, checkpointOf, complete, dropNode, levelLabel } from "./plan.js";
|
|
26
|
+
import { activePath, checkpointOf, complete, deleteNode, dropNode, levelLabel } from "./plan.js";
|
|
27
|
+
import { SETTING_KEYS, settingsSnapshot } from "./settings.js";
|
|
25
28
|
|
|
26
29
|
/** 统一的 JSON 响应。 */
|
|
27
30
|
function sendJson(response, status, body) {
|
|
@@ -101,9 +104,11 @@ function afterWrite(plan) {
|
|
|
101
104
|
* 注册全部面板接口。
|
|
102
105
|
*
|
|
103
106
|
* @param ctx - 挂载本行的 Cordis 上下文。
|
|
104
|
-
* @param options - `{ store, autoContinue, pause }
|
|
107
|
+
* @param options - `{ store, autoContinue, pause, cfg }`;插件被关闭时 `autoContinue`
|
|
108
|
+
* 与 `pause` 是 null。`cfg` 是那一份**活的对象**:读的时候用它报当前开关,
|
|
109
|
+
* 改的时候就地改它——各模块按事件现读,所以下一刻就生效。
|
|
105
110
|
*/
|
|
106
|
-
export function registerHttp(ctx, { store, autoContinue, pause = null }) {
|
|
111
|
+
export function registerHttp(ctx, { store, autoContinue, pause = null, cfg = null }) {
|
|
107
112
|
ctx.inject(["webServer"], (host) => {
|
|
108
113
|
host.effect(() => {
|
|
109
114
|
// 一棵树的全部信息:结构 + 每层结果 + 检查点 + 当前路径 + 三层折叠游标。
|
|
@@ -130,12 +135,31 @@ export function registerHttp(ctx, { store, autoContinue, pause = null }) {
|
|
|
130
135
|
},
|
|
131
136
|
stopped: autoContinue?.isStopped?.(sessionId) === true,
|
|
132
137
|
paused: pause?.isPaused?.(sessionId) === true,
|
|
138
|
+
settings: settingsSnapshot(cfg),
|
|
133
139
|
});
|
|
134
140
|
},
|
|
135
141
|
},
|
|
136
142
|
"dsh-tree-task-flow: tree",
|
|
137
143
|
);
|
|
138
144
|
|
|
145
|
+
// DSH 设置面板里那一页用的接口:**跟会话无关**,只回两个开关的当前值与总开关
|
|
146
|
+
// 状态。设置面板没有"当前会话"这个概念,所以它不能走 `/tree`。
|
|
147
|
+
host.webServer.register(
|
|
148
|
+
{
|
|
149
|
+
kind: "exact",
|
|
150
|
+
path: "/dsh-tree-task-flow/settings",
|
|
151
|
+
handler: async (request, response) => {
|
|
152
|
+
if (request.method !== "GET") return sendJson(response, 405, { ok: false, error: "只支持 GET" });
|
|
153
|
+
sendJson(response, 200, {
|
|
154
|
+
ok: true,
|
|
155
|
+
enabled: cfg?.enabled === true,
|
|
156
|
+
settings: settingsSnapshot(cfg),
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
"dsh-tree-task-flow: settings",
|
|
161
|
+
);
|
|
162
|
+
|
|
139
163
|
// 面板上的按钮都走这一个端点。
|
|
140
164
|
host.webServer.register(
|
|
141
165
|
{
|
|
@@ -161,6 +185,23 @@ export function registerHttp(ctx, { store, autoContinue, pause = null }) {
|
|
|
161
185
|
});
|
|
162
186
|
}
|
|
163
187
|
|
|
188
|
+
// 面板上的开关。跟会话无关,所以不读 sessionId。
|
|
189
|
+
//
|
|
190
|
+
// 两件事一起做:**写盘**(重启后还在)与**就地改活配置**(这一刻就生效)。
|
|
191
|
+
// 各模块都是"事件来了现读 cfg",所以改完不用重启,下一次事件就是新值。
|
|
192
|
+
if (action === "setting") {
|
|
193
|
+
const key = String(body.key ?? "");
|
|
194
|
+
if (!SETTING_KEYS.includes(key)) {
|
|
195
|
+
return sendJson(response, 400, { ok: false, error: `不认识的开关:${key}` });
|
|
196
|
+
}
|
|
197
|
+
if (typeof body.value !== "boolean") {
|
|
198
|
+
return sendJson(response, 400, { ok: false, error: "开关的值只能是 true 或 false" });
|
|
199
|
+
}
|
|
200
|
+
store.patchSettings({ [key]: body.value });
|
|
201
|
+
if (cfg !== null && typeof cfg === "object") cfg[key] = body.value;
|
|
202
|
+
return sendJson(response, 200, { ok: true, settings: settingsSnapshot(cfg) });
|
|
203
|
+
}
|
|
204
|
+
|
|
164
205
|
if (action === "reset") {
|
|
165
206
|
store.writePlan(sessionId, null);
|
|
166
207
|
// 计划没了,游标也必须跟着清:游标里记的是"这一层总结到哪儿了",
|
|
@@ -175,17 +216,22 @@ export function registerHttp(ctx, { store, autoContinue, pause = null }) {
|
|
|
175
216
|
return sendJson(response, 200, { ok: true, active: null, checkpoint: null, results: null });
|
|
176
217
|
}
|
|
177
218
|
|
|
178
|
-
if (action === "done" || action === "drop") {
|
|
219
|
+
if (action === "done" || action === "drop" || action === "delete") {
|
|
179
220
|
const plan = store.readPlan(sessionId);
|
|
180
221
|
if (plan === null) return sendJson(response, 404, { ok: false, error: "这个会话没有计划" });
|
|
181
222
|
const id = String(body.id ?? "");
|
|
182
223
|
// 完成必须带结果:插件要用它替换掉这个节点的执行过程。
|
|
183
|
-
const outcome =
|
|
224
|
+
const outcome =
|
|
225
|
+
action === "done"
|
|
226
|
+
? complete(plan, id, body.result)
|
|
227
|
+
: action === "drop"
|
|
228
|
+
? dropNode(plan, id)
|
|
229
|
+
: deleteNode(plan, id);
|
|
184
230
|
if (!outcome.ok) return sendJson(response, 200, { ok: false, error: outcome.reason });
|
|
185
231
|
store.writePlan(sessionId, plan);
|
|
186
232
|
return sendJson(response, 200, {
|
|
187
233
|
ok: true,
|
|
188
|
-
events: outcome.events ?? outcome.dropped ?? [],
|
|
234
|
+
events: outcome.events ?? outcome.dropped ?? outcome.removed ?? [],
|
|
189
235
|
...afterWrite(plan),
|
|
190
236
|
});
|
|
191
237
|
}
|
package/lib/index.js
CHANGED
|
@@ -21,9 +21,11 @@ import { registerAutoContinue } from "./auto-continue.js";
|
|
|
21
21
|
import { registerCommands } from "./commands.js";
|
|
22
22
|
import { registerCompactionNotice } from "./compaction.js";
|
|
23
23
|
import { registerHttp } from "./http.js";
|
|
24
|
+
import { registerInterruptRecovery } from "./interrupt.js";
|
|
24
25
|
import { registerPause } from "./pause.js";
|
|
25
26
|
import { DEFAULT_ORDER, registerPromptSection } from "./prompt.js";
|
|
26
27
|
import { registerRegion } from "./region.js";
|
|
28
|
+
import { pickSettings } from "./settings.js";
|
|
27
29
|
import { createStore, defaultRoot } from "./store.js";
|
|
28
30
|
import { registerTools } from "./tools.js";
|
|
29
31
|
|
|
@@ -54,13 +56,35 @@ export const DEFAULT_CONFIG = {
|
|
|
54
56
|
/**
|
|
55
57
|
* 是否允许在模型停下时自动续行。
|
|
56
58
|
*
|
|
57
|
-
* ★
|
|
58
|
-
*
|
|
59
|
-
*
|
|
59
|
+
* ★ **默认关闭。** 它会趁你没说话的时候**往会话里推一条消息**、把模型再叫起来跑一轮,
|
|
60
|
+
* 是最需要人知情的一项。
|
|
61
|
+
*
|
|
62
|
+
* 这里曾经写死过一条规矩:"插件还没有配置界面,功能就不能默认关"——默认关等于这个
|
|
63
|
+
* 功能不存在,没人会去翻 YAML 把它打开。**那条规矩随着设置界面一起作废**:现在面板上
|
|
64
|
+
* 就能点开,默认值该按"会不会打扰人"来定,而不是按"能不能被发现"。
|
|
65
|
+
*
|
|
66
|
+
* 想打开:面板上的设置里点一下,或在 profile 里写 `autoContinue: true`。
|
|
60
67
|
*/
|
|
61
68
|
autoContinue: false,
|
|
62
69
|
/** 连续自动续行的轮次上限,达到就停。 */
|
|
63
70
|
maxAutoRounds: 5,
|
|
71
|
+
/**
|
|
72
|
+
* 意外中断的自动拉起。
|
|
73
|
+
*
|
|
74
|
+
* ★ **默认开启**。它和 `autoContinue` 管的是两件事:`autoContinue` 是"模型自己
|
|
75
|
+
* 正常收尾了、但计划没做完",本项是"模型根本没机会说完就断了"——网络断了、
|
|
76
|
+
* 模型 API 报错、或者进程在工具执行中途没了。后者不加干预就永远停在那儿。
|
|
77
|
+
*
|
|
78
|
+
* 用户按停止(`aborted`)永远不拉起;余额不足(`QUOTA`)也不拉(等再久也不会好)。
|
|
79
|
+
*/
|
|
80
|
+
interruptRecovery: true,
|
|
81
|
+
/**
|
|
82
|
+
* 网络类中断的拉起时刻表,单位毫秒,相对中断那一刻。
|
|
83
|
+
*
|
|
84
|
+
* 默认 `[1 分钟, 5 分钟, 10 分钟, 30 分钟]`——四个**累计时刻**,不是递增间隔。
|
|
85
|
+
* 一条链只跑一遍;跑完还失败就停手,等真人发消息再允许重开。
|
|
86
|
+
*/
|
|
87
|
+
interruptDelaysMs: null,
|
|
64
88
|
/** 工具说明段落的排序。 */
|
|
65
89
|
promptOrder: DEFAULT_ORDER,
|
|
66
90
|
/** 状态目录;null 表示 $DSH_HOME/dsh-task-tree。 */
|
|
@@ -78,6 +102,13 @@ export function apply(ctx, config) {
|
|
|
78
102
|
const root = cfg.rootDir ?? defaultRoot();
|
|
79
103
|
const store = createStore(root);
|
|
80
104
|
|
|
105
|
+
// 面板上改过的开关盖在 profile 的 config 之上:**人在界面上的选择优先**。
|
|
106
|
+
// 不这么做的话,界面改完、profile 一改又被顶回去,人会以为开关坏了。
|
|
107
|
+
//
|
|
108
|
+
// `cfg` 是**活的对象**:面板改开关时就地改它,各模块拿的是同一个引用,
|
|
109
|
+
// 所以立刻生效,不用重启。这也是为什么各模块要按事件现读 `cfg`,不是开机读一次。
|
|
110
|
+
Object.assign(cfg, pickSettings(store.readSettings()));
|
|
111
|
+
|
|
81
112
|
// 被显式关掉时,一个字节都不碰会话:不注册工具、不加提示词段落、不折叠上下文、
|
|
82
113
|
// 不续行——只留一条命令,让人能确认"装了但被关掉了"以及怎么打开。
|
|
83
114
|
if (cfg.enabled !== true) {
|
|
@@ -85,7 +116,7 @@ export function apply(ctx, config) {
|
|
|
85
116
|
// 条目接口照挂:客户端条目与 enabled 无关,照样会来读。读不到计划它就返回
|
|
86
117
|
// null,一个像素都不占;而既有的计划文件仍然能看到。
|
|
87
118
|
// 只读插件自己的状态目录,不碰任何会话。
|
|
88
|
-
registerHttp(ctx, { store, autoContinue: null, pause: null });
|
|
119
|
+
registerHttp(ctx, { store, autoContinue: null, pause: null, cfg });
|
|
89
120
|
ctx.logger?.warn?.(
|
|
90
121
|
"dsh-tree-task-flow: 已按配置关闭(enabled=false)。要打开请在 profile 的 cordis.patch.yml 里把它设为 true。",
|
|
91
122
|
);
|
|
@@ -115,11 +146,19 @@ export function apply(ctx, config) {
|
|
|
115
146
|
// 下一次模型请求还没发出"那个岔口。它同时压住上面的自动续行兜底。
|
|
116
147
|
const pause = registerPause(ctx, { store, autoContinue });
|
|
117
148
|
|
|
149
|
+
// 意外中断的拉起:网络类按 1/5/10/30 分钟各一次,工具类(结果未知的合成错误)
|
|
150
|
+
// 立刻一次,再失败就收走那一次尝试再来一次。它与 autoContinue 并存:
|
|
151
|
+
// 那边管"模型正常收尾但计划没做完",这边管"模型没机会说完就断了"。
|
|
152
|
+
//
|
|
153
|
+
// ★ **无条件注册**:开关(`interruptRecovery`)在面板上随时可改,注册与否取决于
|
|
154
|
+
// 它就没法在运行中打开。真正的闸门放在模块内部——事件来了先问一句 `cfg`。
|
|
155
|
+
registerInterruptRecovery(ctx, { store, cfg, pause });
|
|
156
|
+
|
|
118
157
|
// /task-tree 命令:查看与管理任务树,也是"暂停 / 继续"的开关。
|
|
119
158
|
registerCommands(ctx, { store, cfg, pause, enabled: true });
|
|
120
159
|
|
|
121
|
-
// 任务树条目要用的 HTTP
|
|
122
|
-
registerHttp(ctx, { store, autoContinue, pause });
|
|
160
|
+
// 任务树条目要用的 HTTP 接口。`cfg` 传进去是为了让面板能读到并改掉那几个开关。
|
|
161
|
+
registerHttp(ctx, { store, autoContinue, pause, cfg });
|
|
123
162
|
|
|
124
163
|
ctx.logger?.info?.(`dsh-tree-task-flow: 已启用(状态目录 ${root})`);
|
|
125
164
|
}
|