@winspan/claude-forge 8.35.0 → 8.37.0

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.
Files changed (115) hide show
  1. package/README.md +5 -0
  2. package/dist/agents/registry.d.ts +2 -1
  3. package/dist/agents/registry.d.ts.map +1 -1
  4. package/dist/agents/registry.js +17 -4
  5. package/dist/agents/registry.js.map +1 -1
  6. package/dist/cli/commands/init.d.ts.map +1 -1
  7. package/dist/cli/commands/init.js +5 -1
  8. package/dist/cli/commands/init.js.map +1 -1
  9. package/dist/cli/commands/mcp.d.ts +14 -0
  10. package/dist/cli/commands/mcp.d.ts.map +1 -0
  11. package/dist/cli/commands/mcp.js +164 -0
  12. package/dist/cli/commands/mcp.js.map +1 -0
  13. package/dist/cli/commands/menu.js +33 -0
  14. package/dist/cli/commands/menu.js.map +1 -1
  15. package/dist/cli/index.js +2 -0
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/core/ai/provider.d.ts +23 -1
  18. package/dist/core/ai/provider.d.ts.map +1 -1
  19. package/dist/core/ai/provider.js +67 -1
  20. package/dist/core/ai/provider.js.map +1 -1
  21. package/dist/core/ai/types.d.ts +28 -0
  22. package/dist/core/ai/types.d.ts.map +1 -1
  23. package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
  24. package/dist/daemon/handlers/user-prompt.js +25 -3
  25. package/dist/daemon/handlers/user-prompt.js.map +1 -1
  26. package/dist/daemon/index.d.ts.map +1 -1
  27. package/dist/daemon/index.js +7 -2
  28. package/dist/daemon/index.js.map +1 -1
  29. package/dist/daemon/routing-state.d.ts +14 -0
  30. package/dist/daemon/routing-state.d.ts.map +1 -1
  31. package/dist/daemon/routing-state.js +34 -0
  32. package/dist/daemon/routing-state.js.map +1 -1
  33. package/dist/engine/agent-router.d.ts +37 -0
  34. package/dist/engine/agent-router.d.ts.map +1 -1
  35. package/dist/engine/agent-router.js +58 -0
  36. package/dist/engine/agent-router.js.map +1 -1
  37. package/dist/engine/conventions/routing.yaml +31 -2
  38. package/dist/intelligence/classifier.d.ts +63 -43
  39. package/dist/intelligence/classifier.d.ts.map +1 -1
  40. package/dist/intelligence/classifier.js +256 -191
  41. package/dist/intelligence/classifier.js.map +1 -1
  42. package/dist/intelligence/context-gatherer.d.ts +101 -0
  43. package/dist/intelligence/context-gatherer.d.ts.map +1 -0
  44. package/dist/intelligence/context-gatherer.js +417 -0
  45. package/dist/intelligence/context-gatherer.js.map +1 -0
  46. package/dist/intelligence/cot-classifier.d.ts +95 -0
  47. package/dist/intelligence/cot-classifier.d.ts.map +1 -0
  48. package/dist/intelligence/cot-classifier.js +391 -0
  49. package/dist/intelligence/cot-classifier.js.map +1 -0
  50. package/dist/intelligence/execution-doc-builder.d.ts +90 -0
  51. package/dist/intelligence/execution-doc-builder.d.ts.map +1 -1
  52. package/dist/intelligence/execution-doc-builder.js +459 -42
  53. package/dist/intelligence/execution-doc-builder.js.map +1 -1
  54. package/dist/intelligence/intent-types.d.ts +13 -0
  55. package/dist/intelligence/intent-types.d.ts.map +1 -0
  56. package/dist/intelligence/intent-types.js +19 -0
  57. package/dist/intelligence/intent-types.js.map +1 -0
  58. package/dist/intelligence/multimodal-parser.d.ts +105 -0
  59. package/dist/intelligence/multimodal-parser.d.ts.map +1 -0
  60. package/dist/intelligence/multimodal-parser.js +425 -0
  61. package/dist/intelligence/multimodal-parser.js.map +1 -0
  62. package/dist/mcp/server.d.ts +58 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +201 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/skills/registry.d.ts.map +1 -1
  67. package/dist/skills/registry.js +2 -1
  68. package/dist/skills/registry.js.map +1 -1
  69. package/dist/skills/tools/skill-invoke.d.ts +5 -4
  70. package/dist/skills/tools/skill-invoke.d.ts.map +1 -1
  71. package/dist/skills/tools/skill-invoke.js +6 -5
  72. package/dist/skills/tools/skill-invoke.js.map +1 -1
  73. package/dist/web/server.d.ts +2 -0
  74. package/dist/web/server.d.ts.map +1 -1
  75. package/dist/web/server.js +13 -0
  76. package/dist/web/server.js.map +1 -1
  77. package/dist/web/static/assets/{AIConfig-DiUFET_Q.js → AIConfig-D4VglzCl.js} +2 -2
  78. package/dist/web/static/assets/{AIConfig-DiUFET_Q.js.map → AIConfig-D4VglzCl.js.map} +1 -1
  79. package/dist/web/static/assets/{Agents-bNNGbQnL.js → Agents-ne5lXc7V.js} +2 -2
  80. package/dist/web/static/assets/{Agents-bNNGbQnL.js.map → Agents-ne5lXc7V.js.map} +1 -1
  81. package/dist/web/static/assets/Dashboard-D4j0Zmek.js +2 -0
  82. package/dist/web/static/assets/Dashboard-D4j0Zmek.js.map +1 -0
  83. package/dist/web/static/assets/{Drawer-DOUcx6m1.js → Drawer-Lo5ihVP-.js} +2 -2
  84. package/dist/web/static/assets/{Drawer-DOUcx6m1.js.map → Drawer-Lo5ihVP-.js.map} +1 -1
  85. package/dist/web/static/assets/{Events-DQHP6Uaq.js → Events-DBJ1B7OW.js} +2 -2
  86. package/dist/web/static/assets/{Events-DQHP6Uaq.js.map → Events-DBJ1B7OW.js.map} +1 -1
  87. package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js → ExecutionTrace-Du9XADc1.js} +2 -2
  88. package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js.map → ExecutionTrace-Du9XADc1.js.map} +1 -1
  89. package/dist/web/static/assets/{Routing-BW3eGD-8.js → Routing-BNQ09OlH.js} +2 -2
  90. package/dist/web/static/assets/{Routing-BW3eGD-8.js.map → Routing-BNQ09OlH.js.map} +1 -1
  91. package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js → SessionDetail-BPrPyMNa.js} +2 -2
  92. package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js.map → SessionDetail-BPrPyMNa.js.map} +1 -1
  93. package/dist/web/static/assets/{Sessions-ZQSCgXyy.js → Sessions-o3EXsXz9.js} +2 -2
  94. package/dist/web/static/assets/{Sessions-ZQSCgXyy.js.map → Sessions-o3EXsXz9.js.map} +1 -1
  95. package/dist/web/static/assets/{Skills-C5-5zOSH.js → Skills-Czt5mkyc.js} +2 -2
  96. package/dist/web/static/assets/{Skills-C5-5zOSH.js.map → Skills-Czt5mkyc.js.map} +1 -1
  97. package/dist/web/static/assets/{export-CbQTOt71.js → export-C0mlC4AT.js} +2 -2
  98. package/dist/web/static/assets/{export-CbQTOt71.js.map → export-C0mlC4AT.js.map} +1 -1
  99. package/dist/web/static/assets/index-B1J7nBu0.js +3 -0
  100. package/dist/web/static/assets/index-B1J7nBu0.js.map +1 -0
  101. package/dist/web/static/assets/index-BVqk4bSO.css +1 -0
  102. package/dist/web/static/assets/{lucide-BanPULT1.js → lucide-Bu44HVAM.js} +33 -73
  103. package/dist/web/static/assets/lucide-Bu44HVAM.js.map +1 -0
  104. package/dist/web/static/index.html +3 -3
  105. package/package.json +2 -1
  106. package/dist/web/static/assets/Dashboard-Ciyyw6ph.js +0 -2
  107. package/dist/web/static/assets/Dashboard-Ciyyw6ph.js.map +0 -1
  108. package/dist/web/static/assets/Methodologies-CXNrDXwG.js +0 -5
  109. package/dist/web/static/assets/Methodologies-CXNrDXwG.js.map +0 -1
  110. package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js +0 -2
  111. package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js.map +0 -1
  112. package/dist/web/static/assets/index-DJK5beK6.js +0 -3
  113. package/dist/web/static/assets/index-DJK5beK6.js.map +0 -1
  114. package/dist/web/static/assets/index-phpuytMI.css +0 -1
  115. package/dist/web/static/assets/lucide-BanPULT1.js.map +0 -1
@@ -22,6 +22,70 @@
22
22
  import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
  import { logger } from '../core/utils/logger.js';
25
+ // ── 中文化映射(仅用于正文展示;frontmatter 仍使用英文 key 维持机器可读契约) ─────
26
+ /** TaskType → 中文标签(展示格式:`中文(英文)`) */
27
+ export const TASK_TYPE_CN = {
28
+ build_system: '搭建系统',
29
+ add_feature: '新增功能',
30
+ refactor: '重构代码',
31
+ migrate: '迁移代码',
32
+ fix_bug: '修复 Bug',
33
+ analyze_requirement: '需求分析',
34
+ design_architecture: '架构设计',
35
+ design_api: 'API 设计',
36
+ design_schema: 'Schema 设计',
37
+ write_code: '编写代码',
38
+ write_test: '编写测试',
39
+ review: '代码评审',
40
+ audit_security: '安全审计',
41
+ optimize_perf: '性能优化',
42
+ write_spec: '规范文档',
43
+ review_doc: '文档评审',
44
+ create_pr: '创建 PR',
45
+ explain: '代码解释',
46
+ setup_project: '项目初始化',
47
+ cleanup_code: '代码清理',
48
+ upgrade_dependency: '依赖升级',
49
+ improve_coverage: '提升覆盖率',
50
+ check_quality: '代码质量检查',
51
+ research: '技术调研',
52
+ investigate: '问题排查',
53
+ compare_solutions: '方案对比',
54
+ write_doc: '文档编写',
55
+ analyze_codebase: '代码库分析',
56
+ multi_domain: '跨领域任务',
57
+ other: '其他任务',
58
+ };
59
+ /** Complexity → 中文标签 */
60
+ export const COMPLEXITY_CN = {
61
+ simple: '简单',
62
+ moderate: '中等',
63
+ complex: '复杂',
64
+ };
65
+ /** ActionVerb → 中文标签(V2 字段,留作未来字段使用) */
66
+ export const ACTION_VERB_CN = {
67
+ investigate: '排查',
68
+ fix: '修复',
69
+ create: '创建',
70
+ modify: '修改',
71
+ delete: '删除',
72
+ verify: '验证',
73
+ explain: '解释',
74
+ setup: '配置',
75
+ migrate: '迁移',
76
+ research: '调研',
77
+ };
78
+ /** routed_to_type → 显示标签(仅做首字母大写 + 中文兜底) */
79
+ const ROUTED_TO_TYPE_CN = {
80
+ agent: 'Agent',
81
+ skill: 'Skill',
82
+ none: '未路由',
83
+ };
84
+ /**
85
+ * 从 `by-route/<routeId>/(overview|agent-*|skill-*).md` 返回索引的相对路径。
86
+ * `executions/by-route/<routeId>/x.md` → `executions/index.md` = `../../index.md`。
87
+ */
88
+ const BACK_TO_INDEX_LINK = '> [← 返回索引](../../index.md)';
25
89
  const DEFAULT_AI_TIMEOUT_MS = 15_000;
26
90
  const MAX_AI_SUMMARY_CHARS = 600;
27
91
  const MAX_TIMELINE_ROWS = 30;
@@ -159,6 +223,7 @@ export class ExecutionDocBuilder {
159
223
  events: postUseEvents,
160
224
  });
161
225
  const errorCount = toolEvents.filter(t => t.success === 0).length;
226
+ // Frontmatter 保持英文:机器读取契约(不要改成中文 key)
162
227
  const frontmatter = [
163
228
  '---',
164
229
  'type: agent_execution',
@@ -173,20 +238,24 @@ export class ExecutionDocBuilder {
173
238
  '---',
174
239
  ].join('\n');
175
240
  const promptPreview = this.truncate(route.prompt, 100);
241
+ const taskTypeDisplay = this.taskTypeLabel(taskType);
242
+ const complexityDisplay = this.complexityLabel(complexity);
176
243
  const body = [
177
244
  `# Agent 执行 · ${agentName}`,
178
245
  '',
246
+ BACK_TO_INDEX_LINK,
247
+ '',
179
248
  `> 路由追踪 · [Web 查看](http://localhost:3721/trace?id=${shortId})`,
180
249
  '',
181
250
  '## 一、概览',
182
251
  '',
183
252
  '| 项 | 值 |',
184
253
  '|---|---|',
185
- `| User Prompt | ${this.mdEscape(promptPreview)} |`,
186
- `| 路由决策 | ${taskType} + ${complexity} → ${agentName} |`,
187
- `| 遵守判定 | ${verdict.label} |`,
188
- `| 耗时 | ${this.formatDuration(durationMs)} |`,
189
- `| 工具调用 | ${toolCountsLine || '(无)'} |`,
254
+ `| 用户问题 | ${this.mdEscape(promptPreview)} |`,
255
+ `| 路由决策 | ${taskTypeDisplay} + ${complexityDisplay} → ${agentName} |`,
256
+ `| 是否遵守 | ${verdict.label} |`,
257
+ `| 耗时 | ${this.formatDurationHuman(durationMs)} |`,
258
+ `| 工具调用 | ${toolCountsLine || '(无)'} |`,
190
259
  '',
191
260
  '## 二、时间线',
192
261
  '',
@@ -196,7 +265,7 @@ export class ExecutionDocBuilder {
196
265
  '',
197
266
  '## 三、输入参数',
198
267
  '',
199
- '### Original Prompt',
268
+ '### 原始 Prompt',
200
269
  '',
201
270
  '```',
202
271
  route.prompt,
@@ -212,7 +281,7 @@ export class ExecutionDocBuilder {
212
281
  fileChanges.length > 0
213
282
  ? fileChanges
214
283
  .slice(0, MAX_FILE_CHANGES)
215
- .map(f => `- ${f.action} \`${f.path}\``)
284
+ .map(f => `- ${this.fileActionLabel(f.action)} \`${f.path}\``)
216
285
  .join('\n')
217
286
  : '_无文件改动_',
218
287
  '',
@@ -230,8 +299,8 @@ export class ExecutionDocBuilder {
230
299
  '| 项 | 值 |',
231
300
  '|---|---|',
232
301
  `| 工具事件数 | ${toolEvents.length} |`,
233
- `| 错误 | ${errorCount} |`,
234
- `| Skill 调用 | ${skillInvocations.length} |`,
302
+ `| 错误次数 | ${errorCount} |`,
303
+ `| Skill 调用次数 | ${skillInvocations.length} |`,
235
304
  '',
236
305
  ].join('\n');
237
306
  return `${frontmatter}\n\n${body}`;
@@ -239,6 +308,7 @@ export class ExecutionDocBuilder {
239
308
  // ── Skill execution rendering ─────────────────────────────────────────
240
309
  renderSkillExecution(inv, route) {
241
310
  const invokedAt = new Date(inv.timestamp).toISOString();
311
+ // Frontmatter 保持英文:机器读取契约
242
312
  const frontmatter = [
243
313
  '---',
244
314
  'type: skill_execution',
@@ -255,6 +325,8 @@ export class ExecutionDocBuilder {
255
325
  const body = [
256
326
  `# Skill 调用 · ${inv.skill_id}`,
257
327
  '',
328
+ BACK_TO_INDEX_LINK,
329
+ '',
258
330
  '## 一、调用上下文',
259
331
  '',
260
332
  '| 项 | 值 |',
@@ -263,8 +335,8 @@ export class ExecutionDocBuilder {
263
335
  `| 调用类型 | ${this.skillInvocationTypeLabel(inv.invocation_type)} |`,
264
336
  `| 发起者 | ${inv.agent_id ? `\`${inv.agent_id}\`` : '主 Claude'} |`,
265
337
  `| 嵌套深度 | ${inv.depth} |`,
266
- `| 调用时间 | ${invokedAt} |`,
267
- `| 执行结果 | ${inv.success === 1 ? '成功' : '失败'} |`,
338
+ `| 调用时间 | ${this.formatDateTimeHuman(invokedAt)} |`,
339
+ `| 执行结果 | ${inv.success === 1 ? '成功' : '失败'} |`,
268
340
  inv.error ? `| 错误信息 | ${this.mdEscape(inv.error)} |` : '',
269
341
  '',
270
342
  '## 二、Skill 内容概要',
@@ -282,8 +354,8 @@ export class ExecutionDocBuilder {
282
354
  '',
283
355
  '| 项 | 值 |',
284
356
  '|---|---|',
285
- `| Invocation ID | \`${inv.id}\` |`,
286
- `| Session ID | \`${inv.session_id}\` |`,
357
+ `| 调用 ID | \`${inv.id}\` |`,
358
+ `| 会话 ID | \`${inv.session_id}\` |`,
287
359
  `| 时间戳 | ${inv.timestamp} |`,
288
360
  '',
289
361
  ]
@@ -303,6 +375,8 @@ export class ExecutionDocBuilder {
303
375
  const completedAt = route.completed_ts
304
376
  ? new Date(route.completed_ts).toISOString()
305
377
  : '';
378
+ // Frontmatter 保持英文:机器读取契约
379
+ // prompt 字段供 index 生成器读取(避免它再去解析 body 表格)
306
380
  const frontmatter = [
307
381
  '---',
308
382
  'type: route_overview',
@@ -312,9 +386,10 @@ export class ExecutionDocBuilder {
312
386
  `completed_at: ${completedAt}`,
313
387
  `routed_to: ${route.routed_to_name ?? 'none'}`,
314
388
  `verdict: ${verdict.key}`,
389
+ `prompt: ${this.frontmatterValue(route.prompt)}`,
315
390
  '---',
316
391
  ].join('\n');
317
- // Build execution chain tree
392
+ // 执行链路树
318
393
  const treeLines = [];
319
394
  treeLines.push(`[Router] ${taskType} + ${complexity}`);
320
395
  if (route.routed_to_name) {
@@ -325,22 +400,27 @@ export class ExecutionDocBuilder {
325
400
  treeLines.push(`${prefix} [Skill] ${inv.skill_id} (${status})`);
326
401
  });
327
402
  }
403
+ const taskTypeDisplay = this.taskTypeLabel(taskType);
404
+ const complexityDisplay = this.complexityLabel(complexity);
405
+ const routedToDisplay = this.routedToLabel(route.routed_to_type ?? null, route.routed_to_name ?? null);
328
406
  const body = [
329
407
  `# 路由概览 · ${shortId}`,
330
408
  '',
409
+ BACK_TO_INDEX_LINK,
410
+ '',
331
411
  `> [Web 查看](http://localhost:3721/trace?id=${shortId})`,
332
412
  '',
333
413
  '## 一、路由决策',
334
414
  '',
335
415
  '| 项 | 值 |',
336
416
  '|---|---|',
337
- `| Prompt | ${this.mdEscape(this.truncate(route.prompt, 80))} |`,
338
- `| 任务类型 | ${taskType} |`,
339
- `| 复杂度 | ${complexity} |`,
340
- `| 路由目标 | ${route.routed_to_type ?? 'none'} → ${route.routed_to_name ?? 'N/A'} |`,
341
- `| 遵守判定 | ${verdict.label} |`,
342
- `| 分类耗时 | ${route.classification_ms ?? '-'}ms |`,
343
- `| 执行耗时 | ${this.formatDuration(route.total_execution_ms ?? null)} |`,
417
+ `| 用户问题 | ${this.mdEscape(this.truncate(route.prompt, 80))} |`,
418
+ `| 任务类型 | ${taskTypeDisplay} |`,
419
+ `| 复杂度 | ${complexityDisplay} |`,
420
+ `| 路由目标 | ${routedToDisplay} |`,
421
+ `| 是否遵守 | ${verdict.label} |`,
422
+ `| 分类耗时 | ${this.formatDurationHuman(route.classification_ms ?? null)} |`,
423
+ `| 执行耗时 | ${this.formatDurationHuman(route.total_execution_ms ?? null)} |`,
344
424
  '',
345
425
  '## 二、执行链路',
346
426
  '',
@@ -355,43 +435,288 @@ export class ExecutionDocBuilder {
355
435
  : '_无 Agent 文档_',
356
436
  ...skillInvocations.map((inv, idx) => {
357
437
  const seq = String(idx + 1).padStart(2, '0');
358
- return `- [Skill: ${inv.skill_id}](./skill-${this.slug(inv.skill_id)}-${seq}.md)`;
438
+ return `- [Skill:${inv.skill_id}](./skill-${this.slug(inv.skill_id)}-${seq}.md)`;
359
439
  }),
360
440
  '',
361
441
  '## 四、相关文档',
362
442
  '',
363
- '- [全局索引](../index.md)',
443
+ '- [全局索引](../../index.md)',
364
444
  '',
365
445
  ].join('\n');
366
446
  return `${frontmatter}\n\n${body}`;
367
447
  }
368
448
  // ── Index generation ──────────────────────────────────────────────────
449
+ /**
450
+ * 重写 index.md(人类友好视图)+ index-raw.md(原始技术视图)。
451
+ *
452
+ * 主索引为倒序:最新的执行排在最上面,列包含:
453
+ * - 倒序编号(# 列:最新条目数字最大,等于总数)
454
+ * - 时间(今天 HH:MM / 昨天 HH:MM / MM-DD HH:MM / YYYY-MM-DD HH:MM)
455
+ * - 任务描述(来自 overview.md frontmatter.prompt 或 body 中的 Prompt 行,截 40 字符)
456
+ * - 执行者(聚合 agent-*.md 文件名:单个/× N/最多列两个 +N)
457
+ * - 状态(基于 verdict 字段映射的中文标签)
458
+ * - 详情链接(相对路径)
459
+ *
460
+ * 容错策略:缺少 overview.md / frontmatter 解析失败 → 显示占位符,不中断生成。
461
+ */
369
462
  updateIndex(baseDir) {
370
463
  const byRouteDir = path.join(baseDir, 'by-route');
371
464
  if (!fs.existsSync(byRouteDir))
372
465
  return;
373
- const routeDirs = fs
466
+ const dirNames = fs
374
467
  .readdirSync(byRouteDir, { withFileTypes: true })
375
468
  .filter(d => d.isDirectory())
376
- .map(d => d.name)
377
- .sort();
469
+ .map(d => d.name);
470
+ // 收集每条记录的元数据
471
+ const entries = dirNames.map(d => this.collectIndexEntry(byRouteDir, d));
472
+ // 按 started_at 降序(最新在前)
473
+ entries.sort((a, b) => b.startedAtMs - a.startedAtMs);
474
+ const now = Date.now();
475
+ const indexMd = this.renderIndexMd(entries, now);
476
+ fs.writeFileSync(path.join(baseDir, 'index.md'), indexMd, 'utf-8');
477
+ const rawIndexMd = this.renderRawIndexMd(entries, now);
478
+ fs.writeFileSync(path.join(baseDir, 'index-raw.md'), rawIndexMd, 'utf-8');
479
+ }
480
+ /** 从单个 routeId 目录收集索引所需的元数据 */
481
+ collectIndexEntry(byRouteDir, dirName) {
482
+ const dirPath = path.join(byRouteDir, dirName);
483
+ let files = [];
484
+ try {
485
+ files = fs.readdirSync(dirPath);
486
+ }
487
+ catch {
488
+ files = [];
489
+ }
490
+ // agent-<slug>.md → 提取 <slug> 作为执行者标识
491
+ const agents = files
492
+ .filter(f => f.startsWith('agent-') && f.endsWith('.md'))
493
+ .map(f => f.slice('agent-'.length, -'.md'.length));
494
+ let prompt = '';
495
+ let verdict = '';
496
+ let startedAtMs = this.fallbackStartedAtFromDirName(dirName);
497
+ let hasOverview = false;
498
+ const overviewPath = path.join(dirPath, 'overview.md');
499
+ if (fs.existsSync(overviewPath)) {
500
+ hasOverview = true;
501
+ try {
502
+ const content = fs.readFileSync(overviewPath, 'utf-8');
503
+ const fm = this.parseFrontmatter(content);
504
+ const startedAt = fm.started_at;
505
+ if (startedAt) {
506
+ const parsed = Date.parse(startedAt);
507
+ if (!Number.isNaN(parsed))
508
+ startedAtMs = parsed;
509
+ }
510
+ verdict = fm.verdict ?? '';
511
+ // 优先用 frontmatter 中的 prompt(Agent B 可能后续加入)
512
+ if (fm.prompt) {
513
+ prompt = fm.prompt;
514
+ }
515
+ else {
516
+ prompt = this.extractPromptFromBody(content);
517
+ }
518
+ }
519
+ catch (err) {
520
+ logger.debug(`[ExecutionDoc] parse overview failed for ${dirName}: ${err}`);
521
+ }
522
+ }
523
+ return { dirName, startedAtMs, prompt, verdict, agents, files, hasOverview };
524
+ }
525
+ /** 兜底:当 overview.md 缺失时,从目录名 `<uuid>:<unix_ms>` 中解析时间 */
526
+ fallbackStartedAtFromDirName(dirName) {
527
+ const parts = dirName.split(':');
528
+ if (parts.length >= 2) {
529
+ const tail = parts[parts.length - 1];
530
+ const ts = parseInt(tail, 10);
531
+ if (!Number.isNaN(ts) && ts > 0)
532
+ return ts;
533
+ }
534
+ return 0;
535
+ }
536
+ /** 简化版 YAML frontmatter 解析(key: value 单行格式) */
537
+ parseFrontmatter(content) {
538
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
539
+ if (!m)
540
+ return {};
541
+ const result = {};
542
+ for (const line of m[1].split(/\r?\n/)) {
543
+ const idx = line.indexOf(':');
544
+ if (idx === -1)
545
+ continue;
546
+ const key = line.slice(0, idx).trim();
547
+ const value = line.slice(idx + 1).trim();
548
+ if (key)
549
+ result[key] = value;
550
+ }
551
+ return result;
552
+ }
553
+ /** 当 frontmatter 没有 prompt 时,从 body 的 "| Prompt | ... |" 行兜底提取 */
554
+ extractPromptFromBody(content) {
555
+ const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
556
+ const match = body.match(/^\|\s*Prompt\s*\|\s*([^|]*?)\s*\|/m);
557
+ return match ? match[1].trim() : '';
558
+ }
559
+ /** 渲染主索引(人类友好视图) */
560
+ renderIndexMd(entries, nowMs) {
378
561
  const lines = [
379
- '# Execution Documents Index',
562
+ '# 执行记录',
380
563
  '',
381
- `> 生成时间: ${new Date().toISOString()}`,
564
+ `> 生成时间: ${this.formatGenerationTime(nowMs)} · 共 ${entries.length} 次任务执行`,
382
565
  '',
383
- `共 ${routeDirs.length} 个路由执行记录。`,
566
+ ];
567
+ if (entries.length === 0) {
568
+ lines.push('_暂无执行记录_', '');
569
+ return lines.join('\n');
570
+ }
571
+ lines.push('| # | 时间 | 任务描述 | 执行者 | 状态 | 详情 |', '|---|---|---|---|---|---|');
572
+ entries.forEach((e, idx) => {
573
+ const seq = entries.length - idx; // 倒序编号,最新对应总数
574
+ const time = this.formatIndexTime(e.startedAtMs, nowMs);
575
+ const desc = this.truncatePromptForIndex(e.prompt, 40);
576
+ const agents = this.formatAgentList(e.agents);
577
+ const status = this.mapVerdictToStatus(e.verdict, e.hasOverview);
578
+ const link = `[打开](by-route/${encodeURI(e.dirName)}/)`;
579
+ lines.push(`| ${seq} | ${time} | ${this.mdEscape(desc)} | ${this.mdEscape(agents)} | ${status} | ${link} |`);
580
+ });
581
+ lines.push('', '> 提示:原始技术视图(routeId + 文件列表)见 [index-raw.md](./index-raw.md)', '');
582
+ return lines.join('\n');
583
+ }
584
+ /** 渲染原始技术视图(保留 routeId + 文件列表,供系统/Agent 使用) */
585
+ renderRawIndexMd(entries, nowMs) {
586
+ const lines = [
587
+ '# Execution Documents Index (Raw)',
588
+ '',
589
+ `> 生成时间: ${new Date(nowMs).toISOString()}`,
590
+ '',
591
+ `共 ${entries.length} 个路由执行记录(按 started_at 降序)。`,
384
592
  '',
385
- '| # | Route ID | 文件 |',
386
- '|---|---|---|',
593
+ '| # | Route ID | 开始时间 | 文件 |',
594
+ '|---|---|---|---|',
387
595
  ];
388
- routeDirs.forEach((dir, idx) => {
389
- const files = fs.readdirSync(path.join(byRouteDir, dir));
390
- const fileList = files.map(f => `\`${f}\``).join(', ');
391
- lines.push(`| ${idx + 1} | \`${dir}\` | ${fileList} |`);
596
+ entries.forEach((e, idx) => {
597
+ const seq = entries.length - idx;
598
+ const fileList = e.files.map(f => `\`${f}\``).join(', ') || '(空)';
599
+ const startedAt = e.startedAtMs > 0 ? new Date(e.startedAtMs).toISOString() : '-';
600
+ lines.push(`| ${seq} | \`${e.dirName}\` | ${startedAt} | ${fileList} |`);
392
601
  });
393
602
  lines.push('');
394
- fs.writeFileSync(path.join(baseDir, 'index.md'), lines.join('\n'), 'utf-8');
603
+ return lines.join('\n');
604
+ }
605
+ /** 索引头部生成时间:YYYY-MM-DD HH:MM */
606
+ formatGenerationTime(ms) {
607
+ const d = new Date(ms);
608
+ return (`${d.getFullYear()}-${this.pad2(d.getMonth() + 1)}-${this.pad2(d.getDate())} ` +
609
+ `${this.pad2(d.getHours())}:${this.pad2(d.getMinutes())}`);
610
+ }
611
+ /** 列表中的时间格式:今天/昨天/本年/跨年四档 */
612
+ formatIndexTime(startedAtMs, nowMs) {
613
+ if (startedAtMs <= 0)
614
+ return '—';
615
+ const d = new Date(startedAtMs);
616
+ const now = new Date(nowMs);
617
+ const sameYMD = (a, b) => a.getFullYear() === b.getFullYear() &&
618
+ a.getMonth() === b.getMonth() &&
619
+ a.getDate() === b.getDate();
620
+ if (sameYMD(d, now)) {
621
+ return `${this.pad2(d.getHours())}:${this.pad2(d.getMinutes())}`;
622
+ }
623
+ const yesterday = new Date(now.getTime());
624
+ yesterday.setDate(yesterday.getDate() - 1);
625
+ if (sameYMD(d, yesterday)) {
626
+ return `昨天 ${this.pad2(d.getHours())}:${this.pad2(d.getMinutes())}`;
627
+ }
628
+ if (d.getFullYear() === now.getFullYear()) {
629
+ return (`${this.pad2(d.getMonth() + 1)}-${this.pad2(d.getDate())} ` +
630
+ `${this.pad2(d.getHours())}:${this.pad2(d.getMinutes())}`);
631
+ }
632
+ return (`${d.getFullYear()}-${this.pad2(d.getMonth() + 1)}-${this.pad2(d.getDate())} ` +
633
+ `${this.pad2(d.getHours())}:${this.pad2(d.getMinutes())}`);
634
+ }
635
+ pad2(n) {
636
+ return n.toString().padStart(2, '0');
637
+ }
638
+ /**
639
+ * 任务描述清洗与截断。
640
+ * - 剥离系统消息标签(<task-notification>, <system-reminder>, <local-command-*>)
641
+ * 只保留标签内的实际任务内容;如果整段都是系统消息则降级到 "[系统通知]"
642
+ * - 移除常见 markdown 噪声([Image #N] 等)
643
+ * - 多行/多空白归一为单空格
644
+ * - 超过 maxLen 字符(基于 JS 字符串 length,对中文 1:1)→ 尾加 "..."
645
+ * - 空字符串 → "—"
646
+ */
647
+ truncatePromptForIndex(text, maxLen) {
648
+ if (!text)
649
+ return '—';
650
+ let cleaned = text;
651
+ // 1. 识别系统消息:若整段以 <task-notification> 等系统标签开头,
652
+ // 标记为系统通知(这些不是真实用户输入,而是后台任务完成回调等)
653
+ const isSystemMessage = /^\s*<(task-notification|system-reminder|local-command-stdout|local-command-stderr)\b/i.test(cleaned);
654
+ if (isSystemMessage) {
655
+ // 尝试从 <summary> 或 <result> 标签提取人类可读摘要
656
+ const summaryMatch = cleaned.match(/<summary>([\s\S]*?)<\/summary>/i);
657
+ if (summaryMatch && summaryMatch[1].trim()) {
658
+ cleaned = `[系统] ${summaryMatch[1].trim()}`;
659
+ }
660
+ else {
661
+ return '[系统通知]';
662
+ }
663
+ }
664
+ // 2. 剥离 XML/HTML 风格标签内容(保留文本)
665
+ cleaned = cleaned.replace(/<[^>]+>/g, ' ');
666
+ // 3. 移除图片标记和空白归一
667
+ cleaned = cleaned
668
+ .replace(/\[Image\s*#?\d+\]/gi, '')
669
+ .replace(/\r?\n+/g, ' ')
670
+ .replace(/\s+/g, ' ')
671
+ .trim();
672
+ if (!cleaned)
673
+ return '—';
674
+ if (cleaned.length <= maxLen)
675
+ return cleaned;
676
+ return cleaned.slice(0, maxLen) + '...';
677
+ }
678
+ /**
679
+ * 执行者列表格式化:
680
+ * - 0 个 → "—"
681
+ * - 1 个 → "name"
682
+ * - 多个同名(理论上文件名不会重复,但兼容) → "name × N"
683
+ * - 2 个不同 → "a, b"
684
+ * - 多于 2 个不同 → "a, b, +N"
685
+ */
686
+ formatAgentList(agents) {
687
+ if (agents.length === 0)
688
+ return '—';
689
+ const counts = new Map();
690
+ for (const a of agents) {
691
+ counts.set(a, (counts.get(a) ?? 0) + 1);
692
+ }
693
+ const entries = [...counts.entries()];
694
+ const render = ([name, c]) => c > 1 ? `${name} × ${c}` : name;
695
+ if (entries.length === 1)
696
+ return render(entries[0]);
697
+ if (entries.length <= 2)
698
+ return entries.map(render).join(', ');
699
+ const head = entries.slice(0, 2).map(render).join(', ');
700
+ const rest = entries.length - 2;
701
+ return `${head}, +${rest}`;
702
+ }
703
+ /** verdict → 状态展示标签 */
704
+ mapVerdictToStatus(verdict, hasOverview) {
705
+ if (!hasOverview)
706
+ return '❓ 未知';
707
+ switch (verdict) {
708
+ case 'obeyed':
709
+ case 'not_applicable':
710
+ return '✅ 已完成';
711
+ case 'disobeyed':
712
+ return '⚠️ 未遵守';
713
+ case 'timeout':
714
+ return '⏱️ 超时';
715
+ case 'pending':
716
+ return '🔄 进行中';
717
+ default:
718
+ return verdict ? `❓ ${verdict}` : '❓ 未知';
719
+ }
395
720
  }
396
721
  // ── AI summary ────────────────────────────────────────────────────────
397
722
  async tryGenerateSummary(ctx) {
@@ -482,7 +807,7 @@ export class ExecutionDocBuilder {
482
807
  return '_无子任务调用_';
483
808
  const header = '| # | Skill ID | 类型 | 结果 | 时间 |\n|---|---|---|---|---|';
484
809
  const rows = invocations.map((inv, idx) => {
485
- const status = inv.success === 1 ? 'OK' : 'FAIL';
810
+ const status = inv.success === 1 ? '✅ 成功' : '❌ 失败';
486
811
  const time = this.formatTime(new Date(inv.timestamp).toISOString());
487
812
  const typeLabel = this.skillInvocationTypeLabel(inv.invocation_type);
488
813
  return `| ${idx + 1} | \`${inv.skill_id}\` | ${typeLabel} | ${status} | ${time} |`;
@@ -508,14 +833,14 @@ export class ExecutionDocBuilder {
508
833
  }
509
834
  verdictLabel(route) {
510
835
  if (!route.routed_to_name)
511
- return { key: 'not_applicable', label: '不适用' };
836
+ return { key: 'not_applicable', label: '不适用' };
512
837
  if (route.obeyed === 1)
513
- return { key: 'obeyed', label: '已遵守' };
838
+ return { key: 'obeyed', label: '已遵守' };
514
839
  if (route.obeyed === 0)
515
- return { key: 'disobeyed', label: '未遵守' };
840
+ return { key: 'disobeyed', label: '未遵守' };
516
841
  if (route.completion_reason === 'timeout')
517
- return { key: 'timeout', label: '超时' };
518
- return { key: 'pending', label: '待判定' };
842
+ return { key: 'timeout', label: '⏱️ 超时' };
843
+ return { key: 'pending', label: '🔄 待判定' };
519
844
  }
520
845
  countTools(events) {
521
846
  const counts = new Map();
@@ -543,6 +868,98 @@ export class ExecutionDocBuilder {
543
868
  return `${(ms / 1000).toFixed(1)}s`;
544
869
  return `${(ms / 60_000).toFixed(1)}min`;
545
870
  }
871
+ /**
872
+ * 人性化耗时:
873
+ * - null/<=0 → "—"
874
+ * - < 1s → "<1 秒"
875
+ * - < 60s → "X.X 秒"(一位小数,整秒去掉 .0)
876
+ * - < 60min → "X 分 Y 秒"(整分钟省略秒部分)
877
+ * - >= 60min → "X 小时 Y 分"
878
+ */
879
+ formatDurationHuman(ms) {
880
+ if (ms === null || ms <= 0)
881
+ return '—';
882
+ if (ms < 1000)
883
+ return '<1 秒';
884
+ if (ms < 60_000) {
885
+ const secs = ms / 1000;
886
+ const rounded = Math.round(secs * 10) / 10;
887
+ const display = Number.isInteger(rounded)
888
+ ? rounded.toFixed(0)
889
+ : rounded.toFixed(1);
890
+ return `${display} 秒`;
891
+ }
892
+ if (ms < 3_600_000) {
893
+ const totalSec = Math.round(ms / 1000);
894
+ const mins = Math.floor(totalSec / 60);
895
+ const secs = totalSec % 60;
896
+ return secs === 0 ? `${mins} 分钟` : `${mins} 分 ${secs} 秒`;
897
+ }
898
+ const totalMin = Math.round(ms / 60_000);
899
+ const hours = Math.floor(totalMin / 60);
900
+ const mins = totalMin % 60;
901
+ return mins === 0 ? `${hours} 小时` : `${hours} 小时 ${mins} 分`;
902
+ }
903
+ /** TaskType 显示:`中文(英文)`,未知值原样返回。 */
904
+ taskTypeLabel(taskType) {
905
+ const cn = TASK_TYPE_CN[taskType];
906
+ return cn ? `${cn}(${taskType})` : taskType;
907
+ }
908
+ /** Complexity 显示:`中文(英文)`,未知值原样返回。 */
909
+ complexityLabel(complexity) {
910
+ const cn = COMPLEXITY_CN[complexity];
911
+ return cn ? `${cn}(${complexity})` : complexity;
912
+ }
913
+ /** 路由目标:`Agent → name` / `Skill → name` / `未路由`。 */
914
+ routedToLabel(type, name) {
915
+ if (!name)
916
+ return ROUTED_TO_TYPE_CN.none;
917
+ if (type === 'agent' || type === 'skill') {
918
+ return `${ROUTED_TO_TYPE_CN[type]} → ${name}`;
919
+ }
920
+ return name;
921
+ }
922
+ /** 文件改动动作:英文 → 中文动词。 */
923
+ fileActionLabel(action) {
924
+ if (action === 'Edit')
925
+ return '修改';
926
+ if (action === 'Write')
927
+ return '写入';
928
+ return action;
929
+ }
930
+ /**
931
+ * 人性化日期时间:`YYYY-MM-DD HH:mm:ss`(本地时区)
932
+ * 失败时回退到原值。
933
+ */
934
+ formatDateTimeHuman(isoOrTs) {
935
+ try {
936
+ const d = new Date(isoOrTs);
937
+ if (Number.isNaN(d.getTime()))
938
+ return isoOrTs;
939
+ const pad = (n) => n.toString().padStart(2, '0');
940
+ return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
941
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);
942
+ }
943
+ catch {
944
+ return isoOrTs;
945
+ }
946
+ }
947
+ /**
948
+ * YAML frontmatter 单行字符串值(无引号,依赖 parseFrontmatter 的 indexOf(':') 切分):
949
+ * - 去除换行 / 多余空白(避免破坏行结构)
950
+ * - 去除前导 `---`(避免与 frontmatter 终止符歧义)
951
+ * - 不加引号 —— Agent A 的 parseFrontmatter 不做反引号处理
952
+ *
953
+ * 注意:若 prompt 很长,索引层会再做截断展示。
954
+ */
955
+ frontmatterValue(text) {
956
+ const sanitized = text
957
+ .replace(/\r?\n+/g, ' ')
958
+ .replace(/\s+/g, ' ')
959
+ .replace(/^-{3,}/, '')
960
+ .trim();
961
+ return sanitized;
962
+ }
546
963
  formatTime(isoOrTs) {
547
964
  try {
548
965
  const d = new Date(isoOrTs);