libero-mcp 0.2.18 → 0.2.20
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.
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- S46 (2026-08-07): task difficulty/priority 持久化
|
|
2
|
+
-- 用于"快速启动 + 峰终"智能排程
|
|
3
|
+
-- 难度(easy/medium/hard,默认 medium)
|
|
4
|
+
ALTER TABLE tasks ADD COLUMN difficulty TEXT DEFAULT 'medium';
|
|
5
|
+
-- 优先级(high/medium/low,默认 medium)
|
|
6
|
+
ALTER TABLE tasks ADD COLUMN priority TEXT DEFAULT 'medium';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- S46 (2026-08-07): task difficulty/priority 持久化
|
|
2
|
+
-- 用于"快速启动 + 峰终"智能排程
|
|
3
|
+
-- 难度(easy/medium/hard,默认 medium)
|
|
4
|
+
ALTER TABLE tasks ADD COLUMN difficulty TEXT DEFAULT 'medium';
|
|
5
|
+
-- 优先级(high/medium/low,默认 medium)
|
|
6
|
+
ALTER TABLE tasks ADD COLUMN priority TEXT DEFAULT 'medium';
|
package/dist/mcp/server-core.js
CHANGED
|
@@ -824,7 +824,7 @@ function buildToolRegistry(ctx) {
|
|
|
824
824
|
// ============================================================
|
|
825
825
|
tools.push({
|
|
826
826
|
name: "task.create",
|
|
827
|
-
description: "建单个独立任务(不挂任何 timeblock)。⚠️ 拆解请用 decompose.split,本工具一次只能建一个任务、不能建子任务。",
|
|
827
|
+
description: "建单个独立任务(不挂任何 timeblock)。⚠️ 拆解请用 decompose.split,本工具一次只能建一个任务、不能建子任务。S46 支持 difficulty(easy/medium/hard,默认 medium)+ priority(high/medium/low,默认 medium),用于'快速启动+峰终'智能排程。",
|
|
828
828
|
// S33 v3: 用 z.object().strict() 显式拒 parent_id(zod 默认 strip 不报错,
|
|
829
829
|
// 严格模式让多余键触发 safeParse 失败 → MCP 返回 isError)。
|
|
830
830
|
// 底层 createTask 仍保留 parent_id 能力(task.test.ts 单测不破)。
|
|
@@ -834,6 +834,8 @@ function buildToolRegistry(ctx) {
|
|
|
834
834
|
target_user_id: z.string().min(1).optional(),
|
|
835
835
|
title: z.string().min(1),
|
|
836
836
|
est_minutes: z.number().optional(),
|
|
837
|
+
difficulty: z.enum(["easy", "medium", "hard"]).optional(),
|
|
838
|
+
priority: z.enum(["high", "medium", "low"]).optional(),
|
|
837
839
|
})
|
|
838
840
|
.strict(),
|
|
839
841
|
needsUserIdInjection: true,
|
|
@@ -868,12 +870,14 @@ function buildToolRegistry(ctx) {
|
|
|
868
870
|
});
|
|
869
871
|
tools.push({
|
|
870
872
|
name: "task.update",
|
|
871
|
-
description: "更新任务(title/est_minutes/status;状态机校验非法流转)",
|
|
873
|
+
description: "更新任务(title/est_minutes/status/difficulty/priority;状态机校验非法流转)",
|
|
872
874
|
inputSchema: {
|
|
873
875
|
id: z.string().min(1),
|
|
874
876
|
title: z.string().optional(),
|
|
875
877
|
est_minutes: z.number().optional(),
|
|
876
878
|
status: z.string().optional(),
|
|
879
|
+
difficulty: z.enum(["easy", "medium", "hard"]).optional(),
|
|
880
|
+
priority: z.enum(["high", "medium", "low"]).optional(),
|
|
877
881
|
},
|
|
878
882
|
handler: async ({ id, ...rest }) => asContent(await task.updateTask(id, rest)),
|
|
879
883
|
});
|
|
@@ -886,6 +890,16 @@ function buildToolRegistry(ctx) {
|
|
|
886
890
|
},
|
|
887
891
|
handler: async ({ task_id, block_id }) => asContent(await task.linkTimeblock(task_id, block_id)),
|
|
888
892
|
});
|
|
893
|
+
tools.push({
|
|
894
|
+
name: "task.updateDifficulty",
|
|
895
|
+
description: "S46: 仅更新 task 的 difficulty(easy/medium/hard)或 priority(high/medium/low)。用户主动调整时用,默认 medium。用于'快速启动+峰终'智能排程。",
|
|
896
|
+
inputSchema: {
|
|
897
|
+
task_id: z.string().min(1),
|
|
898
|
+
difficulty: z.enum(["easy", "medium", "hard"]).optional(),
|
|
899
|
+
priority: z.enum(["high", "medium", "low"]).optional(),
|
|
900
|
+
},
|
|
901
|
+
handler: async ({ task_id, difficulty, priority }) => asContent(await task.updateDifficulty(task_id, { difficulty, priority })),
|
|
902
|
+
});
|
|
889
903
|
tools.push({
|
|
890
904
|
name: "task.findPlan",
|
|
891
905
|
description: "按事件名 exact-match 查拆解计划:返回根任务 + 第一块未完成子任务(record 开工时链 task_id,驱动闭环正反馈)",
|
|
@@ -19,6 +19,13 @@ function nowStr() {
|
|
|
19
19
|
return (new Date().toISOString().replace("T", " ").replace("Z", "") +
|
|
20
20
|
String(Math.floor(performance.now() * 100000) % 100000).padStart(5, "0"));
|
|
21
21
|
}
|
|
22
|
+
// S46: difficulty/priority 归一化(不合法值 → medium)
|
|
23
|
+
function normalizeDiff(v) {
|
|
24
|
+
return v === "easy" || v === "medium" || v === "hard" ? v : "medium";
|
|
25
|
+
}
|
|
26
|
+
function normalizePrio(v) {
|
|
27
|
+
return v === "high" || v === "medium" || v === "low" ? v : "medium";
|
|
28
|
+
}
|
|
22
29
|
function rowToTask(row) {
|
|
23
30
|
return {
|
|
24
31
|
id: row.id,
|
|
@@ -30,6 +37,9 @@ function rowToTask(row) {
|
|
|
30
37
|
category_id: row.category_id ?? null,
|
|
31
38
|
linked_timeblock_id: row.linked_timeblock_id ?? null,
|
|
32
39
|
order_index: row.order_index ?? 0,
|
|
40
|
+
// S46: difficulty/priority(migration 014 加的列;旧库未 migrate 时回退 medium)
|
|
41
|
+
difficulty: row.difficulty ?? "medium",
|
|
42
|
+
priority: row.priority ?? "medium",
|
|
33
43
|
created_at: row.created_at,
|
|
34
44
|
updated_at: row.updated_at,
|
|
35
45
|
deleted_at: row.deleted_at ?? null,
|
|
@@ -81,8 +91,18 @@ export function createTaskTools(db) {
|
|
|
81
91
|
}
|
|
82
92
|
const id = crypto.randomUUID();
|
|
83
93
|
const now = nowStr();
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
// S46: difficulty/priority 默认 medium,校验入参合法值
|
|
95
|
+
const difficulty = normalizeDiff(input.difficulty);
|
|
96
|
+
const priority = normalizePrio(input.priority);
|
|
97
|
+
// S46 fix: 同 parent 下 order_index 单调递增(避免 created_at 精度问题导致排序 flaky)
|
|
98
|
+
const parentFilter = input.parent_id ? "AND parent_id = ?" : "AND parent_id IS NULL";
|
|
99
|
+
const parentParams = input.parent_id ? [input.parent_id] : [];
|
|
100
|
+
const maxOrderRow = db
|
|
101
|
+
.prepare(`SELECT COALESCE(MAX(order_index), -1) as max_idx FROM tasks WHERE user_id = ? ${parentFilter} AND deleted_at IS NULL`)
|
|
102
|
+
.get(userId, ...parentParams);
|
|
103
|
+
const orderIndex = maxOrderRow.max_idx + 1;
|
|
104
|
+
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, difficulty, priority, created_at, updated_at, deleted_at)
|
|
105
|
+
VALUES (?, ?, ?, ?, 'draft', ?, NULL, NULL, ?, ?, ?, ?, ?, NULL)`).run(id, userId, input.parent_id ?? null, input.title, input.est_minutes ?? null, orderIndex, difficulty, priority, now, now);
|
|
86
106
|
const row = getTaskRow(id);
|
|
87
107
|
return {
|
|
88
108
|
...rowToTask(row),
|
|
@@ -142,6 +162,15 @@ export function createTaskTools(db) {
|
|
|
142
162
|
sets.push("status = ?");
|
|
143
163
|
params.push(patch.status);
|
|
144
164
|
}
|
|
165
|
+
// S46: difficulty/priority 可独立更新
|
|
166
|
+
if (patch.difficulty !== undefined) {
|
|
167
|
+
sets.push("difficulty = ?");
|
|
168
|
+
params.push(normalizeDiff(patch.difficulty));
|
|
169
|
+
}
|
|
170
|
+
if (patch.priority !== undefined) {
|
|
171
|
+
sets.push("priority = ?");
|
|
172
|
+
params.push(normalizePrio(patch.priority));
|
|
173
|
+
}
|
|
145
174
|
// Always refresh updated_at
|
|
146
175
|
sets.push("updated_at = ?");
|
|
147
176
|
params.push(nowStr());
|
|
@@ -168,6 +197,32 @@ export function createTaskTools(db) {
|
|
|
168
197
|
const row = getTaskRow(taskId);
|
|
169
198
|
return rowToTask(row);
|
|
170
199
|
},
|
|
200
|
+
/** S46: 仅更新 difficulty/priority(用户主动调整) */
|
|
201
|
+
async updateDifficulty(taskId, patch) {
|
|
202
|
+
const existing = getTaskRow(taskId);
|
|
203
|
+
if (!existing) {
|
|
204
|
+
throw new Error("任务不存在");
|
|
205
|
+
}
|
|
206
|
+
if (patch.difficulty === undefined && patch.priority === undefined) {
|
|
207
|
+
throw new Error("至少传一个字段 difficulty 或 priority");
|
|
208
|
+
}
|
|
209
|
+
const sets = [];
|
|
210
|
+
const params = [];
|
|
211
|
+
if (patch.difficulty !== undefined) {
|
|
212
|
+
sets.push("difficulty = ?");
|
|
213
|
+
params.push(normalizeDiff(patch.difficulty));
|
|
214
|
+
}
|
|
215
|
+
if (patch.priority !== undefined) {
|
|
216
|
+
sets.push("priority = ?");
|
|
217
|
+
params.push(normalizePrio(patch.priority));
|
|
218
|
+
}
|
|
219
|
+
sets.push("updated_at = ?");
|
|
220
|
+
params.push(nowStr());
|
|
221
|
+
params.push(taskId);
|
|
222
|
+
db.prepare(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
223
|
+
const row = getTaskRow(taskId);
|
|
224
|
+
return rowToTask(row);
|
|
225
|
+
},
|
|
171
226
|
async findPlan(input) {
|
|
172
227
|
// exact-match a startable root task (aligned/executing) by title
|
|
173
228
|
const rootRow = db
|
|
@@ -241,13 +296,13 @@ export function createTaskTools(db) {
|
|
|
241
296
|
const result = db.transaction(() => {
|
|
242
297
|
const rootId = crypto.randomUUID();
|
|
243
298
|
const now = nowStr();
|
|
244
|
-
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, created_at, updated_at, deleted_at)
|
|
245
|
-
VALUES (?, ?, NULL, ?, 'aligned', NULL, NULL, NULL, 0, ?, ?, NULL)`).run(rootId, plan.user_id, plan.root_title, now, now);
|
|
299
|
+
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, difficulty, priority, created_at, updated_at, deleted_at)
|
|
300
|
+
VALUES (?, ?, NULL, ?, 'aligned', NULL, NULL, NULL, 0, 'medium', 'medium', ?, ?, NULL)`).run(rootId, plan.user_id, plan.root_title, now, now);
|
|
246
301
|
const children = plan.children.map((c, i) => {
|
|
247
302
|
const childId = crypto.randomUUID();
|
|
248
303
|
const cnow = nowStr();
|
|
249
|
-
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, created_at, updated_at, deleted_at)
|
|
250
|
-
VALUES (?, ?, ?, ?, 'draft', ?, NULL, NULL, ?, ?, ?, NULL)`).run(childId, plan.user_id, rootId, c.title, c.est_minutes, i, cnow, cnow);
|
|
304
|
+
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, difficulty, priority, created_at, updated_at, deleted_at)
|
|
305
|
+
VALUES (?, ?, ?, ?, 'draft', ?, NULL, NULL, ?, 'medium', 'medium', ?, ?, NULL)`).run(childId, plan.user_id, rootId, c.title, c.est_minutes, i, cnow, cnow);
|
|
251
306
|
return rowToTask(getTaskRow(childId));
|
|
252
307
|
});
|
|
253
308
|
return { root: rowToTask(getTaskRow(rootId)), children };
|
|
@@ -334,13 +389,13 @@ export function createTaskTools(db) {
|
|
|
334
389
|
const result = db.transaction(() => {
|
|
335
390
|
const rootId = crypto.randomUUID();
|
|
336
391
|
const now = nowStr();
|
|
337
|
-
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, created_at, updated_at, deleted_at)
|
|
338
|
-
VALUES (?, ?, NULL, ?, 'aligned', NULL, NULL, ?, 0, ?, ?, NULL)`).run(rootId, plan.user_id, plan.root_title, plan.timeblock_id, now, now);
|
|
392
|
+
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, difficulty, priority, created_at, updated_at, deleted_at)
|
|
393
|
+
VALUES (?, ?, NULL, ?, 'aligned', NULL, NULL, ?, 0, 'medium', 'medium', ?, ?, NULL)`).run(rootId, plan.user_id, plan.root_title, plan.timeblock_id, now, now);
|
|
339
394
|
const children = plan.children.map((c, i) => {
|
|
340
395
|
const childId = crypto.randomUUID();
|
|
341
396
|
const cnow = nowStr();
|
|
342
|
-
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, created_at, updated_at, deleted_at)
|
|
343
|
-
VALUES (?, ?, ?, ?, 'draft', ?, NULL, NULL, ?, ?, ?, NULL)`).run(childId, plan.user_id, rootId, c.title, c.est_minutes, i, cnow, cnow);
|
|
397
|
+
db.prepare(`INSERT INTO tasks (id, user_id, parent_id, title, status, est_minutes, category_id, linked_timeblock_id, order_index, difficulty, priority, created_at, updated_at, deleted_at)
|
|
398
|
+
VALUES (?, ?, ?, ?, 'draft', ?, NULL, NULL, ?, 'medium', 'medium', ?, ?, NULL)`).run(childId, plan.user_id, rootId, c.title, c.est_minutes, i, cnow, cnow);
|
|
344
399
|
return {
|
|
345
400
|
id: childId,
|
|
346
401
|
title: c.title,
|