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
package/src/openclaw-wizard.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 设计目标:
|
|
6
6
|
* - 一条 inbound 消息 → 一个完整的判断 → 一个 reply 字符串
|
|
7
7
|
* - 状态机存内存(重启丢失也无所谓,向导本来就是短生命周期)
|
|
8
|
-
* - 一句话直说能跳过任意向导步骤("目录 X" / "
|
|
8
|
+
* - 一句话直说能跳过任意向导步骤("目录 X" / "Agent Y")
|
|
9
9
|
* - 与 ask_user pending 共存:wizard 优先,wizard 完成后再吃 ask_user 答复
|
|
10
10
|
*
|
|
11
11
|
* 路由优先级(handleInbound 内):
|
|
@@ -49,17 +49,9 @@ import { resolveTool } from './dispatch.js'
|
|
|
49
49
|
const WIZARD_TIMEOUT_MS = 10 * 60 * 1000
|
|
50
50
|
|
|
51
51
|
const STEP_WORKDIR = 'workdir'
|
|
52
|
-
const
|
|
53
|
-
const STEP_TEMPLATE = 'template'
|
|
52
|
+
const STEP_TEMPLATE = 'template' // 用户面叫 "Agent";代码内部沿用 template key
|
|
54
53
|
const STEP_DONE = 'done'
|
|
55
54
|
|
|
56
|
-
const QUADRANTS = [
|
|
57
|
-
{ id: 1, label: '重要紧急' },
|
|
58
|
-
{ id: 2, label: '重要不紧急' },
|
|
59
|
-
{ id: 3, label: '紧急不重要' },
|
|
60
|
-
{ id: 4, label: '不重要不紧急' },
|
|
61
|
-
]
|
|
62
|
-
|
|
63
55
|
function telegramPermissionMode(cfg = {}) {
|
|
64
56
|
const mode = cfg.telegram?.defaultPermissionMode
|
|
65
57
|
return ['default', 'acceptEdits', 'bypass'].includes(mode) ? mode : 'bypass'
|
|
@@ -99,18 +91,12 @@ function tryExtractWorkdir(text) {
|
|
|
99
91
|
return null
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
/** 解析"
|
|
103
|
-
|
|
104
|
-
const m1 = text.match(/(?:象限|quadrant|q)[::=\s]*([1-4])\b/i)
|
|
105
|
-
if (m1) return Number(m1[1])
|
|
106
|
-
return null
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** 解析"模板 Bug" / "用 X 模板" 等;返回模板名 (string) 或 null */
|
|
94
|
+
/** 解析"agent Bug" / "用 X agent" / "员工 X" 等;返回 agent 名 (string) 或 null。
|
|
95
|
+
* 也兼容历史的"模板 X"写法以免老用户失忆。 */
|
|
110
96
|
function tryExtractTemplateHint(text) {
|
|
111
|
-
const m = text.match(/(?:用|使用)?\s*[「『"]?([^」』"\s,,;;]+)[」』"]?\s
|
|
97
|
+
const m = text.match(/(?:用|使用)?\s*[「『"]?([^」』"\s,,;;]+)[」』"]?\s*(?:agent|员工|模板)/i)
|
|
112
98
|
if (m) return m[1].trim()
|
|
113
|
-
const m2 = text.match(
|
|
99
|
+
const m2 = text.match(/(?:agent|员工|模板)[::=\s]+([^\s,,;;]+)/i)
|
|
114
100
|
if (m2) return m2[1].trim()
|
|
115
101
|
return null
|
|
116
102
|
}
|
|
@@ -143,11 +129,11 @@ function extractTitle(text) {
|
|
|
143
129
|
s = s.replace(/^(新建|开个|开一?个|创建)\s*(任务|todo)?[::\s]*/i, '')
|
|
144
130
|
s = s.replace(/^任务[::]\s*/, '')
|
|
145
131
|
s = s.replace(/^(帮我|帮忙)\s*(做|搞|修|搞定|实现|写一?个|做一?个|修复|重构|调试|debug|加|开发)\s*[::]?\s*/i, '')
|
|
146
|
-
// 剥后缀(目录 /
|
|
132
|
+
// 剥后缀(目录 / agent / 兼容老用户的"象限 N"残余字符)
|
|
147
133
|
s = s.replace(/[,,;;]?\s*(目录|路径|workdir|cwd|文件夹)[::=\s]+[^\s,,;;]+/gi, '')
|
|
148
|
-
s = s.replace(/[,,;;]?\s*(象限|quadrant|q)[::=\s]*[1-4]\b/gi, '')
|
|
149
|
-
s = s.replace(/[,,;;]?\s*(?:用|使用)?\s*[「『"]?[^」』"\s,,;;]+[」』"]?\s
|
|
150
|
-
s = s.replace(/[,,;;]?\s
|
|
134
|
+
s = s.replace(/[,,;;]?\s*(象限|quadrant|q)[::=\s]*[1-4]\b/gi, '') // 象限概念已移除,但清掉残留写法
|
|
135
|
+
s = s.replace(/[,,;;]?\s*(?:用|使用)?\s*[「『"]?[^」』"\s,,;;]+[」』"]?\s*(?:agent|员工|模板)/gi, '')
|
|
136
|
+
s = s.replace(/[,,;;]?\s*(?:agent|员工|模板)[::=\s]+[^\s,,;;]+/gi, '')
|
|
151
137
|
return s.trim()
|
|
152
138
|
}
|
|
153
139
|
|
|
@@ -165,20 +151,12 @@ function buildWorkdirMessage(options) {
|
|
|
165
151
|
return lines.join('\n')
|
|
166
152
|
}
|
|
167
153
|
|
|
168
|
-
function buildQuadrantMessage() {
|
|
169
|
-
const lines = ['🎯 选象限:']
|
|
170
|
-
QUADRANTS.forEach((q) => {
|
|
171
|
-
lines.push(`${q.id}. ${q.label}${q.id === 2 ? ' ✓ 默认' : ''}`)
|
|
172
|
-
})
|
|
173
|
-
return lines.join('\n')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
154
|
function buildTemplateMessage(templates) {
|
|
177
|
-
const lines = ['
|
|
155
|
+
const lines = ['👤 派哪个 Agent 干?']
|
|
178
156
|
templates.forEach((t, i) => {
|
|
179
157
|
lines.push(`${i + 1}. ${t.name}${t.description ? ' — ' + t.description : ''}`)
|
|
180
158
|
})
|
|
181
|
-
lines.push(`${templates.length + 1}.
|
|
159
|
+
lines.push(`${templates.length + 1}. 自由模式(不指派 agent)`)
|
|
182
160
|
return lines.join('\n')
|
|
183
161
|
}
|
|
184
162
|
|
|
@@ -186,8 +164,7 @@ function buildTemplateMessage(templates) {
|
|
|
186
164
|
//
|
|
187
165
|
// callback_data 编码(≤ 64 字节硬限):
|
|
188
166
|
// workdir: qt:wd:<idx> / qt:wd:custom
|
|
189
|
-
//
|
|
190
|
-
// template: qt:t:<idx> / qt:t:none
|
|
167
|
+
// template: qt:t:<idx> / qt:t:none (用户面叫 "agent")
|
|
191
168
|
//
|
|
192
169
|
// 数字按钮 label 沿用 "1. xxx" 序号 —— 跟纯文本 prompt 保持一致,
|
|
193
170
|
// 所以双轨用户(按按钮的 / 回数字的)看到的是同一个心智模型。
|
|
@@ -224,25 +201,8 @@ function buildWorkdirReplyMarkup(options) {
|
|
|
224
201
|
return { inline_keyboard: rows }
|
|
225
202
|
}
|
|
226
203
|
|
|
227
|
-
function buildQuadrantReplyMarkup() {
|
|
228
|
-
// 2×2,Q2 默认勾上
|
|
229
|
-
const label = (q) => `Q${q.id} ${q.label}${q.id === 2 ? ' ✓' : ''}`
|
|
230
|
-
return {
|
|
231
|
-
inline_keyboard: [
|
|
232
|
-
[
|
|
233
|
-
{ text: label(QUADRANTS[0]), callback_data: `${CALLBACK_PREFIX}:q:1` },
|
|
234
|
-
{ text: label(QUADRANTS[1]), callback_data: `${CALLBACK_PREFIX}:q:2` },
|
|
235
|
-
],
|
|
236
|
-
[
|
|
237
|
-
{ text: label(QUADRANTS[2]), callback_data: `${CALLBACK_PREFIX}:q:3` },
|
|
238
|
-
{ text: label(QUADRANTS[3]), callback_data: `${CALLBACK_PREFIX}:q:4` },
|
|
239
|
-
],
|
|
240
|
-
],
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
204
|
function buildTemplateReplyMarkup(templates) {
|
|
245
|
-
//
|
|
205
|
+
// 名称可能带描述 → 每行 1 个稳妥
|
|
246
206
|
const rows = templates.map((t, i) => [{
|
|
247
207
|
text: `${i + 1}. ${ellipsisLabel(t.name, 48)}`,
|
|
248
208
|
callback_data: `${CALLBACK_PREFIX}:t:${i}`,
|
|
@@ -258,10 +218,6 @@ function buildWorkdirPrompt(options) {
|
|
|
258
218
|
return { text: buildWorkdirMessage(options), replyMarkup: buildWorkdirReplyMarkup(options) }
|
|
259
219
|
}
|
|
260
220
|
|
|
261
|
-
function buildQuadrantPrompt() {
|
|
262
|
-
return { text: buildQuadrantMessage(), replyMarkup: buildQuadrantReplyMarkup() }
|
|
263
|
-
}
|
|
264
|
-
|
|
265
221
|
function buildTemplatePrompt(templates) {
|
|
266
222
|
return { text: buildTemplateMessage(templates), replyMarkup: buildTemplateReplyMarkup(templates) }
|
|
267
223
|
}
|
|
@@ -419,7 +375,6 @@ export function createOpenClawWizard({
|
|
|
419
375
|
const routeKey = makeRouteKey(channel, chatId, threadId)
|
|
420
376
|
const title = extractTitle(text) || '(未命名任务)'
|
|
421
377
|
const workdirHint = tryExtractWorkdir(text)
|
|
422
|
-
const quadrantHint = tryExtractQuadrant(text)
|
|
423
378
|
const templateHint = tryExtractTemplateHint(text)
|
|
424
379
|
|
|
425
380
|
const w = {
|
|
@@ -435,23 +390,21 @@ export function createOpenClawWizard({
|
|
|
435
390
|
title,
|
|
436
391
|
workdirOptions: listWorkdirOptions(),
|
|
437
392
|
chosenWorkdir: workdirHint || null,
|
|
438
|
-
chosenQuadrant: quadrantHint || null,
|
|
439
393
|
chosenTemplate: null,
|
|
440
394
|
step: STEP_WORKDIR,
|
|
441
395
|
startedAt: Date.now(),
|
|
442
396
|
updatedAt: Date.now(),
|
|
443
397
|
}
|
|
444
398
|
|
|
445
|
-
//
|
|
399
|
+
// agent (template) hint 解析
|
|
446
400
|
if (templateHint) {
|
|
447
401
|
const tpl = findTemplateByHint(db.listTemplates(), templateHint)
|
|
448
402
|
if (tpl) w.chosenTemplate = { id: tpl.id, name: tpl.name }
|
|
449
403
|
}
|
|
450
404
|
|
|
451
|
-
//
|
|
452
|
-
if (w.chosenWorkdir) w.step =
|
|
453
|
-
if (w.chosenWorkdir && w.
|
|
454
|
-
if (w.chosenWorkdir && w.chosenQuadrant && w.chosenTemplate) w.step = STEP_DONE
|
|
405
|
+
// 自动跳过已填字段(不再有 quadrant 步骤)
|
|
406
|
+
if (w.chosenWorkdir) w.step = STEP_TEMPLATE
|
|
407
|
+
if (w.chosenWorkdir && w.chosenTemplate) w.step = STEP_DONE
|
|
455
408
|
|
|
456
409
|
wizards.set(routeKey, w)
|
|
457
410
|
return w
|
|
@@ -480,8 +433,10 @@ export function createOpenClawWizard({
|
|
|
480
433
|
if (!path) return { reply: '🖋 路径为空,请重发' }
|
|
481
434
|
w.chosenWorkdir = path
|
|
482
435
|
w.awaitingCustomWorkdir = false
|
|
483
|
-
w.step =
|
|
484
|
-
const
|
|
436
|
+
w.step = STEP_TEMPLATE
|
|
437
|
+
const templates = db.listTemplates()
|
|
438
|
+
w.cachedTemplates = templates
|
|
439
|
+
const prompt = buildTemplatePrompt(templates)
|
|
485
440
|
return { reply: prompt.text, replyMarkup: prompt.replyMarkup }
|
|
486
441
|
}
|
|
487
442
|
// 数字选项?
|
|
@@ -489,8 +444,10 @@ export function createOpenClawWizard({
|
|
|
489
444
|
if (idx !== null) {
|
|
490
445
|
if (idx < w.workdirOptions.length) {
|
|
491
446
|
w.chosenWorkdir = w.workdirOptions[idx].path
|
|
492
|
-
w.step =
|
|
493
|
-
const
|
|
447
|
+
w.step = STEP_TEMPLATE
|
|
448
|
+
const templates = db.listTemplates()
|
|
449
|
+
w.cachedTemplates = templates
|
|
450
|
+
const prompt = buildTemplatePrompt(templates)
|
|
494
451
|
return { reply: prompt.text, replyMarkup: prompt.replyMarkup }
|
|
495
452
|
} else {
|
|
496
453
|
// 选了"自定义"
|
|
@@ -500,8 +457,10 @@ export function createOpenClawWizard({
|
|
|
500
457
|
// 自定义路径(文本路径下的隐式触发,老行为)
|
|
501
458
|
if (text.startsWith('/') || text.startsWith('~')) {
|
|
502
459
|
w.chosenWorkdir = text.trim()
|
|
503
|
-
w.step =
|
|
504
|
-
const
|
|
460
|
+
w.step = STEP_TEMPLATE
|
|
461
|
+
const templates = db.listTemplates()
|
|
462
|
+
w.cachedTemplates = templates
|
|
463
|
+
const prompt = buildTemplatePrompt(templates)
|
|
505
464
|
return { reply: prompt.text, replyMarkup: prompt.replyMarkup }
|
|
506
465
|
}
|
|
507
466
|
// 看不懂 → 重发提示(保留按钮)
|
|
@@ -512,28 +471,7 @@ export function createOpenClawWizard({
|
|
|
512
471
|
}
|
|
513
472
|
}
|
|
514
473
|
|
|
515
|
-
// ───
|
|
516
|
-
if (w.step === STEP_QUADRANT) {
|
|
517
|
-
const num = String(text).trim().match(/^([1-4])$/)
|
|
518
|
-
if (num) {
|
|
519
|
-
w.chosenQuadrant = Number(num[1])
|
|
520
|
-
} else if (/默认|default|^$/i.test(text.trim())) {
|
|
521
|
-
w.chosenQuadrant = 2
|
|
522
|
-
} else {
|
|
523
|
-
const prompt = buildQuadrantPrompt()
|
|
524
|
-
return {
|
|
525
|
-
reply: `🤔 请点按钮或回 1-4 选象限,回 "默认" 用 Q2。\n\n${prompt.text}`,
|
|
526
|
-
replyMarkup: prompt.replyMarkup,
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
w.step = STEP_TEMPLATE
|
|
530
|
-
const templates = db.listTemplates()
|
|
531
|
-
w.cachedTemplates = templates
|
|
532
|
-
const prompt = buildTemplatePrompt(templates)
|
|
533
|
-
return { reply: prompt.text, replyMarkup: prompt.replyMarkup }
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// ─── template 步 ───
|
|
474
|
+
// ─── template (agent) 步 ───
|
|
537
475
|
if (w.step === STEP_TEMPLATE) {
|
|
538
476
|
const templates = w.cachedTemplates || db.listTemplates()
|
|
539
477
|
const idx = parseNumericChoice(text, templates.length + 1)
|
|
@@ -554,7 +492,7 @@ export function createOpenClawWizard({
|
|
|
554
492
|
} else {
|
|
555
493
|
const prompt = buildTemplatePrompt(templates)
|
|
556
494
|
return {
|
|
557
|
-
reply: `🤔 请点按钮 / 回数字 1-${templates.length + 1}
|
|
495
|
+
reply: `🤔 请点按钮 / 回数字 1-${templates.length + 1},或 agent 名字(自由/无)。\n\n${prompt.text}`,
|
|
558
496
|
replyMarkup: prompt.replyMarkup,
|
|
559
497
|
}
|
|
560
498
|
}
|
|
@@ -568,10 +506,9 @@ export function createOpenClawWizard({
|
|
|
568
506
|
|
|
569
507
|
async function finalizeWizard(w) {
|
|
570
508
|
try {
|
|
571
|
-
// 创建 todo
|
|
509
|
+
// 创建 todo(quadrant 字段保留在 DB 里只是为了向后兼容;UI 不再展示)
|
|
572
510
|
const todo = db.createTodo({
|
|
573
511
|
title: w.title,
|
|
574
|
-
quadrant: w.chosenQuadrant || 2,
|
|
575
512
|
description: '',
|
|
576
513
|
workDir: w.chosenWorkdir || null,
|
|
577
514
|
brainstorm: false,
|
|
@@ -621,7 +558,7 @@ export function createOpenClawWizard({
|
|
|
621
558
|
`${topicName}`,
|
|
622
559
|
`AI 已启动,后续输出会回复在这个话题里。`,
|
|
623
560
|
``,
|
|
624
|
-
|
|
561
|
+
`Agent ${w.chosenTemplate?.name || '自由模式'} · 目录 ${w.chosenWorkdir || '默认'}`,
|
|
625
562
|
].join('\n')
|
|
626
563
|
// 复用用户当前所在话题 thread:把 intro 作为 thread reply 发进去,PTY 后续输出
|
|
627
564
|
// 也用同一个 anchor 进同一话题,不再开新 thread。
|
|
@@ -770,7 +707,7 @@ export function createOpenClawWizard({
|
|
|
770
707
|
const welcome = [
|
|
771
708
|
`🤖 任务「${w.title}」AI 已启动 (${sessionInfo?.tool || 'claude'})`,
|
|
772
709
|
``,
|
|
773
|
-
|
|
710
|
+
`Agent ${w.chosenTemplate?.name || '自由模式'} · 目录 ${w.chosenWorkdir || '默认'}`,
|
|
774
711
|
``,
|
|
775
712
|
`AI 一轮回话/卡住/结束会推到这里。直接回任意文本会写进 PTY stdin。`,
|
|
776
713
|
].join('\n')
|
|
@@ -787,13 +724,12 @@ export function createOpenClawWizard({
|
|
|
787
724
|
const lines = []
|
|
788
725
|
if (createdThreadId) {
|
|
789
726
|
lines.push(`✅ todo #${shortCode} 已建 → 去 topic 「${topicName}」 看进度`)
|
|
790
|
-
lines.push(`
|
|
727
|
+
lines.push(` Agent ${w.chosenTemplate?.name || '自由模式'} · ${w.chosenWorkdir || '默认目录'}`)
|
|
791
728
|
} else {
|
|
792
729
|
lines.push(`✅ todo #${shortCode} 已建`)
|
|
793
730
|
lines.push(` 标题: ${w.title}`)
|
|
794
|
-
lines.push(`
|
|
731
|
+
lines.push(` Agent: ${w.chosenTemplate?.name || '自由模式'}`)
|
|
795
732
|
lines.push(` 目录: ${w.chosenWorkdir || '默认'}`)
|
|
796
|
-
lines.push(` 模板: ${w.chosenTemplate?.name || '自由模式'}`)
|
|
797
733
|
if (sessionInfo) {
|
|
798
734
|
lines.push(`🤖 ${sessionInfo.tool} 终端已启动 (sessionId: ${sessionInfo.sessionId.slice(-8)})`)
|
|
799
735
|
}
|
|
@@ -1466,15 +1402,11 @@ export function createOpenClawWizard({
|
|
|
1466
1402
|
wizards.delete(routeKey)
|
|
1467
1403
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1468
1404
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1469
|
-
if (w.step === STEP_QUADRANT) {
|
|
1470
|
-
const p = buildQuadrantPrompt()
|
|
1471
|
-
return { reply: `(已重启向导,跳过目录步)\n${p.text}`, replyMarkup: p.replyMarkup }
|
|
1472
|
-
}
|
|
1473
1405
|
if (w.step === STEP_TEMPLATE) {
|
|
1474
1406
|
const tpls = db.listTemplates()
|
|
1475
1407
|
w.cachedTemplates = tpls
|
|
1476
1408
|
const p = buildTemplatePrompt(tpls)
|
|
1477
|
-
return { reply:
|
|
1409
|
+
return { reply: `(已重启向导,跳过目录步)\n${p.text}`, replyMarkup: p.replyMarkup }
|
|
1478
1410
|
}
|
|
1479
1411
|
const p = buildWorkdirPrompt(w.workdirOptions)
|
|
1480
1412
|
return { reply: `(已重启向导)\n${p.text}`, replyMarkup: p.replyMarkup, action: 'wizard_started' }
|
|
@@ -1489,20 +1421,12 @@ export function createOpenClawWizard({
|
|
|
1489
1421
|
if (newTaskGateOpen && NEW_TASK_TRIGGERS.some((re) => re.test(trimmed))) {
|
|
1490
1422
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1491
1423
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1492
|
-
if (w.step === STEP_QUADRANT) {
|
|
1493
|
-
const p = buildQuadrantPrompt()
|
|
1494
|
-
return {
|
|
1495
|
-
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir})\n\n${p.text}`,
|
|
1496
|
-
replyMarkup: p.replyMarkup,
|
|
1497
|
-
action: 'wizard_started',
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
1424
|
if (w.step === STEP_TEMPLATE) {
|
|
1501
1425
|
const tpls = db.listTemplates()
|
|
1502
1426
|
w.cachedTemplates = tpls
|
|
1503
1427
|
const p = buildTemplatePrompt(tpls)
|
|
1504
1428
|
return {
|
|
1505
|
-
reply: `任务: ${w.title}\n
|
|
1429
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1506
1430
|
replyMarkup: p.replyMarkup,
|
|
1507
1431
|
action: 'wizard_started',
|
|
1508
1432
|
}
|
|
@@ -1581,20 +1505,12 @@ export function createOpenClawWizard({
|
|
|
1581
1505
|
logger.info?.(`[wizard] lark auto-create from non-prefix text (unbound thread): chatId=${chatId} thread=${threadId || '-'} title="${trimmed.slice(0, 80)}"`)
|
|
1582
1506
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1583
1507
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1584
|
-
if (w.step === STEP_QUADRANT) {
|
|
1585
|
-
const p = buildQuadrantPrompt()
|
|
1586
|
-
return {
|
|
1587
|
-
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir})\n\n${p.text}`,
|
|
1588
|
-
replyMarkup: p.replyMarkup,
|
|
1589
|
-
action: 'wizard_started',
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
1508
|
if (w.step === STEP_TEMPLATE) {
|
|
1593
1509
|
const tpls = db.listTemplates()
|
|
1594
1510
|
w.cachedTemplates = tpls
|
|
1595
1511
|
const p = buildTemplatePrompt(tpls)
|
|
1596
1512
|
return {
|
|
1597
|
-
reply: `任务: ${w.title}\n
|
|
1513
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1598
1514
|
replyMarkup: p.replyMarkup,
|
|
1599
1515
|
action: 'wizard_started',
|
|
1600
1516
|
}
|
|
@@ -1765,20 +1681,12 @@ export function createOpenClawWizard({
|
|
|
1765
1681
|
logger.info?.(`[wizard] lark auto-create from non-prefix text: chatId=${chatId} thread=${threadId || '-'} title="${trimmed.slice(0, 80)}"`)
|
|
1766
1682
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1767
1683
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1768
|
-
if (w.step === STEP_QUADRANT) {
|
|
1769
|
-
const p = buildQuadrantPrompt()
|
|
1770
|
-
return {
|
|
1771
|
-
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir})\n\n${p.text}`,
|
|
1772
|
-
replyMarkup: p.replyMarkup,
|
|
1773
|
-
action: 'wizard_started',
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
1684
|
if (w.step === STEP_TEMPLATE) {
|
|
1777
1685
|
const tpls = db.listTemplates()
|
|
1778
1686
|
w.cachedTemplates = tpls
|
|
1779
1687
|
const p = buildTemplatePrompt(tpls)
|
|
1780
1688
|
return {
|
|
1781
|
-
reply: `任务: ${w.title}\n
|
|
1689
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1782
1690
|
replyMarkup: p.replyMarkup,
|
|
1783
1691
|
action: 'wizard_started',
|
|
1784
1692
|
}
|
|
@@ -1830,9 +1738,8 @@ export function createOpenClawWizard({
|
|
|
1830
1738
|
* callback_data 协议:
|
|
1831
1739
|
* qt:wd:<idx> 工作目录(按 listWorkdirOptions 顺序)
|
|
1832
1740
|
* qt:wd:custom 自定义路径 → 进入 awaitingCustomWorkdir 子态,等下一条文本
|
|
1833
|
-
* qt:
|
|
1834
|
-
* qt:t
|
|
1835
|
-
* qt:t:none 自由模式(不套模板)
|
|
1741
|
+
* qt:t:<idx> agent(template;保留 t 前缀以避免破坏老的飞书卡片)
|
|
1742
|
+
* qt:t:none 自由模式(不指派 agent)
|
|
1836
1743
|
*
|
|
1837
1744
|
* 安全:所有未知 / 不匹配当前 step 的 callback 都返回 toast,从不 throw —— 让
|
|
1838
1745
|
* telegram-bot 始终能 answerCallbackQuery 关 loading。
|
|
@@ -1908,8 +1815,10 @@ export function createOpenClawWizard({
|
|
|
1908
1815
|
return { toast: '选项无效', action: 'invalid' }
|
|
1909
1816
|
}
|
|
1910
1817
|
w.chosenWorkdir = w.workdirOptions[idx].path
|
|
1911
|
-
w.step =
|
|
1912
|
-
const
|
|
1818
|
+
w.step = STEP_TEMPLATE
|
|
1819
|
+
const templates = db.listTemplates()
|
|
1820
|
+
w.cachedTemplates = templates
|
|
1821
|
+
const prompt = buildTemplatePrompt(templates)
|
|
1913
1822
|
return {
|
|
1914
1823
|
chosenLabel: w.chosenWorkdir,
|
|
1915
1824
|
reply: prompt.text,
|
|
@@ -1918,31 +1827,19 @@ export function createOpenClawWizard({
|
|
|
1918
1827
|
}
|
|
1919
1828
|
}
|
|
1920
1829
|
|
|
1921
|
-
// ── quadrant
|
|
1830
|
+
// ── 已退役:qt:q (quadrant) callback。老飞书/Telegram 卡片仍可能携带 → 友好提示 ──
|
|
1922
1831
|
if (kind === 'q') {
|
|
1923
|
-
if (w.step !== STEP_QUADRANT) {
|
|
1924
|
-
return { toast: '当前步骤不接受象限选择', action: 'invalid' }
|
|
1925
|
-
}
|
|
1926
|
-
const q = Number(value)
|
|
1927
|
-
if (![1, 2, 3, 4].includes(q)) return { toast: '象限无效', action: 'invalid' }
|
|
1928
|
-
w.chosenQuadrant = q
|
|
1929
|
-
w.step = STEP_TEMPLATE
|
|
1930
|
-
const templates = db.listTemplates()
|
|
1931
|
-
w.cachedTemplates = templates
|
|
1932
|
-
const prompt = buildTemplatePrompt(templates)
|
|
1933
|
-
const qLabel = QUADRANTS.find((x) => x.id === q)?.label || ''
|
|
1934
1832
|
return {
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
action: 'wizard_step',
|
|
1833
|
+
toast: '象限步骤已移除,直接选 agent 即可',
|
|
1834
|
+
action: 'deprecated_quadrant',
|
|
1835
|
+
editOriginal: true,
|
|
1939
1836
|
}
|
|
1940
1837
|
}
|
|
1941
1838
|
|
|
1942
|
-
// ── template step ─────────────────────────────
|
|
1839
|
+
// ── template step (用户面叫 agent) ─────────────────────────────
|
|
1943
1840
|
if (kind === 't') {
|
|
1944
1841
|
if (w.step !== STEP_TEMPLATE) {
|
|
1945
|
-
return { toast: '
|
|
1842
|
+
return { toast: '当前步骤不接受 agent 选择', action: 'invalid' }
|
|
1946
1843
|
}
|
|
1947
1844
|
const templates = w.cachedTemplates || db.listTemplates()
|
|
1948
1845
|
let label
|
|
@@ -1952,7 +1849,7 @@ export function createOpenClawWizard({
|
|
|
1952
1849
|
} else {
|
|
1953
1850
|
const idx = Number(value)
|
|
1954
1851
|
if (!Number.isInteger(idx) || idx < 0 || idx >= templates.length) {
|
|
1955
|
-
return { toast: '
|
|
1852
|
+
return { toast: 'agent 无效', action: 'invalid' }
|
|
1956
1853
|
}
|
|
1957
1854
|
w.chosenTemplate = { id: templates[idx].id, name: templates[idx].name }
|
|
1958
1855
|
label = templates[idx].name
|
|
@@ -2209,7 +2106,7 @@ export function createOpenClawWizard({
|
|
|
2209
2106
|
|
|
2210
2107
|
/**
|
|
2211
2108
|
* 找到所有活跃 AI session(status=running / idle / pending_confirm),返回带 todo 上下文的列表:
|
|
2212
|
-
* [{ sid, short, status, lastOutputAt, todo: {id, title, workDir
|
|
2109
|
+
* [{ sid, short, status, lastOutputAt, todo: {id, title, workDir} | null }]
|
|
2213
2110
|
*
|
|
2214
2111
|
* 数据源:
|
|
2215
2112
|
* - aiTerminal.sessions (in-memory PTY session map) —— 状态最准
|
|
@@ -2247,7 +2144,6 @@ export function createOpenClawWizard({
|
|
|
2247
2144
|
id: t.id,
|
|
2248
2145
|
title: t.title || '(未命名)',
|
|
2249
2146
|
workDir: t.workDir || '',
|
|
2250
|
-
quadrant: t.quadrant || 2,
|
|
2251
2147
|
}
|
|
2252
2148
|
}
|
|
2253
2149
|
}
|
|
@@ -2300,7 +2196,7 @@ export function createOpenClawWizard({
|
|
|
2300
2196
|
}
|
|
2301
2197
|
|
|
2302
2198
|
/**
|
|
2303
|
-
* /list 或 /pending —— 列未完成 todos
|
|
2199
|
+
* /list 或 /pending —— 列未完成 todos(扁平显示,按 updated_at 倒序)。
|
|
2304
2200
|
*
|
|
2305
2201
|
* 输出限制:30 条;超出加"去 web 看"提示。
|
|
2306
2202
|
* 状态显示:把 todo.aiSessions 里 running 的标 🟢,便于一眼看哪些任务在跑。
|
|
@@ -2320,29 +2216,18 @@ export function createOpenClawWizard({
|
|
|
2320
2216
|
const activeSids = new Set(findActiveSessions().map((s) => s.sid))
|
|
2321
2217
|
// dispatcher 排队信息:sid → queueSize(busy 期间累积的用户输入)
|
|
2322
2218
|
const dispatcherDesc = sessionInputDispatcher?.describe?.() || { byId: {} }
|
|
2323
|
-
const groups = new Map()
|
|
2324
|
-
for (const q of QUADRANTS) groups.set(q.id, [])
|
|
2325
|
-
for (const t of visible) {
|
|
2326
|
-
const arr = groups.get(t.quadrant) || groups.get(2)
|
|
2327
|
-
arr.push(t)
|
|
2328
|
-
}
|
|
2329
2219
|
const lines = [`📋 待办 (${todos.length}${todos.length > PAGE ? `, 仅显示前 ${PAGE}` : ''})`]
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const statusTag = isRunning ? '🟢' : '·'
|
|
2342
|
-
const queueSize = runningSid ? (dispatcherDesc.byId?.[runningSid]?.queueSize || 0) : 0
|
|
2343
|
-
const queueTag = queueSize > 0 ? ` 📥${queueSize}` : ''
|
|
2344
|
-
lines.push(` ${statusTag} ${short} ${t.title} ${dirTag}${queueTag}`)
|
|
2345
|
-
}
|
|
2220
|
+
lines.push('')
|
|
2221
|
+
for (const t of visible) {
|
|
2222
|
+
const short = String(t.id).slice(0, 4)
|
|
2223
|
+
const dirTag = t.workDir ? `· ${basename(t.workDir)}` : ''
|
|
2224
|
+
const aiSessions = t.aiSessions || (t.aiSession ? [t.aiSession] : [])
|
|
2225
|
+
const runningSid = aiSessions.find((s) => s?.sessionId && activeSids.has(s.sessionId))?.sessionId
|
|
2226
|
+
const isRunning = !!runningSid
|
|
2227
|
+
const statusTag = isRunning ? '🟢' : '·'
|
|
2228
|
+
const queueSize = runningSid ? (dispatcherDesc.byId?.[runningSid]?.queueSize || 0) : 0
|
|
2229
|
+
const queueTag = queueSize > 0 ? ` 📥${queueSize}` : ''
|
|
2230
|
+
lines.push(` ${statusTag} ${short} ${t.title} ${dirTag}${queueTag}`)
|
|
2346
2231
|
}
|
|
2347
2232
|
if (todos.length > PAGE) {
|
|
2348
2233
|
const port = (getConfig?.()?.port) || 5677
|
|
@@ -2519,15 +2404,12 @@ export function createOpenClawWizard({
|
|
|
2519
2404
|
export const __test__ = {
|
|
2520
2405
|
extractTitle,
|
|
2521
2406
|
tryExtractWorkdir,
|
|
2522
|
-
tryExtractQuadrant,
|
|
2523
2407
|
tryExtractTemplateHint,
|
|
2524
2408
|
parseNumericChoice,
|
|
2525
2409
|
findTemplateByHint,
|
|
2526
2410
|
buildWorkdirMessage,
|
|
2527
|
-
buildQuadrantMessage,
|
|
2528
2411
|
buildTemplateMessage,
|
|
2529
2412
|
buildWorkdirReplyMarkup,
|
|
2530
|
-
buildQuadrantReplyMarkup,
|
|
2531
2413
|
buildTemplateReplyMarkup,
|
|
2532
2414
|
CALLBACK_PREFIX,
|
|
2533
2415
|
isGeneralChannel,
|
package/src/prompt-render.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const QUADRANT_LABEL = {
|
|
2
|
-
1: '重要且紧急',
|
|
3
|
-
2: '重要不紧急',
|
|
4
|
-
3: '紧急不重要',
|
|
5
|
-
4: '不重要不紧急',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
1
|
export function buildVars(todo) {
|
|
9
2
|
if (!todo) return {}
|
|
10
3
|
const dueDate = todo.dueDate
|
|
@@ -14,7 +7,6 @@ export function buildVars(todo) {
|
|
|
14
7
|
title: todo.title || '',
|
|
15
8
|
description: todo.description || '',
|
|
16
9
|
workDir: todo.workDir || '',
|
|
17
|
-
quadrant: todo.quadrant ? `Q${todo.quadrant}(${QUADRANT_LABEL[todo.quadrant] || ''})` : '',
|
|
18
10
|
dueDate,
|
|
19
11
|
}
|
|
20
12
|
}
|
|
@@ -667,6 +667,20 @@ export function createAiTerminal({ db, pty, logDir, defaultCwd, getDefaultCwd, o
|
|
|
667
667
|
// resume 路径上面已经 set 过;新会话首次得到 nativeId 时补一次。
|
|
668
668
|
nativeSessionMap.set(`${tool}:${presetNativeId}`, sessionId)
|
|
669
669
|
}
|
|
670
|
+
// Agent 身份快照:派活那一刻 todo.appliedTemplateIds[0] 就是这次的 agent。
|
|
671
|
+
// 写到 session 上,UI 不用反查 templates、用户改 todo agent 也不会改写历史会话归属。
|
|
672
|
+
let agentTemplateId = null
|
|
673
|
+
let agentName = null
|
|
674
|
+
const firstTemplateId = (todo.appliedTemplateIds || [])[0] || null
|
|
675
|
+
if (firstTemplateId) {
|
|
676
|
+
try {
|
|
677
|
+
const tpl = db.getTemplate(firstTemplateId)
|
|
678
|
+
if (tpl) {
|
|
679
|
+
agentTemplateId = tpl.id
|
|
680
|
+
agentName = tpl.name
|
|
681
|
+
}
|
|
682
|
+
} catch { /* 模板查不到就当无 agent */ }
|
|
683
|
+
}
|
|
670
684
|
// 3. 一次性把 nativeSessionId 写进 DB(搬进 try 内:失败时不留脏 DB)。
|
|
671
685
|
db.updateTodo(todoId, {
|
|
672
686
|
status: 'ai_running',
|
|
@@ -680,6 +694,7 @@ export function createAiTerminal({ db, pty, logDir, defaultCwd, getDefaultCwd, o
|
|
|
680
694
|
completedAt: null,
|
|
681
695
|
prompt,
|
|
682
696
|
permissionMode: effectivePermissionMode,
|
|
697
|
+
...(agentTemplateId ? { agentTemplateId, agentName } : {}),
|
|
683
698
|
...(label ? { label } : {}),
|
|
684
699
|
}),
|
|
685
700
|
})
|
package/src/routes/todos.js
CHANGED
|
@@ -13,8 +13,9 @@ export function createTodosRouter({ db, logDir, getPricing, getTools, getLiveSes
|
|
|
13
13
|
router.get('/', (req, res) => {
|
|
14
14
|
try {
|
|
15
15
|
try { db.sweepRecurring(Date.now()) } catch (e) { console.warn('[sweepRecurring]', e?.message) }
|
|
16
|
-
|
|
17
|
-
const
|
|
16
|
+
// quadrant 入参已退役(仍接受以兼容旧客户端,但不再过滤)
|
|
17
|
+
const { status, keyword } = req.query
|
|
18
|
+
const list = db.listTodos({ status, keyword })
|
|
18
19
|
res.json({ ok: true, list })
|
|
19
20
|
} catch (e) {
|
|
20
21
|
res.status(500).json({ ok: false, error: e.message })
|
|
@@ -23,7 +24,7 @@ export function createTodosRouter({ db, logDir, getPricing, getTools, getLiveSes
|
|
|
23
24
|
|
|
24
25
|
router.post('/', (req, res) => {
|
|
25
26
|
try {
|
|
26
|
-
const { title, description,
|
|
27
|
+
const { title, description, dueDate, workDir, brainstorm, appliedTemplateIds, parentId } = req.body || {}
|
|
27
28
|
if (!title || typeof title !== 'string') {
|
|
28
29
|
res.status(400).json({ ok: false, error: 'missing title' })
|
|
29
30
|
return
|
|
@@ -37,15 +38,10 @@ export function createTodosRouter({ db, logDir, getPricing, getTools, getLiveSes
|
|
|
37
38
|
res.status(400).json({ ok: false, error: 'nested_subtodo_not_allowed' })
|
|
38
39
|
return
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
if (![1, 2, 3, 4].includes(q)) {
|
|
42
|
-
res.status(400).json({ ok: false, error: 'invalid quadrant' })
|
|
43
|
-
return
|
|
44
|
-
}
|
|
41
|
+
// quadrant 已退役 —— 不再透传,让 db 层用默认值
|
|
45
42
|
const todo = db.createTodo({
|
|
46
43
|
title,
|
|
47
44
|
description: description || '',
|
|
48
|
-
quadrant: q,
|
|
49
45
|
dueDate: dueDate ?? null,
|
|
50
46
|
workDir: workDir || null,
|
|
51
47
|
brainstorm: !!brainstorm,
|
|
@@ -100,11 +96,9 @@ export function createTodosRouter({ db, logDir, getPricing, getTools, getLiveSes
|
|
|
100
96
|
res.status(400).json({ ok: false, error: 'nested_subtodo_not_allowed' })
|
|
101
97
|
return
|
|
102
98
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
const todo = db.updateTodo(req.params.id, patch)
|
|
99
|
+
// quadrant 入参已退役:剥掉避免传到 db 层(db 仍接受但 UI 不再使用)
|
|
100
|
+
const { quadrant: _ignoredQuadrant, ...sanitizedPatch } = patch
|
|
101
|
+
const todo = db.updateTodo(req.params.id, sanitizedPatch)
|
|
108
102
|
|
|
109
103
|
// Auto-close PTY when status transitions to 'done':
|
|
110
104
|
// - kill all live AI sessions of this todo and its subtodos
|
package/src/stats/report.js
CHANGED
|
@@ -111,7 +111,6 @@ export function buildReport(db, { since, until = Date.now(), pricing, topN = 10
|
|
|
111
111
|
return {
|
|
112
112
|
todoId: b.todoId,
|
|
113
113
|
title: t?.title || '(已删除)',
|
|
114
|
-
quadrant: t?.quadrant || 0,
|
|
115
114
|
activeMs: b.activeMs,
|
|
116
115
|
wallClockMs: todoWall.get(b.todoId) || 0,
|
|
117
116
|
tokens: b.tokens,
|
|
@@ -128,11 +127,8 @@ export function buildReport(db, { since, until = Date.now(), pricing, topN = 10
|
|
|
128
127
|
})
|
|
129
128
|
.slice(0, topN)
|
|
130
129
|
|
|
131
|
-
// byTool /
|
|
130
|
+
// byTool / byModel
|
|
132
131
|
const byTool = aggregateBy(files, logs, f => f.tool, l => l.tool, pricing)
|
|
133
|
-
const byQuadrant = aggregateBy(files, logs,
|
|
134
|
-
f => { const t = todoById.get(effectiveTodoId(f)); return t ? t.quadrant : null },
|
|
135
|
-
l => l.quadrant, pricing)
|
|
136
132
|
const byModel = aggregateBy(files, [], f => f.primary_model || '(unknown)', () => null, pricing, { includeWall: false })
|
|
137
133
|
|
|
138
134
|
// timeline
|
|
@@ -168,7 +164,6 @@ export function buildReport(db, { since, until = Date.now(), pricing, topN = 10
|
|
|
168
164
|
},
|
|
169
165
|
topTodos,
|
|
170
166
|
byTool,
|
|
171
|
-
byQuadrant,
|
|
172
167
|
byModel,
|
|
173
168
|
timeline,
|
|
174
169
|
}
|
package/src/wiki/index.js
CHANGED
|
@@ -142,7 +142,7 @@ export function createWikiService({
|
|
|
142
142
|
function pending() {
|
|
143
143
|
return db.listUnappliedDoneTodos().map(t => ({
|
|
144
144
|
id: t.id, title: t.title, workDir: t.workDir,
|
|
145
|
-
|
|
145
|
+
completedAt: t.updatedAt,
|
|
146
146
|
}))
|
|
147
147
|
}
|
|
148
148
|
|