@co0ontty/wand 4.72.2 → 4.75.0-beta.g1d477a2
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/dist/build-info.json +4 -4
- package/dist/claude-sdk-runner.d.ts +2 -3
- package/dist/config.d.ts +1 -1
- package/dist/config.js +48 -0
- package/dist/daemon-connection.d.ts +9 -0
- package/dist/daemon-connection.js +86 -0
- package/dist/distribution-manager.d.ts +5 -0
- package/dist/distribution-manager.js +79 -1
- package/dist/git-quick-commit.js +12 -0
- package/dist/message-truncator.js +9 -3
- package/dist/models.d.ts +39 -2
- package/dist/models.js +302 -9
- package/dist/native/darwin-arm64/wand-render +0 -0
- package/dist/native/darwin-arm64/wand-render.sha256 +1 -0
- package/dist/native/darwin-arm64/wand-render.version +1 -0
- package/dist/native/darwin-arm64/wand-structured-renderd +0 -0
- package/dist/native/darwin-arm64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/darwin-arm64/wand-structured-renderd.version +1 -0
- package/dist/native/darwin-x64/wand-render +0 -0
- package/dist/native/darwin-x64/wand-render.sha256 +1 -0
- package/dist/native/darwin-x64/wand-render.version +1 -0
- package/dist/native/darwin-x64/wand-structured-renderd +0 -0
- package/dist/native/darwin-x64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/darwin-x64/wand-structured-renderd.version +1 -0
- package/dist/native/linux-arm64/wand-render +0 -0
- package/dist/native/linux-arm64/wand-render.sha256 +1 -0
- package/dist/native/linux-arm64/wand-render.version +1 -0
- package/dist/native/linux-arm64/wand-structured-renderd +0 -0
- package/dist/native/linux-arm64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/linux-arm64/wand-structured-renderd.version +1 -0
- package/dist/native/linux-x64/wand-render +0 -0
- package/dist/native/linux-x64/wand-render.sha256 +1 -0
- package/dist/native/linux-x64/wand-render.version +1 -0
- package/dist/native/linux-x64/wand-structured-renderd +0 -0
- package/dist/native/linux-x64/wand-structured-renderd.sha256 +1 -0
- package/dist/native/linux-x64/wand-structured-renderd.version +1 -0
- package/dist/process-manager.d.ts +12 -1
- package/dist/process-manager.js +88 -7
- package/dist/pty-terminal-state.d.ts +17 -0
- package/dist/pty-terminal-state.js +51 -1
- package/dist/render-binary.d.ts +61 -0
- package/dist/render-binary.js +204 -0
- package/dist/render-daemon-client.d.ts +202 -0
- package/dist/render-daemon-client.js +1164 -0
- package/dist/render-host.d.ts +88 -0
- package/dist/render-host.js +440 -0
- package/dist/render-protocol.d.ts +128 -0
- package/dist/render-protocol.js +99 -0
- package/dist/render-structured-client.d.ts +80 -0
- package/dist/render-structured-client.js +513 -0
- package/dist/render-structured-host.d.ts +31 -0
- package/dist/render-structured-host.js +218 -0
- package/dist/render-structured-protocol.d.ts +101 -0
- package/dist/render-structured-protocol.js +35 -0
- package/dist/server-session-routes.js +114 -12
- package/dist/server-task-routes.js +55 -6
- package/dist/server-update-routes.d.ts +2 -0
- package/dist/server-update-routes.js +44 -0
- package/dist/server.js +43 -13
- package/dist/session-ai-context.d.ts +1 -1
- package/dist/session-ai-context.js +15 -4
- package/dist/session-logger.d.ts +2 -0
- package/dist/session-logger.js +44 -1
- package/dist/session-transport.js +1 -1
- package/dist/storage.d.ts +2 -1
- package/dist/storage.js +14 -5
- package/dist/structured-client-protocol.d.ts +3 -1
- package/dist/structured-client-protocol.js +85 -25
- package/dist/structured-content.d.ts +20 -3
- package/dist/structured-content.js +80 -1
- package/dist/structured-exec-host.js +7 -1
- package/dist/structured-exec-pump.js +6 -2
- package/dist/structured-grok-adapter.js +22 -0
- package/dist/structured-pi-adapter.js +18 -2
- package/dist/structured-provider-common.d.ts +11 -9
- package/dist/structured-provider-common.js +60 -18
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +27 -3
- package/dist/task-types.d.ts +4 -1
- package/dist/terminal-daemon-client.d.ts +9 -2
- package/dist/terminal-daemon-client.js +119 -46
- package/dist/terminal-daemon-server.js +47 -8
- package/dist/terminal-host.d.ts +6 -0
- package/dist/terminal-host.js +3 -0
- package/dist/tui/commands.js +183 -10
- package/dist/tui/ipc-server.js +12 -0
- package/dist/types.d.ts +30 -10
- package/dist/unix-socket-keepalive.d.ts +3 -0
- package/dist/unix-socket-keepalive.js +94 -0
- package/dist/wand-task-sync.js +2 -1
- package/dist/web-ui/content/scripts.js +79 -73
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +21 -0
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/web-ui/thinking-efforts.d.ts +24 -0
- package/dist/web-ui/thinking-efforts.js +81 -0
- package/dist/ws-broadcast.d.ts +1 -1
- package/dist/ws-broadcast.js +28 -9
- package/package.json +8 -3
- package/scripts/install-render-binary.js +499 -0
- package/scripts/stage-render-binaries.js +626 -0
|
@@ -10,9 +10,9 @@ import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
|
10
10
|
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_AGENT_MODE, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, isWandTaskAgentMode, normalizeWandTaskAgentMode, WAND_MILESTONE_NAME_MAX_LENGTH } from "./task-types.js";
|
|
11
11
|
import { archiveBoardTask, ensureWorkspaceTaskForBoardTask, isAutoNameableBoardTask, moveSessionToWorkspaceTask, syncClosedBoardTask, syncUngroupedSessionsToBoard, syncWorkspaceTaskToBoard, taskAutoNameSignature, taskAutoNameSourceText } from "./wand-task-sync.js";
|
|
12
12
|
import { isSessionProvider, providerCliCommand } from "./session-provider.js";
|
|
13
|
+
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
13
14
|
const STATUSES = new Set(["todo", "doing", "done", "archived"]);
|
|
14
15
|
const PRIORITIES = new Set(["none", "low", "medium", "high", "urgent"]);
|
|
15
|
-
const AGENT_EFFORTS = new Set(["off", "standard", "deep", "max"]);
|
|
16
16
|
const TASK_BOARD_LAST_AGENT_KEY = "pref:taskBoardLastAgent";
|
|
17
17
|
function defaultTaskBoardAgent() {
|
|
18
18
|
return { provider: "claude", model: "default", thinkingEffort: "off", mode: DEFAULT_WAND_TASK_AGENT_MODE, kind: DEFAULT_WAND_TASK_AGENT_KIND };
|
|
@@ -53,6 +53,37 @@ function labelsFrom(value) {
|
|
|
53
53
|
return [];
|
|
54
54
|
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean).slice(0, 20);
|
|
55
55
|
}
|
|
56
|
+
/** 新认领只能挂到同项目且正在处理的任务;历史归属在父任务完成后仍保留。 */
|
|
57
|
+
function parentTaskIdFrom(storage, value, workspaceId, childId = null, existingParentId = null) {
|
|
58
|
+
if (value === null || value === "")
|
|
59
|
+
return null;
|
|
60
|
+
if (typeof value !== "string" || !value.trim())
|
|
61
|
+
throw new Error("父任务无效。");
|
|
62
|
+
const parentId = value.trim();
|
|
63
|
+
const parent = storage.getWandTask(parentId);
|
|
64
|
+
if (!parent)
|
|
65
|
+
throw new Error("父任务不存在。");
|
|
66
|
+
if (parent.workspaceId !== workspaceId)
|
|
67
|
+
throw new Error("父任务必须与子任务属于同一项目目录。");
|
|
68
|
+
if (parent.status !== "doing" && parentId !== existingParentId) {
|
|
69
|
+
throw new Error("只能认领到正在处理的父任务。");
|
|
70
|
+
}
|
|
71
|
+
// 不允许自己做父任务,也不允许把祖先接到后代下。
|
|
72
|
+
const visited = new Set();
|
|
73
|
+
let ancestorId = parentId;
|
|
74
|
+
while (ancestorId) {
|
|
75
|
+
if (ancestorId === childId || visited.has(ancestorId))
|
|
76
|
+
throw new Error("父任务不能形成循环。");
|
|
77
|
+
visited.add(ancestorId);
|
|
78
|
+
ancestorId = storage.getWandTask(ancestorId)?.parentTaskId ?? null;
|
|
79
|
+
}
|
|
80
|
+
return parentId;
|
|
81
|
+
}
|
|
82
|
+
function assertTaskWorkspaceMove(storage, taskId, workspaceId) {
|
|
83
|
+
if (storage.listWandTasks().some((task) => task.parentTaskId === taskId && task.workspaceId !== workspaceId)) {
|
|
84
|
+
throw new Error("请先将子任务解除归属,再移动父任务的项目目录。");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
/** 里程碑名:必填、去首尾空白、限长。 */
|
|
57
88
|
function milestoneNameFrom(value) {
|
|
58
89
|
const name = text(value).slice(0, WAND_MILESTONE_NAME_MAX_LENGTH);
|
|
@@ -99,8 +130,8 @@ export function parseTaskAgent(value, fallbackMode = DEFAULT_WAND_TASK_AGENT_MOD
|
|
|
99
130
|
const model = text(body.model) || "default";
|
|
100
131
|
if (model.length > 128)
|
|
101
132
|
throw new Error("模型名称过长。");
|
|
102
|
-
const thinkingEffort =
|
|
103
|
-
if (!
|
|
133
|
+
const thinkingEffort = text(body.thinkingEffort) || "off";
|
|
134
|
+
if (!isThinkingEffort(thinkingEffort))
|
|
104
135
|
throw new Error("思考深度无效。");
|
|
105
136
|
const rawMode = body.mode === undefined || body.mode === null || body.mode === "" ? fallbackMode : body.mode;
|
|
106
137
|
if (!isWandTaskAgentMode(rawMode))
|
|
@@ -350,9 +381,13 @@ export function registerTaskRoutes(app, deps) {
|
|
|
350
381
|
app.post("/api/wand-tasks", (req, res) => {
|
|
351
382
|
try {
|
|
352
383
|
const body = bodyObject(req.body);
|
|
353
|
-
const
|
|
384
|
+
const requestedParent = body.parentTaskId == null ? null : storage.getWandTask(text(body.parentTaskId));
|
|
385
|
+
const workspaceId = body.workspaceId === undefined && requestedParent
|
|
386
|
+
? requestedParent.workspaceId
|
|
387
|
+
: body.workspaceId === null ? null : text(body.workspaceId) || null;
|
|
354
388
|
if (workspaceId && !storage.getWorkspace(workspaceId))
|
|
355
389
|
throw new Error("项目不存在。");
|
|
390
|
+
const parentTaskId = parentTaskIdFrom(storage, body.parentTaskId ?? null, workspaceId);
|
|
356
391
|
const description = text(body.description);
|
|
357
392
|
// 标题是可选字段:用户不写就先用描述首行占位,再由模型在后台覆盖。
|
|
358
393
|
const providedTitle = text(body.title);
|
|
@@ -374,6 +409,7 @@ export function registerTaskRoutes(app, deps) {
|
|
|
374
409
|
const task = storage.transaction(() => {
|
|
375
410
|
const card = storage.createWandTask({
|
|
376
411
|
workspaceId,
|
|
412
|
+
parentTaskId,
|
|
377
413
|
title,
|
|
378
414
|
titleSource,
|
|
379
415
|
description,
|
|
@@ -403,6 +439,11 @@ export function registerTaskRoutes(app, deps) {
|
|
|
403
439
|
app.patch("/api/wand-tasks/:id", (req, res) => {
|
|
404
440
|
try {
|
|
405
441
|
const body = bodyObject(req.body);
|
|
442
|
+
const currentTask = storage.getWandTask(req.params.id);
|
|
443
|
+
if (!currentTask) {
|
|
444
|
+
res.status(404).json({ error: "未找到该任务。" });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
406
447
|
const patch = {};
|
|
407
448
|
if (body.title !== undefined) {
|
|
408
449
|
// 显式改标题(包括原生端的可编辑标题框)都算用户自己写的。
|
|
@@ -444,10 +485,15 @@ export function registerTaskRoutes(app, deps) {
|
|
|
444
485
|
patch.milestoneId = scopedMilestoneId(storage, milestoneId, patch.workspaceId)
|
|
445
486
|
?? defaultMilestoneIdForWrite(storage);
|
|
446
487
|
}
|
|
488
|
+
if (body.parentTaskId !== undefined || body.workspaceId !== undefined) {
|
|
489
|
+
const workspaceId = patch.workspaceId === undefined ? currentTask.workspaceId : patch.workspaceId;
|
|
490
|
+
patch.parentTaskId = parentTaskIdFrom(storage, body.parentTaskId === undefined ? currentTask.parentTaskId : body.parentTaskId, workspaceId, currentTask.id, currentTask.parentTaskId);
|
|
491
|
+
if (workspaceId !== currentTask.workspaceId)
|
|
492
|
+
assertTaskWorkspaceMove(storage, currentTask.id, workspaceId);
|
|
493
|
+
}
|
|
447
494
|
if (body.agent !== undefined) {
|
|
448
495
|
// 老客户端不传 mode / kind:沿用任务当前值,而不是复位成标准 / 结构化。
|
|
449
|
-
|
|
450
|
-
patch.agent = parseTaskAgent(body.agent, current?.agent?.mode, current?.agent?.kind);
|
|
496
|
+
patch.agent = parseTaskAgent(body.agent, currentTask.agent?.mode, currentTask.agent?.kind);
|
|
451
497
|
if (patch.agent)
|
|
452
498
|
writeTaskBoardLastAgent(storage, patch.agent);
|
|
453
499
|
}
|
|
@@ -537,6 +583,9 @@ export function registerTaskRoutes(app, deps) {
|
|
|
537
583
|
const workspaceId = body.workspaceId === null ? null : text(body.workspaceId) || null;
|
|
538
584
|
if (workspaceId && !storage.getWorkspace(workspaceId))
|
|
539
585
|
throw new Error("项目不存在。");
|
|
586
|
+
parentTaskIdFrom(storage, task.parentTaskId, workspaceId, task.id, task.parentTaskId);
|
|
587
|
+
if (workspaceId !== task.workspaceId)
|
|
588
|
+
assertTaskWorkspaceMove(storage, task.id, workspaceId);
|
|
540
589
|
task = storage.updateWandTask(task.id, { workspaceId }) ?? task;
|
|
541
590
|
}
|
|
542
591
|
const workspace = task.workspaceId ? storage.getWorkspace(task.workspaceId) : null;
|
|
@@ -34,6 +34,8 @@ export interface PublicUpdateRoutesDependencies {
|
|
|
34
34
|
computeAssetSha256(asset: DownloadAsset): Promise<string | null>;
|
|
35
35
|
resolveLatestDmg(): Promise<ResolvedUpdateAsset | null>;
|
|
36
36
|
resolveMacosDownload(): Promise<DownloadAsset | null>;
|
|
37
|
+
resolveLatestMacosBeta(): Promise<ResolvedUpdateAsset | null>;
|
|
38
|
+
resolveMacosBetaDownload(fileName: string): Promise<DownloadAsset | null>;
|
|
37
39
|
resolveLatestIpa(): Promise<ResolvedUpdateAsset | null>;
|
|
38
40
|
resolveIosDownload(): Promise<DownloadAsset | null>;
|
|
39
41
|
}
|
|
@@ -137,6 +137,50 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
137
137
|
readErrorMessage: "读取 APK 文件失败。",
|
|
138
138
|
});
|
|
139
139
|
}));
|
|
140
|
+
// 原生 macOS Beta 只使用当前连接服务端的本地分发文件;旧 DMG 接口保持兼容。
|
|
141
|
+
app.get("/api/macos-app-update", asyncRoute(async (req, res) => {
|
|
142
|
+
const currentVersion = requireCurrentVersion(req, res);
|
|
143
|
+
if (currentVersion === null)
|
|
144
|
+
return;
|
|
145
|
+
const latest = await deps.resolveLatestMacosBeta();
|
|
146
|
+
if (!latest) {
|
|
147
|
+
res.json({ updateAvailable: false, currentVersion, latestVersion: null, downloadUrl: null, source: null, channel: "beta" });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const updateAvailable = compareWandInstallOrder(latest.version, currentVersion) > 0;
|
|
151
|
+
res.json({
|
|
152
|
+
updateAvailable,
|
|
153
|
+
currentVersion,
|
|
154
|
+
latestVersion: latest.version,
|
|
155
|
+
downloadUrl: updateAvailable ? latest.downloadUrl : null,
|
|
156
|
+
fileName: updateAvailable ? latest.fileName : null,
|
|
157
|
+
size: updateAvailable ? latest.size : null,
|
|
158
|
+
source: "local",
|
|
159
|
+
channel: "beta",
|
|
160
|
+
sha256: updateAvailable ? latest.sha256 : null,
|
|
161
|
+
});
|
|
162
|
+
}));
|
|
163
|
+
app.get("/macos/update-download", asyncRoute(async (req, res) => {
|
|
164
|
+
const fileName = text(req.query.fileName);
|
|
165
|
+
const asset = await deps.resolveMacosBetaDownload(fileName);
|
|
166
|
+
if (!asset) {
|
|
167
|
+
res.status(404).json({ error: "当前没有可下载的 macOS Beta 更新包。" });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const sha256 = await deps.computeAssetSha256(asset);
|
|
171
|
+
if (!sha256) {
|
|
172
|
+
res.status(503).json({ error: "无法校验 macOS Beta 更新包。" });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
streamFileWithRange(req, res, {
|
|
176
|
+
filePath: asset.filePath,
|
|
177
|
+
size: asset.size,
|
|
178
|
+
contentType: asset.fileName.toLowerCase().endsWith(".zip") ? "application/zip" : "application/x-apple-diskimage",
|
|
179
|
+
disposition: `attachment; filename="${encodeURIComponent(asset.fileName)}"`,
|
|
180
|
+
headers: { "X-MacOS-Sha256": sha256 },
|
|
181
|
+
readErrorMessage: "读取 macOS Beta 更新包失败。",
|
|
182
|
+
});
|
|
183
|
+
}));
|
|
140
184
|
app.get("/api/macos-dmg-update", asyncRoute(async (req, res) => {
|
|
141
185
|
const currentVersion = requireCurrentVersion(req, res);
|
|
142
186
|
if (currentVersion === null)
|
package/dist/server.js
CHANGED
|
@@ -17,7 +17,7 @@ import { ModelCatalogService } from "./models.js";
|
|
|
17
17
|
import { ProcessManager } from "./process-manager.js";
|
|
18
18
|
import { SessionLogger } from "./session-logger.js";
|
|
19
19
|
import { SessionRegistry } from "./session-registry.js";
|
|
20
|
-
import {
|
|
20
|
+
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
21
21
|
import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
22
22
|
import { recordRecentPath, registerFileRoutes } from "./server-file-routes.js";
|
|
23
23
|
import { registerLocalPreviewRoutes } from "./server-local-preview-routes.js";
|
|
@@ -55,7 +55,9 @@ import { isLogBusActive, wandTuiLog } from "./tui/log-bus.js";
|
|
|
55
55
|
import { EMBEDDED_WEB_ASSETS } from "./web-ui/embedded-assets.js";
|
|
56
56
|
import { renderApp } from "./web-ui/index.js";
|
|
57
57
|
import { WsBroadcastManager } from "./ws-broadcast.js";
|
|
58
|
-
import {
|
|
58
|
+
import { TerminalDaemonClient } from "./terminal-daemon-client.js";
|
|
59
|
+
import { createUpgradeAwareTerminalHost } from "./render-host.js";
|
|
60
|
+
import { createUpgradeAwareStructuredHost } from "./render-structured-host.js";
|
|
59
61
|
import { checkRateLimit, recordFailedLogin, resetRateLimit } from "./middleware/rate-limit.js";
|
|
60
62
|
import { updateProviderClis, verifyProviderCliUpdateResults, } from "./provider-cli-updater.js";
|
|
61
63
|
const SERVER_MODULE_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
@@ -78,7 +80,8 @@ const AVATAR_CONTENT_TYPES = {
|
|
|
78
80
|
};
|
|
79
81
|
// ── Update check cache ──
|
|
80
82
|
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
81
|
-
|
|
83
|
+
/** CLI 模型目录和思考档位的探测间隔。页面另有更密的快照拉取。 */
|
|
84
|
+
const MODEL_CATALOG_AUTO_REFRESH_INTERVAL_MS = 10 * 60 * 1000;
|
|
82
85
|
/** Cached update result broadcast to new clients on connect. */
|
|
83
86
|
let cachedUpdateInfo = null;
|
|
84
87
|
const packageUpdateCache = new Map();
|
|
@@ -317,13 +320,27 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
317
320
|
config,
|
|
318
321
|
repositoryUrl: PKG_REPO_URL,
|
|
319
322
|
});
|
|
320
|
-
const
|
|
323
|
+
const knownPtySessionIds = storage.loadSessions()
|
|
324
|
+
.filter((session) => (session.sessionKind ?? "pty") === "pty" && session.status === "running")
|
|
325
|
+
.map((session) => session.id);
|
|
326
|
+
// PTY 所有者可以是 legacy terminald 或常驻的 Rust Render:升级期由复合 host 按所有权
|
|
327
|
+
// 路由(旧会话留在 legacy,新会话进 Render),对客户端契约没有任何改变。
|
|
328
|
+
const ptyHosts = options.terminalHost
|
|
329
|
+
? { host: options.terminalHost, renderHost: null, legacyHost: null }
|
|
330
|
+
: await createUpgradeAwareTerminalHost(configPath, {
|
|
331
|
+
engine: config.render?.engine,
|
|
332
|
+
binaryPath: config.render?.binaryPath,
|
|
333
|
+
knownSessionIds: knownPtySessionIds,
|
|
334
|
+
});
|
|
335
|
+
const terminalHost = ptyHosts.host;
|
|
321
336
|
const processes = new ProcessManager(config, storage, configDir, terminalHost);
|
|
322
337
|
const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
|
|
323
|
-
// Production startup
|
|
324
|
-
//
|
|
325
|
-
const
|
|
326
|
-
|
|
338
|
+
// Production startup provides a daemon-backed host for structured CLI runs
|
|
339
|
+
// even when Render owns every PTY. In-process hosts are for test injection.
|
|
340
|
+
const legacyStructuredHost = ptyHosts.legacyHost
|
|
341
|
+
?? (terminalHost instanceof TerminalDaemonClient ? terminalHost : null);
|
|
342
|
+
const structuredHosts = await createUpgradeAwareStructuredHost(configPath, legacyStructuredHost, config.structured?.processHost);
|
|
343
|
+
const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger, undefined, {}, structuredHosts.host);
|
|
327
344
|
const sessionRegistry = new SessionRegistry(processes, structuredSessions, storage);
|
|
328
345
|
const missions = new Missions(storage, structuredSessions, sessionRegistry);
|
|
329
346
|
const updateState = new ServerUpdateState();
|
|
@@ -679,8 +696,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
679
696
|
selectedModel: null,
|
|
680
697
|
thinkingEffort: config.defaultThinkingEffort,
|
|
681
698
|
};
|
|
682
|
-
|
|
683
|
-
ai = config.systemAi?.enabled ? { ...cli, systemAi: config.systemAi } : cli;
|
|
699
|
+
ai = resolveSystemAiContext(defaultSession, config);
|
|
684
700
|
}
|
|
685
701
|
try {
|
|
686
702
|
const optimized = await optimizePrompt(text, config.language ?? "", cwd, ai);
|
|
@@ -809,9 +825,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
809
825
|
wsManager.setup({
|
|
810
826
|
getSession: (id) => sessionRegistry.get(id),
|
|
811
827
|
getTerminalState: (id) => processes.getTerminalState(id),
|
|
812
|
-
sendPtyInput: (id, input, shortcutKey, userInput) => {
|
|
813
|
-
processes.sendInput(id, input, "terminal", shortcutKey, userInput);
|
|
814
|
-
},
|
|
828
|
+
sendPtyInput: (id, input, shortcutKey, userInput) => processes.sendInputConfirmed(id, input, "terminal", shortcutKey, userInput).then(() => { }),
|
|
815
829
|
resizePty: (id, cols, rows) => {
|
|
816
830
|
processes.resize(id, cols, rows);
|
|
817
831
|
},
|
|
@@ -886,6 +900,10 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
886
900
|
structuredSessions.dispose();
|
|
887
901
|
}
|
|
888
902
|
catch { /* noop */ }
|
|
903
|
+
try {
|
|
904
|
+
structuredHosts.rustClient?.disconnect();
|
|
905
|
+
}
|
|
906
|
+
catch { /* noop */ }
|
|
889
907
|
try {
|
|
890
908
|
structuredLogger.dispose();
|
|
891
909
|
}
|
|
@@ -948,6 +966,14 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
948
966
|
}
|
|
949
967
|
// Model discovery runs exclusively in the server. Its first result is
|
|
950
968
|
// persisted; later checks only write when catalog content changes.
|
|
969
|
+
// 变化后通知已连接的页面,下拉不用等用户点「刷新模型列表」。
|
|
970
|
+
modelCatalog.onChanged((result) => {
|
|
971
|
+
wsManager.emitEvent({
|
|
972
|
+
type: "notification",
|
|
973
|
+
sessionId: "__system__",
|
|
974
|
+
data: { kind: "models", revision: result.revision, refreshedAt: result.refreshedAt },
|
|
975
|
+
});
|
|
976
|
+
});
|
|
951
977
|
let modelCatalogRefreshTimer = null;
|
|
952
978
|
if (!testMode) {
|
|
953
979
|
void modelCatalog.refresh().catch(() => { });
|
|
@@ -1151,6 +1177,10 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1151
1177
|
structuredSessions.dispose();
|
|
1152
1178
|
}
|
|
1153
1179
|
catch { /* best-effort shutdown */ }
|
|
1180
|
+
try {
|
|
1181
|
+
structuredHosts.rustClient?.disconnect();
|
|
1182
|
+
}
|
|
1183
|
+
catch { /* best-effort shutdown */ }
|
|
1154
1184
|
try {
|
|
1155
1185
|
structuredLogger.dispose();
|
|
1156
1186
|
}
|
|
@@ -16,6 +16,6 @@ export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "
|
|
|
16
16
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
17
17
|
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
18
18
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
19
|
-
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi">): SessionAiContext;
|
|
19
|
+
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi" | "systemAiCli" | "systemAiModel">): SessionAiContext;
|
|
20
20
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
21
21
|
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv" | "commitAiSource" | "systemAi">, discoverApis?: typeof discoverCliSystemAiConfigs): SessionAiContext;
|
|
@@ -39,10 +39,21 @@ export function resolveSessionAiContext(snapshot, config) {
|
|
|
39
39
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
40
40
|
export function resolveSystemAiContext(snapshot, config) {
|
|
41
41
|
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
? { ...sessionContext, systemAi: directApi }
|
|
45
|
-
|
|
42
|
+
if (config.systemAi?.enabled) {
|
|
43
|
+
const directApi = usableSystemAi(config.systemAi);
|
|
44
|
+
return directApi ? { ...sessionContext, systemAi: directApi } : sessionContext;
|
|
45
|
+
}
|
|
46
|
+
// Older installations without a system AI CLI preference still follow the
|
|
47
|
+
// current session. A chosen CLI must never inherit another provider's model.
|
|
48
|
+
if (!isSessionProvider(config.systemAiCli))
|
|
49
|
+
return sessionContext;
|
|
50
|
+
return {
|
|
51
|
+
...sessionContext,
|
|
52
|
+
provider: config.systemAiCli,
|
|
53
|
+
model: normalizeModel(config.systemAiModel)
|
|
54
|
+
?? normalizeModel(getDefaultModelForProvider(config, config.systemAiCli)),
|
|
55
|
+
thinkingEffort: config.defaultThinkingEffort,
|
|
56
|
+
};
|
|
46
57
|
}
|
|
47
58
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
48
59
|
export function resolveCommitAiContext(snapshot, config, discoverApis = discoverCliSystemAiConfigs) {
|
package/dist/session-logger.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ export declare class SessionLogger {
|
|
|
70
70
|
appendPtyOutput(sessionId: string, chunk: string): void;
|
|
71
71
|
/** Read the full PTY transcript including rotated logs, oldest first. */
|
|
72
72
|
readPtyOutput(sessionId: string): string | null;
|
|
73
|
+
/** Only read the tail needed by the transport, including rotated files. */
|
|
74
|
+
readPtyOutputTail(sessionId: string, maxBytes?: number): string | null;
|
|
73
75
|
/** Append a native mode NDJSON event */
|
|
74
76
|
appendStreamEvent(sessionId: string, event: unknown): void;
|
|
75
77
|
/** Append raw stdout chunk from a structured-mode child process. */
|
package/dist/session-logger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, rmSync, appendFileSync, writeFileSync, readFileSync, existsSync, statSync, renameSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, rmSync, appendFileSync, writeFileSync, readFileSync, existsSync, statSync, renameSync, unlinkSync, openSync, readSync, closeSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
// ── Constants ──
|
|
@@ -148,6 +148,49 @@ export class SessionLogger {
|
|
|
148
148
|
return null;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
+
/** Only read the tail needed by the transport, including rotated files. */
|
|
152
|
+
readPtyOutputTail(sessionId, maxBytes = 256 * 1024) {
|
|
153
|
+
try {
|
|
154
|
+
this.flushSession(sessionId);
|
|
155
|
+
const dir = this.ensureDir(sessionId);
|
|
156
|
+
const parts = [];
|
|
157
|
+
let remaining = Math.max(1, Math.min(maxBytes, 1024 * 1024));
|
|
158
|
+
for (let index = 0; index <= this.ptyLogMaxRotations && remaining > 0; index++) {
|
|
159
|
+
const file = path.join(dir, index ? `pty-output.log.${index}` : "pty-output.log");
|
|
160
|
+
const size = tryStatSize(file);
|
|
161
|
+
if (!size)
|
|
162
|
+
continue;
|
|
163
|
+
const length = Math.min(remaining, size);
|
|
164
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
165
|
+
const fd = openSync(file, "r");
|
|
166
|
+
try {
|
|
167
|
+
let read = 0;
|
|
168
|
+
while (read < length) {
|
|
169
|
+
const count = readSync(fd, buffer, read, length - read, size - length + read);
|
|
170
|
+
if (!count)
|
|
171
|
+
break;
|
|
172
|
+
read += count;
|
|
173
|
+
}
|
|
174
|
+
parts.unshift(buffer.subarray(0, read));
|
|
175
|
+
remaining -= read;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
closeSync(fd);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!parts.length)
|
|
182
|
+
return null;
|
|
183
|
+
const tail = Buffer.concat(parts);
|
|
184
|
+
// A byte-range may begin inside a multibyte scalar; don't inject U+FFFD.
|
|
185
|
+
let start = 0;
|
|
186
|
+
while (start < tail.length && (tail[start] & 0xc0) === 0x80)
|
|
187
|
+
start++;
|
|
188
|
+
return tail.subarray(start).toString("utf8");
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
151
194
|
/** Append a native mode NDJSON event */
|
|
152
195
|
appendStreamEvent(sessionId, event) {
|
|
153
196
|
if (this.disposed)
|
|
@@ -87,7 +87,7 @@ export function toSessionDetailDTO(snapshot, options = {}) {
|
|
|
87
87
|
outputOffset,
|
|
88
88
|
outputTotal: rawOutput.length,
|
|
89
89
|
outputTruncated: outputOffset > 0,
|
|
90
|
-
...(options.messages !== undefined ? { messages: enrichStructuredMessages(options.messages) } : {}),
|
|
90
|
+
...(options.messages !== undefined ? { messages: enrichStructuredMessages(options.messages, snapshot.id) } : {}),
|
|
91
91
|
...(options.messageOffset !== undefined ? { messageOffset: options.messageOffset } : {}),
|
|
92
92
|
...(options.messageTotal !== undefined ? { messageTotal: options.messageTotal } : {}),
|
|
93
93
|
...(options.leadingBlockOffset !== undefined ? { leadingBlockOffset: options.leadingBlockOffset } : {}),
|
package/dist/storage.d.ts
CHANGED
|
@@ -125,6 +125,7 @@ export declare class WandStorage {
|
|
|
125
125
|
createWandTask(input: {
|
|
126
126
|
workspaceId?: string | null;
|
|
127
127
|
workspaceTaskId?: string | null;
|
|
128
|
+
parentTaskId?: string | null;
|
|
128
129
|
title: string;
|
|
129
130
|
titleSource?: import("./task-types.js").WandTaskTitleSource;
|
|
130
131
|
description?: string;
|
|
@@ -135,7 +136,7 @@ export declare class WandStorage {
|
|
|
135
136
|
milestoneId?: string | null;
|
|
136
137
|
agent?: import("./task-types.js").WandTaskAgent | null;
|
|
137
138
|
}): import("./task-types.js").WandTask;
|
|
138
|
-
updateWandTask(id: string, patch: Partial<Pick<import("./task-types.js").WandTask, "workspaceId" | "workspaceTaskId" | "title" | "titleSource" | "autoTitleSignature" | "description" | "status" | "priority" | "labels" | "dueDate" | "milestoneId" | "sortOrder" | "agent">>): import("./task-types.js").WandTask | null;
|
|
139
|
+
updateWandTask(id: string, patch: Partial<Pick<import("./task-types.js").WandTask, "workspaceId" | "workspaceTaskId" | "parentTaskId" | "title" | "titleSource" | "autoTitleSignature" | "description" | "status" | "priority" | "labels" | "dueDate" | "milestoneId" | "sortOrder" | "agent">>): import("./task-types.js").WandTask | null;
|
|
139
140
|
deleteWandTask(id: string): void;
|
|
140
141
|
/**
|
|
141
142
|
* 里程碑列表。传工作区 id 时返回「该工作区 + 全局(workspace_id IS NULL)」;
|
package/dist/storage.js
CHANGED
|
@@ -755,6 +755,7 @@ const INIT_SQL = `
|
|
|
755
755
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
756
756
|
agent_json TEXT,
|
|
757
757
|
workspace_task_id TEXT,
|
|
758
|
+
parent_task_id TEXT,
|
|
758
759
|
title_source TEXT,
|
|
759
760
|
auto_title_signature TEXT,
|
|
760
761
|
created_at TEXT NOT NULL,
|
|
@@ -784,6 +785,9 @@ function ensureWandTaskSchema(db) {
|
|
|
784
785
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN agent_json TEXT");
|
|
785
786
|
if (columns.length > 0 && !names.has("workspace_task_id"))
|
|
786
787
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN workspace_task_id TEXT");
|
|
788
|
+
// 父任务归属只加列;历史任务仍然是顶层任务。
|
|
789
|
+
if (columns.length > 0 && !names.has("parent_task_id"))
|
|
790
|
+
db.exec("ALTER TABLE wand_tasks ADD COLUMN parent_task_id TEXT");
|
|
787
791
|
// 标题来源:'user' = 用户手写,'auto' = 由描述/会话内容自动生成。只加列,历史行保持 NULL(读取时视为 user)。
|
|
788
792
|
if (columns.length > 0 && !names.has("title_source"))
|
|
789
793
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN title_source TEXT");
|
|
@@ -798,6 +802,7 @@ function ensureWandTaskSchema(db) {
|
|
|
798
802
|
// INIT_SQL cannot create it: CREATE TABLE IF NOT EXISTS is a no-op on old
|
|
799
803
|
// wand_tasks, and CREATE INDEX would then fail with "no such column".
|
|
800
804
|
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_tasks_workspace_task ON wand_tasks(workspace_task_id)");
|
|
805
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_tasks_parent ON wand_tasks(parent_task_id)");
|
|
801
806
|
const rows = db.prepare("SELECT id FROM wand_tasks WHERE identifier IS NULL OR identifier = '' ORDER BY created_at, id").all();
|
|
802
807
|
const update = db.prepare("UPDATE wand_tasks SET identifier = ? WHERE id = ?");
|
|
803
808
|
rows.forEach((row, index) => update.run(`TASK-${index + 1}`, row.id));
|
|
@@ -1296,7 +1301,7 @@ export class WandStorage {
|
|
|
1296
1301
|
}
|
|
1297
1302
|
/** GET /api/wand-tasks 的顺序:新创建在前。客户端按此顺序渲染,不再本地排序。 */
|
|
1298
1303
|
listWandTasks(workspaceId) {
|
|
1299
|
-
const rows = this.db.prepare(`SELECT id, identifier, workspace_id, workspace_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at
|
|
1304
|
+
const rows = this.db.prepare(`SELECT id, identifier, workspace_id, workspace_task_id, parent_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at
|
|
1300
1305
|
FROM wand_tasks ${workspaceId === undefined ? "" : "WHERE workspace_id IS ?"}
|
|
1301
1306
|
ORDER BY created_at DESC, rowid DESC`).all(...(workspaceId === undefined ? [] : [workspaceId]));
|
|
1302
1307
|
return rows.map((row) => this.mapWandTaskRow(row));
|
|
@@ -1307,6 +1312,7 @@ export class WandStorage {
|
|
|
1307
1312
|
identifier: typeof row.identifier === "string" && row.identifier ? row.identifier : `TASK-${String(row.id).slice(0, 6)}`,
|
|
1308
1313
|
workspaceId: typeof row.workspace_id === "string" ? row.workspace_id : null,
|
|
1309
1314
|
workspaceTaskId: typeof row.workspace_task_id === "string" && row.workspace_task_id ? row.workspace_task_id : null,
|
|
1315
|
+
parentTaskId: typeof row.parent_task_id === "string" && row.parent_task_id ? row.parent_task_id : null,
|
|
1310
1316
|
title: String(row.title),
|
|
1311
1317
|
titleSource: row.title_source === "auto" ? "auto" : "user",
|
|
1312
1318
|
autoTitleSignature: typeof row.auto_title_signature === "string" && row.auto_title_signature ? row.auto_title_signature : null,
|
|
@@ -1326,7 +1332,7 @@ export class WandStorage {
|
|
|
1326
1332
|
return this.listWandTasks().find((task) => task.id === id) ?? null;
|
|
1327
1333
|
}
|
|
1328
1334
|
getWandTaskByWorkspaceTaskId(workspaceTaskId) {
|
|
1329
|
-
const row = this.db.prepare(`SELECT id, identifier, workspace_id, workspace_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at
|
|
1335
|
+
const row = this.db.prepare(`SELECT id, identifier, workspace_id, workspace_task_id, parent_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at
|
|
1330
1336
|
FROM wand_tasks WHERE workspace_task_id = ? ORDER BY updated_at DESC LIMIT 1`).get(workspaceTaskId);
|
|
1331
1337
|
return row ? this.mapWandTaskRow(row) : null;
|
|
1332
1338
|
}
|
|
@@ -1338,7 +1344,7 @@ export class WandStorage {
|
|
|
1338
1344
|
const max = this.db.prepare("SELECT COALESCE(MAX(sort_order), -1) AS value FROM wand_tasks WHERE workspace_id IS ? AND status = ?").get(input.workspaceId ?? null, status);
|
|
1339
1345
|
const numberRow = this.db.prepare("SELECT COALESCE(MAX(CAST(substr(identifier, 6) AS INTEGER)), 0) AS value FROM wand_tasks WHERE identifier GLOB 'TASK-[0-9]*'").get();
|
|
1340
1346
|
const identifier = `TASK-${(numberRow?.value ?? 0) + 1}`;
|
|
1341
|
-
this.db.prepare(`INSERT INTO wand_tasks (id, identifier, workspace_id, workspace_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, identifier, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, priority, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, (max?.value ?? -1) + 1, input.agent ? JSON.stringify(input.agent) : null, now, now);
|
|
1347
|
+
this.db.prepare(`INSERT INTO wand_tasks (id, identifier, workspace_id, workspace_task_id, parent_task_id, title, title_source, auto_title_signature, description, status, priority, labels_json, due_date, milestone_id, sort_order, agent_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, identifier, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.parentTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, priority, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, (max?.value ?? -1) + 1, input.agent ? JSON.stringify(input.agent) : null, now, now);
|
|
1342
1348
|
return this.getWandTask(id);
|
|
1343
1349
|
}
|
|
1344
1350
|
updateWandTask(id, patch) {
|
|
@@ -1346,7 +1352,7 @@ export class WandStorage {
|
|
|
1346
1352
|
if (!current)
|
|
1347
1353
|
return null;
|
|
1348
1354
|
const next = { ...current, ...patch, updatedAt: nowIso() };
|
|
1349
|
-
this.db.prepare(`UPDATE wand_tasks SET workspace_id = ?, workspace_task_id = ?, title = ?, title_source = ?, auto_title_signature = ?, description = ?, status = ?, priority = ?, labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, updated_at = ? WHERE id = ?`).run(next.workspaceId, next.workspaceTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.updatedAt, id);
|
|
1355
|
+
this.db.prepare(`UPDATE wand_tasks SET workspace_id = ?, workspace_task_id = ?, parent_task_id = ?, title = ?, title_source = ?, auto_title_signature = ?, description = ?, status = ?, priority = ?, labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, updated_at = ? WHERE id = ?`).run(next.workspaceId, next.workspaceTaskId, next.parentTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.updatedAt, id);
|
|
1350
1356
|
if (next.workspaceTaskId) {
|
|
1351
1357
|
const workspaceId = next.workspaceId ?? this.ensureGlobalWorkspace().id;
|
|
1352
1358
|
this.db.prepare(`UPDATE workspace_tasks SET name = ?, status = ?, milestone_id = ?, workspace_id = ? WHERE id = ?`)
|
|
@@ -1356,7 +1362,10 @@ export class WandStorage {
|
|
|
1356
1362
|
}
|
|
1357
1363
|
return next;
|
|
1358
1364
|
}
|
|
1359
|
-
deleteWandTask(id) {
|
|
1365
|
+
deleteWandTask(id) {
|
|
1366
|
+
this.db.prepare("UPDATE wand_tasks SET parent_task_id = NULL WHERE parent_task_id = ?").run(id);
|
|
1367
|
+
this.db.prepare("DELETE FROM wand_tasks WHERE id = ?").run(id);
|
|
1368
|
+
}
|
|
1360
1369
|
// ── 里程碑(迭代,归属工作区;任务只存 milestoneId)──
|
|
1361
1370
|
/**
|
|
1362
1371
|
* 里程碑列表。传工作区 id 时返回「该工作区 + 全局(workspace_id IS NULL)」;
|
|
@@ -3,5 +3,7 @@ export declare const WAND_PROTOCOL_VERSION = 2;
|
|
|
3
3
|
/**
|
|
4
4
|
* Add Wand-owned semantics without mutating persisted provider blocks.
|
|
5
5
|
* This is the external interface consumed by every client.
|
|
6
|
+
*
|
|
7
|
+
* 传 sessionId 时额外把内联图片改写成取图 URL(见 rewriteInlineToolImageUrls)。
|
|
6
8
|
*/
|
|
7
|
-
export declare function enrichStructuredMessages(messages: ConversationTurn[]): ConversationTurn[];
|
|
9
|
+
export declare function enrichStructuredMessages(messages: ConversationTurn[], sessionId?: string): ConversationTurn[];
|