evolcore 0.0.2 → 0.0.4

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 (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -3,6 +3,7 @@ import { getCodexEfforts } from '../../agents/codex-runner.js';
3
3
  import { buildEnvelope } from '../message/message-utils.js';
4
4
  import { resolvePaths, getPackageRoot } from '../../paths.js';
5
5
  import { logger } from '../../utils/logger.js';
6
+ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
6
7
  import crypto from 'crypto';
7
8
  import path from 'path';
8
9
  import fs from 'fs';
@@ -186,7 +187,7 @@ function triggerOperationForSlash(content) {
186
187
  return 'trigger.show';
187
188
  if (subcommand === 'history')
188
189
  return 'trigger.history';
189
- if (subcommand === 'set')
190
+ if (subcommand === 'create' || subcommand === 'set')
190
191
  return 'trigger.create';
191
192
  if (subcommand === 'update')
192
193
  return 'trigger.update';
@@ -351,7 +352,7 @@ async function getGitWorkingDirInfo(projectPath) {
351
352
  return null; // git 命令失败时静默返回 null
352
353
  }
353
354
  }
354
- export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject) {
355
+ export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext) {
355
356
  // 卡片回调的 chatType 不可靠(飞书 bot 单聊 chatId 也是 oc_ 前缀),
356
357
  // 不应覆盖 session 中已有的正确值
357
358
  if (source === 'card-trigger')
@@ -431,9 +432,23 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
431
432
  ...params,
432
433
  subject: authSubject,
433
434
  });
434
- // roleGuard 仅对进程级命令(/restart /reload)放行 daemon owner 绕过,
435
+ const canAccessSlashOperation = (operation, scope) => {
436
+ const args = scope === 'relation'
437
+ ? buildSlashRelationIntentArgs({ selfAid: authSubject.selfAid, peerKey: authSubject.peerKey })
438
+ : {};
439
+ return authorizeOperation({
440
+ source: 'slash',
441
+ intent: { operation, scope, source: 'slash', args },
442
+ subject: authSubject,
443
+ audit: false,
444
+ }).allow;
445
+ };
446
+ // roleGuard 仅对进程级命令(/restart /upgrade /reload)放行 daemon owner 绕过,
435
447
  // 其余命令严格按 agent-channel 的 isAdmin 判定,不越权。
436
- const isProcessLevelSlash = normalizedContent === '/restart' || normalizedContent === '/reload' || normalizedContent.startsWith('/reload ');
448
+ const isProcessLevelSlash = normalizedContent === '/restart'
449
+ || normalizedContent === '/upgrade'
450
+ || normalizedContent === '/reload'
451
+ || normalizedContent.startsWith('/reload ');
437
452
  const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
438
453
  if (roleGuard)
439
454
  return roleGuard;
@@ -458,18 +473,50 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
458
473
  return undefined;
459
474
  // /help 命令不需要会话
460
475
  if (normalizedContent === '/help') {
476
+ const canReadModel = canAccessSlashOperation('model.current', 'relation')
477
+ || canAccessSlashOperation('model.list', 'relation');
478
+ const canUseModel = canAccessSlashOperation('model.use', 'relation');
479
+ const canSetEffort = canAccessSlashOperation('model.effort', 'relation');
480
+ const canReadPermission = canAccessSlashOperation('permission.current', 'relation');
481
+ const canAnswerPermission = canAccessSlashOperation('permission.answer', 'relation');
482
+ const canReadChatmode = canAccessSlashOperation('chatmode.current', 'relation');
483
+ const canSetChatmode = canAccessSlashOperation('chatmode.update', 'relation');
484
+ const canReadMentionmode = activeChatType === 'group'
485
+ && canAccessSlashOperation('mentionmode.current', 'relation');
486
+ const canSetMentionmode = activeChatType === 'group'
487
+ && canAccessSlashOperation('mentionmode.update', 'relation');
488
+ const canUseFile = canAccessSlashOperation('file.fetch', 'filesystem');
461
489
  const appendProcessCommands = (lines) => {
462
490
  if (!isDaemonOwner)
463
491
  return;
464
- lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /reload [aid] - 热重载 Agent 配置');
492
+ lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置');
465
493
  };
466
494
  if (!isAdmin && activeChatType === 'group') {
467
495
  const lines = [
468
496
  '可用命令:',
497
+ ...((canReadModel || canUseModel || canSetEffort) ? [
498
+ '',
499
+ '🤖 Agent 与模型:',
500
+ ...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
501
+ ...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
502
+ ] : []),
503
+ ...((canReadChatmode || canSetChatmode || canReadMentionmode || canSetMentionmode) ? [
504
+ '',
505
+ '💬 聊天设置:',
506
+ ...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`] : []),
507
+ ...(canReadMentionmode || canSetMentionmode ? [` /mentionmode${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '或切换' : ''}群聊 @ 处理模式`] : []),
508
+ ] : []),
509
+ ...(canReadPermission || canAnswerPermission ? [
510
+ '',
511
+ '🔐 权限管理:',
512
+ ...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
513
+ ...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
514
+ ] : []),
469
515
  '',
470
516
  '其他:',
471
517
  ' /status - 显示会话状态',
472
518
  ' /check - 检查 EvolAgent 实例健康',
519
+ ...(canUseFile ? [' /file <path> - 发送项目内文件'] : []),
473
520
  ' /help - 显示此帮助信息',
474
521
  ];
475
522
  appendProcessCommands(lines);
@@ -486,6 +533,28 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
486
533
  ' /del <名称> - 删除指定会话(仅解绑,不删除文件)',
487
534
  ' /status - 显示会话状态',
488
535
  ' /check - 检查 EvolAgent 实例健康',
536
+ ...((canReadModel || canUseModel || canSetEffort) ? [
537
+ '',
538
+ '🤖 Agent 与模型:',
539
+ ...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
540
+ ...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
541
+ ] : []),
542
+ ...((canReadChatmode || canSetChatmode) ? [
543
+ '',
544
+ '💬 聊天设置:',
545
+ ` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`,
546
+ ] : []),
547
+ ...(canReadPermission || canAnswerPermission ? [
548
+ '',
549
+ '🔐 权限管理:',
550
+ ...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
551
+ ...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
552
+ ] : []),
553
+ ...(canUseFile ? [
554
+ '',
555
+ '🧰 工具:',
556
+ ' /file <path> - 发送项目内文件',
557
+ ] : []),
489
558
  '',
490
559
  '❓ 帮助:',
491
560
  ' /help - 显示此帮助信息',
@@ -511,18 +580,18 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
511
580
  '',
512
581
  '🤖 Agent 与模型:',
513
582
  ' /baseagent [name] - 查看或切换 Agent 后端(别名: /base)',
514
- ' /model [model] - 查看或切换模型',
515
- ' /effort [level] - 查看或切换推理强度',
583
+ ...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
584
+ ...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
516
585
  '',
517
586
  '💬 聊天设置:',
518
587
  ' /activity [all|text|none] - 查看/控制中间输出显示模式',
519
- ' /chatmode [interactive|proactive] - 查看/切换会话模式(被动响应或主动推进)',
520
- ' /mentionmode [mention-only|disabled] - 查看/切换群聊 @ 处理模式(仅@响应或全部响应,仅群聊)',
588
+ ...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '/切换' : ''}会话模式(被动响应或主动推进)`] : []),
589
+ ...(canReadMentionmode || canSetMentionmode ? [` /mentionmode${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '/切换' : ''}群聊 @ 处理模式`] : []),
521
590
  '',
522
591
  '🔐 权限管理:',
523
- ' /perm - 查看当前权限模式',
524
- ...(isAdmin ? [' 权限模式请通过角色策略编辑器修改'] : []),
525
- ' /perm allow|always|deny - 审批权限请求',
592
+ ...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
593
+ ...(canReadPermission ? [' 权限模式请通过角色策略编辑器修改'] : []),
594
+ ...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
526
595
  '',
527
596
  '🛠️ 运维:',
528
597
  ' /status - 显示会话状态',
@@ -538,7 +607,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
538
607
  ...(!isDaemonOwner && isAdmin ? [
539
608
  ' /reload - 热重载当前 Agent 配置',
540
609
  ] : []),
541
- ...(isAdmin ? [
610
+ ...(canUseFile ? [
542
611
  '',
543
612
  '🧰 工具:',
544
613
  ` /file ${isOwner ? '[channel] ' : ''}<path> - 发送项目内文件`,
@@ -552,6 +621,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
552
621
  // /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
553
622
  if (normalizedContent === '/evolhelp') {
554
623
  const cmds = [];
624
+ const canReadModel = canAccessSlashOperation('model.current', 'relation')
625
+ || canAccessSlashOperation('model.list', 'relation');
626
+ const canUseModel = canAccessSlashOperation('model.use', 'relation');
627
+ const canSetEffort = canAccessSlashOperation('model.effort', 'relation');
628
+ const canReadPermission = canAccessSlashOperation('permission.current', 'relation');
629
+ const canAnswerPermission = canAccessSlashOperation('permission.answer', 'relation');
630
+ const canReadChatmode = canAccessSlashOperation('chatmode.current', 'relation');
631
+ const canSetChatmode = canAccessSlashOperation('chatmode.update', 'relation');
632
+ const canReadMentionmode = activeChatType === 'group'
633
+ && canAccessSlashOperation('mentionmode.current', 'relation');
634
+ const canSetMentionmode = activeChatType === 'group'
635
+ && canAccessSlashOperation('mentionmode.update', 'relation');
636
+ const canUseFile = canAccessSlashOperation('file.fetch', 'filesystem');
555
637
  // 项目
556
638
  cmds.push({ command: '/pwd', description: '显示当前项目路径', category: '项目', roles: ['admin', 'owner'] });
557
639
  // 会话管理
@@ -565,16 +647,17 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
565
647
  cmds.push({ command: '/compact', description: '压缩会话上下文(减少 token 用量)', category: '会话管理', roles: ['admin', 'owner'] });
566
648
  }
567
649
  // Agent 与模型
568
- if (isAdmin) {
650
+ if (isAdmin)
569
651
  cmds.push({ command: '/baseagent', aliases: ['/base'], args: '[name]', description: '查看或切换 Agent 后端', category: 'Agent 与模型', roles: ['admin', 'owner'] });
570
- cmds.push({ command: '/model', args: '[model]', description: '查看或切换模型', category: 'Agent 与模型', roles: ['admin', 'owner'] });
571
- cmds.push({ command: '/effort', args: '[level]', description: '查看或切换推理强度', category: 'Agent 与模型', roles: ['admin', 'owner'] });
572
- }
652
+ if (canReadModel || canUseModel)
653
+ cmds.push({ command: '/model', ...(canUseModel ? { args: '[model]' } : {}), description: `查看${canUseModel ? '或切换' : ''}模型`, category: 'Agent 与模型', roles: [identity.role] });
654
+ if (canSetEffort)
655
+ cmds.push({ command: '/effort', args: '[level]', description: '查看或切换推理强度', category: 'Agent 与模型', roles: [identity.role] });
573
656
  // 权限管理
574
- if (isAdmin) {
575
- cmds.push({ command: '/perm', description: '查看当前角色决定的权限模式', category: '权限管理', roles: ['admin', 'owner'] });
576
- cmds.push({ command: '/perm', args: 'allow|always|deny', description: '审批权限请求', category: '权限管理', roles: ['admin', 'owner'] });
577
- }
657
+ if (canReadPermission)
658
+ cmds.push({ command: '/perm', description: '查看当前角色决定的权限模式', category: '权限管理', roles: [identity.role] });
659
+ if (canAnswerPermission)
660
+ cmds.push({ command: '/perm', args: 'allow|always|deny', description: '审批权限请求', category: '权限管理', roles: [identity.role] });
578
661
  // 运维
579
662
  cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
580
663
  cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
@@ -587,19 +670,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
587
670
  }
588
671
  if (isDaemonOwner) {
589
672
  cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['daemon-owner'] });
673
+ cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
590
674
  cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
591
675
  }
592
676
  if (!isDaemonOwner && isAdmin) {
593
677
  cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
594
678
  }
595
- if (isAdmin) {
596
- cmds.push({ command: '/file', args: isOwner ? '[channel] <path>' : '<path>', description: '发送项目内文件', category: '工具', roles: ['admin', 'owner'] });
597
- }
679
+ if (canUseFile)
680
+ cmds.push({ command: '/file', args: isOwner ? '[channel] <path>' : '<path>', description: '发送项目内文件', category: '工具', roles: [identity.role] });
598
681
  // 聊天设置
599
- if (isAdmin) {
600
- cmds.push({ command: '/chatmode', args: '[interactive|proactive]', description: '查看/切换会话模式(被动响应或主动推进)', category: '聊天设置', roles: ['admin', 'owner'] });
601
- cmds.push({ command: '/mentionmode', args: '[mention-only|disabled]', description: '查看/切换群聊 @ 处理模式(仅@响应或全部响应)', category: '聊天设置', roles: ['admin', 'owner'] });
602
- }
682
+ if (canReadChatmode || canSetChatmode)
683
+ cmds.push({ command: '/chatmode', ...(canSetChatmode ? { args: '[interactive|proactive]' } : {}), description: `查看${canSetChatmode ? '/切换' : ''}会话模式(被动响应或主动推进)`, category: '聊天设置', roles: [identity.role] });
684
+ if (canReadMentionmode || canSetMentionmode)
685
+ cmds.push({ command: '/mentionmode', ...(canSetMentionmode ? { args: '[mention-only|disabled]' } : {}), description: `查看${canSetMentionmode ? '/切换' : ''}群聊 @ 处理模式`, category: '聊天设置', roles: [identity.role] });
603
686
  // 交互
604
687
  cmds.push({ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
605
688
  // 帮助
@@ -1628,11 +1711,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1628
1711
  }
1629
1712
  if (normalizedContent === '/activity' || normalizedContent.startsWith('/activity ')) {
1630
1713
  const activityArg = normalizedContent.slice(9).trim();
1714
+ const activitySession = await getExistingSessionForCommand();
1631
1715
  // 带参(写操作)需 admin+;无参查询对所有人开放(owner 门在具体切换点还有一道)
1632
1716
  if (activityArg && !isAdmin)
1633
1717
  return { kind: 'command.error', text: '❌ 无权限:此命令仅限管理员使用' };
1634
1718
  // proactive 模式下流式输出全部静默,activity 配置无意义
1635
- if (getEffectiveChatmode(activeSession) === 'proactive') {
1719
+ if (getEffectiveChatmode(activitySession) === 'proactive') {
1636
1720
  return { kind: 'command.error', text: '❌ 当前会话为 proactive 模式,不支持 activity 配置(流式输出已全部静默)' };
1637
1721
  }
1638
1722
  const modeMap = {
@@ -1655,7 +1739,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1655
1739
  type: 'interaction',
1656
1740
  id: `activity-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
1657
1741
  channelId,
1658
- sessionId: activeSession?.id || '',
1742
+ sessionId: activitySession?.id || '',
1659
1743
  initiatorId: userId,
1660
1744
  kind: {
1661
1745
  kind: 'command-card',
@@ -1669,7 +1753,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1669
1753
  })),
1670
1754
  },
1671
1755
  };
1672
- const replyCtx = activeSession ? this.getReplyContext(activeSession) : undefined;
1756
+ const replyCtx = replyContext ?? (activitySession ? this.getReplyContext(activitySession) : undefined);
1673
1757
  const cardResult = await this.sendCommandCard({ channel, channelId, interaction, replyCtx, canWrite: isOwner });
1674
1758
  if (cardResult === null)
1675
1759
  return null;
@@ -2081,7 +2165,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2081
2165
  await sendMessage(channelId, `⏳ 正在创建新会话${sessionName ? `: ${sessionName}` : ''}...`, this.getReplyContext(session));
2082
2166
  }
2083
2167
  const newSessionBaseagent = this.agentRegistry?.resolveByChannel(channel)?.baseagent || this.parseDefaultBaseagent();
2084
- const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent);
2168
+ const previousMetadata = session?.metadata || activeSession?.metadata || {};
2169
+ const newSessionIdentityMetadata = activeChatType === 'group'
2170
+ ? {
2171
+ ...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
2172
+ groupId: previousMetadata.groupId || channelId,
2173
+ ...(previousMetadata.groupName ? { groupName: previousMetadata.groupName } : {}),
2174
+ ...(previousMetadata.dispatchMode ? { dispatchMode: previousMetadata.dispatchMode } : {}),
2175
+ }
2176
+ : {
2177
+ ...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
2178
+ ...((userId || previousMetadata.peerId) ? { peerId: userId || previousMetadata.peerId } : {}),
2179
+ ...(previousMetadata.peerName ? { peerName: previousMetadata.peerName } : {}),
2180
+ ...(previousMetadata.peerType ? { peerType: previousMetadata.peerType } : {}),
2181
+ };
2182
+ const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent, newSessionIdentityMetadata);
2085
2183
  const previousAgent = getActiveAgentIfAvailable();
2086
2184
  if (session && previousAgent) {
2087
2185
  // Reset agent backend state so the new
@@ -2370,6 +2468,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2370
2468
  });
2371
2469
  // 执行重启逻辑(共用于卡片回调和文本确认)
2372
2470
  const executeRestart = async () => {
2471
+ const suppressRealRestart = shouldSuppressRealRestart();
2373
2472
  let replyContext;
2374
2473
  if (threadId) {
2375
2474
  const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
@@ -2382,15 +2481,20 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2382
2481
  timestamp: Date.now(),
2383
2482
  ...(replyContext?.replyToMessageId ? { rootId: replyContext.replyToMessageId } : {}),
2384
2483
  };
2385
- const dataDir = resolvePaths().dataDir;
2386
- fs.mkdirSync(dataDir, { recursive: true });
2387
- fs.writeFileSync(path.join(dataDir, 'restart-pending.json'), JSON.stringify(restartInfo));
2388
- const { spawn } = await import('child_process');
2389
- spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
2390
- detached: true,
2391
- stdio: 'ignore',
2392
- env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
2393
- }).unref();
2484
+ if (!suppressRealRestart) {
2485
+ const dataDir = resolvePaths().dataDir;
2486
+ fs.mkdirSync(dataDir, { recursive: true });
2487
+ fs.writeFileSync(path.join(dataDir, 'restart-pending.json'), JSON.stringify(restartInfo));
2488
+ const { spawn } = await import('child_process');
2489
+ spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
2490
+ detached: true,
2491
+ stdio: 'ignore',
2492
+ env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
2493
+ }).unref();
2494
+ }
2495
+ else {
2496
+ logger.info('[System] Suppressed real restart in test runtime');
2497
+ }
2394
2498
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
2395
2499
  // 先发送重启反馈消息,等待发送完成后再 kill 进程
2396
2500
  // 避免消息还没发出去进程就退出了
@@ -2416,10 +2520,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2416
2520
  // 发 SIGTERM 而非直接 process.exit(0),让 index.ts 的 shutdown() 先
2417
2521
  // 正常关闭所有 channel(包括 Feishu WebSocket close frame),
2418
2522
  // 避免 Feishu 服务端因连接异常断开而重推未 ack 的消息给新进程。
2419
- setTimeout(() => {
2420
- logger.info('[System] Restarting by user command...');
2421
- process.kill(process.pid, 'SIGTERM');
2422
- }, 1000);
2523
+ if (!suppressRealRestart) {
2524
+ setTimeout(() => {
2525
+ logger.info('[System] Restarting by user command...');
2526
+ process.kill(process.pid, 'SIGTERM');
2527
+ }, 1000);
2528
+ }
2423
2529
  return true;
2424
2530
  };
2425
2531
  // 文本确认流程
@@ -2450,8 +2556,24 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2450
2556
  }
2451
2557
  // /upgrade 命令:检查版本更新,提示用户手动重启
2452
2558
  if (normalizedContent === '/upgrade') {
2453
- if (!isAdmin)
2454
- return { kind: 'command.error', text: '❌ 无权限:升级检查仅限管理员使用' };
2559
+ const upgradeAuthDenied = await authorizeIntent({
2560
+ intent: {
2561
+ operation: 'system.upgrade',
2562
+ scope: 'process',
2563
+ source: 'slash',
2564
+ args: {},
2565
+ dangerous: true,
2566
+ },
2567
+ identity: isDaemonOwner ? { ...identity, role: 'owner' } : identity,
2568
+ session: activeSession,
2569
+ channel,
2570
+ channelId,
2571
+ userId,
2572
+ selfAid: this.agentRegistry?.resolveByChannel(channel)?.aid,
2573
+ isDaemonOwner,
2574
+ });
2575
+ if (upgradeAuthDenied)
2576
+ return upgradeAuthDenied;
2455
2577
  if (isLinkedInstall()) {
2456
2578
  return { kind: 'command.result', text: '⏭ 开发模式,跳过升级检查' };
2457
2579
  }
@@ -2483,11 +2605,26 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2483
2605
  }
2484
2606
  // /file 命令:发送项目内文件,支持 /file path 和 /file channel path
2485
2607
  if (normalizedContent.startsWith('/file')) {
2486
- if (!isAdmin)
2487
- return { kind: 'command.error', text: '❌ 无权限:此命令仅限管理员使用' };
2488
2608
  // 飞书会将 .md 等后缀自动转为 Markdown 链接: foo.md → [foo.md](http://foo.md/)
2489
2609
  // 还原: 将 [text](url) 替换为 text
2490
2610
  const rawArg = normalizedContent.slice(5).trim().replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
2611
+ const fileAuthDenied = await authorizeIntent({
2612
+ intent: {
2613
+ operation: 'file.fetch',
2614
+ scope: 'filesystem',
2615
+ source: 'slash',
2616
+ args: rawArg ? { filePath: rawArg } : {},
2617
+ },
2618
+ identity,
2619
+ session: activeSession,
2620
+ channel,
2621
+ channelId,
2622
+ userId,
2623
+ selfAid: this.agentRegistry?.resolveByChannel(channel)?.aid,
2624
+ isDaemonOwner,
2625
+ });
2626
+ if (fileAuthDenied)
2627
+ return fileAuthDenied;
2491
2628
  if (!rawArg) {
2492
2629
  const usage = isOwner
2493
2630
  ? '用法: /file <相对路径> 或 /file <渠道> <相对路径>\n示例: /file src/index.ts\n示例: /file feishu report.md'
@@ -12,7 +12,7 @@
12
12
  * 策略(reload/重启永远全量失效,无视策略;策略只决定"平时每次读怎么检查"):
13
13
  * - on-reload:平时不检查,直接用缓存(kits 文件、persona)。靠 reload/重启刷新。
14
14
  * - manual:同 on-reload,额外支持显式 invalidate(file) 单刷。
15
- * - mtime:每次读 statSync 门控 mtime,变了自动重读(working.md、config.json)。
15
+ * - mtime:每次读 statSync 门控 size + mtime,变了自动重读(working.md、config.json)。
16
16
  *
17
17
  * 容量:项数可无界增长的组(如 relation-prefs,每 peer 一文件)设 LRU 硬上限
18
18
  * (见 GROUP_CAPS),命中/写入移到末尾、超限驱逐同组最旧项;无 timer。
@@ -71,9 +71,9 @@ export class FileCache {
71
71
  const read = opts.read ?? readFileOrNull;
72
72
  this.bump(opts.group, opts.policy, 'gets');
73
73
  if (opts.policy === 'mtime') {
74
- const mtimeMs = statMtime(file);
74
+ const fingerprint = statFingerprint(file);
75
75
  this.bump(opts.group, opts.policy, 'statChecks');
76
- if (existing && existing.mtimeMs === mtimeMs) {
76
+ if (existing && sameFingerprint(existing.fingerprint, fingerprint)) {
77
77
  this.bump(opts.group, opts.policy, 'hits');
78
78
  this.touch(file, existing);
79
79
  return existing.value;
@@ -84,7 +84,7 @@ export class FileCache {
84
84
  this.bump(opts.group, opts.policy, 'misses');
85
85
  const raw = read(file);
86
86
  const value = loader(raw);
87
- this.store(file, { policy: 'mtime', group: opts.group, mtimeMs, value, bytes: approxBytes(raw) });
87
+ this.store(file, { policy: 'mtime', group: opts.group, fingerprint, value, bytes: approxBytes(raw) });
88
88
  return value;
89
89
  }
90
90
  // on-reload / manual:命中即用,不检查文件
@@ -192,14 +192,20 @@ export class FileCache {
192
192
  };
193
193
  }
194
194
  }
195
- function statMtime(file) {
195
+ function statFingerprint(file) {
196
196
  try {
197
- return fs.statSync(file).mtimeMs;
197
+ const stat = fs.statSync(file);
198
+ return { size: stat.size, mtimeMs: stat.mtimeMs };
198
199
  }
199
200
  catch {
200
201
  return null; // 文件不存在
201
202
  }
202
203
  }
204
+ function sameFingerprint(left, right) {
205
+ if (left === null || right === null)
206
+ return left === right;
207
+ return !!left && left.size === right.size && left.mtimeMs === right.mtimeMs;
208
+ }
203
209
  function readFileOrNull(file) {
204
210
  try {
205
211
  return fs.readFileSync(file, 'utf-8');
@@ -208,6 +214,34 @@ function readFileOrNull(file) {
208
214
  return null;
209
215
  }
210
216
  }
217
+ /**
218
+ * 带错误捕获的 reader。默认 reader 把所有读盘异常吞成 null,调用方无从区分
219
+ * ENOENT(文件确实没有)与 EACCES/EISDIR(部署或权限事故)——对随包发布的
220
+ * 文件(schema、角色模板)来说这层信息正是定位问题的关键。
221
+ *
222
+ * 用法:建一个实例,把 `read` 交给 fileCache,loader 里拿到 null 时用
223
+ * `lastError()` 拼文案、把 `lastErrorCause()` 挂到新 Error 的 `cause` 上——
224
+ * 后者保住原始异常对象,`code`/`path`/`errno` 这些结构化字段不至于只剩字符串。
225
+ * 注意 loader 只在未命中时执行,与 read 同步成对调用,因此闭包里的 last
226
+ * 不会串到别的文件上。
227
+ */
228
+ export function createErrorCapturingReader() {
229
+ let last = null;
230
+ return {
231
+ read: (file) => {
232
+ try {
233
+ last = null;
234
+ return fs.readFileSync(file, 'utf-8');
235
+ }
236
+ catch (error) {
237
+ last = error;
238
+ return null;
239
+ }
240
+ },
241
+ lastError: () => (last instanceof Error ? last.message : last === null ? 'unreadable' : String(last)),
242
+ lastErrorCause: () => last,
243
+ };
244
+ }
211
245
  /** 缓存值内存占用近似:用读入的原始字符串长度(字节级近似),null 记 0。 */
212
246
  function approxBytes(raw) {
213
247
  return raw === null ? 0 : raw.length;
@@ -89,9 +89,37 @@ const CATALOG = [
89
89
  { path: 'name', type: 'string', optional: true },
90
90
  { path: 'chatType', type: 'string', optional: true },
91
91
  { path: 'threadId', type: 'string', optional: true },
92
+ { path: 'cause', type: 'string', optional: true },
93
+ { path: 'sourceSessionId', type: 'string', optional: true },
94
+ { path: 'renewSource', type: 'string', optional: true },
92
95
  { path: 'timestamp', type: 'number', optional: true },
93
96
  ],
94
97
  },
98
+ {
99
+ type: 'session:renew-evaluated',
100
+ namespace: 'session',
101
+ name: 'renew-evaluated',
102
+ description: 'Session Renew 判定完成',
103
+ fields: [
104
+ { path: 'candidateSessionId', type: 'string' },
105
+ { path: 'selectedSessionId', type: 'string' },
106
+ { path: 'renewed', type: 'boolean' },
107
+ { path: 'decision', type: 'string' },
108
+ { path: 'source', type: 'string' },
109
+ { path: 'channel', type: 'string' },
110
+ { path: 'channelName', type: 'string', optional: true },
111
+ { path: 'channelId', type: 'string' },
112
+ { path: 'selfAid', type: 'string', optional: true },
113
+ { path: 'idleHours', type: 'number' },
114
+ { path: 'responseMode', type: 'string', optional: true },
115
+ { path: 'baseagent', type: 'string', optional: true },
116
+ { path: 'model', type: 'string', optional: true },
117
+ { path: 'effort', type: 'string', optional: true },
118
+ { path: 'fallbackReason', type: 'string', optional: true },
119
+ { path: 'durationMs', type: 'number' },
120
+ { path: 'timestamp', type: 'number' },
121
+ ],
122
+ },
95
123
  {
96
124
  type: 'session:switched',
97
125
  namespace: 'session',
@@ -355,6 +383,77 @@ const CATALOG = [
355
383
  { path: 'preTokens', type: 'number' },
356
384
  ],
357
385
  },
386
+ {
387
+ type: 'runner:task-started',
388
+ namespace: 'runner',
389
+ name: 'task-started',
390
+ description: 'Runner 子任务已创建',
391
+ fields: [
392
+ { path: 'sessionId', type: 'string' },
393
+ { path: 'taskId', type: 'string' },
394
+ { path: 'taskKind', type: 'string' },
395
+ { path: 'toolUseId', type: 'string', optional: true },
396
+ { path: 'description', type: 'string' },
397
+ { path: 'subagentType', type: 'string', optional: true },
398
+ { path: 'taskType', type: 'string', optional: true },
399
+ { path: 'workflowName', type: 'string', optional: true },
400
+ { path: 'prompt', type: 'string', optional: true },
401
+ { path: 'skipTranscript', type: 'boolean', optional: true },
402
+ { path: 'timestamp', type: 'number', optional: true },
403
+ ],
404
+ },
405
+ {
406
+ type: 'runner:task-progress',
407
+ namespace: 'runner',
408
+ name: 'task-progress',
409
+ description: 'Runner 子任务进度',
410
+ fields: [
411
+ { path: 'sessionId', type: 'string' },
412
+ { path: 'taskId', type: 'string', optional: true },
413
+ { path: 'taskKind', type: 'string', optional: true },
414
+ { path: 'toolUseId', type: 'string', optional: true },
415
+ { path: 'description', type: 'string', optional: true },
416
+ { path: 'subagentType', type: 'string', optional: true },
417
+ { path: 'summary', type: 'string', optional: true },
418
+ { path: 'lastToolName', type: 'string', optional: true },
419
+ { path: 'totalTokens', type: 'number', optional: true },
420
+ { path: 'toolUses', type: 'number', optional: true },
421
+ { path: 'durationMs', type: 'number', optional: true },
422
+ { path: 'timestamp', type: 'number', optional: true },
423
+ ],
424
+ },
425
+ {
426
+ type: 'runner:task-notification',
427
+ namespace: 'runner',
428
+ name: 'task-notification',
429
+ description: 'Runner 子任务终态和输出',
430
+ fields: [
431
+ { path: 'sessionId', type: 'string' },
432
+ { path: 'taskId', type: 'string' },
433
+ { path: 'taskKind', type: 'string' },
434
+ { path: 'toolUseId', type: 'string', optional: true },
435
+ { path: 'description', type: 'string', optional: true },
436
+ { path: 'subagentType', type: 'string', optional: true },
437
+ { path: 'status', type: 'string' },
438
+ { path: 'terminalReason', type: 'string', optional: true },
439
+ { path: 'outputFile', type: 'string', optional: true },
440
+ { path: 'summary', type: 'string', optional: true },
441
+ { path: 'usage', type: 'object', optional: true },
442
+ { path: 'skipTranscript', type: 'boolean', optional: true },
443
+ { path: 'timestamp', type: 'number', optional: true },
444
+ ],
445
+ },
446
+ {
447
+ type: 'runner:background-tasks-changed',
448
+ namespace: 'runner',
449
+ name: 'background-tasks-changed',
450
+ description: 'Runner 后台任务集合变化',
451
+ fields: [
452
+ { path: 'sessionId', type: 'string' },
453
+ { path: 'tasks', type: 'array' },
454
+ { path: 'timestamp', type: 'number', optional: true },
455
+ ],
456
+ },
358
457
  {
359
458
  type: 'runner:model-changed',
360
459
  namespace: 'runner',