agentquad 0.4.7 → 0.4.8
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 +4 -3
- package/dist-web/assets/index-DdqC2CwH.css +32 -0
- package/dist-web/assets/{index-BEiPvgk7.js → index-DkI6ZJx_.js} +399 -389
- package/dist-web/assets/logo-Cxw7XzHl.png +0 -0
- package/dist-web/favicon.png +0 -0
- package/dist-web/index.html +2 -2
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/db.js +32 -8
- package/src/export/todoMarkdown.js +1 -9
- package/src/mcp/tools/destructive/index.js +22 -16
- package/src/mcp/tools/openclaw/index.js +11 -15
- package/src/mcp/tools/read/index.js +7 -7
- package/src/mcp/tools/write/index.js +9 -6
- package/src/openclaw-wizard.js +67 -185
- package/src/prompt-render.js +0 -8
- package/src/routes/ai-terminal.js +15 -0
- package/src/routes/todos.js +8 -14
- package/src/stats/report.js +1 -6
- package/src/wiki/index.js +1 -1
- package/src/wiki/sources.js +0 -1
- package/dist-web/assets/index-qY2UiOW2.css +0 -32
- package/dist-web/assets/logo-D4DDtU-r.png +0 -0
|
Binary file
|
package/dist-web/favicon.png
CHANGED
|
Binary file
|
package/dist-web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>AgentQuad</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DkI6ZJx_.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DdqC2CwH.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -667,7 +667,7 @@ export async function runStart(cmdOpts = {}) {
|
|
|
667
667
|
const program = new Command()
|
|
668
668
|
program
|
|
669
669
|
.name('agentquad')
|
|
670
|
-
.description('Local
|
|
670
|
+
.description('Local status-board todo CLI with embedded Claude Code / Codex / Cursor terminal')
|
|
671
671
|
.version(loadPkgVersion())
|
|
672
672
|
|
|
673
673
|
program.command('start')
|
package/src/db.js
CHANGED
|
@@ -1116,49 +1116,72 @@ export function openDb(file = ':memory:') {
|
|
|
1116
1116
|
// for existing users on next restart.
|
|
1117
1117
|
const BUILTIN_TEMPLATE_SEEDS = [
|
|
1118
1118
|
{
|
|
1119
|
-
name: '
|
|
1119
|
+
name: '方案顾问(脑爆)',
|
|
1120
1120
|
description: '先脑爆方向,不急着动手',
|
|
1121
1121
|
content: '请先不要直接动手实现。先针对下面的任务 brainstorm:\n- 列出 2-3 种可选方案,说明优缺点\n- 指出风险点与需要用户拍板的关键决策\n- 明确验收标准\n\n在我确认方案后再进入实现。',
|
|
1122
1122
|
},
|
|
1123
1123
|
{
|
|
1124
|
-
name: '
|
|
1124
|
+
name: '全自动工程师(自动驾驶)',
|
|
1125
1125
|
description: '内心脑爆 → 自选最优 → 跑完 → 最后报告',
|
|
1126
1126
|
content: '按"自动驾驶"模式处理下面的任务,不要停下来问我。\n\n1. 先在心里 brainstorm 2-3 种实现思路,挑出最合理的一种,但不需要列出来征求我的同意。\n2. 直接执行:理解 → 实现 → 自测(跑相关测试 / 编译 / lint) → 提交。\n3. 遇到不可避免必须我决策的歧义点(例:要不要删数据、要不要对外发版),才停下来问;普通选型不要问。\n4. 跑完后输出一份三段式报告:\n - 变更摘要:改了什么、为什么这么改\n - 验证结果:跑了哪些自测、是否通过、有没有遗留\n - 仍需我确认的事项:列出来 / 没有就写"无"\n5. 我考虑过哪些方案、为什么选这个,请在"变更摘要"里用一两句说明。',
|
|
1127
1127
|
},
|
|
1128
1128
|
{
|
|
1129
|
-
name: '
|
|
1129
|
+
name: '守稳派开发(稳定优先)',
|
|
1130
1130
|
description: '改动面最小、不引新依赖、不动公共 API',
|
|
1131
1131
|
content: '本任务执行时请优先保证"稳定",含义:\n- 改动面尽量小,不顺手重构、不删看似无用的代码\n- 不引入新依赖、不升级现有依赖\n- 不改公共 API / 接口签名 / 数据库 schema(除非任务本身就是改这个)\n- 优先补测试覆盖现有行为;改动 hot path 时尽量保留旧路径作为兜底\n- 选型偏保守:用已经在项目里用过的库 / 写法\n如果"稳定"和任务目标冲突,告诉我,让我决定。',
|
|
1132
1132
|
},
|
|
1133
1133
|
{
|
|
1134
|
-
name: '
|
|
1134
|
+
name: '前瞻派架构师(未来发展优先)',
|
|
1135
1135
|
description: '允许重构 / 引入抽象 / 留扩展点',
|
|
1136
1136
|
content: '本任务执行时请优先考虑"长期可扩展性",含义:\n- 允许并鼓励顺手重构邻近代码,让结构更清晰\n- 可以引入新抽象 / 接口,为可预见的下一步需求留扩展点\n- 公共 API / 类型 / 数据结构允许调整,但要在报告里列出影响面(调用方 / 测试 / 文档)\n- 选型可以挑当前社区主流而非项目已有的旧写法,但要说明替换原因\n- 不要为完全假想的需求过度设计 —— 只服务"已经看到苗头"的下一步\n完成后在报告里说明:哪些是为长期留的扩展点,分别服务什么场景。',
|
|
1137
1137
|
},
|
|
1138
1138
|
{
|
|
1139
|
-
name: 'Bug
|
|
1139
|
+
name: 'Bug 侦探',
|
|
1140
1140
|
description: '复现 → 定位 → 最小用例 → 修复 → 回归',
|
|
1141
1141
|
content: '按 bug 修复流程处理下面的问题:\n1. 先复现(给出复现步骤和实际 vs 预期)\n2. 定位根因(不要过早修改代码)\n3. 写一个能复现该 bug 的最小用例(如果有测试框架)\n4. 修复根因,不是修现象\n5. 回归:跑相关测试;考虑同类 bug 是否还存在',
|
|
1142
1142
|
},
|
|
1143
1143
|
{
|
|
1144
|
-
name: '
|
|
1144
|
+
name: '重构师',
|
|
1145
1145
|
description: '先读懂 → 列出影响面 → 小步重构',
|
|
1146
1146
|
content: '按照小步重构原则处理下面的任务:\n1. 先通读相关代码,复述你的理解\n2. 列出此次重构的影响面(调用方 / 测试 / 类型)\n3. 每一步只改一件事,保持可运行\n4. 每步后跑一次测试(如果有)\n5. 不要顺手加功能、不要引入新抽象,除非当前任务要求',
|
|
1147
1147
|
},
|
|
1148
1148
|
{
|
|
1149
|
-
name: '
|
|
1149
|
+
name: '测试工程师',
|
|
1150
1150
|
description: 'TDD:红 → 绿 → 重构',
|
|
1151
1151
|
content: '用 TDD 的方式处理下面的任务:\n1. 先列出测试矩阵(输入 × 场景)\n2. 先写一个最简失败用例(红)\n3. 用最小改动让它通过(绿)\n4. 重构(保持绿)\n5. 重复 2-4 直到覆盖矩阵\n不 mock 真实依赖(除非跨网络/支付等)。',
|
|
1152
1152
|
},
|
|
1153
1153
|
{
|
|
1154
|
-
name: '
|
|
1154
|
+
name: '代码评审员',
|
|
1155
1155
|
description: '只评审,不改代码',
|
|
1156
1156
|
content: '请只做代码评审,不要修改代码。按下面的维度给出具体反馈:\n- 可读性:命名、结构、注释\n- 正确性:边界、错误处理、并发\n- 安全性:注入、鉴权、敏感数据\n- 性能:明显的 N+1 / 无谓复制\n- 简洁性:是否有过度设计 / 可删除的冗余\n每条反馈给出文件:行号 + 建议。',
|
|
1157
1157
|
},
|
|
1158
1158
|
]
|
|
1159
|
+
// 把老用户库里旧名字的 builtin 行就地改名成新名字,避免新种子重复插入。
|
|
1160
|
+
// 仅在新名尚未占位时改名;用户复制版(builtin=0)不动。
|
|
1161
|
+
const BUILTIN_RENAMES = [
|
|
1162
|
+
['Brainstorm(脑爆)', '方案顾问(脑爆)'],
|
|
1163
|
+
['自动驾驶(Autopilot)', '全自动工程师(自动驾驶)'],
|
|
1164
|
+
['稳定优先(Stability First)', '守稳派开发(稳定优先)'],
|
|
1165
|
+
['未来发展优先(Future First)', '前瞻派架构师(未来发展优先)'],
|
|
1166
|
+
['Bug 修复', 'Bug 侦探'],
|
|
1167
|
+
['重构', '重构师'],
|
|
1168
|
+
['写测试', '测试工程师'],
|
|
1169
|
+
['代码评审', '代码评审员'],
|
|
1170
|
+
]
|
|
1159
1171
|
const findBuiltinByName = db.prepare(
|
|
1160
1172
|
`SELECT id FROM prompt_templates WHERE builtin = 1 AND name = ? LIMIT 1`,
|
|
1161
1173
|
)
|
|
1174
|
+
const renameBuiltin = db.prepare(
|
|
1175
|
+
`UPDATE prompt_templates SET name = ?, updated_at = ? WHERE builtin = 1 AND name = ?`,
|
|
1176
|
+
)
|
|
1177
|
+
function migrateBuiltinNames() {
|
|
1178
|
+
const now = Date.now()
|
|
1179
|
+
for (const [oldName, newName] of BUILTIN_RENAMES) {
|
|
1180
|
+
if (findBuiltinByName.get(newName)) continue // 新名已存在则跳过
|
|
1181
|
+
if (!findBuiltinByName.get(oldName)) continue // 旧名也不在就没事
|
|
1182
|
+
renameBuiltin.run(newName, now, oldName)
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1162
1185
|
function ensureBuiltinTemplates() {
|
|
1163
1186
|
const now = Date.now()
|
|
1164
1187
|
BUILTIN_TEMPLATE_SEEDS.forEach((s, i) => {
|
|
@@ -1175,6 +1198,7 @@ export function openDb(file = ':memory:') {
|
|
|
1175
1198
|
})
|
|
1176
1199
|
})
|
|
1177
1200
|
}
|
|
1201
|
+
migrateBuiltinNames()
|
|
1178
1202
|
ensureBuiltinTemplates()
|
|
1179
1203
|
|
|
1180
1204
|
const wikiStmts = {
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { parseTranscriptFile } from '../transcripts/scanner.js'
|
|
2
2
|
import { estimateCost, DEFAULT_PRICING } from '../pricing.js'
|
|
3
3
|
|
|
4
|
-
const QUADRANT_LABEL = {
|
|
5
|
-
1: 'Q1 紧急且重要',
|
|
6
|
-
2: 'Q2 重要不紧急',
|
|
7
|
-
3: 'Q3 紧急不重要',
|
|
8
|
-
4: 'Q4 不紧急不重要',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
4
|
const STATUS_LABEL = {
|
|
12
5
|
todo: '待办',
|
|
13
6
|
ai_pending: 'AI 待确认',
|
|
@@ -30,7 +23,7 @@ export async function buildTodoExport(db, todoId, { turns = 'summary', turnLimit
|
|
|
30
23
|
if (!todo) return null
|
|
31
24
|
const comments = db.listComments(todoId)
|
|
32
25
|
const aiSessions = Array.isArray(todo.aiSessions) ? todo.aiSessions : []
|
|
33
|
-
const subtodos = todo.parentId ? [] : db.listTodos(
|
|
26
|
+
const subtodos = todo.parentId ? [] : db.listTodos().filter(item => item.parentId === todo.id)
|
|
34
27
|
|
|
35
28
|
const sessionRows = []
|
|
36
29
|
for (const s of aiSessions) {
|
|
@@ -122,7 +115,6 @@ export function renderTodoMarkdown(report) {
|
|
|
122
115
|
lines.push(`# ${todo.title}`)
|
|
123
116
|
lines.push('')
|
|
124
117
|
const meta = [
|
|
125
|
-
`**象限**:${QUADRANT_LABEL[todo.quadrant] || todo.quadrant}`,
|
|
126
118
|
`**状态**:${STATUS_LABEL[todo.status] || todo.status}`,
|
|
127
119
|
]
|
|
128
120
|
if (todo.dueDate) meta.push(`**截止**:${fmtDateTime(todo.dueDate)}`)
|
|
@@ -195,12 +195,15 @@ export function registerDestructiveTools(server, { db, audit }) {
|
|
|
195
195
|
'bulk_update',
|
|
196
196
|
{
|
|
197
197
|
description:
|
|
198
|
-
'对一组 todo id 批量 patch。允许字段:
|
|
198
|
+
'对一组 todo id 批量 patch。允许字段:status / archived / dueDate。' +
|
|
199
|
+
'(quadrant 字段保留接口以兼容旧调用方,传入会被忽略。)' +
|
|
200
|
+
'默认 confirm:false 返回 preview(列出将要修改的 todos,最多 20 条)。',
|
|
199
201
|
inputSchema: {
|
|
200
202
|
ids: z.array(z.string().min(1)).min(1),
|
|
201
203
|
patch: z
|
|
202
204
|
.object({
|
|
203
|
-
quadrant: z.number().int().min(1).max(4).optional()
|
|
205
|
+
quadrant: z.number().int().min(1).max(4).optional()
|
|
206
|
+
.describe('【已退役】象限概念已移除,传入会被忽略'),
|
|
204
207
|
status: z.enum(['todo', 'done']).optional(),
|
|
205
208
|
archived: z.boolean().optional(),
|
|
206
209
|
dueDate: z.number().int().nullable().optional(),
|
|
@@ -211,40 +214,43 @@ export function registerDestructiveTools(server, { db, audit }) {
|
|
|
211
214
|
},
|
|
212
215
|
},
|
|
213
216
|
async (args) => {
|
|
217
|
+
// 剥掉退役字段,避免 db 层试图写 quadrant
|
|
218
|
+
const { quadrant: _ignored, ...effectivePatch } = args.patch || {}
|
|
219
|
+
const sanitizedArgs = { ...args, patch: effectivePatch }
|
|
214
220
|
try {
|
|
215
221
|
const preview = {
|
|
216
|
-
ids:
|
|
217
|
-
patch:
|
|
222
|
+
ids: sanitizedArgs.ids,
|
|
223
|
+
patch: sanitizedArgs.patch,
|
|
218
224
|
affected: [],
|
|
219
225
|
missing: [],
|
|
220
226
|
}
|
|
221
|
-
for (const id of
|
|
227
|
+
for (const id of sanitizedArgs.ids.slice(0, 20)) {
|
|
222
228
|
const t = db.getTodo(id)
|
|
223
|
-
if (t) preview.affected.push({ id: t.id, title: t.title,
|
|
229
|
+
if (t) preview.affected.push({ id: t.id, title: t.title, status: t.status })
|
|
224
230
|
else preview.missing.push(id)
|
|
225
231
|
}
|
|
226
|
-
preview.totalTargeted =
|
|
227
|
-
preview.previewTruncated =
|
|
228
|
-
if (!
|
|
229
|
-
const patchKeys = Object.keys(
|
|
232
|
+
preview.totalTargeted = sanitizedArgs.ids.length
|
|
233
|
+
preview.previewTruncated = sanitizedArgs.ids.length > 20
|
|
234
|
+
if (!sanitizedArgs.confirm) {
|
|
235
|
+
const patchKeys = Object.keys(sanitizedArgs.patch).join(', ') || '(空 — quadrant 已退役)'
|
|
230
236
|
return previewResponse({
|
|
231
237
|
toolName: 'bulk_update',
|
|
232
|
-
summary: `将对 ${
|
|
238
|
+
summary: `将对 ${sanitizedArgs.ids.length} 条 todo 批量 patch 以下字段:${patchKeys}。命中 ${preview.affected.length} 条、未找到 ${preview.missing.length} 条。`,
|
|
233
239
|
impact: preview,
|
|
234
|
-
args,
|
|
240
|
+
args: sanitizedArgs,
|
|
235
241
|
})
|
|
236
242
|
}
|
|
237
|
-
const result = db.bulkUpdateTodos({ ids:
|
|
243
|
+
const result = db.bulkUpdateTodos({ ids: sanitizedArgs.ids, patch: sanitizedArgs.patch })
|
|
238
244
|
audit?.append({
|
|
239
245
|
tool: 'bulk_update',
|
|
240
246
|
ok: true,
|
|
241
|
-
args: { ids:
|
|
247
|
+
args: { ids: sanitizedArgs.ids, patch: sanitizedArgs.patch },
|
|
242
248
|
result: { changedCount: result.count, changedIds: result.changedIds },
|
|
243
|
-
confirmNote:
|
|
249
|
+
confirmNote: sanitizedArgs.confirmNote || null,
|
|
244
250
|
})
|
|
245
251
|
return asText({ ok: true, result })
|
|
246
252
|
} catch (e) {
|
|
247
|
-
audit?.append({ tool: 'bulk_update', ok: false, args: { ids:
|
|
253
|
+
audit?.append({ tool: 'bulk_update', ok: false, args: { ids: sanitizedArgs.ids, patch: sanitizedArgs.patch }, error: e?.message })
|
|
248
254
|
return asError(e?.message || 'bulk_update_failed')
|
|
249
255
|
}
|
|
250
256
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClaw 双向桥接的 MCP
|
|
2
|
+
* OpenClaw 双向桥接的 MCP 工具集:
|
|
3
3
|
*
|
|
4
4
|
* 创建任务向导(OpenClaw skill 调):
|
|
5
5
|
* - list_workdir_options
|
|
6
|
-
* - list_quadrants
|
|
7
|
-
* - list_templates
|
|
6
|
+
* - list_quadrants (已废弃,仅为兼容旧 skill;返回空数组 + deprecated 标记)
|
|
7
|
+
* - list_templates (用户面叫 "agent / 员工",工具名保留向后兼容)
|
|
8
8
|
*
|
|
9
9
|
* 启动 PTY(OpenClaw skill 调):
|
|
10
10
|
* - start_ai_session
|
|
@@ -37,13 +37,6 @@ function asError(message, extra = null) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const QUADRANTS = [
|
|
41
|
-
{ id: 1, label: '重要紧急', shortLabel: 'Q1', isDefault: false },
|
|
42
|
-
{ id: 2, label: '重要不紧急', shortLabel: 'Q2', isDefault: true },
|
|
43
|
-
{ id: 3, label: '紧急不重要', shortLabel: 'Q3', isDefault: false },
|
|
44
|
-
{ id: 4, label: '不重要不紧急', shortLabel: 'Q4', isDefault: false },
|
|
45
|
-
]
|
|
46
|
-
|
|
47
40
|
function expandHome(p) {
|
|
48
41
|
if (!p) return p
|
|
49
42
|
if (p === '~' || p.startsWith('~/')) return p === '~' ? homedir() : join(homedir(), p.slice(2))
|
|
@@ -105,22 +98,25 @@ export function registerOpenClawTools(server, deps) {
|
|
|
105
98
|
},
|
|
106
99
|
)
|
|
107
100
|
|
|
108
|
-
// ─── 2. list_quadrants
|
|
101
|
+
// ─── 2. list_quadrants(已废弃;保留以兼容旧版 OpenClaw skill)──
|
|
109
102
|
server.registerTool(
|
|
110
103
|
'list_quadrants',
|
|
111
104
|
{
|
|
112
|
-
description:
|
|
105
|
+
description:
|
|
106
|
+
'【已废弃】象限概念已从 AgentQuad 移除。本工具仅为兼容旧 skill 而保留,' +
|
|
107
|
+
'返回空数组 + deprecated 标记。新创建任务无需再选象限。',
|
|
113
108
|
inputSchema: {},
|
|
114
109
|
},
|
|
115
|
-
async () => asText({ quadrants:
|
|
110
|
+
async () => asText({ quadrants: [], deprecated: true }),
|
|
116
111
|
)
|
|
117
112
|
|
|
118
|
-
// ─── 3. list_templates
|
|
113
|
+
// ─── 3. list_templates (用户面叫 agent / 员工) ─────────────────────
|
|
119
114
|
server.registerTool(
|
|
120
115
|
'list_templates',
|
|
121
116
|
{
|
|
122
117
|
description:
|
|
123
|
-
'
|
|
118
|
+
'返回所有可指派的 agent / 员工(id/name/description/builtin/contentPreview),' +
|
|
119
|
+
'创建向导选 agent 那一步用。' +
|
|
124
120
|
'完整 content 不返回(避免上下文爆);启动会话时按 templateId 自动注入。',
|
|
125
121
|
inputSchema: {},
|
|
126
122
|
},
|
|
@@ -53,7 +53,8 @@ export function registerReadTools(server, { db, searchService, wikiDir, transcri
|
|
|
53
53
|
description:
|
|
54
54
|
'按过滤条件列出 todos。不做全文搜索——用 search 做模糊搜索。返回元数据数组,不含 AI 会话详情(用 get_todo 取)。',
|
|
55
55
|
inputSchema: {
|
|
56
|
-
quadrant: z.number().int().min(1).max(4).optional()
|
|
56
|
+
quadrant: z.number().int().min(1).max(4).optional()
|
|
57
|
+
.describe('【已退役】象限概念已移除;保留参数用于兼容旧调用方,传入会被忽略'),
|
|
57
58
|
status: z.enum(['todo', 'done', 'all']).optional().describe('默认 all'),
|
|
58
59
|
archived: z.union([z.boolean(), z.literal('all')]).optional().describe('默认 false 只看未归档;"all" 两者都要'),
|
|
59
60
|
parentId: z.string().optional().describe('仅列某 parent 下的子任务'),
|
|
@@ -63,8 +64,8 @@ export function registerReadTools(server, { db, searchService, wikiDir, transcri
|
|
|
63
64
|
async (args = {}) => {
|
|
64
65
|
try {
|
|
65
66
|
const rawStatus = args.status === 'all' ? '' : args.status
|
|
67
|
+
// quadrant 已退役,不再透传 db 层
|
|
66
68
|
const list = db.listTodos({
|
|
67
|
-
quadrant: args.quadrant,
|
|
68
69
|
status: rawStatus,
|
|
69
70
|
archived: args.archived,
|
|
70
71
|
})
|
|
@@ -78,7 +79,6 @@ export function registerReadTools(server, { db, searchService, wikiDir, transcri
|
|
|
78
79
|
id: t.id,
|
|
79
80
|
parentId: t.parentId,
|
|
80
81
|
title: t.title,
|
|
81
|
-
quadrant: t.quadrant,
|
|
82
82
|
status: t.status,
|
|
83
83
|
dueDate: t.dueDate,
|
|
84
84
|
workDir: t.workDir,
|
|
@@ -151,7 +151,7 @@ export function registerReadTools(server, { db, searchService, wikiDir, transcri
|
|
|
151
151
|
'get_stats',
|
|
152
152
|
{
|
|
153
153
|
description:
|
|
154
|
-
'
|
|
154
|
+
'一个当前快照:按 todo 状态分布、今日截止、本周完成、最近 7 天活跃度、归档数量。轻量实时计算。',
|
|
155
155
|
inputSchema: {},
|
|
156
156
|
},
|
|
157
157
|
async () => {
|
|
@@ -162,15 +162,15 @@ export function registerReadTools(server, { db, searchService, wikiDir, transcri
|
|
|
162
162
|
const all = db.listTodos({ archived: 'all' })
|
|
163
163
|
const open = all.filter((t) => t.status !== 'done' && t.status !== 'missed' && t.archivedAt == null)
|
|
164
164
|
const archivedCount = all.filter((t) => t.archivedAt != null).length
|
|
165
|
-
const
|
|
166
|
-
for (const t of open)
|
|
165
|
+
const byStatus = {}
|
|
166
|
+
for (const t of open) byStatus[t.status] = (byStatus[t.status] || 0) + 1
|
|
167
167
|
const overdue = open.filter((t) => t.dueDate && t.dueDate < now)
|
|
168
168
|
const dueToday = open.filter((t) => t.dueDate && t.dueDate >= startOfDay.getTime() && t.dueDate < startOfDay.getTime() + 86_400_000)
|
|
169
169
|
const weekDone = db.listCompletedTodos({ since: startOfWeek.getTime(), until: now + 1 })
|
|
170
170
|
return asText({
|
|
171
171
|
openCount: open.length,
|
|
172
172
|
archivedCount,
|
|
173
|
-
|
|
173
|
+
byStatus,
|
|
174
174
|
overdue: { count: overdue.length, ids: overdue.slice(0, 10).map((t) => t.id) },
|
|
175
175
|
dueToday: { count: dueToday.length, ids: dueToday.map((t) => t.id) },
|
|
176
176
|
completedThisWeek: weekDone.length,
|
|
@@ -23,12 +23,14 @@ export function registerWriteTools(server, { db }) {
|
|
|
23
23
|
'create_todo',
|
|
24
24
|
{
|
|
25
25
|
description:
|
|
26
|
-
'新建一条 todo
|
|
26
|
+
'新建一条 todo。只需 title。' +
|
|
27
|
+
'(quadrant 字段已退役但接口仍接受以兼容旧调用方;传入会被忽略,DB 写入默认值。)',
|
|
27
28
|
inputSchema: {
|
|
28
29
|
title: z.string().min(1).describe('标题,必填'),
|
|
29
|
-
quadrant: z.number().int().min(1).max(4).
|
|
30
|
+
quadrant: z.number().int().min(1).max(4).optional()
|
|
31
|
+
.describe('【已退役】象限概念已移除,传入会被忽略'),
|
|
30
32
|
description: z.string().optional(),
|
|
31
|
-
parentId: z.string().optional().describe('
|
|
33
|
+
parentId: z.string().optional().describe('已退役:子任务概念已从 UI 移除;接口保留但不再分组展示'),
|
|
32
34
|
dueDate: z.number().int().optional().describe('截止时间戳(毫秒 epoch)'),
|
|
33
35
|
workDir: z.string().optional().describe('关联的代码仓路径'),
|
|
34
36
|
brainstorm: z.boolean().optional(),
|
|
@@ -39,7 +41,7 @@ export function registerWriteTools(server, { db }) {
|
|
|
39
41
|
if (!args.title?.trim()) return asError('title_required')
|
|
40
42
|
const created = db.createTodo({
|
|
41
43
|
title: args.title.trim(),
|
|
42
|
-
quadrant
|
|
44
|
+
// quadrant 不再透传 —— 让 db 层用默认值
|
|
43
45
|
description: args.description || '',
|
|
44
46
|
parentId: args.parentId,
|
|
45
47
|
dueDate: args.dueDate,
|
|
@@ -63,7 +65,8 @@ export function registerWriteTools(server, { db }) {
|
|
|
63
65
|
id: z.string().min(1),
|
|
64
66
|
title: z.string().optional(),
|
|
65
67
|
description: z.string().optional(),
|
|
66
|
-
quadrant: z.number().int().min(1).max(4).optional()
|
|
68
|
+
quadrant: z.number().int().min(1).max(4).optional()
|
|
69
|
+
.describe('【已退役】象限概念已移除,传入会被忽略'),
|
|
67
70
|
dueDate: z.number().int().nullable().optional().describe('传 null 显式清除'),
|
|
68
71
|
workDir: z.string().nullable().optional(),
|
|
69
72
|
parentId: z.string().nullable().optional().describe('传 null 升为顶层 todo'),
|
|
@@ -71,7 +74,7 @@ export function registerWriteTools(server, { db }) {
|
|
|
71
74
|
},
|
|
72
75
|
async (args) => {
|
|
73
76
|
try {
|
|
74
|
-
const { id, ...patch } = args
|
|
77
|
+
const { id, quadrant: _ignored, ...patch } = args
|
|
75
78
|
if (!id) return asError('id_required')
|
|
76
79
|
// 禁止通过此工具改 status,status 变更有专用工具(complete/archive)
|
|
77
80
|
const cleanPatch = {}
|