@co0ontty/wand 4.70.0-beta.gff952fe → 4.71.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/dist/build-info.json +4 -4
- package/dist/milestone-scope.d.ts +6 -0
- package/dist/milestone-scope.js +20 -0
- package/dist/missions.js +3 -0
- package/dist/provider-cli-updater.d.ts +15 -1
- package/dist/provider-cli-updater.js +141 -8
- package/dist/server-session-routes.js +3 -0
- package/dist/server-task-routes.d.ts +5 -2
- package/dist/server-task-routes.js +94 -36
- package/dist/server-update-routes.js +1 -1
- package/dist/server-workspace-routes.js +3 -2
- package/dist/server.js +4 -1
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +41 -9
- package/dist/structured-client-protocol.js +72 -1
- package/dist/structured-session-manager.js +1 -1
- package/dist/task-types.d.ts +20 -2
- package/dist/task-types.js +9 -0
- package/dist/wand-task-sync.d.ts +10 -0
- package/dist/wand-task-sync.js +47 -18
- package/dist/web-ui/content/scripts.js +67 -65
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/web-ui/sidebar-layout.d.ts +30 -0
- package/dist/web-ui/sidebar-layout.js +28 -0
- package/package.json +2 -1
package/dist/server.js
CHANGED
|
@@ -45,6 +45,7 @@ import { RuntimeConfigState } from "./runtime-config.js";
|
|
|
45
45
|
import { safeServiceInstalled } from "./tui/runtime-utils.js";
|
|
46
46
|
import { checkManagedServiceUpdatePreflight, } from "./update-helper.js";
|
|
47
47
|
import { toSessionDetailDTO } from "./session-transport.js";
|
|
48
|
+
import { syncWorkspaceTaskToBoard } from "./wand-task-sync.js";
|
|
48
49
|
import { registerUploadRoutes } from "./upload-routes.js";
|
|
49
50
|
import { optimizePrompt, PromptOptimizeError } from "./prompt-optimizer.js";
|
|
50
51
|
import { resolveDatabasePath, WandStorage } from "./storage.js";
|
|
@@ -625,7 +626,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
625
626
|
});
|
|
626
627
|
registerGithubRoutes(app, { storage, requireAdmin, sessions: sessionRegistry });
|
|
627
628
|
// 任务管理与 Missions 一样只需登录:原生 connected-app 也要能列/建/派发。
|
|
628
|
-
registerTaskRoutes(app, { storage, sessions: sessionRegistry, structured: structuredSessions, config });
|
|
629
|
+
registerTaskRoutes(app, { storage, sessions: sessionRegistry, structured: structuredSessions, processes, config });
|
|
629
630
|
registerAdminUpdateRoutes(app, {
|
|
630
631
|
storage,
|
|
631
632
|
config,
|
|
@@ -748,6 +749,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
748
749
|
...origin,
|
|
749
750
|
}));
|
|
750
751
|
recordRecentPath(storage, snapshot.cwd);
|
|
752
|
+
// 会话一落地就挂到任务的看板卡片上,不等下一次看板列表的兜底同步。
|
|
753
|
+
syncWorkspaceTaskToBoard(storage, snapshot.workspaceTaskId);
|
|
751
754
|
res.status(201).json(toSessionDetailDTO(snapshot));
|
|
752
755
|
}
|
|
753
756
|
catch (error) {
|
package/dist/storage.d.ts
CHANGED
|
@@ -137,15 +137,21 @@ export declare class WandStorage {
|
|
|
137
137
|
}): import("./task-types.js").WandTask;
|
|
138
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
139
|
deleteWandTask(id: string): void;
|
|
140
|
-
|
|
140
|
+
/**
|
|
141
|
+
* 里程碑列表。传工作区 id 时返回「该工作区 + 全局(workspace_id IS NULL)」;
|
|
142
|
+
* 未指定工作区(null / undefined / 空串)时返回全部。
|
|
143
|
+
*/
|
|
144
|
+
listWandMilestones(workspaceId?: string | null): import("./task-types.js").WandTaskMilestone[];
|
|
141
145
|
getWandMilestone(id: string): import("./task-types.js").WandTaskMilestone | null;
|
|
142
146
|
createWandMilestone(input: {
|
|
143
147
|
name: string;
|
|
144
148
|
dueDate?: string | null;
|
|
149
|
+
workspaceId?: string | null;
|
|
145
150
|
}): import("./task-types.js").WandTaskMilestone;
|
|
146
151
|
updateWandMilestone(id: string, patch: {
|
|
147
152
|
name?: string;
|
|
148
153
|
dueDate?: string | null;
|
|
154
|
+
workspaceId?: string | null;
|
|
149
155
|
}): import("./task-types.js").WandTaskMilestone | null;
|
|
150
156
|
/** 删除里程碑只解绑任务(milestone_id 置空),绝不删除任务。 */
|
|
151
157
|
deleteWandMilestone(id: string): boolean;
|
package/dist/storage.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
5
5
|
import { GLOBAL_WORKSPACE_ID } from "./types.js";
|
|
6
6
|
import { normalizeSessionDirectory } from "./session-directory-tree.js";
|
|
7
7
|
import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider } from "./session-provider.js";
|
|
8
|
-
import { normalizeWandTaskAgentMode } from "./task-types.js";
|
|
8
|
+
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
9
9
|
import { firstLayoutTabId } from "./layout-tree.js";
|
|
10
10
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
11
11
|
import { DEFAULT_PASSWORD_VAULT_ID, DEFAULT_PASSWORD_VAULT_NAME, decryptVaultSecret, encryptVaultSecret, itemMatchesFilter, normalizePasswordItemInput, normalizeVaultName, nowIso, } from "./password-manager.js";
|
|
@@ -38,7 +38,9 @@ export function parseWandTaskAgent(raw) {
|
|
|
38
38
|
return null;
|
|
39
39
|
// mode 是后加列:历史行没有该字段时按标准模式读取,不因此整条配置退化成 null。
|
|
40
40
|
const mode = normalizeWandTaskAgentMode(provider, value.mode);
|
|
41
|
-
|
|
41
|
+
// kind 同样是后加字段:老数据 / 老客户端没带时按结构化会话读取。
|
|
42
|
+
const kind = isWandTaskAgentKind(value.kind) ? value.kind : DEFAULT_WAND_TASK_AGENT_KIND;
|
|
43
|
+
return { provider, model: model.trim(), thinkingEffort, mode, kind };
|
|
42
44
|
}
|
|
43
45
|
/** `wand_milestones` 行 → 领域对象;名字必须非空,脏行直接跳过。 */
|
|
44
46
|
function mapWandMilestoneRow(row) {
|
|
@@ -49,6 +51,7 @@ function mapWandMilestoneRow(row) {
|
|
|
49
51
|
id: String(row.id),
|
|
50
52
|
name,
|
|
51
53
|
dueDate: typeof row.due_date === "string" && row.due_date ? row.due_date : null,
|
|
54
|
+
workspaceId: typeof row.workspace_id === "string" && row.workspace_id ? row.workspace_id : null,
|
|
52
55
|
createdAt: String(row.created_at),
|
|
53
56
|
updatedAt: String(row.updated_at),
|
|
54
57
|
};
|
|
@@ -688,6 +691,7 @@ const INIT_SQL = `
|
|
|
688
691
|
id TEXT PRIMARY KEY,
|
|
689
692
|
name TEXT NOT NULL,
|
|
690
693
|
due_date TEXT,
|
|
694
|
+
workspace_id TEXT,
|
|
691
695
|
created_at TEXT NOT NULL,
|
|
692
696
|
updated_at TEXT NOT NULL
|
|
693
697
|
);
|
|
@@ -774,6 +778,19 @@ function ensureWorkspaceSchema(db) {
|
|
|
774
778
|
db.exec("ALTER TABLE workspace_tasks ADD COLUMN milestone_id TEXT");
|
|
775
779
|
}
|
|
776
780
|
}
|
|
781
|
+
function ensureWandMilestoneSchema(db) {
|
|
782
|
+
const columns = db.prepare("PRAGMA table_info(wand_milestones)").all();
|
|
783
|
+
const names = new Set(columns.map((column) => column.name));
|
|
784
|
+
// 里程碑归属工作区:只加列,历史行保持 NULL(读作全局里程碑,所有工作区可见)。
|
|
785
|
+
if (columns.length > 0 && !names.has("workspace_id")) {
|
|
786
|
+
db.exec("ALTER TABLE wand_milestones ADD COLUMN workspace_id TEXT");
|
|
787
|
+
}
|
|
788
|
+
if (columns.length > 0) {
|
|
789
|
+
// 索引必须等列存在后再建:老库的 CREATE TABLE IF NOT EXISTS 是空操作,
|
|
790
|
+
// 直接在 INIT_SQL 里建索引会因缺列报错。
|
|
791
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_milestones_workspace ON wand_milestones(workspace_id, created_at DESC)");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
777
794
|
function ensureConnectorSchema(db) {
|
|
778
795
|
// The table is created by INIT_SQL for new databases; this repairs an
|
|
779
796
|
// existing wand.db that predates the connector feature (additive-only).
|
|
@@ -802,6 +819,7 @@ export function ensureDatabaseFile(dbPath) {
|
|
|
802
819
|
ensureCommandSessionSchema(db);
|
|
803
820
|
ensureWorkspaceSchema(db);
|
|
804
821
|
ensureWandTaskSchema(db);
|
|
822
|
+
ensureWandMilestoneSchema(db);
|
|
805
823
|
ensureConnectorSchema(db);
|
|
806
824
|
{
|
|
807
825
|
const missionColumns = db.prepare("PRAGMA table_info(missions)").all();
|
|
@@ -832,6 +850,7 @@ export class WandStorage {
|
|
|
832
850
|
ensureCommandSessionSchema(this.db);
|
|
833
851
|
ensureWorkspaceSchema(this.db);
|
|
834
852
|
ensureWandTaskSchema(this.db);
|
|
853
|
+
ensureWandMilestoneSchema(this.db);
|
|
835
854
|
ensureConnectorSchema(this.db);
|
|
836
855
|
this.ensureDefaultPasswordVault();
|
|
837
856
|
}
|
|
@@ -1236,7 +1255,7 @@ export class WandStorage {
|
|
|
1236
1255
|
const id = crypto.randomUUID();
|
|
1237
1256
|
const now = nowIso();
|
|
1238
1257
|
const status = input.status ?? "todo";
|
|
1239
|
-
const priority = input.priority ??
|
|
1258
|
+
const priority = input.priority ?? DEFAULT_WAND_TASK_PRIORITY;
|
|
1240
1259
|
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);
|
|
1241
1260
|
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();
|
|
1242
1261
|
const identifier = `TASK-${(numberRow?.value ?? 0) + 1}`;
|
|
@@ -1259,19 +1278,29 @@ export class WandStorage {
|
|
|
1259
1278
|
return next;
|
|
1260
1279
|
}
|
|
1261
1280
|
deleteWandTask(id) { this.db.prepare("DELETE FROM wand_tasks WHERE id = ?").run(id); }
|
|
1262
|
-
// ──
|
|
1263
|
-
|
|
1264
|
-
|
|
1281
|
+
// ── 里程碑(迭代,归属工作区;任务只存 milestoneId)──
|
|
1282
|
+
/**
|
|
1283
|
+
* 里程碑列表。传工作区 id 时返回「该工作区 + 全局(workspace_id IS NULL)」;
|
|
1284
|
+
* 未指定工作区(null / undefined / 空串)时返回全部。
|
|
1285
|
+
*/
|
|
1286
|
+
listWandMilestones(workspaceId) {
|
|
1287
|
+
const scoped = typeof workspaceId === "string" ? workspaceId.trim() : "";
|
|
1288
|
+
const select = "SELECT id, name, due_date, workspace_id, created_at, updated_at FROM wand_milestones";
|
|
1289
|
+
const order = " ORDER BY created_at DESC, rowid DESC";
|
|
1290
|
+
const rows = (scoped
|
|
1291
|
+
? this.db.prepare(`${select} WHERE workspace_id = ? OR workspace_id IS NULL${order}`).all(scoped)
|
|
1292
|
+
: this.db.prepare(`${select}${order}`).all());
|
|
1265
1293
|
return rows.map(mapWandMilestoneRow).filter((item) => item !== null);
|
|
1266
1294
|
}
|
|
1267
1295
|
getWandMilestone(id) {
|
|
1268
|
-
const row = this.db.prepare(`SELECT id, name, due_date, created_at, updated_at FROM wand_milestones WHERE id = ?`).get(id);
|
|
1296
|
+
const row = this.db.prepare(`SELECT id, name, due_date, workspace_id, created_at, updated_at FROM wand_milestones WHERE id = ?`).get(id);
|
|
1269
1297
|
return row ? mapWandMilestoneRow(row) : null;
|
|
1270
1298
|
}
|
|
1271
1299
|
createWandMilestone(input) {
|
|
1272
1300
|
const id = crypto.randomUUID();
|
|
1273
1301
|
const now = nowIso();
|
|
1274
|
-
|
|
1302
|
+
const workspaceId = typeof input.workspaceId === "string" && input.workspaceId.trim() ? input.workspaceId.trim() : null;
|
|
1303
|
+
this.db.prepare(`INSERT INTO wand_milestones (id, name, due_date, workspace_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.name, input.dueDate ?? null, workspaceId, now, now);
|
|
1275
1304
|
return this.getWandMilestone(id);
|
|
1276
1305
|
}
|
|
1277
1306
|
updateWandMilestone(id, patch) {
|
|
@@ -1281,9 +1310,12 @@ export class WandStorage {
|
|
|
1281
1310
|
const next = {
|
|
1282
1311
|
name: patch.name ?? current.name,
|
|
1283
1312
|
dueDate: patch.dueDate === undefined ? current.dueDate : patch.dueDate,
|
|
1313
|
+
workspaceId: patch.workspaceId === undefined
|
|
1314
|
+
? current.workspaceId
|
|
1315
|
+
: (typeof patch.workspaceId === "string" && patch.workspaceId.trim() ? patch.workspaceId.trim() : null),
|
|
1284
1316
|
updatedAt: nowIso(),
|
|
1285
1317
|
};
|
|
1286
|
-
this.db.prepare(`UPDATE wand_milestones SET name = ?, due_date = ?, updated_at = ? WHERE id = ?`).run(next.name, next.dueDate, next.updatedAt, id);
|
|
1318
|
+
this.db.prepare(`UPDATE wand_milestones SET name = ?, due_date = ?, workspace_id = ?, updated_at = ? WHERE id = ?`).run(next.name, next.dueDate, next.workspaceId, next.updatedAt, id);
|
|
1287
1319
|
return this.getWandMilestone(id);
|
|
1288
1320
|
}
|
|
1289
1321
|
/** 删除里程碑只解绑任务(milestone_id 置空),绝不删除任务。 */
|
|
@@ -43,6 +43,77 @@ function questionsFromInput(input) {
|
|
|
43
43
|
}
|
|
44
44
|
return questions;
|
|
45
45
|
}
|
|
46
|
+
/** 派发子 Agent 的工具名:Claude Code 的 Task/Agent,以及 Wand pi 扩展的 subagent。 */
|
|
47
|
+
const SUBAGENT_TOOL_NAMES = new Set(["Task", "Agent"]);
|
|
48
|
+
const PI_SUBAGENT_TOOL_NAME = "Pi/subagent";
|
|
49
|
+
/**
|
|
50
|
+
* 历史 turn / 非 Claude provider 的子 Agent 工具调用没有 `__subagent` 盖章,
|
|
51
|
+
* 按调用参数现场补一份,让「子 Agent」面板在所有端都拿得到同一份分组。
|
|
52
|
+
*
|
|
53
|
+
* 只认一次真正的派发:Claude 的 Task/Agent(`subagent_type` 可省),
|
|
54
|
+
* pi 扩展的 `Pi/subagent` 必须带 `agent`,避免把管理类调用误当子任务。
|
|
55
|
+
*/
|
|
56
|
+
function deriveSubagentMeta(block) {
|
|
57
|
+
const existing = block.__subagent;
|
|
58
|
+
if (existing?.taskId)
|
|
59
|
+
return existing;
|
|
60
|
+
const input = asRecord(block.input) ?? {};
|
|
61
|
+
if (block.name === PI_SUBAGENT_TOOL_NAME) {
|
|
62
|
+
const agent = text(input.agent);
|
|
63
|
+
const task = text(input.task);
|
|
64
|
+
if (!agent && !task)
|
|
65
|
+
return null;
|
|
66
|
+
return {
|
|
67
|
+
taskId: block.id,
|
|
68
|
+
...(agent ? { agentType: agent } : {}),
|
|
69
|
+
...(task ? { taskDescription: task } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const agentType = text(input.subagent_type);
|
|
73
|
+
// Claude 的 Task/Agent 允许省 `subagent_type`;其他工具只有在真给了 `subagent_type` 时才算派发。
|
|
74
|
+
if (!SUBAGENT_TOOL_NAMES.has(block.name) && !agentType)
|
|
75
|
+
return null;
|
|
76
|
+
const description = text(input.description);
|
|
77
|
+
return {
|
|
78
|
+
taskId: block.id,
|
|
79
|
+
...(agentType ? { agentType } : {}),
|
|
80
|
+
...(description ? { taskDescription: description } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 给没有盖章的子 Agent 调用打上 `__subagent`:派发用的 tool_use 自己带一份,
|
|
85
|
+
* 对应的 tool_result(`tool_use_id` 命中 taskId)带同一份,客户端才好判定完成态。
|
|
86
|
+
*/
|
|
87
|
+
function stampDerivedSubagents(messages) {
|
|
88
|
+
const byTaskId = new Map();
|
|
89
|
+
for (const turn of messages) {
|
|
90
|
+
for (const block of turn.content) {
|
|
91
|
+
if (block.type !== "tool_use")
|
|
92
|
+
continue;
|
|
93
|
+
const meta = deriveSubagentMeta(block);
|
|
94
|
+
if (meta)
|
|
95
|
+
byTaskId.set(meta.taskId, meta);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (byTaskId.size === 0)
|
|
99
|
+
return messages;
|
|
100
|
+
return messages.map((turn) => ({
|
|
101
|
+
...turn,
|
|
102
|
+
content: turn.content.map((block) => {
|
|
103
|
+
if (block.__subagent)
|
|
104
|
+
return block;
|
|
105
|
+
if (block.type === "tool_use") {
|
|
106
|
+
const meta = byTaskId.get(block.id);
|
|
107
|
+
return meta ? { ...block, __subagent: meta } : block;
|
|
108
|
+
}
|
|
109
|
+
if (block.type === "tool_result") {
|
|
110
|
+
const meta = byTaskId.get(block.tool_use_id);
|
|
111
|
+
return meta ? { ...block, __subagent: meta } : block;
|
|
112
|
+
}
|
|
113
|
+
return block;
|
|
114
|
+
}),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
46
117
|
function toolResultText(block) {
|
|
47
118
|
if (block.type !== "tool_result")
|
|
48
119
|
return "";
|
|
@@ -124,7 +195,7 @@ function tasksFromSegment(messages, start, end) {
|
|
|
124
195
|
* This is the external interface consumed by every client.
|
|
125
196
|
*/
|
|
126
197
|
export function enrichStructuredMessages(messages) {
|
|
127
|
-
const enriched = messages.map((turn) => ({
|
|
198
|
+
const enriched = stampDerivedSubagents(messages).map((turn) => ({
|
|
128
199
|
...turn,
|
|
129
200
|
content: turn.content.map((block) => {
|
|
130
201
|
if (block.type !== "tool_use" || block.name !== "AskUserQuestion")
|
|
@@ -1278,7 +1278,7 @@ export class StructuredSessionManager {
|
|
|
1278
1278
|
provider: "pi",
|
|
1279
1279
|
commandLabel: "pi --mode json --print",
|
|
1280
1280
|
logKind: "pi-json",
|
|
1281
|
-
installHint: "请安装 @
|
|
1281
|
+
installHint: "请安装 @earendil-works/pi-coding-agent(或兼容的 Pi CLI),或重跑 `wand service:install` 刷新服务的 PATH",
|
|
1282
1282
|
});
|
|
1283
1283
|
}
|
|
1284
1284
|
else if (runner === "claude-sdk") {
|
package/dist/task-types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export type WandTaskStatus = "todo" | "doing" | "done" | "archived";
|
|
|
2
2
|
/** 已确认或已归档:不再算未完成,也不会被未分组会话同步重新打开。 */
|
|
3
3
|
export declare function isClosedWandTaskStatus(status: WandTaskStatus): boolean;
|
|
4
4
|
export type WandTaskPriority = "none" | "low" | "medium" | "high" | "urgent";
|
|
5
|
+
/** 新建任务的默认优先级:用户没挑就是「低」,不再落成「无优先级」。 */
|
|
6
|
+
export declare const DEFAULT_WAND_TASK_PRIORITY: WandTaskPriority;
|
|
5
7
|
/** 任务派发时选择的 CLI 工具;空串表示尚未指定。 */
|
|
6
8
|
export type WandTaskAgentProvider = "claude" | "codex" | "opencode" | "grok" | "qoder" | "pi";
|
|
7
9
|
export type WandTaskAgentModel = string;
|
|
@@ -26,7 +28,18 @@ export declare function isWandTaskAgentMode(value: unknown): value is WandTaskAg
|
|
|
26
28
|
export declare function supportedWandTaskAgentModes(provider: WandTaskAgentProvider): readonly WandTaskAgentMode[];
|
|
27
29
|
/** 把任意(含旧数据 / 其它客户端缺省的)模式夹到该 provider 真正支持的值。 */
|
|
28
30
|
export declare function normalizeWandTaskAgentMode(provider: WandTaskAgentProvider, mode: unknown): WandTaskAgentMode;
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* 派发时创建的会话形态:
|
|
33
|
+
* - structured:结构化对话,走 provider 的结构化 runner;
|
|
34
|
+
* - pty:原始 CLI 终端,与「新建工作窗口」里的 PTY 一致。
|
|
35
|
+
*/
|
|
36
|
+
export type WandTaskAgentKind = "structured" | "pty";
|
|
37
|
+
/** 合法会话形态清单;顺序即下拉顺序。 */
|
|
38
|
+
export declare const WAND_TASK_AGENT_KINDS: readonly WandTaskAgentKind[];
|
|
39
|
+
/** 历史行 / 老客户端没带 kind 时的兜底:结构化对话,与旧派发行为一致。 */
|
|
40
|
+
export declare const DEFAULT_WAND_TASK_AGENT_KIND: WandTaskAgentKind;
|
|
41
|
+
export declare function isWandTaskAgentKind(value: unknown): value is WandTaskAgentKind;
|
|
42
|
+
/** 任务上的默认派发配置:先选工具 / 模型 / 思考深度 / 工作模式 / 会话形态,再一键交给 Agent。 */
|
|
30
43
|
export interface WandTaskAgent {
|
|
31
44
|
provider: WandTaskAgentProvider;
|
|
32
45
|
/** 具体模型 ID;"default" 表示跟随服务端为该 provider 选择的默认模型。 */
|
|
@@ -34,13 +47,16 @@ export interface WandTaskAgent {
|
|
|
34
47
|
thinkingEffort: WandTaskAgentEffort;
|
|
35
48
|
/** 执行模式;缺省时按标准模式处理。 */
|
|
36
49
|
mode: WandTaskAgentMode;
|
|
50
|
+
/** 派发出来的会话是结构化对话还是 PTY 终端;缺省时按结构化处理。 */
|
|
51
|
+
kind: WandTaskAgentKind;
|
|
37
52
|
}
|
|
38
53
|
/** 任务标题来源;标题是可选字段,留空时由服务端按描述自动生成。 */
|
|
39
54
|
export type WandTaskTitleSource = "user" | "auto";
|
|
40
55
|
/** 里程碑名上限;所有任务面板的新增入口共用同一约束。 */
|
|
41
56
|
export declare const WAND_MILESTONE_NAME_MAX_LENGTH = 60;
|
|
42
57
|
/**
|
|
43
|
-
*
|
|
58
|
+
* 里程碑(迭代):归属某个工作区,新建任务时按所选工作区过滤展示,也可以不选。
|
|
59
|
+
* workspaceId 为 null 表示全局里程碑(历史数据,或未指定工作区时创建),任何工作区都能选。
|
|
44
60
|
* 任务只保存 milestoneId,删除里程碑时只解绑任务、不删除任务。
|
|
45
61
|
*/
|
|
46
62
|
export interface WandTaskMilestone {
|
|
@@ -48,6 +64,8 @@ export interface WandTaskMilestone {
|
|
|
48
64
|
name: string;
|
|
49
65
|
/** YYYY-MM-DD,可为空。 */
|
|
50
66
|
dueDate: string | null;
|
|
67
|
+
/** 所属工作区;null = 全局里程碑。 */
|
|
68
|
+
workspaceId: string | null;
|
|
51
69
|
createdAt: string;
|
|
52
70
|
updatedAt: string;
|
|
53
71
|
}
|
package/dist/task-types.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export function isClosedWandTaskStatus(status) {
|
|
3
3
|
return status === "done" || status === "archived";
|
|
4
4
|
}
|
|
5
|
+
/** 新建任务的默认优先级:用户没挑就是「低」,不再落成「无优先级」。 */
|
|
6
|
+
export const DEFAULT_WAND_TASK_PRIORITY = "low";
|
|
5
7
|
/** 合法执行模式清单;顺序即下拉顺序。 */
|
|
6
8
|
export const WAND_TASK_AGENT_MODES = ["managed", "full-access", "default"];
|
|
7
9
|
/** 历史行没有 mode 字段时的兜底:标准模式,与旧 `mode: "agent"` 的权限姿态一致。 */
|
|
@@ -28,5 +30,12 @@ export function normalizeWandTaskAgentMode(provider, mode) {
|
|
|
28
30
|
? DEFAULT_WAND_TASK_AGENT_MODE
|
|
29
31
|
: supported[0];
|
|
30
32
|
}
|
|
33
|
+
/** 合法会话形态清单;顺序即下拉顺序。 */
|
|
34
|
+
export const WAND_TASK_AGENT_KINDS = ["structured", "pty"];
|
|
35
|
+
/** 历史行 / 老客户端没带 kind 时的兜底:结构化对话,与旧派发行为一致。 */
|
|
36
|
+
export const DEFAULT_WAND_TASK_AGENT_KIND = "structured";
|
|
37
|
+
export function isWandTaskAgentKind(value) {
|
|
38
|
+
return value === "structured" || value === "pty";
|
|
39
|
+
}
|
|
31
40
|
/** 里程碑名上限;所有任务面板的新增入口共用同一约束。 */
|
|
32
41
|
export const WAND_MILESTONE_NAME_MAX_LENGTH = 60;
|
package/dist/wand-task-sync.d.ts
CHANGED
|
@@ -29,6 +29,16 @@ export declare function ensureWorkspaceTaskForBoardTask(storage: WandStorage, ca
|
|
|
29
29
|
export declare function moveSessionToWorkspaceTask(storage: WandStorage, sessionId: string, taskId: string | null): void;
|
|
30
30
|
/** Reconcile legacy records by explicit ids. Unassigned sessions remain unassigned. */
|
|
31
31
|
export declare function syncSidebarTasksFromBoard(storage: WandStorage): void;
|
|
32
|
+
/**
|
|
33
|
+
* 会话建立 / 移动后立刻把任务归属同步到看板卡片,卡片不必等下一次 `GET /api/wand-tasks`
|
|
34
|
+
* 的全量兜底同步,也不必等某个客户端碰巧拉了看板列表。
|
|
35
|
+
* 看板侧的写入失败只记日志:会话已经跑起来了,不能因为看板同步失败把创建请求打断。
|
|
36
|
+
*/
|
|
37
|
+
export declare function syncWorkspaceTaskToBoard(storage: WandStorage, workspaceTaskId: string | null | undefined): void;
|
|
38
|
+
/**
|
|
39
|
+
* 兜底全量同步:只处理「会话带着 workspace_task_id 但卡片上还没有绑定」的历史 / 外部写入。
|
|
40
|
+
* 正常路径由 `syncWorkspaceTaskToBoard` 在会话建立时点完成。
|
|
41
|
+
*/
|
|
32
42
|
export declare function syncUngroupedSessionsToBoard(storage: WandStorage): void;
|
|
33
43
|
/** Status and title projection is centralized in the storage writer. */
|
|
34
44
|
export declare function syncClosedBoardTask(storage: WandStorage, task: WandTask): void;
|
package/dist/wand-task-sync.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
2
3
|
import { provisionalTaskTitleFromDescription } from "./task-title.js";
|
|
3
4
|
import { isSessionProvider } from "./session-provider.js";
|
|
4
5
|
import { isClosedWandTaskStatus, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
@@ -83,6 +84,8 @@ function agentFromSession(session) {
|
|
|
83
84
|
model: session.selectedModel || session.structuredState?.model || "default",
|
|
84
85
|
thinkingEffort: effort === "standard" || effort === "deep" || effort === "max" ? effort : "off",
|
|
85
86
|
mode: normalizeWandTaskAgentMode(session.provider, session.mode),
|
|
87
|
+
// 会话形态按会话自身的 sessionKind 还原,PTY 会话不会在「再指派」时被误当结构化。
|
|
88
|
+
kind: (session.sessionKind ?? "pty") === "structured" ? "structured" : "pty",
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
/** Identity is the task id, never its title. Same-named tasks stay independent. */
|
|
@@ -195,28 +198,54 @@ export function syncSidebarTasksFromBoard(storage) {
|
|
|
195
198
|
}
|
|
196
199
|
});
|
|
197
200
|
}
|
|
198
|
-
|
|
199
|
-
|
|
201
|
+
/** 单个任务的会话归属落到看板卡片:补卡片、按会话补 Agent、绑定会话、todo → doing。 */
|
|
202
|
+
function syncTaskSessionsToBoard(storage, task, workspace) {
|
|
200
203
|
storage.transaction(() => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
...(started ? { status: "doing" } : {}),
|
|
212
|
-
}) ?? card;
|
|
213
|
-
}
|
|
214
|
-
for (const session of sessions)
|
|
215
|
-
storage.bindWandTaskSession(card.id, session.id);
|
|
216
|
-
}
|
|
204
|
+
let card = ensureBoardTaskForWorkspaceTask(storage, task, workspace);
|
|
205
|
+
const sessions = storage.listSessionsByWorkspaceTask(task.id);
|
|
206
|
+
const agent = sessions.map(agentFromSession).find((value) => value !== null);
|
|
207
|
+
const bound = new Set(storage.listWandTaskSessionIds(card.id));
|
|
208
|
+
const started = card.status === "todo" && sessions.some((session) => !bound.has(session.id));
|
|
209
|
+
if ((!card.agent && agent) || started) {
|
|
210
|
+
card = storage.updateWandTask(card.id, {
|
|
211
|
+
...(!card.agent && agent ? { agent } : {}),
|
|
212
|
+
...(started ? { status: "doing" } : {}),
|
|
213
|
+
}) ?? card;
|
|
217
214
|
}
|
|
215
|
+
for (const session of sessions)
|
|
216
|
+
storage.bindWandTaskSession(card.id, session.id);
|
|
218
217
|
});
|
|
219
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* 会话建立 / 移动后立刻把任务归属同步到看板卡片,卡片不必等下一次 `GET /api/wand-tasks`
|
|
221
|
+
* 的全量兜底同步,也不必等某个客户端碰巧拉了看板列表。
|
|
222
|
+
* 看板侧的写入失败只记日志:会话已经跑起来了,不能因为看板同步失败把创建请求打断。
|
|
223
|
+
*/
|
|
224
|
+
export function syncWorkspaceTaskToBoard(storage, workspaceTaskId) {
|
|
225
|
+
if (!workspaceTaskId)
|
|
226
|
+
return;
|
|
227
|
+
try {
|
|
228
|
+
const task = storage.getWorkspaceTask(workspaceTaskId);
|
|
229
|
+
const workspace = task ? storage.getWorkspace(task.workspaceId) : null;
|
|
230
|
+
if (!task || !workspace)
|
|
231
|
+
return;
|
|
232
|
+
syncTaskSessionsToBoard(storage, task, workspace);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error(`[WandTask] Failed to sync task ${workspaceTaskId} onto the board:`, getErrorMessage(error));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 兜底全量同步:只处理「会话带着 workspace_task_id 但卡片上还没有绑定」的历史 / 外部写入。
|
|
240
|
+
* 正常路径由 `syncWorkspaceTaskToBoard` 在会话建立时点完成。
|
|
241
|
+
*/
|
|
242
|
+
export function syncUngroupedSessionsToBoard(storage) {
|
|
243
|
+
syncSidebarTasksFromBoard(storage);
|
|
244
|
+
for (const workspace of storage.listWorkspaces()) {
|
|
245
|
+
for (const task of storage.listWorkspaceTasks(workspace.id))
|
|
246
|
+
syncTaskSessionsToBoard(storage, task, workspace);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
220
249
|
/** Status and title projection is centralized in the storage writer. */
|
|
221
250
|
export function syncClosedBoardTask(storage, task) {
|
|
222
251
|
if (isClosedWandTaskStatus(task.status) && task.workspaceTaskId) {
|