evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -28,7 +28,7 @@ import { isSystemControlChannel } from '../system-channels.js';
28
28
  import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } from '../../utils/cross-platform.js';
29
29
  import { scanInstances } from '../../utils/instance-registry.js';
30
30
  import { inspectDataMigrationRequirement } from '../data-migration.js';
31
- import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
31
+ import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
32
32
  const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
33
33
  const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
34
34
  function defaultSlashChatmode(field) {
@@ -368,7 +368,7 @@ async function getGitWorkingDirInfo(projectPath) {
368
368
  return null; // git 命令失败时静默返回 null
369
369
  }
370
370
  }
371
- export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext) {
371
+ export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext, applyGroupSlashPolicy = false) {
372
372
  // 卡片回调的 chatType 不可靠(飞书 bot 单聊 chatId 也是 oc_ 前缀),
373
373
  // 不应覆盖 session 中已有的正确值
374
374
  if (source === 'card-trigger')
@@ -434,9 +434,18 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
434
434
  logger.warn(`[CommandHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
435
435
  }
436
436
  }
437
- const activeChatType = topicAuthSession?.chatType
437
+ const activeChatType = (applyGroupSlashPolicy
438
+ ? replyContext?.delivery?.chatType || overrideSubject?.chatType || chatType
439
+ : undefined)
440
+ || topicAuthSession?.chatType
438
441
  || activeSession?.chatType
439
442
  || (chatType === 'group' ? 'group' : 'private');
443
+ const groupSlash = applyGroupSlashPolicy && activeChatType === 'group';
444
+ if (applyGroupSlashPolicy) {
445
+ const groupGuard = guardGroupSlashCommand(normalizedContent, activeChatType);
446
+ if (groupGuard)
447
+ return groupGuard;
448
+ }
440
449
  const getExistingSessionForCommand = async () => {
441
450
  if (threadId) {
442
451
  if (topicAuthSessionLookupFailed)
@@ -485,7 +494,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
485
494
  || normalizedContent === '/upgrade'
486
495
  || normalizedContent === '/reload'
487
496
  || normalizedContent.startsWith('/reload ')
488
- || normalizedContent === '/pair';
497
+ || normalizedContent === '/pair'
498
+ || /^\/diag(?:\s|$)/.test(normalizedContent);
489
499
  const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
490
500
  if (roleGuard)
491
501
  return roleGuard;
@@ -617,6 +627,30 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
617
627
  };
618
628
  // /help 命令不需要会话
619
629
  if (normalizedContent === '/help') {
630
+ if (groupSlash) {
631
+ const lines = [
632
+ '群聊可用命令:',
633
+ ' /help - 显示帮助',
634
+ ' /status - 显示会话状态',
635
+ ' /check - 检查 Agent 健康',
636
+ ' /model [current|list|check] - 查询模型或检查网关',
637
+ ' /effort - 查看推理强度',
638
+ ' /baseagent - 查看 Agent 后端',
639
+ ' /perm - 查看权限模式',
640
+ ' /mentionmode - 查看群聊 @ 处理模式',
641
+ ' /ask <选项> - 回答待处理问题',
642
+ ' /pause - 在下一次工具调用前暂停任务',
643
+ ' /resume - 继续已暂停的任务',
644
+ ' /stop - 中断当前任务',
645
+ ...(isAdmin ? [
646
+ ' /compact - 压缩会话上下文',
647
+ ...(threadId ? [' /renew - 轮换当前话题的后端会话'] : []),
648
+ ] : []),
649
+ '',
650
+ '模型、推理强度、后端、权限与 @ 模式在群聊中仅支持查询。',
651
+ ];
652
+ return { kind: 'command.result', text: lines.join('\n') };
653
+ }
620
654
  const canReadModel = canAccessSlashOperation('model.current', 'relation')
621
655
  || canAccessSlashOperation('model.list', 'relation');
622
656
  const canUseModel = canAccessSlashOperation('model.use', 'relation');
@@ -633,7 +667,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
633
667
  const appendProcessCommands = (lines) => {
634
668
  if (!isDaemonOwner)
635
669
  return;
636
- lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码');
670
+ lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请远程诊断;/diag stop 撤销授权');
637
671
  };
638
672
  if (!isAdmin && activeChatType === 'group') {
639
673
  const lines = [
@@ -677,7 +711,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
677
711
  ' /del <名称> - 删除指定会话(仅解绑,不删除文件)',
678
712
  ...(threadId ? [' /renew - 在当前话题中轮换 backend'] : []),
679
713
  ' /pause - 在下一次工具调用前暂停当前任务',
680
- ' /resume - 继续已暂停的当前任务',
714
+ ' /resume - 继续暂停或异常中断的当前任务',
681
715
  ' /stop - 中断当前任务',
682
716
  ' /status - 显示会话状态',
683
717
  ' /check - 检查 EvolAgent 实例健康',
@@ -727,7 +761,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
727
761
  ' /compact - 压缩会话上下文(减少 token 用量)',
728
762
  ' /renew - 在话题中轮换 backend(下一条消息懒初始化)',
729
763
  ' /pause - 在下一次工具调用前暂停当前任务',
730
- ' /resume - 继续已暂停的当前任务',
764
+ ' /resume - 继续暂停或异常中断的当前任务',
731
765
  ' /stop - 中断当前任务',
732
766
  '',
733
767
  '🤖 Agent 与模型:',
@@ -748,7 +782,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
748
782
  '🛠️ 运维:',
749
783
  ' /status - 显示会话状态',
750
784
  ' /pause - 在下一次工具调用前暂停当前任务',
751
- ' /resume - 继续已暂停的当前任务',
785
+ ' /resume - 继续暂停或异常中断的当前任务',
752
786
  ' /stop - 中断当前任务',
753
787
  ' /check - 检查 EvolAgent 实例健康',
754
788
  ...(isOwner ? [
@@ -773,6 +807,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
773
807
  ];
774
808
  return { kind: 'command.result', text: lines.join('\n') };
775
809
  }
810
+ if (/^\/diag(?:\s|$)/.test(normalizedContent)) {
811
+ if (!isDaemonOwner || !userId || !this.diagService)
812
+ return { kind: 'command.error', text: '❌ /diag 需要本机 DaemonOwner 和可用的诊断入口' };
813
+ const problem = content.trim().slice(5).trim();
814
+ const result = await this.diagService.execute(problem.toLowerCase() === 'stop'
815
+ ? { action: 'stop' } : { action: 'start', problem }, userId);
816
+ return { kind: result.ok ? 'command.result' : 'command.error', text: result.message };
817
+ }
776
818
  // /pair is also recognized on an Agent channel so it cannot fall through
777
819
  // into model input. The daemon control channel has its own fast path; this
778
820
  // branch preserves the same daemon-owner boundary for ordinary AUN chats.
@@ -783,15 +825,35 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
783
825
  text: '❌ /pair 仅限 daemon-owner 使用;请向 daemon 控制 AID 发送 /pair。',
784
826
  };
785
827
  }
786
- const pair = remoteAccessService.getPairingCode(userId);
828
+ const pair = remoteAccessService.getPairingCode(userId, Date.now(), 'pair');
787
829
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
788
830
  return {
789
831
  kind: 'command.result',
790
- text: `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)\n可用于 ECWeb 登录或远程 Menu 客户端授权`,
832
+ text: `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效,仅可配对一次)\n可用于 ECWeb 登录或远程 Menu 客户端授权`,
791
833
  };
792
834
  }
793
835
  // /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
794
836
  if (normalizedContent === '/evolhelp') {
837
+ if (groupSlash) {
838
+ const commands = [
839
+ { command: '/help', description: '显示帮助信息', category: '帮助', roles: ['visitor', 'member', 'admin', 'owner'] },
840
+ { command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
841
+ { command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
842
+ { command: '/model', args: '[current|list|check]', description: '查询模型或检查模型网关', category: 'Agent 与模型', roles: [identity.role] },
843
+ { command: '/effort', description: '查看推理强度', category: 'Agent 与模型', roles: [identity.role] },
844
+ { command: '/baseagent', description: '查看 Agent 后端', category: 'Agent 与模型', roles: [identity.role] },
845
+ { command: '/perm', description: '查看当前权限模式', category: '权限管理', roles: [identity.role] },
846
+ { command: '/mentionmode', description: '查看群聊 @ 处理模式', category: '聊天设置', roles: [identity.role] },
847
+ { command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
848
+ { command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] },
849
+ { command: '/resume', description: '继续已暂停的当前任务', category: '运维', roles: ['admin', 'owner'] },
850
+ { command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] },
851
+ ];
852
+ if (isAdmin) {
853
+ commands.push({ command: '/compact', description: '压缩会话上下文', category: '会话管理', roles: ['admin', 'owner'] }, ...(threadId ? [{ command: '/renew', description: '轮换当前话题 backend', category: '会话管理', roles: ['admin', 'owner'] }] : []));
854
+ }
855
+ return { kind: 'command.result', text: JSON.stringify({ commands, categories: [...new Set(commands.map(c => c.category))] }) };
856
+ }
795
857
  const cmds = [];
796
858
  const canReadModel = canAccessSlashOperation('model.current', 'relation')
797
859
  || canAccessSlashOperation('model.list', 'relation');
@@ -836,7 +898,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
836
898
  // 运维
837
899
  cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
838
900
  cmds.push({ command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] });
839
- cmds.push({ command: '/resume', description: '继续已暂停的当前任务', category: '运维', roles: ['admin', 'owner'] });
901
+ cmds.push({ command: '/resume', description: '继续暂停或异常中断的当前任务', category: '运维', roles: ['admin', 'owner'] });
840
902
  cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
841
903
  cmds.push({ command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
842
904
  if (isAdmin) {
@@ -851,6 +913,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
851
913
  cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
852
914
  cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
853
915
  cmds.push({ command: '/pair', description: '获取远程控制配对码', category: '运维', roles: ['daemon-owner'] });
916
+ cmds.push({ command: '/diag', args: '<问题描述>|stop', description: '邀请远程诊断或撤销授权', category: '运维', roles: ['daemon-owner'] });
854
917
  }
855
918
  if (!isDaemonOwner && isAdmin) {
856
919
  cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
@@ -1008,19 +1071,20 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1008
1071
  if (!args) {
1009
1072
  if (!askSession)
1010
1073
  return { kind: 'command.result', text: '当前没有待回答的问题' };
1011
- const pendingIds = this.interactionRouter?.getPending(askSession.id) || [];
1074
+ const pendingIds = this.interactionRouter?.getPending(askSession.id, 'ask') || [];
1012
1075
  if (pendingIds.length === 0)
1013
1076
  return { kind: 'command.result', text: '当前没有待回答的问题' };
1014
- return { kind: 'command.result', text: `当前有 ${pendingIds.length} 个待回答问题,请回复 /ask <选项>` };
1077
+ return { kind: 'command.result',
1078
+ text: `当前有 ${pendingIds.length} 个待回答问题,请回复 /ask <问题ID> <回答>:\n${pendingIds.join('\n')}` };
1015
1079
  }
1016
1080
  if (!askSession)
1017
1081
  return { kind: 'command.error', text: '❌ 当前没有待回答的问题' };
1018
- const fb = await this.handleInteractionFallback('ask', args, askSession.id, userId);
1082
+ const fb = await this.handleInteractionFallback('ask', args, askSession.id, userId, this.getAdapter(channel)?.channelKey);
1019
1083
  if (fb.matched)
1020
1084
  return { kind: 'command.result', text: fb.result ?? '✓ 已回答' };
1021
1085
  return { kind: 'command.error', text: '❌ 当前没有待回答的问题' };
1022
1086
  }
1023
- // /pause and /resume control the current session's next-tool gate.
1087
+ // /pause gates the next tool; /resume also recovers a failed backend turn.
1024
1088
  if (normalizedContent.startsWith('/pause ')) {
1025
1089
  return { kind: 'command.error', text: '用法: /pause' };
1026
1090
  }
@@ -1048,8 +1112,16 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1048
1112
  const resumeSession = await getExistingSessionForCommand();
1049
1113
  if (!resumeSession)
1050
1114
  return { kind: 'command.result', text: '当前没有活跃会话' };
1051
- const resumed = this.processor.resumeSession(resumeSession.id);
1052
- return { kind: 'command.result', text: resumed ? '✓ 已继续当前任务' : '当前任务未处于暂停状态' };
1115
+ const resumed = await this.processor.recoverTask({
1116
+ sessionId: resumeSession.id,
1117
+ agentAid: resumeSession.selfAID,
1118
+ peerId: userId,
1119
+ encrypted: replyContext?.metadata?.encrypted,
1120
+ });
1121
+ return {
1122
+ kind: resumed.ok ? 'command.result' : 'command.error',
1123
+ text: resumed.message ?? (resumed.ok ? '已继续当前任务' : '任务恢复失败'),
1124
+ };
1053
1125
  }
1054
1126
  // /baseagent 命令:查看或切换 Agent 后端
1055
1127
  if (normalizedContent === '/baseagent' || normalizedContent.startsWith('/baseagent ')) {
@@ -1063,7 +1135,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1063
1135
  if (!args) {
1064
1136
  const activeBaseagent = owningAgent?.baseagent || this.parseDefaultBaseagent();
1065
1137
  // 尝试发送 CommandCard 卡片
1066
- if (this.interactionRouter && available.length > 1) {
1138
+ if (!groupSlash && this.interactionRouter && available.length > 1) {
1067
1139
  const baseagentSession = await getExistingSessionForCommand();
1068
1140
  const interaction = {
1069
1141
  type: 'interaction',
@@ -1090,7 +1162,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1090
1162
  }
1091
1163
  // 降级:文本
1092
1164
  const list = available.map((a) => `${a === activeBaseagent ? ' ✓' : ' '} ${a}`).join('\n');
1093
- const canSwitchAgent = isOwner;
1165
+ const canSwitchAgent = isOwner && !groupSlash;
1094
1166
  if (canSwitchAgent) {
1095
1167
  return { kind: 'command.result', text: `当前 Baseagent: ${activeBaseagent}\n\n可用:\n${list}\n用法: /baseagent <name>` };
1096
1168
  }
@@ -1348,7 +1420,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1348
1420
  isDaemonOwner,
1349
1421
  });
1350
1422
  // 尝试发送 CommandCard 卡片
1351
- if (!args && canReadModelList && this.interactionRouter && models.length > 0) {
1423
+ if (!groupSlash && !args && canReadModelList && this.interactionRouter && models.length > 0) {
1352
1424
  const interaction = {
1353
1425
  type: 'interaction',
1354
1426
  id: `model-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
@@ -1379,7 +1451,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1379
1451
  const catalogHint = `[${catalog.source}/${catalog.freshness}] ${catalog.diagnostics.map(d => d.message).join('; ')}`;
1380
1452
  const modelList = models.map((m) => ` ${modelMatches(modelAgent, m, currentModel) ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
1381
1453
  const effortHint = efforts.length > 0
1382
- ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
1454
+ ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort}${groupSlash ? '' : ' (使用 /effort 调整)'}`
1383
1455
  : '';
1384
1456
  if (args === 'current') {
1385
1457
  return {
@@ -1389,7 +1461,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1389
1461
  };
1390
1462
  }
1391
1463
  if (canReadModelList) {
1392
- const usage = !args && isAdmin ? '\n\n用法: /model <模型>' : '';
1464
+ const usage = !groupSlash && !args && isAdmin ? '\n\n用法: /model <模型>' : '';
1393
1465
  return {
1394
1466
  kind: 'command.result',
1395
1467
  text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}${rawModels.includes(currentModel) ? '' : ' [当前配置,未列入目录]'}\n${catalogHint}\n\n可用模型:\n${modelList}${usage}`,
@@ -1473,10 +1545,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1473
1545
  }
1474
1546
  else {
1475
1547
  const resolvedArg = hasModelSwitcher(modelAgent) ? (modelAgent.resolveModelId?.(arg) ?? arg) : arg;
1476
- if (models.includes(resolvedArg)) {
1477
- newModel = resolvedArg;
1478
- }
1479
- else if (models.includes(arg)) {
1548
+ if (models.includes(resolvedArg) || models.includes(arg)) {
1480
1549
  newModel = arg;
1481
1550
  }
1482
1551
  else {
@@ -1503,7 +1572,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1503
1572
  const errorLabel = allEfforts.includes(effortArg) ? '⚠️' : '❌';
1504
1573
  return { kind: 'command.result', text: `${errorLabel} ${modelArg} 不支持 ${effortArg} 推理强度\n可选: ${targetEfforts.join(' / ')}` };
1505
1574
  }
1506
- newModel = modelArg;
1575
+ newModel = modelArgRaw;
1507
1576
  newEffort = effortArg;
1508
1577
  }
1509
1578
  // 优先写关系级覆盖;只有无法定位关系且具备管理权限时才回退到 runner/agent 级。
@@ -1543,11 +1612,13 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1543
1612
  requestedModel: newModel,
1544
1613
  models,
1545
1614
  resolveModelId: modelAgent.resolveModelId?.bind(modelAgent),
1615
+ selfAid: modelSelector.self,
1546
1616
  });
1547
1617
  if (!decision.ok) {
1548
1618
  return { kind: 'command.error', text: decision.message || `invalid model: ${newModel}` };
1549
1619
  }
1550
- newModel = decision.model || newModel;
1620
+ // The decision validates the resolved model; retain the user's selection
1621
+ // for configuration and replies, just like menu.update and local model use.
1551
1622
  }
1552
1623
  const changes = [];
1553
1624
  if (!('error' in relationTarget)) {
@@ -1659,7 +1730,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1659
1730
  return { kind: 'command.error', text: '⚠️ 当前模型不支持推理强度设置' };
1660
1731
  }
1661
1732
  // /effort(无参数):显示当前推理强度 + 发送 CommandCard 卡片
1662
- if (this.interactionRouter) {
1733
+ if (!groupSlash && this.interactionRouter) {
1663
1734
  const allItems = [...efforts, 'auto'];
1664
1735
  const interaction = {
1665
1736
  type: 'interaction',
@@ -1687,7 +1758,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1687
1758
  // 降级:文本
1688
1759
  const effortDisplay = currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort;
1689
1760
  const effortOptions = [...efforts, 'auto'].join(' / ');
1690
- if (isAdmin) {
1761
+ if (isAdmin && !groupSlash) {
1691
1762
  return { kind: 'command.result', text: `推理强度: ${effortDisplay} 可选: ${effortOptions} 用法: /effort <level>` };
1692
1763
  }
1693
1764
  return { kind: 'command.result', text: `推理强度: ${effortDisplay}` };
@@ -2109,6 +2180,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2109
2180
  const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
2110
2181
  if (!arg) {
2111
2182
  const displayMode = currentMode ?? '未设置(跟随群设置)';
2183
+ if (groupSlash)
2184
+ return { kind: 'command.result', text: `@ 处理模式: ${displayMode}` };
2112
2185
  // 尝试发送 CommandCard 卡片
2113
2186
  if (isAdmin) {
2114
2187
  const modes = [
@@ -2195,6 +2268,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2195
2268
  const hasActive = stopAgent.hasActiveStream(sessionKey);
2196
2269
  const isProcessing = this.messageQueue.isProcessing(sessionKey);
2197
2270
  if (queueLength === 0 && !hasActive && !isProcessing) {
2271
+ // Even an idle session can have an undelivered task:error recovery.
2272
+ // Persist the stop fence before acknowledging that nothing is running.
2273
+ await this.processor.interruptSession(sessionKey, 'stop');
2198
2274
  return { kind: 'command.result', text: '当前没有正在处理的任务' };
2199
2275
  }
2200
2276
  // 发布中断事件,让 MessageProcessor 标记为 interrupted(而非 done)
@@ -2486,6 +2562,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2486
2562
  session.name = fileInfo.title;
2487
2563
  }
2488
2564
  }
2565
+ const sessionSubtasks = this.sessionManager.getRunnerTaskHistory(session.id).length;
2489
2566
  const lines = [];
2490
2567
  const chatMode = getEffectiveChatmode(session);
2491
2568
  const sessionRole = session.identity?.role || identity.role || 'none';
@@ -2508,7 +2585,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2508
2585
  if (gitInfo) {
2509
2586
  lines.push(`Git: ${gitInfo}`);
2510
2587
  }
2511
- lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
2588
+ lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns} / 已记录子任务数: ${sessionSubtasks}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
2512
2589
  if (health.consecutiveErrors > 0) {
2513
2590
  lines.push(`异常计数: ${health.consecutiveErrors}`);
2514
2591
  }
@@ -2516,7 +2593,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2516
2593
  }
2517
2594
  else {
2518
2595
  lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${channel} / 项目: ${projectName} / ${session.baseagent}会话`);
2519
- lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
2596
+ lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns} / 已记录子任务数: ${sessionSubtasks}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
2520
2597
  }
2521
2598
  if (health.lastError) {
2522
2599
  lines.push('');
@@ -2863,7 +2940,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2863
2940
  text: `❌ ${excludeKey ? '其他会话' : '该 Agent'} 有 ${busyInfo.count} 个任务执行中,无法重启。\n\n处理中的会话:\n${processingLines}\n\n建议:等待这些任务完成后重试。可通过 ec ctl status 查看队列状态;典型等待时间 30-60 秒,群聊大型任务可能更长。`,
2864
2941
  };
2865
2942
  }
2866
- const allSessions = await this.sessionManager.listSessions(channel, channelId);
2943
+ // Archiving only changes list visibility; pending work must still be checked.
2944
+ const allSessions = await this.sessionManager.listSessions(channel, channelId, { archiveFilter: 'all' });
2867
2945
  const sessionsWithMessages = allSessions
2868
2946
  .filter((s) => this.messageCache.hasMessages(s.id))
2869
2947
  .map((s) => {
@@ -3223,10 +3301,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3223
3301
  // /slist cli — 仅 CLI 会话(未导入的)
3224
3302
  if (normalizedContent === '/slist' || normalizedContent === '/slist cli') {
3225
3303
  if (!session) {
3226
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3227
-
3228
- 请先执行以下操作之一:
3229
- 1. 发送任意消息 - 自动创建新会话
3304
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3305
+
3306
+ 请先执行以下操作之一:
3307
+ 1. 发送任意消息 - 自动创建新会话
3230
3308
  2. /new [名称] - 创建命名会话` };
3231
3309
  }
3232
3310
  const showCliOnly = normalizedContent === '/slist cli';
@@ -3497,11 +3575,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3497
3575
  if (!newName)
3498
3576
  return { kind: 'command.result', text: '用法: /name <新名称> 或 /rename <新名称>' };
3499
3577
  if (!session) {
3500
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3501
-
3502
- 请先执行以下操作之一:
3503
- 1. 发送任意消息 - 自动创建新会话
3504
- 2. /new [名称] - 创建命名会话
3578
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3579
+
3580
+ 请先执行以下操作之一:
3581
+ 1. 发送任意消息 - 自动创建新会话
3582
+ 2. /new [名称] - 创建命名会话
3505
3583
  3. /session <名称> - 切换到已有会话` };
3506
3584
  }
3507
3585
  const existing = await this.sessionManager.getSessionByName(channel, channelId, newName);
@@ -3584,7 +3662,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3584
3662
  }
3585
3663
  await interruptPausedSessionBeforeReplacement(session);
3586
3664
  try {
3587
- const forkedSessionId = await forkAgent.forkSession(session.agentSessionId, session.projectPath, forkName);
3665
+ const forkedSessionId = await forkAgent.forkSession(session.agentSessionId, session.projectPath, forkName, {
3666
+ sessionId: session.id, permissionMode: authSubject.permissionMode, role: authSubject.role,
3667
+ });
3588
3668
  const newSession = await this.sessionManager.createForkedSession(session, forkedSessionId, forkName);
3589
3669
  await forkAgent.updateSessionMetadata?.(forkedSessionId, {
3590
3670
  gitInfo: {
@@ -294,6 +294,10 @@ const CATALOG = [
294
294
  description: 'agent 任务失败',
295
295
  fields: [
296
296
  { path: 'sessionId', type: 'string' },
297
+ { path: 'agentAid', type: 'string', optional: true },
298
+ { path: 'peerId', type: 'string', optional: true },
299
+ { path: 'encrypted', type: 'boolean', optional: true },
300
+ { path: 'originTriggerId', type: 'string', optional: true },
297
301
  { path: 'taskId', type: 'string', optional: true },
298
302
  { path: 'generation', type: 'number', optional: true },
299
303
  { path: 'error', type: 'string' },
@@ -6,6 +6,7 @@ import { agentPersonalDir } from '../paths.js';
6
6
  import { invalidateAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
7
7
  import { loadAllAgents, ensureAgentDirSkeleton, loadAgent, validateAgentConfig, } from '../config-store.js';
8
8
  import { ConfigTarget, read as readConfig, resolveEffective } from '../config/config-manager.js';
9
+ import { resolveAgentLifecycle } from '../config/lifecycle.js';
9
10
  // ── Channel Fingerprint ───────────────────────────────────────────────────
10
11
  // 用于检测多 agent 之间复用同一外部凭证的冲突(appId、aid、token 等)。
11
12
  // 格式:{type}:{primaryKey}
@@ -58,6 +59,8 @@ export class EvolAgentRegistry {
58
59
  agents = new Map();
59
60
  /** channel key (`<type>#<selfAID>#<name>`) → agent aid */
60
61
  channelIndex = new Map();
62
+ /** Agents stopped through stopAgent; distinct from the initial load state. */
63
+ manuallyStopped = new Set();
61
64
  /** 启动期被 ConfigStore 跳过的目录(命名非法 / 缺 config.json / 校验失败等) */
62
65
  skipped = [];
63
66
  /**
@@ -81,6 +84,7 @@ export class EvolAgentRegistry {
81
84
  void _globalConfig;
82
85
  this.agents.clear();
83
86
  this.channelIndex.clear();
87
+ this.manuallyStopped.clear();
84
88
  this.skipped = [];
85
89
  const { agents: rawAgents, skipped, invalidAgents = [] } = loadAllAgents({ includeInvalid: true });
86
90
  this.skipped = skipped;
@@ -285,6 +289,7 @@ export class EvolAgentRegistry {
285
289
  if (!hotLoad)
286
290
  throw new Error(`Cannot enable agent "${aidOrName}": hot-load handler not initialized`);
287
291
  this.agents.delete(oldAgent.aid);
292
+ this.manuallyStopped.delete(oldAgent.aid);
288
293
  this.channelIndex.clear();
289
294
  this.buildChannelIndex();
290
295
  await hotLoad(oldAgent.aid);
@@ -298,6 +303,7 @@ export class EvolAgentRegistry {
298
303
  const previousConfig = oldAgent.captureConfigSnapshot();
299
304
  const previousStatus = oldAgent.status;
300
305
  const previousError = oldAgent.error;
306
+ const previousManuallyStopped = this.manuallyStopped.has(oldAgent.aid);
301
307
  try {
302
308
  await hooks.prepareHandoffReload?.(oldAgent.aid);
303
309
  prepared = true;
@@ -312,6 +318,7 @@ export class EvolAgentRegistry {
312
318
  }
313
319
  oldAgent.swapConfig(raw, merged);
314
320
  oldAgent.status = 'disabled';
321
+ this.manuallyStopped.delete(oldAgent.aid);
315
322
  this.channelIndex.clear();
316
323
  this.buildChannelIndex();
317
324
  runnerTransaction?.commit();
@@ -331,6 +338,10 @@ export class EvolAgentRegistry {
331
338
  oldAgent.invalidatePersonaCache();
332
339
  oldAgent.status = previousStatus;
333
340
  oldAgent.error = previousError;
341
+ if (previousManuallyStopped)
342
+ this.manuallyStopped.add(oldAgent.aid);
343
+ else
344
+ this.manuallyStopped.delete(oldAgent.aid);
334
345
  await runnerTransaction?.rollback().catch(rollbackError => {
335
346
  rollbackErrors.push(`runners: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
336
347
  });
@@ -370,11 +381,16 @@ export class EvolAgentRegistry {
370
381
  const previousConfig = oldAgent.captureConfigSnapshot();
371
382
  const previousStatus = oldAgent.status;
372
383
  const previousError = oldAgent.error;
384
+ const previousManuallyStopped = this.manuallyStopped.has(oldAgent.aid);
373
385
  try {
374
386
  await hooks.prepareHandoffReload?.(oldAgent.aid);
375
387
  prepared = true;
376
388
  runnerTransaction = await hooks.stageAgentRunners?.(new EvolAgent(raw, merged));
377
- const oldChannels = new Set(oldAgent.channelInstanceNames());
389
+ // stopAgent removes runtime transports but preserves configuration.
390
+ // Reload transitions a manually stopped Agent back to running.
391
+ const oldChannels = this.manuallyStopped.has(oldAgent.aid)
392
+ ? new Set()
393
+ : new Set(oldAgent.channelInstanceNames());
378
394
  const aunKey = oldAgent.effectiveChannelName('aun', 'main');
379
395
  const otherKeys = merged.channels.filter(c => c.type !== 'aun').map(c => oldAgent.effectiveChannelName(c.type, c.name));
380
396
  const newChannels = new Set([aunKey, ...otherKeys]);
@@ -404,6 +420,7 @@ export class EvolAgentRegistry {
404
420
  addedSuccessfully.push(ch);
405
421
  }
406
422
  oldAgent.status = 'running';
423
+ this.manuallyStopped.delete(oldAgent.aid);
407
424
  this.channelIndex.clear();
408
425
  this.buildChannelIndex();
409
426
  runnerTransaction?.commit();
@@ -466,6 +483,10 @@ export class EvolAgentRegistry {
466
483
  if (rollbackErrors.length === 0) {
467
484
  oldAgent.status = previousStatus;
468
485
  oldAgent.error = previousError;
486
+ if (previousManuallyStopped)
487
+ this.manuallyStopped.add(oldAgent.aid);
488
+ else
489
+ this.manuallyStopped.delete(oldAgent.aid);
469
490
  }
470
491
  else {
471
492
  oldAgent.status = 'error';
@@ -481,8 +502,10 @@ export class EvolAgentRegistry {
481
502
  throw new Error(`Agent "${aidOrName}" not found`);
482
503
  if (agent.status === 'disabled')
483
504
  throw new Error('Agent is disabled; use enable/disable instead');
484
- if (agent.status === 'stopped')
505
+ if (agent.status === 'stopped') {
506
+ this.manuallyStopped.add(agent.aid);
485
507
  return;
508
+ }
486
509
  for (const ch of agent.channelInstanceNames()) {
487
510
  try {
488
511
  await hooks.disconnectChannel(ch);
@@ -490,6 +513,7 @@ export class EvolAgentRegistry {
490
513
  catch { }
491
514
  }
492
515
  agent.status = 'stopped';
516
+ this.manuallyStopped.add(agent.aid);
493
517
  this.channelIndex.clear();
494
518
  this.buildChannelIndex();
495
519
  logger.info(`[Registry] Stopped agent ${aidOrName}`);
@@ -508,6 +532,7 @@ export class EvolAgentRegistry {
508
532
  await hooks.startChannel(agent, ch);
509
533
  }
510
534
  agent.status = 'running';
535
+ this.manuallyStopped.delete(agent.aid);
511
536
  this.channelIndex.clear();
512
537
  this.buildChannelIndex();
513
538
  logger.info(`[Registry] Started agent ${aidOrName}`);
@@ -575,6 +600,7 @@ export class EvolAgentRegistry {
575
600
  personalName,
576
601
  aid: agent.aid,
577
602
  status: agent.status,
603
+ lifecycle: resolveAgentLifecycle(agent.config),
578
604
  channels,
579
605
  projectPath,
580
606
  baseagent,