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.
@@ -5,7 +5,7 @@
5
5
  * 设计目标:
6
6
  * - 一条 inbound 消息 → 一个完整的判断 → 一个 reply 字符串
7
7
  * - 状态机存内存(重启丢失也无所谓,向导本来就是短生命周期)
8
- * - 一句话直说能跳过任意向导步骤("目录 X" / "象限 N" / "模板 Y")
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 STEP_QUADRANT = 'quadrant'
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
- /** 解析"象限 N" / "quadrant N" / "Q1" 等;返回 1-4 或 null */
103
- function tryExtractQuadrant(text) {
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(/模板[::=\s]+([^\s,,;;]+)/)
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*模板/gi, '')
150
- s = s.replace(/[,,;;]?\s*模板[::=\s]+[^\s,,;;]+/gi, '')
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
- // quadrant: qt:q:<1..4>
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
- // 模板名也可能带描述 → 每行 1 个稳妥
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
- // 模板 hint 解析
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 = STEP_QUADRANT
453
- if (w.chosenWorkdir && w.chosenQuadrant) w.step = STEP_TEMPLATE
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 = STEP_QUADRANT
484
- const prompt = buildQuadrantPrompt()
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 = STEP_QUADRANT
493
- const prompt = buildQuadrantPrompt()
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 = STEP_QUADRANT
504
- const prompt = buildQuadrantPrompt()
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
- // ─── quadrant 步 ───
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},或模板名(自由/无)。\n\n${prompt.text}`,
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
- `象限 Q${w.chosenQuadrant || 2} · 目录 ${w.chosenWorkdir || '默认'} · 模板 ${w.chosenTemplate?.name || '自由模式'}`,
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
- `象限 Q${w.chosenQuadrant || 2} · 目录 ${w.chosenWorkdir || '默认'} · 模板 ${w.chosenTemplate?.name || '自由模式'}`,
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(` Q${w.chosenQuadrant || 2} · ${w.chosenWorkdir || '默认目录'} · ${w.chosenTemplate?.name || '自由模式'}`)
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(` 象限: Q${w.chosenQuadrant || 2}`)
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: `(已重启向导,跳过目录+象限步)\n${p.text}`, replyMarkup: p.replyMarkup }
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(目录+象限已识别)\n\n${p.text}`,
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(目录+象限已识别)\n\n${p.text}`,
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(目录+象限已识别)\n\n${p.text}`,
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:q:<1..4> 象限
1834
- * qt:t:<idx> 模板
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 = STEP_QUADRANT
1912
- const prompt = buildQuadrantPrompt()
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 step ─────────────────────────────
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
- chosenLabel: `Q${q} ${qLabel}`.trim(),
1936
- reply: prompt.text,
1937
- replyMarkup: prompt.replyMarkup,
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: '当前步骤不接受模板选择', action: 'invalid' }
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: '模板无效', action: 'invalid' }
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, quadrant} | null }]
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
- for (const q of QUADRANTS) {
2331
- const arr = groups.get(q.id)
2332
- if (!arr || arr.length === 0) continue
2333
- lines.push('')
2334
- lines.push(`Q${q.id} ${q.label}`)
2335
- for (const t of arr) {
2336
- const short = String(t.id).slice(0, 4)
2337
- const dirTag = t.workDir ? ${basename(t.workDir)}` : ''
2338
- const aiSessions = t.aiSessions || (t.aiSession ? [t.aiSession] : [])
2339
- const runningSid = aiSessions.find((s) => s?.sessionId && activeSids.has(s.sessionId))?.sessionId
2340
- const isRunning = !!runningSid
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,
@@ -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
  })
@@ -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
- const { quadrant, status, keyword } = req.query
17
- const list = db.listTodos({ quadrant, status, keyword })
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, quadrant, dueDate, workDir, brainstorm, appliedTemplateIds, parentId } = req.body || {}
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
- const q = parent ? parent.quadrant : (Number(quadrant) || 4)
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
- if (parent && patch.quadrant !== undefined && Number(patch.quadrant) !== parent.quadrant) {
104
- res.status(400).json({ ok: false, error: 'parent_quadrant_mismatch' })
105
- return
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
@@ -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 / byQuadrant / byModel
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
- quadrant: t.quadrant, completedAt: t.updatedAt,
145
+ completedAt: t.updatedAt,
146
146
  }))
147
147
  }
148
148