@co0ontty/wand 4.77.0 → 4.78.0
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 +21 -46
- package/dist/agent-dispatch.js +3 -2
- package/dist/ai-team-live.d.ts +25 -0
- package/dist/ai-team-live.js +135 -0
- package/dist/ai-team-prompts.d.ts +10 -6
- package/dist/ai-team-prompts.js +74 -41
- package/dist/ai-team-runner.d.ts +85 -3
- package/dist/ai-team-runner.js +336 -33
- package/dist/ai-team-types.d.ts +57 -2
- package/dist/ai-team-types.js +4 -0
- package/dist/build-info.json +3 -3
- package/dist/commit-task-archive.js +14 -18
- package/dist/config.js +14 -6
- package/dist/git-quick-commit.js +1 -1
- package/dist/middleware/rate-limit.d.ts +18 -4
- package/dist/middleware/rate-limit.js +55 -23
- package/dist/missions.js +0 -3
- package/dist/models.d.ts +6 -1
- package/dist/models.js +35 -0
- package/dist/path-repair.d.ts +6 -5
- package/dist/path-repair.js +3 -3
- package/dist/render-protocol.d.ts +1 -1
- package/dist/render-protocol.js +1 -1
- package/dist/server-ai-team-routes.d.ts +1 -0
- package/dist/server-ai-team-routes.js +28 -14
- package/dist/server-local-preview-routes.js +1 -1
- package/dist/server-session-routes.js +0 -3
- package/dist/server-settings-routes.d.ts +1 -1
- package/dist/server-settings-routes.js +3 -2
- package/dist/server-task-routes.js +16 -32
- package/dist/server-workspace-routes.js +31 -24
- package/dist/server.js +60 -21
- package/dist/storage.d.ts +54 -21
- package/dist/storage.js +485 -122
- package/dist/types.d.ts +7 -0
- package/dist/upload-routes.d.ts +2 -2
- package/dist/upload-routes.js +10 -3
- package/dist/wand-task-sync.d.ts +2 -34
- package/dist/wand-task-sync.js +0 -206
- package/dist/web-ui/asset-version.d.ts +5 -0
- package/dist/web-ui/asset-version.js +5 -0
- package/dist/web-ui/content/ai-teams.js +17 -8
- package/dist/web-ui/content/scripts.js +67 -66
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +0 -21
- package/dist/web-ui/embedded-assets.js +8 -7
- package/dist/web-ui/index.js +9 -17
- package/dist/web-ui/scripts.d.ts +3 -1
- package/dist/web-ui/scripts.js +52 -25
- package/dist/web-ui/styles.d.ts +2 -0
- package/dist/web-ui/styles.js +12 -0
- package/package.json +13 -12
- package/scripts/install-render-binary.js +2 -2
- package/scripts/stage-render-binaries.js +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -526,6 +526,13 @@ export interface ConversationAuthor {
|
|
|
526
526
|
avatar?: string;
|
|
527
527
|
leader?: boolean;
|
|
528
528
|
provider?: SessionProvider;
|
|
529
|
+
/**
|
|
530
|
+
* 这条发言**实际使用候选**的模型 id(真值,不是展示文案;`"default"` 表示跟随该 provider
|
|
531
|
+
* 的服务端默认模型)。老数据没有这个字段,读端要能只按 provider 显示。
|
|
532
|
+
*/
|
|
533
|
+
model?: string;
|
|
534
|
+
/** 实际使用候选的思考深度(`off/standard/deep/max` 或 `provider:<原生档位>`);同上,可缺省。 */
|
|
535
|
+
thinkingEffort?: string;
|
|
529
536
|
/** 这条发言对应的成员会话,可点开看完整过程。 */
|
|
530
537
|
sessionId?: string;
|
|
531
538
|
}
|
package/dist/upload-routes.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Express } from "express";
|
|
2
|
-
import type {
|
|
3
|
-
export declare function registerUploadRoutes(app: Express,
|
|
2
|
+
import type { SessionRegistry } from "./session-registry.js";
|
|
3
|
+
export declare function registerUploadRoutes(app: Express, sessions: Pick<SessionRegistry, "getLatest">): void;
|
package/dist/upload-routes.js
CHANGED
|
@@ -7,7 +7,7 @@ const MAX_FILES = 5;
|
|
|
7
7
|
function sanitizeFilename(name) {
|
|
8
8
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 200);
|
|
9
9
|
}
|
|
10
|
-
export function registerUploadRoutes(app,
|
|
10
|
+
export function registerUploadRoutes(app, sessions) {
|
|
11
11
|
const storage = multer.diskStorage({
|
|
12
12
|
destination(_req, _file, cb) {
|
|
13
13
|
const cwd = _req.uploadCwd;
|
|
@@ -32,12 +32,19 @@ export function registerUploadRoutes(app, processes) {
|
|
|
32
32
|
});
|
|
33
33
|
function requireUploadSession(req, res, next) {
|
|
34
34
|
const sessionId = req.params.id;
|
|
35
|
-
|
|
35
|
+
// Group chats are structured relay sessions; ProcessManager does not own them.
|
|
36
|
+
// Resolve through the same registry used by the rest of the session API so
|
|
37
|
+
// PTY, structured and stored sessions all upload into their own cwd.
|
|
38
|
+
const session = sessions.getLatest(sessionId);
|
|
36
39
|
if (!session) {
|
|
37
40
|
res.status(404).json({ error: "会话不存在。" });
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
if (!session.cwd || !path.isAbsolute(session.cwd)) {
|
|
44
|
+
res.status(400).json({ error: "会话工作目录无效。" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
req.uploadCwd = session.cwd;
|
|
41
48
|
next();
|
|
42
49
|
}
|
|
43
50
|
app.post("/api/sessions/:id/upload", requireUploadSession, upload.array("files", MAX_FILES), (req, res) => {
|
package/dist/wand-task-sync.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WandStorage } from "./storage.js";
|
|
2
|
-
import {
|
|
3
|
-
import type { SessionSnapshot
|
|
2
|
+
import type { WandTask } from "./task-types.js";
|
|
3
|
+
import type { SessionSnapshot } from "./types.js";
|
|
4
4
|
/** 未命名任务的占位名;服务端新建时写入,自动命名成功后被真实标题覆盖。 */
|
|
5
5
|
export declare const UNNAMED_WORKSPACE_TASK_NAME = "\u672A\u547D\u540D\u4EFB\u52A1";
|
|
6
6
|
export declare function isUnnamedWorkspaceTaskName(name: string): boolean;
|
|
@@ -18,35 +18,3 @@ export declare function isAutoNameableBoardTask(card: Pick<WandTask, "title" | "
|
|
|
18
18
|
export declare function taskAutoNameSourceText(storage: WandStorage, card: WandTask): string;
|
|
19
19
|
/** 输入指纹:内容没变就没必要再总结一次,也避免自动标题与模型结果来回震荡。 */
|
|
20
20
|
export declare function taskAutoNameSignature(source: string): string;
|
|
21
|
-
/** Identity is the task id, never its title. Same-named tasks stay independent. */
|
|
22
|
-
export declare function ensureBoardTaskForWorkspaceTask(storage: WandStorage, task: WorkspaceTask, workspace: Workspace, options?: {
|
|
23
|
-
titleSource?: WandTaskTitleSource;
|
|
24
|
-
description?: string;
|
|
25
|
-
parentTaskId?: string | null;
|
|
26
|
-
}): WandTask;
|
|
27
|
-
/** A board task always has a sidebar container, even before its first session. */
|
|
28
|
-
export declare function ensureWorkspaceTaskForBoardTask(storage: WandStorage, card: WandTask): WorkspaceTask;
|
|
29
|
-
/** Atomic exclusive ownership change. Never changes cwd, messages, or execution state. */
|
|
30
|
-
export declare function moveSessionToWorkspaceTask(storage: WandStorage, sessionId: string, taskId: string | null): void;
|
|
31
|
-
/** Reconcile legacy records by explicit ids. Unassigned sessions remain unassigned. */
|
|
32
|
-
export declare function syncSidebarTasksFromBoard(storage: WandStorage): void;
|
|
33
|
-
/**
|
|
34
|
-
* 会话建立 / 移动后立刻把任务归属同步到看板卡片,卡片不必等下一次 `GET /api/wand-tasks`
|
|
35
|
-
* 的全量兜底同步,也不必等某个客户端碰巧拉了看板列表。
|
|
36
|
-
* 看板侧的写入失败只记日志:会话已经跑起来了,不能因为看板同步失败把创建请求打断。
|
|
37
|
-
*/
|
|
38
|
-
export declare function syncWorkspaceTaskToBoard(storage: WandStorage, workspaceTaskId: string | null | undefined): void;
|
|
39
|
-
/**
|
|
40
|
-
* 兜底全量同步:只处理「会话带着 workspace_task_id 但卡片上还没有绑定」的历史 / 外部写入。
|
|
41
|
-
* 正常路径由 `syncWorkspaceTaskToBoard` 在会话建立时点完成。
|
|
42
|
-
*/
|
|
43
|
-
export declare function syncUngroupedSessionsToBoard(storage: WandStorage): void;
|
|
44
|
-
/** Status and title projection is centralized in the storage writer. */
|
|
45
|
-
export declare function syncClosedBoardTask(storage: WandStorage, task: WandTask): void;
|
|
46
|
-
export declare function archiveBoardTask(storage: WandStorage, id: string): WandTask | null;
|
|
47
|
-
/**
|
|
48
|
-
* 归档侧栏任务是软删除:终端继续运行、worktree 与布局都保留,只有看板卡片进入
|
|
49
|
-
* 归档文件夹、侧栏不再显示。恢复方式是看板里把卡片拖回任一列(或右键恢复)。
|
|
50
|
-
*/
|
|
51
|
-
export declare function archiveWorkspaceTask(storage: WandStorage, task: WorkspaceTask): WandTask | null;
|
|
52
|
-
export declare function archiveBoardTaskForWorkspaceTask(storage: WandStorage, workspaceTaskId: string): void;
|
package/dist/wand-task-sync.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { getErrorMessage } from "./error-utils.js";
|
|
3
2
|
import { provisionalTaskTitleFromDescription } from "./task-title.js";
|
|
4
|
-
import { isSessionProvider } from "./session-provider.js";
|
|
5
|
-
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
6
|
-
import { isClosedWandTaskStatus, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
7
|
-
import { firstLayoutTabId } from "./layout-tree.js";
|
|
8
3
|
/** 未命名任务的占位名;服务端新建时写入,自动命名成功后被真实标题覆盖。 */
|
|
9
4
|
export const UNNAMED_WORKSPACE_TASK_NAME = "未命名任务";
|
|
10
5
|
/** 历史客户端(Android / 桌面)留空时回传的默认名,同样视为未命名。 */
|
|
@@ -91,204 +86,3 @@ export function taskAutoNameSourceText(storage, card) {
|
|
|
91
86
|
export function taskAutoNameSignature(source) {
|
|
92
87
|
return createHash("sha1").update(source).digest("hex");
|
|
93
88
|
}
|
|
94
|
-
function agentFromSession(session) {
|
|
95
|
-
if (!isSessionProvider(session.provider))
|
|
96
|
-
return null;
|
|
97
|
-
const effort = session.thinkingEffort;
|
|
98
|
-
return {
|
|
99
|
-
provider: session.provider,
|
|
100
|
-
model: session.selectedModel || session.structuredState?.model || "default",
|
|
101
|
-
thinkingEffort: isThinkingEffort(effort) ? effort : "off",
|
|
102
|
-
mode: normalizeWandTaskAgentMode(session.provider, session.mode),
|
|
103
|
-
// 会话形态按会话自身的 sessionKind 还原,PTY 会话不会在「再指派」时被误当结构化。
|
|
104
|
-
kind: (session.sessionKind ?? "pty") === "structured" ? "structured" : "pty",
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/** Identity is the task id, never its title. Same-named tasks stay independent. */
|
|
108
|
-
export function ensureBoardTaskForWorkspaceTask(storage, task, workspace, options = {}) {
|
|
109
|
-
const existing = storage.getWandTaskByWorkspaceTaskId(task.id);
|
|
110
|
-
if (existing)
|
|
111
|
-
return existing;
|
|
112
|
-
return storage.createWandTask({
|
|
113
|
-
workspaceId: workspace.kind === "global" ? null : workspace.id,
|
|
114
|
-
workspaceTaskId: task.id,
|
|
115
|
-
parentTaskId: options.parentTaskId ?? null,
|
|
116
|
-
title: task.name,
|
|
117
|
-
// 占位名(未命名任务 / 新任务 / 空)走自动命名;用户填过名就是 user,永不被模型覆盖。
|
|
118
|
-
titleSource: options.titleSource ?? (isUnnamedWorkspaceTaskName(task.name) ? "auto" : "user"),
|
|
119
|
-
description: options.description ?? "",
|
|
120
|
-
status: task.status === "done" ? "done" : "todo",
|
|
121
|
-
milestoneId: task.milestoneId ?? null,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
/** A board task always has a sidebar container, even before its first session. */
|
|
125
|
-
export function ensureWorkspaceTaskForBoardTask(storage, card) {
|
|
126
|
-
const existing = card.workspaceTaskId ? storage.getWorkspaceTask(card.workspaceTaskId) : null;
|
|
127
|
-
if (existing)
|
|
128
|
-
return existing;
|
|
129
|
-
const workspace = card.workspaceId ? storage.getWorkspace(card.workspaceId) : storage.ensureGlobalWorkspace();
|
|
130
|
-
if (!workspace)
|
|
131
|
-
throw new Error("任务所属工作区不存在。");
|
|
132
|
-
const task = storage.createWorkspaceTask({
|
|
133
|
-
workspaceId: workspace.id,
|
|
134
|
-
name: card.title,
|
|
135
|
-
status: isClosedWandTaskStatus(card.status) ? "done" : "active",
|
|
136
|
-
milestoneId: card.milestoneId,
|
|
137
|
-
});
|
|
138
|
-
storage.updateWandTask(card.id, { workspaceTaskId: task.id });
|
|
139
|
-
return task;
|
|
140
|
-
}
|
|
141
|
-
function removeSessionTab(node, sessionId) {
|
|
142
|
-
if (node.type === "split")
|
|
143
|
-
return {
|
|
144
|
-
...node,
|
|
145
|
-
children: [removeSessionTab(node.children[0], sessionId), removeSessionTab(node.children[1], sessionId)],
|
|
146
|
-
};
|
|
147
|
-
const activeId = node.tabs[node.active]?.id;
|
|
148
|
-
const tabs = node.tabs.filter((tab) => tab.kind !== "session" || tab.sessionId !== sessionId);
|
|
149
|
-
const active = tabs.findIndex((tab) => tab.id === activeId);
|
|
150
|
-
return { ...node, tabs, active: active >= 0 ? active : Math.min(node.active, Math.max(0, tabs.length - 1)) };
|
|
151
|
-
}
|
|
152
|
-
/** Remove stale source tabs so opening an old task cannot re-open a moved session. */
|
|
153
|
-
function detachSessionLayout(storage, task, sessionId) {
|
|
154
|
-
if (!task.layout) {
|
|
155
|
-
storage.saveWorkspaceTaskLayout(task.id, null);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const windows = task.layout.windows.map((window) => {
|
|
159
|
-
const layout = removeSessionTab(window.layout, sessionId);
|
|
160
|
-
return { ...window, layout, activeTabId: firstLayoutTabId(layout) };
|
|
161
|
-
});
|
|
162
|
-
storage.saveWorkspaceTaskLayout(task.id, { ...task.layout, windows });
|
|
163
|
-
}
|
|
164
|
-
/** Atomic exclusive ownership change. Never changes cwd, messages, or execution state. */
|
|
165
|
-
export function moveSessionToWorkspaceTask(storage, sessionId, taskId) {
|
|
166
|
-
storage.transaction(() => {
|
|
167
|
-
const session = storage.getSession(sessionId);
|
|
168
|
-
if (!session)
|
|
169
|
-
throw new Error("未找到该会话。");
|
|
170
|
-
const target = taskId ? storage.getWorkspaceTask(taskId) : null;
|
|
171
|
-
if (taskId && !target)
|
|
172
|
-
throw new Error("未找到目标任务。");
|
|
173
|
-
const source = session.workspaceTaskId ? storage.getWorkspaceTask(session.workspaceTaskId) : null;
|
|
174
|
-
if (source && source.id !== target?.id)
|
|
175
|
-
detachSessionLayout(storage, source, sessionId);
|
|
176
|
-
// Explicit task metadata is owned by storage, not by a runner checkpoint.
|
|
177
|
-
storage.setSessionWorkspaceTaskId(sessionId, target?.id ?? null);
|
|
178
|
-
for (const card of storage.listWandTasks()) {
|
|
179
|
-
if (card.workspaceTaskId !== target?.id)
|
|
180
|
-
storage.unbindWandTaskSession(card.id, sessionId);
|
|
181
|
-
}
|
|
182
|
-
if (!target)
|
|
183
|
-
return;
|
|
184
|
-
const workspace = storage.getWorkspace(target.workspaceId);
|
|
185
|
-
if (!workspace)
|
|
186
|
-
throw new Error("目标工作区不存在。");
|
|
187
|
-
storage.setSessionWorkspaceId(sessionId, workspace.id);
|
|
188
|
-
const card = ensureBoardTaskForWorkspaceTask(storage, target, workspace);
|
|
189
|
-
const newlyBound = !storage.listWandTaskSessionIds(card.id).includes(sessionId);
|
|
190
|
-
storage.bindWandTaskSession(card.id, sessionId);
|
|
191
|
-
if (newlyBound && card.status === "todo")
|
|
192
|
-
storage.updateWandTask(card.id, { status: "doing" });
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
/** Reconcile legacy records by explicit ids. Unassigned sessions remain unassigned. */
|
|
196
|
-
export function syncSidebarTasksFromBoard(storage) {
|
|
197
|
-
storage.transaction(() => {
|
|
198
|
-
for (const card of storage.listWandTasks()) {
|
|
199
|
-
// Deleted sidebar tasks leave an archived card, not a resurrected container.
|
|
200
|
-
if (!card.workspaceTaskId && card.status === "archived")
|
|
201
|
-
continue;
|
|
202
|
-
const task = ensureWorkspaceTaskForBoardTask(storage, card);
|
|
203
|
-
for (const sessionId of storage.listWandTaskSessionIds(card.id)) {
|
|
204
|
-
// 只需要归属两列:不要用 getSession(),那会解析十几 MB 的 messages。
|
|
205
|
-
const session = storage.getSessionWorkspace(sessionId);
|
|
206
|
-
if (!session)
|
|
207
|
-
continue;
|
|
208
|
-
if (session.workspaceTaskId && session.workspaceTaskId !== task.id) {
|
|
209
|
-
storage.unbindWandTaskSession(card.id, sessionId);
|
|
210
|
-
}
|
|
211
|
-
else if (session.workspaceTaskId !== task.id || session.workspaceId !== task.workspaceId) {
|
|
212
|
-
storage.setSessionWorkspaceTaskId(sessionId, task.id);
|
|
213
|
-
storage.setSessionWorkspaceId(sessionId, task.workspaceId);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
/** 单个任务的会话归属落到看板卡片:补卡片、按会话补 Agent、绑定会话、todo → doing。 */
|
|
220
|
-
function syncTaskSessionsToBoard(storage, task, workspace) {
|
|
221
|
-
storage.transaction(() => {
|
|
222
|
-
let card = ensureBoardTaskForWorkspaceTask(storage, task, workspace);
|
|
223
|
-
const sessions = storage.listSessionsByWorkspaceTaskSlim(task.id);
|
|
224
|
-
const agent = sessions.map(agentFromSession).find((value) => value !== null);
|
|
225
|
-
const bound = new Set(storage.listWandTaskSessionIds(card.id));
|
|
226
|
-
const started = card.status === "todo" && sessions.some((session) => !bound.has(session.id));
|
|
227
|
-
if ((!card.agent && agent) || started) {
|
|
228
|
-
card = storage.updateWandTask(card.id, {
|
|
229
|
-
...(!card.agent && agent ? { agent } : {}),
|
|
230
|
-
...(started ? { status: "doing" } : {}),
|
|
231
|
-
}) ?? card;
|
|
232
|
-
}
|
|
233
|
-
for (const session of sessions)
|
|
234
|
-
storage.bindWandTaskSession(card.id, session.id);
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* 会话建立 / 移动后立刻把任务归属同步到看板卡片,卡片不必等下一次 `GET /api/wand-tasks`
|
|
239
|
-
* 的全量兜底同步,也不必等某个客户端碰巧拉了看板列表。
|
|
240
|
-
* 看板侧的写入失败只记日志:会话已经跑起来了,不能因为看板同步失败把创建请求打断。
|
|
241
|
-
*/
|
|
242
|
-
export function syncWorkspaceTaskToBoard(storage, workspaceTaskId) {
|
|
243
|
-
if (!workspaceTaskId)
|
|
244
|
-
return;
|
|
245
|
-
try {
|
|
246
|
-
const task = storage.getWorkspaceTask(workspaceTaskId);
|
|
247
|
-
const workspace = task ? storage.getWorkspace(task.workspaceId) : null;
|
|
248
|
-
if (!task || !workspace)
|
|
249
|
-
return;
|
|
250
|
-
syncTaskSessionsToBoard(storage, task, workspace);
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
console.error(`[WandTask] Failed to sync task ${workspaceTaskId} onto the board:`, getErrorMessage(error));
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* 兜底全量同步:只处理「会话带着 workspace_task_id 但卡片上还没有绑定」的历史 / 外部写入。
|
|
258
|
-
* 正常路径由 `syncWorkspaceTaskToBoard` 在会话建立时点完成。
|
|
259
|
-
*/
|
|
260
|
-
export function syncUngroupedSessionsToBoard(storage) {
|
|
261
|
-
syncSidebarTasksFromBoard(storage);
|
|
262
|
-
for (const workspace of storage.listWorkspaces()) {
|
|
263
|
-
for (const task of storage.listWorkspaceTasks(workspace.id))
|
|
264
|
-
syncTaskSessionsToBoard(storage, task, workspace);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/** Status and title projection is centralized in the storage writer. */
|
|
268
|
-
export function syncClosedBoardTask(storage, task) {
|
|
269
|
-
if (isClosedWandTaskStatus(task.status) && task.workspaceTaskId) {
|
|
270
|
-
storage.updateWorkspaceTask(task.workspaceTaskId, { status: "done" });
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
export function archiveBoardTask(storage, id) {
|
|
274
|
-
return storage.updateWandTask(id, { status: "archived" });
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* 归档侧栏任务是软删除:终端继续运行、worktree 与布局都保留,只有看板卡片进入
|
|
278
|
-
* 归档文件夹、侧栏不再显示。恢复方式是看板里把卡片拖回任一列(或右键恢复)。
|
|
279
|
-
*/
|
|
280
|
-
export function archiveWorkspaceTask(storage, task) {
|
|
281
|
-
return storage.transaction(() => {
|
|
282
|
-
const card = storage.getWandTaskByWorkspaceTaskId(task.id);
|
|
283
|
-
// 先归档卡片,再把侧栏任务标成已完成:反向投影见到 archived 就不会降级成 done。
|
|
284
|
-
if (card)
|
|
285
|
-
storage.updateWandTask(card.id, { status: "archived" });
|
|
286
|
-
storage.updateWorkspaceTask(task.id, { status: "done" });
|
|
287
|
-
return card ? storage.getWandTask(card.id) : null;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
export function archiveBoardTaskForWorkspaceTask(storage, workspaceTaskId) {
|
|
291
|
-
const linked = storage.getWandTaskByWorkspaceTaskId(workspaceTaskId);
|
|
292
|
-
if (linked && !isClosedWandTaskStatus(linked.status))
|
|
293
|
-
storage.updateWandTask(linked.id, { status: "done" });
|
|
294
|
-
}
|