agentquad 0.4.5 → 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-By--XlP3.js → index-DkI6ZJx_.js} +411 -399
- 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 +7 -1
- package/src/claude-prompt-detector.js +72 -0
- package/src/cli.js +1 -1
- package/src/codex-hook-installer.js +1 -1
- package/src/codex-prompt-detector.js +104 -13
- package/src/config.js +33 -5
- package/src/db.js +77 -31
- package/src/export/todoMarkdown.js +1 -9
- package/src/lark-bot.js +44 -5
- package/src/mcp/tools/destructive/index.js +22 -16
- package/src/mcp/tools/openclaw/index.js +12 -16
- package/src/mcp/tools/read/index.js +7 -7
- package/src/mcp/tools/write/index.js +9 -6
- package/src/openclaw-bridge.js +176 -28
- package/src/openclaw-hook-installer.js +2 -1
- package/src/openclaw-hook.js +127 -9
- package/src/openclaw-wizard.js +168 -191
- package/src/permission-prompt.js +113 -31
- package/src/prompt-render.js +0 -8
- package/src/pty.js +183 -49
- package/src/routes/ai-terminal.js +90 -26
- package/src/routes/telegram-sync.js +7 -5
- package/src/routes/todos.js +8 -14
- package/src/server.js +90 -12
- package/src/session-input-dispatcher.js +48 -4
- package/src/stats/report.js +1 -6
- package/src/telegram-bot.js +82 -15
- package/src/telegram-loading-status.js +1 -1
- package/src/templates/claude-hooks/notify.js +1 -1
- package/src/templates/codex-hooks/notify.js +1 -1
- package/src/wiki/index.js +1 -1
- package/src/wiki/sources.js +0 -1
- package/dist-web/assets/index-8A0oLLcX.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
|
}
|
|
@@ -821,9 +757,9 @@ export function createOpenClawWizard({
|
|
|
821
757
|
if (!sessionId || !todoId) return { ok: false, reason: 'missing_args' }
|
|
822
758
|
if (!telegramBot?.createForumTopic) return { ok: false, reason: 'no_telegram_bot' }
|
|
823
759
|
|
|
824
|
-
//
|
|
825
|
-
const existing = openclaw?.resolveRoute?.(sessionId)
|
|
826
|
-
if (existing
|
|
760
|
+
// 已有 telegram 路由 → 跳过(per-channel resolveRoute;避免被 lark 路由误判)
|
|
761
|
+
const existing = openclaw?.resolveRoute?.(sessionId, 'telegram')
|
|
762
|
+
if (existing?.threadId) return { ok: true, action: 'already_bound' }
|
|
827
763
|
|
|
828
764
|
// DB 里已持久化(rehydrate 时常见)→ 重注路由就行
|
|
829
765
|
const todo = db.getTodo(todoId)
|
|
@@ -896,8 +832,9 @@ export function createOpenClawWizard({
|
|
|
896
832
|
if (!sessionId || !todoId) return { ok: false, reason: 'missing_args' }
|
|
897
833
|
if (!larkBot?.sendMessage) return { ok: false, reason: 'no_lark_bot' }
|
|
898
834
|
|
|
899
|
-
|
|
900
|
-
|
|
835
|
+
// 显式取 lark 路由,避免被 telegram 路由误判
|
|
836
|
+
const existing = openclaw?.resolveRoute?.(sessionId, 'lark')
|
|
837
|
+
if (existing?.rootMessageId) {
|
|
901
838
|
return { ok: true, action: 'already_bound' }
|
|
902
839
|
}
|
|
903
840
|
|
|
@@ -916,7 +853,7 @@ export function createOpenClawWizard({
|
|
|
916
853
|
|
|
917
854
|
const shortCode = String(todoId).replace(/[^a-zA-Z0-9]/g, '').slice(-4).toLowerCase() || 'auto'
|
|
918
855
|
const title = (todo.title || `todo-${shortCode}`).slice(0, 96)
|
|
919
|
-
const topicName =
|
|
856
|
+
const topicName = title.slice(0, 128)
|
|
920
857
|
const intro = [
|
|
921
858
|
`${topicName}`,
|
|
922
859
|
`AI 已启动(自动镜像 from web/CLI),后续输出会回复在这条消息的 thread 里。`,
|
|
@@ -1015,7 +952,7 @@ export function createOpenClawWizard({
|
|
|
1015
952
|
const todo = db.getTodo(sess.todoId)
|
|
1016
953
|
if (todo) {
|
|
1017
954
|
// 构造一个假 aiSession(DB 里没持久化,能跑就行)
|
|
1018
|
-
const route = openclaw.resolveRoute?.(sid) || {}
|
|
955
|
+
const route = openclaw.resolveRoute?.(sid, 'telegram') || {}
|
|
1019
956
|
const fakeAi = {
|
|
1020
957
|
sessionId: sid,
|
|
1021
958
|
tool: sess.tool,
|
|
@@ -1168,7 +1105,7 @@ export function createOpenClawWizard({
|
|
|
1168
1105
|
if (sess?.todoId) {
|
|
1169
1106
|
const todo = db.getTodo(sess.todoId)
|
|
1170
1107
|
if (todo) {
|
|
1171
|
-
const route = openclaw.resolveRoute?.(sid) || {}
|
|
1108
|
+
const route = openclaw.resolveRoute?.(sid, 'lark') || {}
|
|
1172
1109
|
const fakeAi = {
|
|
1173
1110
|
sessionId: sid,
|
|
1174
1111
|
tool: sess.tool,
|
|
@@ -1272,6 +1209,16 @@ export function createOpenClawWizard({
|
|
|
1272
1209
|
const routeKey = makeRouteKey(channel, chatId, threadId)
|
|
1273
1210
|
const isLarkThreadReply = channel === 'lark' && (threadId || rootMessageId)
|
|
1274
1211
|
|
|
1212
|
+
// 飞书无前缀建任务守门:channel + 配置 + 文本 + slash 守门
|
|
1213
|
+
// 调用方需自行加 newTaskGateOpen + targetSid 缺失等额外条件。
|
|
1214
|
+
function shouldLarkAutoCreate() {
|
|
1215
|
+
if (channel !== 'lark') return false
|
|
1216
|
+
if (getConfig?.()?.lark?.autoCreateTodo === false) return false
|
|
1217
|
+
if (!trimmed) return false
|
|
1218
|
+
if (/^\/[a-z][a-z0-9_]*\b/i.test(trimmed)) return false
|
|
1219
|
+
return true
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1275
1222
|
// Lark 任务话题/root 回复必须严格隔离到原始路由:不允许被全局 ask_user、新任务触发词、
|
|
1276
1223
|
// lastPush 或单活跃 session 等模糊 fallback 消费,避免把群内任务线程回复送到不相关会话。
|
|
1277
1224
|
//
|
|
@@ -1455,15 +1402,11 @@ export function createOpenClawWizard({
|
|
|
1455
1402
|
wizards.delete(routeKey)
|
|
1456
1403
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1457
1404
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1458
|
-
if (w.step === STEP_QUADRANT) {
|
|
1459
|
-
const p = buildQuadrantPrompt()
|
|
1460
|
-
return { reply: `(已重启向导,跳过目录步)\n${p.text}`, replyMarkup: p.replyMarkup }
|
|
1461
|
-
}
|
|
1462
1405
|
if (w.step === STEP_TEMPLATE) {
|
|
1463
1406
|
const tpls = db.listTemplates()
|
|
1464
1407
|
w.cachedTemplates = tpls
|
|
1465
1408
|
const p = buildTemplatePrompt(tpls)
|
|
1466
|
-
return { reply:
|
|
1409
|
+
return { reply: `(已重启向导,跳过目录步)\n${p.text}`, replyMarkup: p.replyMarkup }
|
|
1467
1410
|
}
|
|
1468
1411
|
const p = buildWorkdirPrompt(w.workdirOptions)
|
|
1469
1412
|
return { reply: `(已重启向导)\n${p.text}`, replyMarkup: p.replyMarkup, action: 'wizard_started' }
|
|
@@ -1478,20 +1421,12 @@ export function createOpenClawWizard({
|
|
|
1478
1421
|
if (newTaskGateOpen && NEW_TASK_TRIGGERS.some((re) => re.test(trimmed))) {
|
|
1479
1422
|
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1480
1423
|
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1481
|
-
if (w.step === STEP_QUADRANT) {
|
|
1482
|
-
const p = buildQuadrantPrompt()
|
|
1483
|
-
return {
|
|
1484
|
-
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir})\n\n${p.text}`,
|
|
1485
|
-
replyMarkup: p.replyMarkup,
|
|
1486
|
-
action: 'wizard_started',
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
1424
|
if (w.step === STEP_TEMPLATE) {
|
|
1490
1425
|
const tpls = db.listTemplates()
|
|
1491
1426
|
w.cachedTemplates = tpls
|
|
1492
1427
|
const p = buildTemplatePrompt(tpls)
|
|
1493
1428
|
return {
|
|
1494
|
-
reply: `任务: ${w.title}\n
|
|
1429
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1495
1430
|
replyMarkup: p.replyMarkup,
|
|
1496
1431
|
action: 'wizard_started',
|
|
1497
1432
|
}
|
|
@@ -1565,6 +1500,28 @@ export function createOpenClawWizard({
|
|
|
1565
1500
|
}
|
|
1566
1501
|
}
|
|
1567
1502
|
if (targetSid && typeof targetSid === 'object' && targetSid.notFound) {
|
|
1503
|
+
// 未绑定 lark thread 的首条消息:默认起新建任务向导(受 autoCreateTodo 控制)
|
|
1504
|
+
if (newTaskGateOpen && shouldLarkAutoCreate()) {
|
|
1505
|
+
logger.info?.(`[wizard] lark auto-create from non-prefix text (unbound thread): chatId=${chatId} thread=${threadId || '-'} title="${trimmed.slice(0, 80)}"`)
|
|
1506
|
+
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1507
|
+
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1508
|
+
if (w.step === STEP_TEMPLATE) {
|
|
1509
|
+
const tpls = db.listTemplates()
|
|
1510
|
+
w.cachedTemplates = tpls
|
|
1511
|
+
const p = buildTemplatePrompt(tpls)
|
|
1512
|
+
return {
|
|
1513
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1514
|
+
replyMarkup: p.replyMarkup,
|
|
1515
|
+
action: 'wizard_started',
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const p = buildWorkdirPrompt(w.workdirOptions)
|
|
1519
|
+
return {
|
|
1520
|
+
reply: `任务: ${w.title}\n\n${p.text}`,
|
|
1521
|
+
replyMarkup: p.replyMarkup,
|
|
1522
|
+
action: 'wizard_started',
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1568
1525
|
return {
|
|
1569
1526
|
reply: '没有找到对应运行中的任务',
|
|
1570
1527
|
action: 'session_not_found',
|
|
@@ -1718,6 +1675,30 @@ export function createOpenClawWizard({
|
|
|
1718
1675
|
}
|
|
1719
1676
|
}
|
|
1720
1677
|
|
|
1678
|
+
// 5.5 飞书无前缀建任务兜底:lark + autoCreateTodo + step 5 没匹配任何 PTY target →
|
|
1679
|
+
// 把消息原文当 title 起 wizard。Telegram/微信/openclaw 不受影响(channel 守门)。
|
|
1680
|
+
if (newTaskGateOpen && shouldLarkAutoCreate()) {
|
|
1681
|
+
logger.info?.(`[wizard] lark auto-create from non-prefix text: chatId=${chatId} thread=${threadId || '-'} title="${trimmed.slice(0, 80)}"`)
|
|
1682
|
+
const w = startWizard({ channel, chatId, threadId, text: trimmed, messageId, rootMessageId, imagePaths, userId: fromUserId })
|
|
1683
|
+
if (w.step === STEP_DONE) return await finalizeWizard(w)
|
|
1684
|
+
if (w.step === STEP_TEMPLATE) {
|
|
1685
|
+
const tpls = db.listTemplates()
|
|
1686
|
+
w.cachedTemplates = tpls
|
|
1687
|
+
const p = buildTemplatePrompt(tpls)
|
|
1688
|
+
return {
|
|
1689
|
+
reply: `任务: ${w.title}\n(目录已识别为 ${w.chosenWorkdir || '默认'})\n\n${p.text}`,
|
|
1690
|
+
replyMarkup: p.replyMarkup,
|
|
1691
|
+
action: 'wizard_started',
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
const p = buildWorkdirPrompt(w.workdirOptions)
|
|
1695
|
+
return {
|
|
1696
|
+
reply: `任务: ${w.title}\n\n${p.text}`,
|
|
1697
|
+
replyMarkup: p.replyMarkup,
|
|
1698
|
+
action: 'wizard_started',
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1721
1702
|
// 6. fallback
|
|
1722
1703
|
// General 频道里专门提示:保护 PTY 上下文不被污染
|
|
1723
1704
|
if (isInGeneralOfSupergroup) {
|
|
@@ -1757,9 +1738,8 @@ export function createOpenClawWizard({
|
|
|
1757
1738
|
* callback_data 协议:
|
|
1758
1739
|
* qt:wd:<idx> 工作目录(按 listWorkdirOptions 顺序)
|
|
1759
1740
|
* qt:wd:custom 自定义路径 → 进入 awaitingCustomWorkdir 子态,等下一条文本
|
|
1760
|
-
* qt:
|
|
1761
|
-
* qt:t
|
|
1762
|
-
* qt:t:none 自由模式(不套模板)
|
|
1741
|
+
* qt:t:<idx> agent(template;保留 t 前缀以避免破坏老的飞书卡片)
|
|
1742
|
+
* qt:t:none 自由模式(不指派 agent)
|
|
1763
1743
|
*
|
|
1764
1744
|
* 安全:所有未知 / 不匹配当前 step 的 callback 都返回 toast,从不 throw —— 让
|
|
1765
1745
|
* telegram-bot 始终能 answerCallbackQuery 关 loading。
|
|
@@ -1782,7 +1762,7 @@ export function createOpenClawWizard({
|
|
|
1782
1762
|
// ── 权限按钮路径(qt:perm:<short>:allow|deny)──────────────────
|
|
1783
1763
|
const permCb = parsePermissionCallback(callbackData)
|
|
1784
1764
|
if (permCb) {
|
|
1785
|
-
return handlePermissionCallback(permCb, { chatId, threadId })
|
|
1765
|
+
return handlePermissionCallback(permCb, { chatId, threadId, channel: args.channel || null })
|
|
1786
1766
|
}
|
|
1787
1767
|
if (callbackData.startsWith(`${CALLBACK_PREFIX}:${PERMISSION_CALLBACK_KIND}:`)) {
|
|
1788
1768
|
return { toast: '无效的权限按钮', action: 'invalid', editOriginal: true }
|
|
@@ -1835,8 +1815,10 @@ export function createOpenClawWizard({
|
|
|
1835
1815
|
return { toast: '选项无效', action: 'invalid' }
|
|
1836
1816
|
}
|
|
1837
1817
|
w.chosenWorkdir = w.workdirOptions[idx].path
|
|
1838
|
-
w.step =
|
|
1839
|
-
const
|
|
1818
|
+
w.step = STEP_TEMPLATE
|
|
1819
|
+
const templates = db.listTemplates()
|
|
1820
|
+
w.cachedTemplates = templates
|
|
1821
|
+
const prompt = buildTemplatePrompt(templates)
|
|
1840
1822
|
return {
|
|
1841
1823
|
chosenLabel: w.chosenWorkdir,
|
|
1842
1824
|
reply: prompt.text,
|
|
@@ -1845,31 +1827,19 @@ export function createOpenClawWizard({
|
|
|
1845
1827
|
}
|
|
1846
1828
|
}
|
|
1847
1829
|
|
|
1848
|
-
// ── quadrant
|
|
1830
|
+
// ── 已退役:qt:q (quadrant) callback。老飞书/Telegram 卡片仍可能携带 → 友好提示 ──
|
|
1849
1831
|
if (kind === 'q') {
|
|
1850
|
-
if (w.step !== STEP_QUADRANT) {
|
|
1851
|
-
return { toast: '当前步骤不接受象限选择', action: 'invalid' }
|
|
1852
|
-
}
|
|
1853
|
-
const q = Number(value)
|
|
1854
|
-
if (![1, 2, 3, 4].includes(q)) return { toast: '象限无效', action: 'invalid' }
|
|
1855
|
-
w.chosenQuadrant = q
|
|
1856
|
-
w.step = STEP_TEMPLATE
|
|
1857
|
-
const templates = db.listTemplates()
|
|
1858
|
-
w.cachedTemplates = templates
|
|
1859
|
-
const prompt = buildTemplatePrompt(templates)
|
|
1860
|
-
const qLabel = QUADRANTS.find((x) => x.id === q)?.label || ''
|
|
1861
1832
|
return {
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
action: 'wizard_step',
|
|
1833
|
+
toast: '象限步骤已移除,直接选 agent 即可',
|
|
1834
|
+
action: 'deprecated_quadrant',
|
|
1835
|
+
editOriginal: true,
|
|
1866
1836
|
}
|
|
1867
1837
|
}
|
|
1868
1838
|
|
|
1869
|
-
// ── template step ─────────────────────────────
|
|
1839
|
+
// ── template step (用户面叫 agent) ─────────────────────────────
|
|
1870
1840
|
if (kind === 't') {
|
|
1871
1841
|
if (w.step !== STEP_TEMPLATE) {
|
|
1872
|
-
return { toast: '
|
|
1842
|
+
return { toast: '当前步骤不接受 agent 选择', action: 'invalid' }
|
|
1873
1843
|
}
|
|
1874
1844
|
const templates = w.cachedTemplates || db.listTemplates()
|
|
1875
1845
|
let label
|
|
@@ -1879,7 +1849,7 @@ export function createOpenClawWizard({
|
|
|
1879
1849
|
} else {
|
|
1880
1850
|
const idx = Number(value)
|
|
1881
1851
|
if (!Number.isInteger(idx) || idx < 0 || idx >= templates.length) {
|
|
1882
|
-
return { toast: '
|
|
1852
|
+
return { toast: 'agent 无效', action: 'invalid' }
|
|
1883
1853
|
}
|
|
1884
1854
|
w.chosenTemplate = { id: templates[idx].id, name: templates[idx].name }
|
|
1885
1855
|
label = templates[idx].name
|
|
@@ -1899,30 +1869,51 @@ export function createOpenClawWizard({
|
|
|
1899
1869
|
}
|
|
1900
1870
|
|
|
1901
1871
|
// ─── 权限按钮回调 ───────────────────────────────────────────────
|
|
1902
|
-
async function handlePermissionCallback({ short, action } = {}, { chatId, threadId } = {}) {
|
|
1903
|
-
const stale = () =>
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1872
|
+
async function handlePermissionCallback({ short, action } = {}, { chatId, threadId, channel = null } = {}) {
|
|
1873
|
+
const stale = (why) => {
|
|
1874
|
+
logger.warn?.(`[wizard/perm] stale short=${short} action=${action} channel=${channel || 'null'} chatId=${chatId || 'null'} threadId=${threadId || 'null'} reason=${why}`)
|
|
1875
|
+
return {
|
|
1876
|
+
toast: '会话已结束',
|
|
1877
|
+
reply: `⚠️ 会话已结束(#${short}),无法发送权限选择。`,
|
|
1878
|
+
action: 'permission_session_stale',
|
|
1879
|
+
editOriginal: true,
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1909
1882
|
|
|
1910
1883
|
const sid = openclaw?.findSessionByShortId?.(short) || null
|
|
1911
|
-
if (!sid
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1884
|
+
if (!sid) return stale('no_sid_for_short')
|
|
1885
|
+
if (!pty?.has?.(sid)) return stale(`pty_no_session sid=${sid}`)
|
|
1886
|
+
// channel hint 关键:同一 session 经常同时绑 telegram + lark,resolveRoute(sid)
|
|
1887
|
+
// 不带 channel 时返回最后注册的 route(通常是 telegram),导致 lark 点击的 sameChat
|
|
1888
|
+
// 校验失败被误判为 stale。caller 已经知道点击来自哪个渠道,必须传过来。
|
|
1889
|
+
const route = openclaw?.resolveRoute?.(sid, channel) || null
|
|
1890
|
+
if (!route) return stale(`no_route sid=${sid} channel=${channel || 'null'}`)
|
|
1891
|
+
const sameChat = String(route.targetUserId) === String(chatId)
|
|
1892
|
+
// 飞书 card.action.trigger 事件实测经常不带 open_thread_id(即使卡片是在 thread
|
|
1893
|
+
// 里发的);用 sameThread 硬校验会把所有 lark click 卡成 stale。lark 渠道下
|
|
1894
|
+
// 信任 sameChat + shortid(已经锁到具体 session)就够了,threadId 只在 telegram
|
|
1895
|
+
// 渠道生效(topic 隔离才有意义)。
|
|
1896
|
+
const sameThread = route.channel === 'lark'
|
|
1897
|
+
? true
|
|
1898
|
+
: (route.threadId || null) === (threadId || null)
|
|
1915
1899
|
// 允许 telegram / lark 渠道的权限回调;老的"threadId 非空就算"留作 legacy 兜底。
|
|
1916
|
-
const isRoutedChannel = route
|
|
1917
|
-
if (!sameChat || !sameThread || !isRoutedChannel)
|
|
1900
|
+
const isRoutedChannel = route.channel === 'telegram' || route.channel === 'lark' || route.threadId != null
|
|
1901
|
+
if (!sameChat || !sameThread || !isRoutedChannel) {
|
|
1902
|
+
return stale(`route_mismatch sid=${sid} route=${JSON.stringify({channel: route.channel, targetUserId: route.targetUserId, threadId: route.threadId})} sameChat=${sameChat} sameThread=${sameThread} isRoutedChannel=${isRoutedChannel}`)
|
|
1903
|
+
}
|
|
1918
1904
|
|
|
1919
1905
|
if (action === PERMISSION_ACTION_ALLOW) {
|
|
1920
1906
|
try {
|
|
1921
1907
|
pty.write(sid, '\r')
|
|
1922
1908
|
} catch (e) {
|
|
1923
1909
|
logger.warn?.(`[wizard] permission allow write failed: ${e.message}`)
|
|
1924
|
-
return stale()
|
|
1910
|
+
return stale('write_failed_allow')
|
|
1925
1911
|
}
|
|
1912
|
+
// Lark click 直写 PTY 绕过了 /api/ai-terminal/input 路径,web 端的
|
|
1913
|
+
// permissionPrompt + 卡片不会自动消失。手工调一下 awaitingReply(false) 让
|
|
1914
|
+
// ai-terminal 走 markSessionRunningAfterInput → 翻 running、清 permissionPrompt、
|
|
1915
|
+
// 广播 pending_cleared,前端 webview 那张"AI 等待授权"卡随之消失。
|
|
1916
|
+
try { aiTerminal?.markSessionAwaitingReply?.(sid, false) } catch { /* ignore */ }
|
|
1926
1917
|
return {
|
|
1927
1918
|
toast: '已发送 Enter',
|
|
1928
1919
|
chosenLabel: '允许(Enter)',
|
|
@@ -1936,8 +1927,9 @@ export function createOpenClawWizard({
|
|
|
1936
1927
|
pty.write(sid, '\x1b')
|
|
1937
1928
|
} catch (e) {
|
|
1938
1929
|
logger.warn?.(`[wizard] permission deny write failed: ${e.message}`)
|
|
1939
|
-
return stale()
|
|
1930
|
+
return stale('write_failed_deny')
|
|
1940
1931
|
}
|
|
1932
|
+
try { aiTerminal?.markSessionAwaitingReply?.(sid, false) } catch { /* ignore */ }
|
|
1941
1933
|
return {
|
|
1942
1934
|
toast: '已发送 Esc',
|
|
1943
1935
|
chosenLabel: '拒绝/退出(Esc)',
|
|
@@ -2114,7 +2106,7 @@ export function createOpenClawWizard({
|
|
|
2114
2106
|
|
|
2115
2107
|
/**
|
|
2116
2108
|
* 找到所有活跃 AI session(status=running / idle / pending_confirm),返回带 todo 上下文的列表:
|
|
2117
|
-
* [{ sid, short, status, lastOutputAt, todo: {id, title, workDir
|
|
2109
|
+
* [{ sid, short, status, lastOutputAt, todo: {id, title, workDir} | null }]
|
|
2118
2110
|
*
|
|
2119
2111
|
* 数据源:
|
|
2120
2112
|
* - aiTerminal.sessions (in-memory PTY session map) —— 状态最准
|
|
@@ -2152,7 +2144,6 @@ export function createOpenClawWizard({
|
|
|
2152
2144
|
id: t.id,
|
|
2153
2145
|
title: t.title || '(未命名)',
|
|
2154
2146
|
workDir: t.workDir || '',
|
|
2155
|
-
quadrant: t.quadrant || 2,
|
|
2156
2147
|
}
|
|
2157
2148
|
}
|
|
2158
2149
|
}
|
|
@@ -2205,7 +2196,7 @@ export function createOpenClawWizard({
|
|
|
2205
2196
|
}
|
|
2206
2197
|
|
|
2207
2198
|
/**
|
|
2208
|
-
* /list 或 /pending —— 列未完成 todos
|
|
2199
|
+
* /list 或 /pending —— 列未完成 todos(扁平显示,按 updated_at 倒序)。
|
|
2209
2200
|
*
|
|
2210
2201
|
* 输出限制:30 条;超出加"去 web 看"提示。
|
|
2211
2202
|
* 状态显示:把 todo.aiSessions 里 running 的标 🟢,便于一眼看哪些任务在跑。
|
|
@@ -2225,29 +2216,18 @@ export function createOpenClawWizard({
|
|
|
2225
2216
|
const activeSids = new Set(findActiveSessions().map((s) => s.sid))
|
|
2226
2217
|
// dispatcher 排队信息:sid → queueSize(busy 期间累积的用户输入)
|
|
2227
2218
|
const dispatcherDesc = sessionInputDispatcher?.describe?.() || { byId: {} }
|
|
2228
|
-
const groups = new Map()
|
|
2229
|
-
for (const q of QUADRANTS) groups.set(q.id, [])
|
|
2230
|
-
for (const t of visible) {
|
|
2231
|
-
const arr = groups.get(t.quadrant) || groups.get(2)
|
|
2232
|
-
arr.push(t)
|
|
2233
|
-
}
|
|
2234
2219
|
const lines = [`📋 待办 (${todos.length}${todos.length > PAGE ? `, 仅显示前 ${PAGE}` : ''})`]
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const statusTag = isRunning ? '🟢' : '·'
|
|
2247
|
-
const queueSize = runningSid ? (dispatcherDesc.byId?.[runningSid]?.queueSize || 0) : 0
|
|
2248
|
-
const queueTag = queueSize > 0 ? ` 📥${queueSize}` : ''
|
|
2249
|
-
lines.push(` ${statusTag} ${short} ${t.title} ${dirTag}${queueTag}`)
|
|
2250
|
-
}
|
|
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}`)
|
|
2251
2231
|
}
|
|
2252
2232
|
if (todos.length > PAGE) {
|
|
2253
2233
|
const port = (getConfig?.()?.port) || 5677
|
|
@@ -2424,15 +2404,12 @@ export function createOpenClawWizard({
|
|
|
2424
2404
|
export const __test__ = {
|
|
2425
2405
|
extractTitle,
|
|
2426
2406
|
tryExtractWorkdir,
|
|
2427
|
-
tryExtractQuadrant,
|
|
2428
2407
|
tryExtractTemplateHint,
|
|
2429
2408
|
parseNumericChoice,
|
|
2430
2409
|
findTemplateByHint,
|
|
2431
2410
|
buildWorkdirMessage,
|
|
2432
|
-
buildQuadrantMessage,
|
|
2433
2411
|
buildTemplateMessage,
|
|
2434
2412
|
buildWorkdirReplyMarkup,
|
|
2435
|
-
buildQuadrantReplyMarkup,
|
|
2436
2413
|
buildTemplateReplyMarkup,
|
|
2437
2414
|
CALLBACK_PREFIX,
|
|
2438
2415
|
isGeneralChannel,
|