@michengai/dsh-automation 0.1.25 → 0.1.26
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 +5 -0
- package/CHANGELOG.zh-CN.md +5 -0
- package/lib/index.js +250 -246
- package/lib/types/executor.d.ts +7 -1
- package/package.json +16 -16
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/00-/351/230/205/350/257/273/345/257/274/350/210/252.md +0 -11
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/01-/351/241/271/347/233/256/346/246/202/350/247/210.md +0 -22
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/02-/345/275/223/345/211/215/347/212/266/346/200/201.md +0 -9
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/03-/345/276/205/345/212/236/344/270/216/351/230/273/345/241/236.md +0 -7
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/04-/350/277/255/344/273/243/350/267/257/347/272/277/345/233/276.md +0 -5
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/05-/346/212/200/346/234/257/346/236/266/346/236/204/345/237/272/347/272/277.md +0 -10
- package/docs/00-/344/272/244/346/216/245/345/205/245/345/217/243/06-/346/226/207/346/241/243/347/274/226/345/206/231/350/247/204/350/214/203.md +0 -8
- package/docs/01-/345/275/223/345/211/215/345/267/245/344/275/234/I001-/344/273/273/345/212/241/350/207/252/345/212/250/345/214/226/346/217/222/344/273/266/00-/350/277/255/344/273/243/346/200/273/350/247/210.md +0 -56
- package/docs/03-/346/212/200/346/234/257/346/236/266/346/236/204/00-/351/242/206/345/237/237/347/264/242/345/274/225.md +0 -5
- package/docs/03-/346/212/200/346/234/257/346/236/266/346/236/204/01-/350/260/203/345/272/246/344/270/216/346/211/247/350/241/214/346/250/241/345/236/213.md +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
The five most recent published versions are listed below. Git tags and GitHub Releases now mirror these entries; historical sections retain links to their original release commits.
|
|
6
6
|
|
|
7
|
+
## 0.1.26 — 2026-09-03
|
|
8
|
+
|
|
9
|
+
- Updated the DSH development baseline to `0.1.2-rc.1`.
|
|
10
|
+
- Read Session events through `snapshotEvents()` on current Hosts while retaining the legacy `events` fallback for older Hosts.
|
|
11
|
+
|
|
7
12
|
## 0.1.25 — 2026-09-03
|
|
8
13
|
|
|
9
14
|
- Fixed scheduled-task select menus so their backgrounds, borders, shadows, and hover states follow the DSH theme. Time options now remain readable in light mode.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
以下记录最近发布的五个版本。Git 标签与 GitHub Release 现已和这些条目同步;历史条目继续保留原始发布提交链接。
|
|
6
6
|
|
|
7
|
+
## 0.1.26 — 2026-09-03
|
|
8
|
+
|
|
9
|
+
- 将 DSH 开发基线升级到 `0.1.2-rc.1`。
|
|
10
|
+
- 当前 Host 使用 `snapshotEvents()` 读取 Session 事件,同时保留旧 Host 的 `events` 回退。
|
|
11
|
+
|
|
7
12
|
## 0.1.25 — 2026-09-03
|
|
8
13
|
|
|
9
14
|
- 修复定时任务下拉菜单的背景、边框、阴影和悬停态未跟随 DSH 主题的问题,浅色模式下的时间选项恢复清晰可读。
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,255 @@ var __export = (target, all) => {
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import z2 from "@deepseek-ai/schemastery";
|
|
9
9
|
|
|
10
|
+
// src/executor.ts
|
|
11
|
+
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
12
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
13
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
14
|
+
import { setApprovalPolicy } from "@deepseek-ai/dsh-user-approval";
|
|
15
|
+
import { WorkspaceId } from "@deepseek-ai/dsh-workspace";
|
|
16
|
+
|
|
17
|
+
// src/run-title.ts
|
|
18
|
+
function formatRunStamp(iso) {
|
|
19
|
+
const value = new Date(iso);
|
|
20
|
+
if (Number.isNaN(value.getTime())) return iso;
|
|
21
|
+
const date5 = `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
|
|
22
|
+
const time3 = `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
|
|
23
|
+
return `${date5} ${time3}`;
|
|
24
|
+
}
|
|
25
|
+
function automationSessionTitle(taskName, iso) {
|
|
26
|
+
return `${formatRunStamp(iso)} - ${taskName}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/executor.ts
|
|
30
|
+
var UNATTENDED_TOOL_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
31
|
+
"run_code",
|
|
32
|
+
"bash",
|
|
33
|
+
"pwsh",
|
|
34
|
+
"read",
|
|
35
|
+
"read_image",
|
|
36
|
+
"write",
|
|
37
|
+
"edit",
|
|
38
|
+
"str_replace_editor",
|
|
39
|
+
"glob",
|
|
40
|
+
"grep",
|
|
41
|
+
"lsp",
|
|
42
|
+
"web_search",
|
|
43
|
+
"web_fetch",
|
|
44
|
+
"skill",
|
|
45
|
+
"session_search",
|
|
46
|
+
"session_trace",
|
|
47
|
+
"session_event_read",
|
|
48
|
+
"session_event_search",
|
|
49
|
+
"session_event_trace"
|
|
50
|
+
]);
|
|
51
|
+
var CANCEL_CONVERGENCE_TIMEOUT_MS = 1e4;
|
|
52
|
+
async function settlesWithin(promise2, timeoutMs) {
|
|
53
|
+
let timer;
|
|
54
|
+
try {
|
|
55
|
+
return await Promise.race([
|
|
56
|
+
promise2.then(() => true, () => false),
|
|
57
|
+
new Promise((resolve) => {
|
|
58
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
59
|
+
})
|
|
60
|
+
]);
|
|
61
|
+
} finally {
|
|
62
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function unattendedToolGuardReason(name2, args) {
|
|
66
|
+
if ((name2 === "bash" || name2 === "pwsh") && typeof args === "object" && args !== null && args.run_in_background === true) {
|
|
67
|
+
return "\u65E0\u4EBA\u503C\u5B88\u8FD0\u884C\u4E0D\u5141\u8BB8\u542F\u52A8\u540E\u53F0\u8FDB\u7A0B\u3002";
|
|
68
|
+
}
|
|
69
|
+
return UNATTENDED_TOOL_ALLOWLIST.has(name2) ? void 0 : `\u5DE5\u5177 '${name2}' \u4E0D\u5728\u65E0\u4EBA\u503C\u5B88\u81EA\u52A8\u5316\u5141\u8BB8\u5217\u8868\u4E2D\u3002`;
|
|
70
|
+
}
|
|
71
|
+
function applyUnattendedPermission(presets, session, permission) {
|
|
72
|
+
presets.set(session, permission);
|
|
73
|
+
setApprovalPolicy(session, "never");
|
|
74
|
+
}
|
|
75
|
+
function readSessionEvents(session) {
|
|
76
|
+
if (typeof session.snapshotEvents === "function") return session.snapshotEvents();
|
|
77
|
+
return session.events ?? [];
|
|
78
|
+
}
|
|
79
|
+
function summarizeRun(events, firstSeq) {
|
|
80
|
+
let started = false;
|
|
81
|
+
let text = "";
|
|
82
|
+
let reason;
|
|
83
|
+
for (const event of events) {
|
|
84
|
+
if (event.seq < firstSeq) continue;
|
|
85
|
+
if (event.type === "turn/start") {
|
|
86
|
+
started = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!started) continue;
|
|
90
|
+
if (event.type === "assistant/message") {
|
|
91
|
+
const blocks = event.data.message?.content ?? [];
|
|
92
|
+
const joined = blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
93
|
+
if (joined !== "") text = joined;
|
|
94
|
+
}
|
|
95
|
+
if (event.type === "turn/end") reason = event.data.reason;
|
|
96
|
+
}
|
|
97
|
+
return { text, ...reason === void 0 ? {} : { reason } };
|
|
98
|
+
}
|
|
99
|
+
function boundSummary(value) {
|
|
100
|
+
const normalized = value.trim();
|
|
101
|
+
if (normalized === "") return void 0;
|
|
102
|
+
return normalized.length <= 2e3 ? normalized : `${normalized.slice(0, 1999)}\u2026`;
|
|
103
|
+
}
|
|
104
|
+
function reasonError(reason) {
|
|
105
|
+
if (reason === void 0) return { code: "no_turn_result", message: "\u672C\u6B21\u81EA\u52A8\u5316\u6CA1\u6709\u4EA7\u751F\u5B8C\u6574 turn\u3002" };
|
|
106
|
+
if (reason.kind === "error") {
|
|
107
|
+
return {
|
|
108
|
+
code: typeof reason.error?.code === "string" ? reason.error.code : "agent_error",
|
|
109
|
+
message: typeof reason.error?.message === "string" ? reason.error.message : "\u81EA\u52A8\u5316 Agent \u6267\u884C\u5931\u8D25\u3002"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { code: `turn_${String(reason.kind)}`, message: `\u81EA\u52A8\u5316\u4EE5 ${String(reason.kind)} \u7ED3\u675F\u3002` };
|
|
113
|
+
}
|
|
114
|
+
async function executeAutomationRun(ctx, definition, run, config2) {
|
|
115
|
+
if (config2.signal?.aborted === true) {
|
|
116
|
+
return { status: "cancelled", error: { code: "cancelled", message: "\u81EA\u52A8\u5316\u5728\u542F\u52A8\u524D\u5DF2\u88AB\u53D6\u6D88\u3002" } };
|
|
117
|
+
}
|
|
118
|
+
const target = run.targetSnapshot;
|
|
119
|
+
const workspace = ctx.workspaceRegistry.get(WorkspaceId(target.workspaceId));
|
|
120
|
+
if (workspace === void 0) {
|
|
121
|
+
return { status: "failed", error: { code: "workspace_not_found", message: "\u76EE\u6807\u5DE5\u4F5C\u533A\u5DF2\u4E0D\u5B58\u5728\u3002" } };
|
|
122
|
+
}
|
|
123
|
+
if (await workspace.status() !== "ok" || workspace.path !== target.cwd) {
|
|
124
|
+
return { status: "failed", error: { code: "workspace_unavailable", message: "\u76EE\u6807\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u53EF\u7528\u6216\u5DF2\u53D8\u66F4\u3002" } };
|
|
125
|
+
}
|
|
126
|
+
const fallbackSelection = ctx.agentDefaultModel.currentSelection();
|
|
127
|
+
const selection = target.provider !== null && target.model !== null ? {
|
|
128
|
+
provider: target.provider,
|
|
129
|
+
model: target.model,
|
|
130
|
+
...target.reasoningEffort ? { reasoningEffort: target.reasoningEffort } : {}
|
|
131
|
+
} : fallbackSelection;
|
|
132
|
+
const sessionId = SessionId(config2.sessionId);
|
|
133
|
+
let handle;
|
|
134
|
+
let timeout;
|
|
135
|
+
let removeCancellationListener = () => {
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
handle = await ctx.agents.withoutInitiator(() => ctx.agents.create({
|
|
139
|
+
sessionId,
|
|
140
|
+
...config2.signal === void 0 ? {} : { signal: config2.signal },
|
|
141
|
+
meta: { cwd: target.cwd, agentPreset: target.agentPreset },
|
|
142
|
+
agentOptions: { provider: selection.provider, model: selection.model },
|
|
143
|
+
setup: async (agentCtx) => {
|
|
144
|
+
await ctx.agentPresets.mount(agentCtx, target.agentPreset);
|
|
145
|
+
installModelSelection(agentCtx, { current: selection, assembled: void 0 });
|
|
146
|
+
const agent = agentCtx.agent;
|
|
147
|
+
if (agent === void 0) throw new Error("automation setup has no scoped Agent");
|
|
148
|
+
applyUnattendedPermission(ctx.permissionPresets, agent.session, target.permissionPreset);
|
|
149
|
+
agentCtx.tools.guard((exec) => unattendedToolGuardReason(exec.name, exec.arguments));
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
await handle.agent.whenIdle();
|
|
153
|
+
await workspace.attachSession(sessionId);
|
|
154
|
+
pinAutomationSessionTitle(ctx, handle.agent.session, automationSessionTitle(definition.name, run.startedAt ?? run.scheduledFor));
|
|
155
|
+
const firstSeq = handle.agent.session.seq;
|
|
156
|
+
handle.agent.followup(createUserMessage({
|
|
157
|
+
content: [{ type: "text", text: run.promptSnapshot }],
|
|
158
|
+
source: {
|
|
159
|
+
kind: "automation",
|
|
160
|
+
automationId: definition.id,
|
|
161
|
+
runId: run.id,
|
|
162
|
+
scheduledFor: run.scheduledFor
|
|
163
|
+
}
|
|
164
|
+
}));
|
|
165
|
+
let timedOut = false;
|
|
166
|
+
let aborted2 = false;
|
|
167
|
+
const idle = handle.agent.whenIdle();
|
|
168
|
+
const deadline = new Promise((resolve) => {
|
|
169
|
+
timeout = setTimeout(() => {
|
|
170
|
+
timedOut = true;
|
|
171
|
+
handle?.agent.cancel({ kind: "hook", reason: "automation run timeout" });
|
|
172
|
+
resolve();
|
|
173
|
+
}, config2.runTimeoutMs);
|
|
174
|
+
});
|
|
175
|
+
const cancellation = new Promise((resolve) => {
|
|
176
|
+
if (config2.signal === void 0) return;
|
|
177
|
+
const cancel = () => {
|
|
178
|
+
aborted2 = true;
|
|
179
|
+
handle?.agent.cancel({ kind: "hook", reason: "automation service disposed" });
|
|
180
|
+
resolve();
|
|
181
|
+
};
|
|
182
|
+
if (config2.signal.aborted) cancel();
|
|
183
|
+
else {
|
|
184
|
+
config2.signal.addEventListener("abort", cancel, { once: true });
|
|
185
|
+
removeCancellationListener = () => {
|
|
186
|
+
config2.signal?.removeEventListener("abort", cancel);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
await Promise.race([idle, deadline, cancellation]);
|
|
191
|
+
removeCancellationListener();
|
|
192
|
+
if ((timedOut || aborted2) && !await settlesWithin(idle, CANCEL_CONVERGENCE_TIMEOUT_MS)) {
|
|
193
|
+
return {
|
|
194
|
+
sessionId: String(sessionId),
|
|
195
|
+
status: aborted2 ? "cancelled" : "failed",
|
|
196
|
+
error: {
|
|
197
|
+
code: "cancel_convergence_timeout",
|
|
198
|
+
message: "\u81EA\u52A8\u5316\u53D6\u6D88\u540E\u672A\u80FD\u5728\u5B89\u5168\u65F6\u9650\u5185\u505C\u6B62\u3002"
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
203
|
+
await ctx.sessions.flush(handle.agent.session);
|
|
204
|
+
const outcome = summarizeRun(readSessionEvents(handle.agent.session), firstSeq);
|
|
205
|
+
const summary = boundSummary(outcome.text);
|
|
206
|
+
if (aborted2) {
|
|
207
|
+
return {
|
|
208
|
+
sessionId: String(sessionId),
|
|
209
|
+
status: "cancelled",
|
|
210
|
+
...summary === void 0 ? {} : { summary },
|
|
211
|
+
error: { code: "cancelled", message: "\u81EA\u52A8\u5316\u56E0\u5176\u6240\u5C5E\u670D\u52A1\u505C\u6B62\u800C\u88AB\u53D6\u6D88\u3002" }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (timedOut) {
|
|
215
|
+
return {
|
|
216
|
+
sessionId: String(sessionId),
|
|
217
|
+
status: "failed",
|
|
218
|
+
...summary === void 0 ? {} : { summary },
|
|
219
|
+
error: { code: "timeout", message: "\u81EA\u52A8\u5316\u8D85\u8FC7\u6700\u5927\u8FD0\u884C\u65F6\u9650\u3002" }
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (outcome.reason?.kind === "completed") {
|
|
223
|
+
return { sessionId: String(sessionId), status: "succeeded", ...summary === void 0 ? {} : { summary } };
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
sessionId: String(sessionId),
|
|
227
|
+
status: "failed",
|
|
228
|
+
...summary === void 0 ? {} : { summary },
|
|
229
|
+
error: reasonError(outcome.reason)
|
|
230
|
+
};
|
|
231
|
+
} catch (error51) {
|
|
232
|
+
return {
|
|
233
|
+
...handle === void 0 ? {} : { sessionId: String(sessionId) },
|
|
234
|
+
status: "failed",
|
|
235
|
+
error: {
|
|
236
|
+
code: "executor_error",
|
|
237
|
+
message: error51 instanceof Error ? error51.message : "\u81EA\u52A8\u5316\u6267\u884C\u5668\u5931\u8D25\u3002"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
} finally {
|
|
241
|
+
removeCancellationListener();
|
|
242
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
243
|
+
if (handle !== void 0) {
|
|
244
|
+
await settlesWithin(handle.dispose().catch(() => {
|
|
245
|
+
}), CANCEL_CONVERGENCE_TIMEOUT_MS);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function pinAutomationSessionTitle(ctx, session, title) {
|
|
250
|
+
const service = ctx.get("sessionTitle");
|
|
251
|
+
if (service === void 0 || typeof service.rename !== "function") return;
|
|
252
|
+
try {
|
|
253
|
+
service.rename(session, title);
|
|
254
|
+
} catch (error51) {
|
|
255
|
+
ctx.logger.warn(`dsh-automation: failed to pin session title: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
10
259
|
// src/prompt.ts
|
|
11
260
|
var AUTOMATION_PROMPT_NAME = "tool:automation";
|
|
12
261
|
var AUTOMATION_PROMPT_ORDER = 118;
|
|
@@ -21540,251 +21789,6 @@ function positiveInteger(value, field) {
|
|
|
21540
21789
|
return value;
|
|
21541
21790
|
}
|
|
21542
21791
|
|
|
21543
|
-
// src/executor.ts
|
|
21544
|
-
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
21545
|
-
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
21546
|
-
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
21547
|
-
import { setApprovalPolicy } from "@deepseek-ai/dsh-user-approval";
|
|
21548
|
-
import { WorkspaceId } from "@deepseek-ai/dsh-workspace";
|
|
21549
|
-
|
|
21550
|
-
// src/run-title.ts
|
|
21551
|
-
function formatRunStamp(iso) {
|
|
21552
|
-
const value = new Date(iso);
|
|
21553
|
-
if (Number.isNaN(value.getTime())) return iso;
|
|
21554
|
-
const date5 = `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
|
|
21555
|
-
const time3 = `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
|
|
21556
|
-
return `${date5} ${time3}`;
|
|
21557
|
-
}
|
|
21558
|
-
function automationSessionTitle(taskName, iso) {
|
|
21559
|
-
return `${formatRunStamp(iso)} - ${taskName}`;
|
|
21560
|
-
}
|
|
21561
|
-
|
|
21562
|
-
// src/executor.ts
|
|
21563
|
-
var UNATTENDED_TOOL_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
21564
|
-
"run_code",
|
|
21565
|
-
"bash",
|
|
21566
|
-
"pwsh",
|
|
21567
|
-
"read",
|
|
21568
|
-
"read_image",
|
|
21569
|
-
"write",
|
|
21570
|
-
"edit",
|
|
21571
|
-
"str_replace_editor",
|
|
21572
|
-
"glob",
|
|
21573
|
-
"grep",
|
|
21574
|
-
"lsp",
|
|
21575
|
-
"web_search",
|
|
21576
|
-
"web_fetch",
|
|
21577
|
-
"skill",
|
|
21578
|
-
"session_search",
|
|
21579
|
-
"session_trace",
|
|
21580
|
-
"session_event_read",
|
|
21581
|
-
"session_event_search",
|
|
21582
|
-
"session_event_trace"
|
|
21583
|
-
]);
|
|
21584
|
-
var CANCEL_CONVERGENCE_TIMEOUT_MS = 1e4;
|
|
21585
|
-
async function settlesWithin(promise2, timeoutMs) {
|
|
21586
|
-
let timer;
|
|
21587
|
-
try {
|
|
21588
|
-
return await Promise.race([
|
|
21589
|
-
promise2.then(() => true, () => false),
|
|
21590
|
-
new Promise((resolve) => {
|
|
21591
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
21592
|
-
})
|
|
21593
|
-
]);
|
|
21594
|
-
} finally {
|
|
21595
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
21596
|
-
}
|
|
21597
|
-
}
|
|
21598
|
-
function unattendedToolGuardReason(name2, args) {
|
|
21599
|
-
if ((name2 === "bash" || name2 === "pwsh") && typeof args === "object" && args !== null && args.run_in_background === true) {
|
|
21600
|
-
return "\u65E0\u4EBA\u503C\u5B88\u8FD0\u884C\u4E0D\u5141\u8BB8\u542F\u52A8\u540E\u53F0\u8FDB\u7A0B\u3002";
|
|
21601
|
-
}
|
|
21602
|
-
return UNATTENDED_TOOL_ALLOWLIST.has(name2) ? void 0 : `\u5DE5\u5177 '${name2}' \u4E0D\u5728\u65E0\u4EBA\u503C\u5B88\u81EA\u52A8\u5316\u5141\u8BB8\u5217\u8868\u4E2D\u3002`;
|
|
21603
|
-
}
|
|
21604
|
-
function applyUnattendedPermission(presets, session, permission) {
|
|
21605
|
-
presets.set(session, permission);
|
|
21606
|
-
setApprovalPolicy(session, "never");
|
|
21607
|
-
}
|
|
21608
|
-
function summarizeRun(events, firstSeq) {
|
|
21609
|
-
let started = false;
|
|
21610
|
-
let text = "";
|
|
21611
|
-
let reason;
|
|
21612
|
-
for (const event of events) {
|
|
21613
|
-
if (event.seq < firstSeq) continue;
|
|
21614
|
-
if (event.type === "turn/start") {
|
|
21615
|
-
started = true;
|
|
21616
|
-
continue;
|
|
21617
|
-
}
|
|
21618
|
-
if (!started) continue;
|
|
21619
|
-
if (event.type === "assistant/message") {
|
|
21620
|
-
const blocks = event.data.message?.content ?? [];
|
|
21621
|
-
const joined = blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
21622
|
-
if (joined !== "") text = joined;
|
|
21623
|
-
}
|
|
21624
|
-
if (event.type === "turn/end") reason = event.data.reason;
|
|
21625
|
-
}
|
|
21626
|
-
return { text, ...reason === void 0 ? {} : { reason } };
|
|
21627
|
-
}
|
|
21628
|
-
function boundSummary(value) {
|
|
21629
|
-
const normalized = value.trim();
|
|
21630
|
-
if (normalized === "") return void 0;
|
|
21631
|
-
return normalized.length <= 2e3 ? normalized : `${normalized.slice(0, 1999)}\u2026`;
|
|
21632
|
-
}
|
|
21633
|
-
function reasonError(reason) {
|
|
21634
|
-
if (reason === void 0) return { code: "no_turn_result", message: "\u672C\u6B21\u81EA\u52A8\u5316\u6CA1\u6709\u4EA7\u751F\u5B8C\u6574 turn\u3002" };
|
|
21635
|
-
if (reason.kind === "error") {
|
|
21636
|
-
return {
|
|
21637
|
-
code: typeof reason.error?.code === "string" ? reason.error.code : "agent_error",
|
|
21638
|
-
message: typeof reason.error?.message === "string" ? reason.error.message : "\u81EA\u52A8\u5316 Agent \u6267\u884C\u5931\u8D25\u3002"
|
|
21639
|
-
};
|
|
21640
|
-
}
|
|
21641
|
-
return { code: `turn_${String(reason.kind)}`, message: `\u81EA\u52A8\u5316\u4EE5 ${String(reason.kind)} \u7ED3\u675F\u3002` };
|
|
21642
|
-
}
|
|
21643
|
-
async function executeAutomationRun(ctx, definition, run, config2) {
|
|
21644
|
-
if (config2.signal?.aborted === true) {
|
|
21645
|
-
return { status: "cancelled", error: { code: "cancelled", message: "\u81EA\u52A8\u5316\u5728\u542F\u52A8\u524D\u5DF2\u88AB\u53D6\u6D88\u3002" } };
|
|
21646
|
-
}
|
|
21647
|
-
const target = run.targetSnapshot;
|
|
21648
|
-
const workspace = ctx.workspaceRegistry.get(WorkspaceId(target.workspaceId));
|
|
21649
|
-
if (workspace === void 0) {
|
|
21650
|
-
return { status: "failed", error: { code: "workspace_not_found", message: "\u76EE\u6807\u5DE5\u4F5C\u533A\u5DF2\u4E0D\u5B58\u5728\u3002" } };
|
|
21651
|
-
}
|
|
21652
|
-
if (await workspace.status() !== "ok" || workspace.path !== target.cwd) {
|
|
21653
|
-
return { status: "failed", error: { code: "workspace_unavailable", message: "\u76EE\u6807\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u53EF\u7528\u6216\u5DF2\u53D8\u66F4\u3002" } };
|
|
21654
|
-
}
|
|
21655
|
-
const fallbackSelection = ctx.agentDefaultModel.currentSelection();
|
|
21656
|
-
const selection = target.provider !== null && target.model !== null ? {
|
|
21657
|
-
provider: target.provider,
|
|
21658
|
-
model: target.model,
|
|
21659
|
-
...target.reasoningEffort ? { reasoningEffort: target.reasoningEffort } : {}
|
|
21660
|
-
} : fallbackSelection;
|
|
21661
|
-
const sessionId = SessionId(config2.sessionId);
|
|
21662
|
-
let handle;
|
|
21663
|
-
let timeout;
|
|
21664
|
-
let removeCancellationListener = () => {
|
|
21665
|
-
};
|
|
21666
|
-
try {
|
|
21667
|
-
handle = await ctx.agents.withoutInitiator(() => ctx.agents.create({
|
|
21668
|
-
sessionId,
|
|
21669
|
-
...config2.signal === void 0 ? {} : { signal: config2.signal },
|
|
21670
|
-
meta: { cwd: target.cwd, agentPreset: target.agentPreset },
|
|
21671
|
-
agentOptions: { provider: selection.provider, model: selection.model },
|
|
21672
|
-
setup: async (agentCtx) => {
|
|
21673
|
-
await ctx.agentPresets.mount(agentCtx, target.agentPreset);
|
|
21674
|
-
installModelSelection(agentCtx, { current: selection, assembled: void 0 });
|
|
21675
|
-
const agent = agentCtx.agent;
|
|
21676
|
-
if (agent === void 0) throw new Error("automation setup has no scoped Agent");
|
|
21677
|
-
applyUnattendedPermission(ctx.permissionPresets, agent.session, target.permissionPreset);
|
|
21678
|
-
agentCtx.tools.guard((exec) => unattendedToolGuardReason(exec.name, exec.arguments));
|
|
21679
|
-
}
|
|
21680
|
-
}));
|
|
21681
|
-
await handle.agent.whenIdle();
|
|
21682
|
-
await workspace.attachSession(sessionId);
|
|
21683
|
-
pinAutomationSessionTitle(ctx, handle.agent.session, automationSessionTitle(definition.name, run.startedAt ?? run.scheduledFor));
|
|
21684
|
-
const firstSeq = handle.agent.session.seq;
|
|
21685
|
-
handle.agent.followup(createUserMessage({
|
|
21686
|
-
content: [{ type: "text", text: run.promptSnapshot }],
|
|
21687
|
-
source: {
|
|
21688
|
-
kind: "automation",
|
|
21689
|
-
automationId: definition.id,
|
|
21690
|
-
runId: run.id,
|
|
21691
|
-
scheduledFor: run.scheduledFor
|
|
21692
|
-
}
|
|
21693
|
-
}));
|
|
21694
|
-
let timedOut = false;
|
|
21695
|
-
let aborted2 = false;
|
|
21696
|
-
const idle = handle.agent.whenIdle();
|
|
21697
|
-
const deadline = new Promise((resolve) => {
|
|
21698
|
-
timeout = setTimeout(() => {
|
|
21699
|
-
timedOut = true;
|
|
21700
|
-
handle?.agent.cancel({ kind: "hook", reason: "automation run timeout" });
|
|
21701
|
-
resolve();
|
|
21702
|
-
}, config2.runTimeoutMs);
|
|
21703
|
-
});
|
|
21704
|
-
const cancellation = new Promise((resolve) => {
|
|
21705
|
-
if (config2.signal === void 0) return;
|
|
21706
|
-
const cancel = () => {
|
|
21707
|
-
aborted2 = true;
|
|
21708
|
-
handle?.agent.cancel({ kind: "hook", reason: "automation service disposed" });
|
|
21709
|
-
resolve();
|
|
21710
|
-
};
|
|
21711
|
-
if (config2.signal.aborted) cancel();
|
|
21712
|
-
else {
|
|
21713
|
-
config2.signal.addEventListener("abort", cancel, { once: true });
|
|
21714
|
-
removeCancellationListener = () => {
|
|
21715
|
-
config2.signal?.removeEventListener("abort", cancel);
|
|
21716
|
-
};
|
|
21717
|
-
}
|
|
21718
|
-
});
|
|
21719
|
-
await Promise.race([idle, deadline, cancellation]);
|
|
21720
|
-
removeCancellationListener();
|
|
21721
|
-
if ((timedOut || aborted2) && !await settlesWithin(idle, CANCEL_CONVERGENCE_TIMEOUT_MS)) {
|
|
21722
|
-
return {
|
|
21723
|
-
sessionId: String(sessionId),
|
|
21724
|
-
status: aborted2 ? "cancelled" : "failed",
|
|
21725
|
-
error: {
|
|
21726
|
-
code: "cancel_convergence_timeout",
|
|
21727
|
-
message: "\u81EA\u52A8\u5316\u53D6\u6D88\u540E\u672A\u80FD\u5728\u5B89\u5168\u65F6\u9650\u5185\u505C\u6B62\u3002"
|
|
21728
|
-
}
|
|
21729
|
-
};
|
|
21730
|
-
}
|
|
21731
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
21732
|
-
await ctx.sessions.flush(handle.agent.session);
|
|
21733
|
-
const outcome = summarizeRun(handle.agent.session.events, firstSeq);
|
|
21734
|
-
const summary = boundSummary(outcome.text);
|
|
21735
|
-
if (aborted2) {
|
|
21736
|
-
return {
|
|
21737
|
-
sessionId: String(sessionId),
|
|
21738
|
-
status: "cancelled",
|
|
21739
|
-
...summary === void 0 ? {} : { summary },
|
|
21740
|
-
error: { code: "cancelled", message: "\u81EA\u52A8\u5316\u56E0\u5176\u6240\u5C5E\u670D\u52A1\u505C\u6B62\u800C\u88AB\u53D6\u6D88\u3002" }
|
|
21741
|
-
};
|
|
21742
|
-
}
|
|
21743
|
-
if (timedOut) {
|
|
21744
|
-
return {
|
|
21745
|
-
sessionId: String(sessionId),
|
|
21746
|
-
status: "failed",
|
|
21747
|
-
...summary === void 0 ? {} : { summary },
|
|
21748
|
-
error: { code: "timeout", message: "\u81EA\u52A8\u5316\u8D85\u8FC7\u6700\u5927\u8FD0\u884C\u65F6\u9650\u3002" }
|
|
21749
|
-
};
|
|
21750
|
-
}
|
|
21751
|
-
if (outcome.reason?.kind === "completed") {
|
|
21752
|
-
return { sessionId: String(sessionId), status: "succeeded", ...summary === void 0 ? {} : { summary } };
|
|
21753
|
-
}
|
|
21754
|
-
return {
|
|
21755
|
-
sessionId: String(sessionId),
|
|
21756
|
-
status: "failed",
|
|
21757
|
-
...summary === void 0 ? {} : { summary },
|
|
21758
|
-
error: reasonError(outcome.reason)
|
|
21759
|
-
};
|
|
21760
|
-
} catch (error51) {
|
|
21761
|
-
return {
|
|
21762
|
-
...handle === void 0 ? {} : { sessionId: String(sessionId) },
|
|
21763
|
-
status: "failed",
|
|
21764
|
-
error: {
|
|
21765
|
-
code: "executor_error",
|
|
21766
|
-
message: error51 instanceof Error ? error51.message : "\u81EA\u52A8\u5316\u6267\u884C\u5668\u5931\u8D25\u3002"
|
|
21767
|
-
}
|
|
21768
|
-
};
|
|
21769
|
-
} finally {
|
|
21770
|
-
removeCancellationListener();
|
|
21771
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
21772
|
-
if (handle !== void 0) {
|
|
21773
|
-
await settlesWithin(handle.dispose().catch(() => {
|
|
21774
|
-
}), CANCEL_CONVERGENCE_TIMEOUT_MS);
|
|
21775
|
-
}
|
|
21776
|
-
}
|
|
21777
|
-
}
|
|
21778
|
-
function pinAutomationSessionTitle(ctx, session, title) {
|
|
21779
|
-
const service = ctx.get("sessionTitle");
|
|
21780
|
-
if (service === void 0 || typeof service.rename !== "function") return;
|
|
21781
|
-
try {
|
|
21782
|
-
service.rename(session, title);
|
|
21783
|
-
} catch (error51) {
|
|
21784
|
-
ctx.logger.warn(`dsh-automation: failed to pin session title: ${error51 instanceof Error ? error51.message : String(error51)}`);
|
|
21785
|
-
}
|
|
21786
|
-
}
|
|
21787
|
-
|
|
21788
21792
|
// src/permission-presets.ts
|
|
21789
21793
|
function normalizePermissionPreset(input, names) {
|
|
21790
21794
|
if (typeof input !== "string") return void 0;
|
|
@@ -23079,7 +23083,7 @@ async function apply(ctx, rawConfig) {
|
|
|
23079
23083
|
};
|
|
23080
23084
|
try {
|
|
23081
23085
|
const mountTools = (agent) => {
|
|
23082
|
-
if (!alive || agentTools.has(agent) || service.ownsSession(String(agent.id), agent.session
|
|
23086
|
+
if (!alive || agentTools.has(agent) || service.ownsSession(String(agent.id), readSessionEvents(agent.session))) return;
|
|
23083
23087
|
if (!ctx.agents.roots().includes(agent)) return;
|
|
23084
23088
|
const dispose = agent.ctx.effect(
|
|
23085
23089
|
() => registerAutomationTools(service, agent),
|
package/lib/types/executor.d.ts
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import type { PermissionPresetService } from './permission-presets.ts';
|
|
4
4
|
import type { AutomationDefinition, AutomationRun } from './types.ts';
|
|
5
|
-
interface SessionEventLike {
|
|
5
|
+
export interface SessionEventLike {
|
|
6
6
|
readonly seq: number;
|
|
7
7
|
readonly type: string;
|
|
8
8
|
readonly data: Record<string, any>;
|
|
9
9
|
}
|
|
10
|
+
interface SessionEventReader {
|
|
11
|
+
readonly events?: readonly SessionEventLike[];
|
|
12
|
+
snapshotEvents?(): readonly SessionEventLike[];
|
|
13
|
+
}
|
|
10
14
|
/** 对不保证及时响应 AbortSignal 的宿主任务设置第二道退出上限。 */
|
|
11
15
|
export declare function settlesWithin(promise: Promise<unknown>, timeoutMs: number): Promise<boolean>;
|
|
12
16
|
export declare function unattendedToolGuardReason(name: string, args: unknown): string | undefined;
|
|
@@ -26,6 +30,8 @@ export interface ExecutorConfig {
|
|
|
26
30
|
}
|
|
27
31
|
/** 先应用官方预设的完整语义,再让无人值守审批 fail-closed。 */
|
|
28
32
|
export declare function applyUnattendedPermission(presets: PermissionPresetService, session: unknown, permission: AutomationDefinition['permissionPreset']): void;
|
|
33
|
+
/** 优先读取新版按需快照,避免保留旧版 Session.events 的内部日志引用。 */
|
|
34
|
+
export declare function readSessionEvents(session: SessionEventReader): readonly SessionEventLike[];
|
|
29
35
|
export declare function summarizeRun(events: readonly SessionEventLike[], firstSeq: number): {
|
|
30
36
|
readonly text: string;
|
|
31
37
|
readonly reason?: Record<string, any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-automation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "在独立 DSH Session 中按计划执行编码任务,支持 Web 设置页与 Agent 双入口管理。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -91,21 +91,21 @@
|
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@deepseek-ai/cordis": "4.0.2",
|
|
94
|
-
"@deepseek-ai/dsh-agent": "0.1.2-
|
|
95
|
-
"@deepseek-ai/dsh-agent-default-model": "0.1.2-
|
|
96
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.2-
|
|
97
|
-
"@deepseek-ai/dsh-client-connection": "0.1.2-
|
|
98
|
-
"@deepseek-ai/dsh-client-locale": "0.1.2-
|
|
99
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-
|
|
100
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-
|
|
101
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-
|
|
102
|
-
"@deepseek-ai/dsh-llm": "0.1.2-
|
|
103
|
-
"@deepseek-ai/dsh-permission-presets": "0.1.2-
|
|
104
|
-
"@deepseek-ai/dsh-session": "0.1.2-
|
|
105
|
-
"@deepseek-ai/dsh-storage-domain": "0.1.2-
|
|
106
|
-
"@deepseek-ai/dsh-tools": "0.1.2-
|
|
107
|
-
"@deepseek-ai/dsh-user-approval": "0.1.2-
|
|
108
|
-
"@deepseek-ai/dsh-workspace": "0.1.2-
|
|
94
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.2-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-permission-presets": "0.1.2-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-storage-domain": "0.1.2-rc.1",
|
|
106
|
+
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
107
|
+
"@deepseek-ai/dsh-user-approval": "0.1.2-rc.1",
|
|
108
|
+
"@deepseek-ai/dsh-workspace": "0.1.2-rc.1",
|
|
109
109
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
110
110
|
"@types/luxon": "^3.7.1",
|
|
111
111
|
"@types/node": "^22.0.0",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 阅读导航
|
|
2
|
-
|
|
3
|
-
更新时间:2026-08-16 18:30(Asia/Shanghai)
|
|
4
|
-
|
|
5
|
-
1. [项目概览](01-项目概览.md) — 先看插件定位和稳定边界。
|
|
6
|
-
2. [当前状态](02-当前状态.md) — 再看当前阶段和有效风险。
|
|
7
|
-
3. [待办与阻塞](03-待办与阻塞.md) — 只看尚未关闭的事项。
|
|
8
|
-
4. [迭代路线图](04-迭代路线图.md) — 只看迭代顺序。
|
|
9
|
-
5. [技术架构基线](05-技术架构基线.md) — 看当前生效的硬约束。
|
|
10
|
-
6. [调度与执行模型](..\03-技术架构\01-调度与执行模型.md) — 看领域设计。
|
|
11
|
-
7. [I001 迭代总览](..\01-当前工作\I001-任务自动化插件\00-迭代总览.md) — 看本次交付范围。
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# 项目概览
|
|
2
|
-
|
|
3
|
-
更新时间:2026-08-16 18:30(Asia/Shanghai)
|
|
4
|
-
|
|
5
|
-
`@michengai/dsh-automation` 是 DeepSeek Harness Web Profile 的社区插件,用于创建独立、可审计的定时编码任务。
|
|
6
|
-
|
|
7
|
-
## 当前阶段
|
|
8
|
-
|
|
9
|
-
首个可安装版本:支持设置页、Agent 工具,以及单次/间隔/每天/每周计划。
|
|
10
|
-
|
|
11
|
-
## 核心能力
|
|
12
|
-
|
|
13
|
-
- 每次到期都创建全新 root Agent 和 Session,不继承来源对话。
|
|
14
|
-
- 规则、修订号和运行历史持久化。
|
|
15
|
-
- 同一条规则同时最多一个 active run;超时、重叠和 Host 中断都有明确终态。
|
|
16
|
-
- 权限预设与 Chat 保持一致,完整使用 Host 提供的列表与默认值;切换到 `danger-full-access` 时显示同款风险确认。
|
|
17
|
-
|
|
18
|
-
## 稳定边界
|
|
19
|
-
|
|
20
|
-
- 不替代 DSH Core Schedule 的同会话提醒。
|
|
21
|
-
- 不是操作系统守护进程,Host 必须正在运行。
|
|
22
|
-
- 不提供 raw cron、DAG、外部通知或 worktree 生命周期。
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# 当前状态
|
|
2
|
-
|
|
3
|
-
更新时间:2026-09-03 00:00(Asia/Shanghai)
|
|
4
|
-
|
|
5
|
-
- 阶段:I001 进行中,DSH peer 兼容范围为 `>=0.1.0-rc.5 <0.2.0`,开发与最高验证基线为 `0.1.2-alpha.5`。
|
|
6
|
-
- 活动迭代:[I001-任务自动化插件](..\01-当前工作\I001-任务自动化插件\00-迭代总览.md)
|
|
7
|
-
- 有效风险:
|
|
8
|
-
- DSH `0.1.2-alpha.5` 为当日预发布版本,已对其 41 个精确包版本配置最小发布时间例外;发布前需在真实 Web Profile 完成组合验收。
|
|
9
|
-
- Host 未运行时不会补跑历史 backlog。
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# 技术架构基线
|
|
2
|
-
|
|
3
|
-
更新时间:2026-08-16 18:30(Asia/Shanghai)
|
|
4
|
-
|
|
5
|
-
- 包名:`@michengai/dsh-automation`
|
|
6
|
-
- 运行时:Node.js 22.19+ / 24+,Cordis Host + DSH Web client
|
|
7
|
-
- 持久化:`ctx.storageDomain`,domain 名 `dsh_automation`
|
|
8
|
-
- 调度语义:at-most-once dispatch;无自动重试
|
|
9
|
-
- 权限:`read-only` / `workspace-write`;审批策略 `never`
|
|
10
|
-
- 构建:`scripts\build.mjs` 产出 Host ESM 与 `__ModuleLoader__` Web bundle
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# I001 任务自动化插件
|
|
2
|
-
|
|
3
|
-
- 状态:进行中
|
|
4
|
-
- 负责人:维护者
|
|
5
|
-
- 优先级:高
|
|
6
|
-
- 更新时间:2026-09-03 00:00(Asia/Shanghai)
|
|
7
|
-
|
|
8
|
-
## 目标与业务边界
|
|
9
|
-
|
|
10
|
-
实现 DeepSeek Harness 的独立任务自动化:用户或 Agent 保存一条自包含任务,Host 到期后在全新 Session 中执行,并留下可审计历史。设置页提供任务列表、执行记录和新建模态。
|
|
11
|
-
|
|
12
|
-
## 任务清单
|
|
13
|
-
|
|
14
|
-
- [x] 领域模型、周期计算
|
|
15
|
-
- [x] Service / Executor / Tools / RPC
|
|
16
|
-
- [x] Web Automations 页
|
|
17
|
-
- [x] 原生侧栏定时页签:包裹官方任务树,不依赖 dsh-codex-ui
|
|
18
|
-
- [x] 单测与包契约
|
|
19
|
-
- [x] 定时侧栏会话菜单互斥,避免点第二条时与第一条重叠
|
|
20
|
-
- [x] 新建弹窗只允许选择 Host 已注册工作区,新增目录统一从 Host 工作区入口完成
|
|
21
|
-
- [x] 修复任务树过滤导致的 Loading plugins / 点任务卡死
|
|
22
|
-
- [x] 会话创建定时任务按官方审批策略分流,并补系统提示避免走 crontab
|
|
23
|
-
- [x] 工作区标题栏对齐官方位置与搜索点击
|
|
24
|
-
- [x] DSH `>=0.1.0-rc.5 <0.2.0` 兼容及 alpha.5 验证基线迁移,设置标题增加 GitHub 与 Issues 入口
|
|
25
|
-
- [ ] 真实 Web Profile 组合验收
|
|
26
|
-
|
|
27
|
-
## 阻塞事项
|
|
28
|
-
|
|
29
|
-
无;alpha.5 预发布版本组合验收待在真实 DSH Web Profile 执行。
|
|
30
|
-
|
|
31
|
-
## 验收标准
|
|
32
|
-
|
|
33
|
-
1. Web 和 Agent 都能创建、列出、暂停、恢复、立即运行和删除。
|
|
34
|
-
2. 到期后创建新 Session,消息 source 为 `automation`。
|
|
35
|
-
4. 重启不会偷偷重跑遗留 running。
|
|
36
|
-
5. 删除定义不删除运行历史。
|
|
37
|
-
|
|
38
|
-
## 已完成内容
|
|
39
|
-
|
|
40
|
-
仓库内已落地 Host 插件、Web 客户端、原生侧栏定时页签、文档和单测。定时会话更多菜单改为列表级互斥,并用固定定位弹出,避免两条菜单叠在一起。新建弹窗只消费 Host 已注册工作区,避免插件 RPC 自行扩大目录边界;点遮罩不再关闭。DSH peer 兼容范围为 `>=0.1.0-rc.5 <0.2.0`,开发与最高验证基线为 `0.1.2-alpha.5`;客户端仅声明旧新版本共有的注入,并在设置标题旁加入 GitHub 与 Issues 入口。
|
|
41
|
-
|
|
42
|
-
## 待处理事项
|
|
43
|
-
|
|
44
|
-
真实 DSH `0.1.2-alpha.5` Host 上重启 Web,验证设置页、侧栏、创建和立即运行。
|
|
45
|
-
|
|
46
|
-
## 关联提交
|
|
47
|
-
|
|
48
|
-
待发布 `@michengai/dsh-automation@0.1.24`。
|
|
49
|
-
|
|
50
|
-
## 阅读顺序
|
|
51
|
-
|
|
52
|
-
1. [项目概览](..\..\00-交接入口\01-项目概览.md)
|
|
53
|
-
2. [调度与执行模型](..\..\03-技术架构\01-调度与执行模型.md)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# 调度与执行模型
|
|
2
|
-
|
|
3
|
-
更新时间:2026-08-26(Asia/Shanghai)
|
|
4
|
-
|
|
5
|
-
## 分层
|
|
6
|
-
|
|
7
|
-
- Store:只保存 definition 与 run。
|
|
8
|
-
- Clock:只回答下次到期时间。
|
|
9
|
-
- Executor:只执行已经认领的 run。
|
|
10
|
-
- Tools / RPC:只调用 service。
|
|
11
|
-
- Web client:只展示快照并提交明确 mutation。
|
|
12
|
-
|
|
13
|
-
## 调度
|
|
14
|
-
|
|
15
|
-
- `once` / `interval` / `hourly` / `daily` / `weekly` / `monthly` / `custom` 会编译为校验过的 RRULE。
|
|
16
|
-
- `interval` 最短 5 分钟,创建后不会立刻跑第一次。
|
|
17
|
-
- 每天/每周/每月/自定义间隔天数使用 IANA 本地 `HH:mm`;夏令时中不存在的本地时间会被跳过。
|
|
18
|
-
- 立即运行不受计划时间限制。
|
|
19
|
-
|
|
20
|
-
## 恢复
|
|
21
|
-
|
|
22
|
-
- 先持久化 `queued`,再创建 Agent。
|
|
23
|
-
- Host 重启后,遗留 `queued`/`running` 变为 `failed(host_interrupted)`。
|
|
24
|
-
- 补跑窗口默认 15 分钟,只物化最新一条 overdue occurrence。
|
|
25
|
-
|
|
26
|
-
## 侧栏
|
|
27
|
-
|
|
28
|
-
- 有 `dsh-codex-ui` 时只填充 `sidebar.schedule`。
|
|
29
|
-
- 没有 Codex UI 时不换掉官方任务树,只在外面套「任务 / 定时」页签;官方搜索、工作区和会话菜单都保留。若存在 `sidebar.channels` 再显示「频道」。
|
|
30
|
-
- 定时树按任务名称分组,子会话标题为 `YYYY-MM-DD HH:mm - 任务名`。
|