feat: 升级至 v0.3.13 — 任务管理系统重构(删除 todo_write + IPC 越权防护 + UI 自动刷新)

This commit is contained in:
2026-07-21 17:01:15 +08:00
parent 058ee2de36
commit f640c9b48a
13 changed files with 114 additions and 280 deletions
+37 -9
View File
@@ -11,6 +11,7 @@
import { ipcMain, BrowserWindow, shell, app, dialog } from 'electron';
import { join } from 'path';
import { nanoid } from 'nanoid';
import type { SessionService } from '../services/session.service';
import type { ConfigService } from '../services/config.service';
import type { WorkspaceService } from '../services/workspace.service';
@@ -1567,18 +1568,35 @@ export function registerAllIPCHandlers(
return { success: false, error: 'Invalid parentId' };
}
const db = sessionService.getDB();
const id = `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
// P4 统一(v0.3.13: ID 生成方式与 task_manager 工具一致(nanoid
const id = `task_${nanoid(12)}`;
try {
// P4 统一: 计算 order_idx = MAX(同 session+parent 的 order_idx) + 1(与 task_manager 工具一致)
// 注意:parent_id IS NULL 时需要单独处理,COALESCE(NULL, NULL) 仍为 NULL
const parentId = (req.parentId as string | null) ?? null;
let orderIdx = 0;
if (parentId === null) {
const orderRow = db.prepare(
'SELECT COALESCE(MAX(order_idx), -1) AS maxOrder FROM tasks WHERE session_id = ? AND parent_id IS NULL'
).get(req.sessionId) as { maxOrder: number } | undefined;
orderIdx = (orderRow?.maxOrder ?? -1) + 1;
} else {
const orderRow = db.prepare(
'SELECT COALESCE(MAX(order_idx), -1) AS maxOrder FROM tasks WHERE session_id = ? AND parent_id = ?'
).get(req.sessionId, parentId) as { maxOrder: number } | undefined;
orderIdx = (orderRow?.maxOrder ?? -1) + 1;
}
db.prepare(`
INSERT INTO tasks (id, session_id, title, description, status, priority, parent_id, created_at, updated_at)
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)
INSERT INTO tasks (id, session_id, title, description, status, priority, parent_id, order_idx, created_at, updated_at)
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)
`).run(
id,
req.sessionId,
req.title,
typeof req.description === 'string' ? req.description : '',
(req.priority as string) ?? 'medium',
(req.parentId as string | null) ?? null,
parentId,
orderIdx,
Date.now(),
Date.now(),
);
@@ -1588,11 +1606,15 @@ export function registerAllIPCHandlers(
}
});
ipcMain.handle('tasks:update', async (_event, id: unknown, updates: unknown) => {
ipcMain.handle('tasks:update', async (_event, id: unknown, updates: unknown, sessionId: unknown) => {
// M-47 修复: 校验 id 和 updates 结构/枚举
if (typeof id !== 'string' || !id) {
return { success: false, error: 'Invalid task id' };
}
// P1 修复(v0.3.13: 补 session_id 越权保护(与 task_manager 工具一致)
if (typeof sessionId !== 'string' || !sessionId) {
return { success: false, error: 'Invalid sessionId' };
}
if (!updates || typeof updates !== 'object') {
return { success: false, error: 'Invalid updates' };
}
@@ -1625,22 +1647,28 @@ export function registerAllIPCHandlers(
if (fields.length === 0) return { success: true };
fields.push('updated_at = ?'); values.push(Date.now());
if (u.status === 'completed') { fields.push('completed_at = ?'); values.push(Date.now()); }
values.push(id);
db.prepare(`UPDATE tasks SET ${fields.join(', ')} WHERE id = ?`).run(...values);
values.push(id, sessionId);
// P1 修复(v0.3.13: WHERE 补 session_id 校验,防越权修改其他会话任务
db.prepare(`UPDATE tasks SET ${fields.join(', ')} WHERE id = ? AND session_id = ?`).run(...values);
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message };
}
});
ipcMain.handle('tasks:delete', async (_event, id: unknown) => {
ipcMain.handle('tasks:delete', async (_event, id: unknown, sessionId: unknown) => {
// M-48 修复: 校验 id 类型
if (typeof id !== 'string' || !id) {
return { success: false, error: 'Invalid task id' };
}
// P1 修复(v0.3.13: 补 session_id 越权保护
if (typeof sessionId !== 'string' || !sessionId) {
return { success: false, error: 'Invalid sessionId' };
}
const db = sessionService.getDB();
try {
db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
// P1 修复: WHERE 补 session_id 校验
db.prepare('DELETE FROM tasks WHERE id = ? AND session_id = ?').run(id, sessionId);
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message };