evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -51,6 +51,8 @@ export function parseMenuClientProtectedHeaders(value) {
51
51
  result.client_version = normalized.client_version;
52
52
  if (normalized.evol_version !== undefined)
53
53
  result.evol_version = normalized.evol_version;
54
+ if (normalized.sdk_version !== undefined)
55
+ result.sdk_version = normalized.sdk_version;
54
56
  if (normalized.auth_method === 'daemon-owner' || normalized.auth_method === 'remote-access-token' || normalized.auth_method === 'ecweb-token')
55
57
  result.auth_method = normalized.auth_method;
56
58
  else if (normalized.auth_method !== undefined)
@@ -118,11 +120,16 @@ export const MENU_NAME_COMMANDS = Object.freeze({
118
120
  dispatch: '/dispatch',
119
121
  observable: '/observable',
120
122
  system: '/system',
123
+ baseagents: '/baseagents',
121
124
  cli: '/cli',
122
125
  agent: '/agent',
123
126
  aid: '/aid',
124
127
  trigger: '/trigger',
125
128
  file: '/file',
129
+ 'agent-data-root': '/agent-data-root',
130
+ 'relation-data-root': '/relation-data-root',
131
+ 'agent-storage-root': '/agent-storage-root',
132
+ storage: '/storage',
126
133
  gateway: '/gateway',
127
134
  config: '/config',
128
135
  capability: '/capability',
@@ -208,11 +215,17 @@ export function isEvolMenuVersionGateEnabled() {
208
215
  export function isAunMenuTokenRequired() {
209
216
  return loadDaemonConfig().aun?.menuTokenRequired === true;
210
217
  }
211
- export function evolMenuResponseTransportMetadata() {
218
+ export function evolMenuResponseTransportMetadata(sdkVersion) {
212
219
  return {
213
220
  // Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
214
221
  encrypted: undefined,
215
- protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
222
+ protectedHeaders: {
223
+ ec_version: readInstalledEvolcoreVersion(),
224
+ ...(typeof sdkVersion === 'string' && sdkVersion.trim() ? { sdk_version: sdkVersion.trim() } : {}),
225
+ },
226
+ ...(typeof sdkVersion === 'string' && sdkVersion.trim()
227
+ ? { menuClientSdkVersion: sdkVersion.trim() }
228
+ : {}),
216
229
  };
217
230
  }
218
231
  const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
@@ -302,6 +315,9 @@ export function validateMenuRequest(request) {
302
315
  if (request.args !== undefined && (!request.args || typeof request.args !== 'object' || Array.isArray(request.args))) {
303
316
  return { code: 'INVALID_ARGUMENT', message: 'Menu request args must be an object' };
304
317
  }
318
+ if (type === 'menu.list' && Object.hasOwn(request, 'context')) {
319
+ return { code: 'INVALID_ARGUMENT', message: 'menu.list context must be inside args.context (for a daemon catalog, use args.context.kind=process)' };
320
+ }
305
321
  if (type === 'menu.update') {
306
322
  const args = request.args;
307
323
  const scopeError = validateConfigWriteScope(request.name, args)
@@ -329,6 +345,12 @@ export function validateMenuRequest(request) {
329
345
  return { code: 'INVALID_ARGUMENT', message: 'File fetch requires args.path' };
330
346
  }
331
347
  }
348
+ if ((request.name === 'file' || request.cmd === '/file') && request.args !== undefined) {
349
+ const args = request.args;
350
+ if (args.rootId !== undefined && (typeof args.rootId !== 'string' || args.rootId.trim().length === 0)) {
351
+ return { code: 'INVALID_ARGUMENT', message: 'File rootId must be a non-empty string' };
352
+ }
353
+ }
332
354
  return null;
333
355
  }
334
356
  export function menuSuccess(header, data) {
@@ -371,10 +393,10 @@ export function enforceMenuResponseSize(response, maxBytes = 128 * 1024) {
371
393
  });
372
394
  }
373
395
  const INVALID_ARGUMENT_CODES = new Set([
374
- 'ARGUMENT_MISMATCH', 'FILE_TOO_LARGE', 'INVALID_ARGS', 'INVALID_DELEGATION',
396
+ 'ARGUMENT_MISMATCH', 'INVALID_ARGS', 'INVALID_DELEGATION',
375
397
  'INVALID_FIELD', 'INVALID_REVISION', 'INVALID_SCOPE', 'INVALID_TYPE', 'INVALID_VALUE', 'MISSING_AID',
376
398
  'MISSING_ARGV', 'MISSING_BASEAGENT', 'MISSING_CMD', 'MISSING_PEER', 'MISSING_VALUE',
377
- 'NOT_A_DIRECTORY', 'REVISION_REQUIRED', 'ROLE_VIOLATION',
399
+ 'REVISION_REQUIRED', 'ROLE_VIOLATION',
378
400
  ]);
379
401
  const STABLE_CODES = new Set([
380
402
  'ROLE_ACCESS_DENIED', 'PERMISSION_DENIED', 'UNAUTHORIZED', 'FORBIDDEN',
@@ -386,6 +408,9 @@ const STABLE_CODES = new Set([
386
408
  'RATE_LIMITED', 'CATALOG_STALE', 'DATA_MIGRATION_REQUIRED',
387
409
  'INVALID_CLIENT', 'REMOTE_ACCESS_TOKEN_INVALID', 'ECWEB_TOKEN_INVALID', 'CLIENT_BUSY', 'INVALID_RESPONSE', 'TIMEOUT',
388
410
  'RESPONSE_TOO_LARGE',
411
+ 'TOPIC_SESSION_NOT_FOUND', 'TOPIC_SESSION_LOOKUP_FAILED',
412
+ 'ROOT_NOT_FOUND', 'ROOT_FORBIDDEN', 'ROOT_EXPIRED', 'ROOT_SCOPE_MISMATCH', 'ROOT_PEER_MISMATCH',
413
+ 'INVALID_ROOT_ID', 'MISSING_ROOT_ID', 'INVALID_ROOT_PATH', 'NOT_A_DIRECTORY', 'FILE_TOO_LARGE',
389
414
  ]);
390
415
  export function normalizeMenuError(error) {
391
416
  const source = error && typeof error === 'object' ? error : {};
@@ -436,6 +461,16 @@ export class MenuRequestDeduper {
436
461
  this.ttlMs = ttlMs;
437
462
  this.maxEntries = maxEntries;
438
463
  }
464
+ /** Replace an acknowledged deferred operation's result after it fails. */
465
+ async replaceResult(key, fingerprint, previous, value) {
466
+ const entry = this.entries.get(key);
467
+ if (!entry || entry.fingerprint !== fingerprint || await entry.promise !== previous)
468
+ return;
469
+ if (this.entries.get(key) !== entry)
470
+ return;
471
+ entry.promise = Promise.resolve(value);
472
+ entry.expiresAt = Date.now() + this.ttlMs;
473
+ }
439
474
  async execute(key, fingerprint, operation) {
440
475
  this.prune();
441
476
  const existing = this.entries.get(key);
@@ -510,14 +545,16 @@ const MAX_LOG_DEPTH = 8;
510
545
  const MAX_LOG_KEYS = 100;
511
546
  const MAX_LOG_ARRAY_ITEMS = 100;
512
547
  const MAX_LOG_STRING_LENGTH = 4096;
513
- const SENSITIVE_LOG_KEY = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|menu[_-]?token|token|secret(?:[_-]?key)?|password|passwd|credentials?|authorization|cookie|private[_-]?key|seed)(?:$|[_-])/i;
548
+ const SENSITIVE_LOG_KEY = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|menu[_-]?token|root[_-]?id|token|secret(?:[_-]?key)?|password|passwd|credentials?|authorization|cookie|private[_-]?key|seed)(?:$|[_-])/i;
514
549
  const SENSITIVE_LOG_ARG = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|menu[_-]?token|token|secret|password|passwd|credential|authorization|cookie|private[_-]?key|seed)/i;
515
550
  function isSensitiveLogKey(key) {
516
551
  const normalized = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2');
517
- return SENSITIVE_LOG_KEY.test(normalized);
552
+ return SENSITIVE_LOG_KEY.test(normalized) || /^(?:next[_-]?)?cursor$|^(?:download[_-]?)?url$|^ticket$/i.test(normalized);
518
553
  }
519
554
  function sanitizeInlineLogSecrets(value) {
520
555
  return value
556
+ .replace(/\b(?:sr1|sc1|dr1)\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[REDACTED]')
557
+ .replace(/https?:\/\/[^\s"'<>]*[?&](?:t|token|signature|x-amz-signature)=[^\s"'<>]*/gi, '[REDACTED_URL]')
521
558
  .replace(/\b(authorization\s*:\s*Bearer\s+)[^\s"',;]+/gi, '$1[REDACTED]')
522
559
  .replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [REDACTED]')
523
560
  .replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|menu[_-]?token|token|password|passwd|secret|credential|authorization|cookie)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, '$1$2[REDACTED]')
@@ -16,9 +16,9 @@ import { fileCache } from '../daemon-file-cache.js';
16
16
  import { currentVersion } from '../../config/schema-registry.js';
17
17
  import { loadSchema } from '../../config/schema-registry.js';
18
18
  import { rolePermissionFields, rolePermissionFieldOptions, rolePermissionFieldPatterns, validateRoleConfig, } from '../../config/role-schema.js';
19
- import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/msg/group.js';
19
+ import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/group/index.js';
20
20
  import { aidLookup, isValidAid } from '../../aun/aid/identity.js';
21
- import { isExplicitGroupId } from '../../aun/group-identity.js';
21
+ import { isExplicitGroupId } from '../../aun/group/index.js';
22
22
  import { readJsonFile, readLastJsonlLine, scanChatDirs, scanMetaFiles } from '../session/session-fs-store.js';
23
23
  import { constrainResolvedModelForRole } from '../model/model-permission.js';
24
24
  import { resolveRuntimePermissionMode, resolveRuntimeStringField } from '../role/runtime-policy.js';
@@ -7,7 +7,7 @@ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
7
7
  import crypto from 'crypto';
8
8
  import path from 'path';
9
9
  import fs from 'fs';
10
- import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions } from '../../utils/npm-ops.js';
10
+ import { ensureFreshPackageVersionInfo, refreshManagedPackageVersions } from '../../utils/package-version-info.js';
11
11
  import { loadDaemonConfig } from '../../config-store.js';
12
12
  import { remoteAccessService } from '../auth/remote-access-store.js';
13
13
  import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, } from '../../config/config-manager.js';
@@ -667,7 +667,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
667
667
  const appendProcessCommands = (lines) => {
668
668
  if (!isDaemonOwner)
669
669
  return;
670
- lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请远程诊断;/diag stop 撤销授权');
670
+ lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请或追加远程诊断;/diag stop 撤销授权');
671
671
  };
672
672
  if (!isAdmin && activeChatType === 'group') {
673
673
  const lines = [
@@ -711,7 +711,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
711
711
  ' /del <名称> - 删除指定会话(仅解绑,不删除文件)',
712
712
  ...(threadId ? [' /renew - 在当前话题中轮换 backend'] : []),
713
713
  ' /pause - 在下一次工具调用前暂停当前任务',
714
- ' /resume - 继续已暂停的当前任务',
714
+ ' /resume - 继续暂停或异常中断的当前任务',
715
715
  ' /stop - 中断当前任务',
716
716
  ' /status - 显示会话状态',
717
717
  ' /check - 检查 EvolAgent 实例健康',
@@ -761,7 +761,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
761
761
  ' /compact - 压缩会话上下文(减少 token 用量)',
762
762
  ' /renew - 在话题中轮换 backend(下一条消息懒初始化)',
763
763
  ' /pause - 在下一次工具调用前暂停当前任务',
764
- ' /resume - 继续已暂停的当前任务',
764
+ ' /resume - 继续暂停或异常中断的当前任务',
765
765
  ' /stop - 中断当前任务',
766
766
  '',
767
767
  '🤖 Agent 与模型:',
@@ -782,7 +782,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
782
782
  '🛠️ 运维:',
783
783
  ' /status - 显示会话状态',
784
784
  ' /pause - 在下一次工具调用前暂停当前任务',
785
- ' /resume - 继续已暂停的当前任务',
785
+ ' /resume - 继续暂停或异常中断的当前任务',
786
786
  ' /stop - 中断当前任务',
787
787
  ' /check - 检查 EvolAgent 实例健康',
788
788
  ...(isOwner ? [
@@ -825,11 +825,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
825
825
  text: '❌ /pair 仅限 daemon-owner 使用;请向 daemon 控制 AID 发送 /pair。',
826
826
  };
827
827
  }
828
- const pair = remoteAccessService.getPairingCode(userId);
828
+ const pair = remoteAccessService.getPairingCode(userId, Date.now(), 'pair');
829
829
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
830
830
  return {
831
831
  kind: 'command.result',
832
- text: `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)\n可用于 ECWeb 登录或远程 Menu 客户端授权`,
832
+ text: `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效,仅可配对一次)\n可用于 ECWeb 登录或远程 Menu 客户端授权`,
833
833
  };
834
834
  }
835
835
  // /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
@@ -898,7 +898,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
898
898
  // 运维
899
899
  cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
900
900
  cmds.push({ command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] });
901
- cmds.push({ command: '/resume', description: '继续已暂停的当前任务', category: '运维', roles: ['admin', 'owner'] });
901
+ cmds.push({ command: '/resume', description: '继续暂停或异常中断的当前任务', category: '运维', roles: ['admin', 'owner'] });
902
902
  cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
903
903
  cmds.push({ command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
904
904
  if (isAdmin) {
@@ -913,7 +913,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
913
913
  cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
914
914
  cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
915
915
  cmds.push({ command: '/pair', description: '获取远程控制配对码', category: '运维', roles: ['daemon-owner'] });
916
- cmds.push({ command: '/diag', args: '<问题描述>|stop', description: '邀请远程诊断或撤销授权', category: '运维', roles: ['daemon-owner'] });
916
+ cmds.push({ command: '/diag', args: '<问题描述>|stop', description: '邀请/追加远程诊断或撤销授权', category: '运维', roles: ['daemon-owner'] });
917
917
  }
918
918
  if (!isDaemonOwner && isAdmin) {
919
919
  cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
@@ -1071,19 +1071,20 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1071
1071
  if (!args) {
1072
1072
  if (!askSession)
1073
1073
  return { kind: 'command.result', text: '当前没有待回答的问题' };
1074
- const pendingIds = this.interactionRouter?.getPending(askSession.id) || [];
1074
+ const pendingIds = this.interactionRouter?.getPending(askSession.id, 'ask') || [];
1075
1075
  if (pendingIds.length === 0)
1076
1076
  return { kind: 'command.result', text: '当前没有待回答的问题' };
1077
- return { kind: 'command.result', text: `当前有 ${pendingIds.length} 个待回答问题,请回复 /ask <选项>` };
1077
+ return { kind: 'command.result',
1078
+ text: `当前有 ${pendingIds.length} 个待回答问题,请回复 /ask <问题ID> <回答>:\n${pendingIds.join('\n')}` };
1078
1079
  }
1079
1080
  if (!askSession)
1080
1081
  return { kind: 'command.error', text: '❌ 当前没有待回答的问题' };
1081
- 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);
1082
1083
  if (fb.matched)
1083
1084
  return { kind: 'command.result', text: fb.result ?? '✓ 已回答' };
1084
1085
  return { kind: 'command.error', text: '❌ 当前没有待回答的问题' };
1085
1086
  }
1086
- // /pause and /resume control the current session's next-tool gate.
1087
+ // /pause gates the next tool; /resume also recovers a failed backend turn.
1087
1088
  if (normalizedContent.startsWith('/pause ')) {
1088
1089
  return { kind: 'command.error', text: '用法: /pause' };
1089
1090
  }
@@ -1111,8 +1112,16 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1111
1112
  const resumeSession = await getExistingSessionForCommand();
1112
1113
  if (!resumeSession)
1113
1114
  return { kind: 'command.result', text: '当前没有活跃会话' };
1114
- const resumed = this.processor.resumeSession(resumeSession.id);
1115
- 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
+ };
1116
1125
  }
1117
1126
  // /baseagent 命令:查看或切换 Agent 后端
1118
1127
  if (normalizedContent === '/baseagent' || normalizedContent.startsWith('/baseagent ')) {
@@ -1536,10 +1545,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1536
1545
  }
1537
1546
  else {
1538
1547
  const resolvedArg = hasModelSwitcher(modelAgent) ? (modelAgent.resolveModelId?.(arg) ?? arg) : arg;
1539
- if (models.includes(resolvedArg)) {
1540
- newModel = resolvedArg;
1541
- }
1542
- else if (models.includes(arg)) {
1548
+ if (models.includes(resolvedArg) || models.includes(arg)) {
1543
1549
  newModel = arg;
1544
1550
  }
1545
1551
  else {
@@ -1566,7 +1572,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1566
1572
  const errorLabel = allEfforts.includes(effortArg) ? '⚠️' : '❌';
1567
1573
  return { kind: 'command.result', text: `${errorLabel} ${modelArg} 不支持 ${effortArg} 推理强度\n可选: ${targetEfforts.join(' / ')}` };
1568
1574
  }
1569
- newModel = modelArg;
1575
+ newModel = modelArgRaw;
1570
1576
  newEffort = effortArg;
1571
1577
  }
1572
1578
  // 优先写关系级覆盖;只有无法定位关系且具备管理权限时才回退到 runner/agent 级。
@@ -1606,11 +1612,13 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1606
1612
  requestedModel: newModel,
1607
1613
  models,
1608
1614
  resolveModelId: modelAgent.resolveModelId?.bind(modelAgent),
1615
+ selfAid: modelSelector.self,
1609
1616
  });
1610
1617
  if (!decision.ok) {
1611
1618
  return { kind: 'command.error', text: decision.message || `invalid model: ${newModel}` };
1612
1619
  }
1613
- 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.
1614
1622
  }
1615
1623
  const changes = [];
1616
1624
  if (!('error' in relationTarget)) {
@@ -2260,6 +2268,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2260
2268
  const hasActive = stopAgent.hasActiveStream(sessionKey);
2261
2269
  const isProcessing = this.messageQueue.isProcessing(sessionKey);
2262
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');
2263
2274
  return { kind: 'command.result', text: '当前没有正在处理的任务' };
2264
2275
  }
2265
2276
  // 发布中断事件,让 MessageProcessor 标记为 interrupted(而非 done)
@@ -2551,6 +2562,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2551
2562
  session.name = fileInfo.title;
2552
2563
  }
2553
2564
  }
2565
+ const sessionSubtasks = this.sessionManager.getRunnerTaskHistory(session.id).length;
2554
2566
  const lines = [];
2555
2567
  const chatMode = getEffectiveChatmode(session);
2556
2568
  const sessionRole = session.identity?.role || identity.role || 'none';
@@ -2573,7 +2585,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2573
2585
  if (gitInfo) {
2574
2586
  lines.push(`Git: ${gitInfo}`);
2575
2587
  }
2576
- lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
2588
+ lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns} / 已记录子任务数: ${sessionSubtasks}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
2577
2589
  if (health.consecutiveErrors > 0) {
2578
2590
  lines.push(`异常计数: ${health.consecutiveErrors}`);
2579
2591
  }
@@ -2581,7 +2593,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2581
2593
  }
2582
2594
  else {
2583
2595
  lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${channel} / 项目: ${projectName} / ${session.baseagent}会话`);
2584
- lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
2596
+ lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns} / 已记录子任务数: ${sessionSubtasks}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
2585
2597
  }
2586
2598
  if (health.lastError) {
2587
2599
  lines.push('');
@@ -2928,7 +2940,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2928
2940
  text: `❌ ${excludeKey ? '其他会话' : '该 Agent'} 有 ${busyInfo.count} 个任务执行中,无法重启。\n\n处理中的会话:\n${processingLines}\n\n建议:等待这些任务完成后重试。可通过 ec ctl status 查看队列状态;典型等待时间 30-60 秒,群聊大型任务可能更长。`,
2929
2941
  };
2930
2942
  }
2931
- 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' });
2932
2945
  const sessionsWithMessages = allSessions
2933
2946
  .filter((s) => this.messageCache.hasMessages(s.id))
2934
2947
  .map((s) => {
@@ -2984,6 +2997,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2984
2997
  logger.warn(`[System] Refusing restart while data migration is pending: operations=${migrationRequirement.operationCount}`);
2985
2998
  return { code: 'DATA_MIGRATION_REQUIRED', operationCount: migrationRequirement.operationCount };
2986
2999
  }
3000
+ if (!suppressRealRestart) {
3001
+ await ensureFreshPackageVersionInfo(undefined, {
3002
+ source: 'slash-restart',
3003
+ log: message => logger.info(`[System] ${message}`),
3004
+ });
3005
+ }
2987
3006
  const restartInfo = {
2988
3007
  channel,
2989
3008
  channelId,
@@ -3127,18 +3146,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3127
3146
  });
3128
3147
  if (upgradeAuthDenied)
3129
3148
  return upgradeAuthDenied;
3130
- if (isLinkedInstall()) {
3131
- return { kind: 'command.result', text: '⏭ 开发模式,跳过升级检查' };
3132
- }
3133
- const localVer = getLocalVersion();
3134
- const remoteVer = await checkLatestVersion();
3135
- if (!remoteVer) {
3136
- return { kind: 'command.result', text: `⚠️ 无法连接 npm registry(当前版本 ${localVer})` };
3137
- }
3138
- if (compareVersions(localVer, remoteVer) >= 0) {
3139
- return { kind: 'command.result', text: `✓ 已是最新版本 (${localVer})` };
3140
- }
3141
- return { kind: 'command.result', text: `📦 发现新版本 ${localVer} → ${remoteVer}\n执行 /restart 升级` };
3149
+ const results = await refreshManagedPackageVersions(undefined, { source: 'slash-upgrade' });
3150
+ const lines = results.filter(result => result.status !== 'not-installed').map(result => {
3151
+ const { packageName, status, entry } = result;
3152
+ if (status === 'skipped')
3153
+ return `⏭ ${packageName}:开发模式,跳过升级检查`;
3154
+ if (status === 'unavailable') {
3155
+ return `⚠️ ${packageName}:本次检查失败(当前 ${entry.currentVersion ?? '未知'});`
3156
+ + (entry.checkedAt ? `最近成功检查:${entry.checkedAt}` : '尚无成功检查记录');
3157
+ }
3158
+ return `${result.hasUpdate ? '📦' : '✓'} ${packageName}:当前 ${entry.currentVersion},最新 ${entry.latestVersion}`
3159
+ + `;检查时间 ${entry.checkedAt}${result.hasUpdate ? '' : ',无需升级'}`;
3160
+ });
3161
+ if (results.some(result => result.hasUpdate))
3162
+ lines.push('执行 /restart 应用升级');
3163
+ return { kind: 'command.result', text: lines.join('\n') };
3142
3164
  }
3143
3165
  // /pwd 命令:显示当前项目路径
3144
3166
  if (normalizedContent === '/pwd') {
@@ -3649,7 +3671,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3649
3671
  }
3650
3672
  await interruptPausedSessionBeforeReplacement(session);
3651
3673
  try {
3652
- const forkedSessionId = await forkAgent.forkSession(session.agentSessionId, session.projectPath, forkName);
3674
+ const forkedSessionId = await forkAgent.forkSession(session.agentSessionId, session.projectPath, forkName, {
3675
+ sessionId: session.id, permissionMode: authSubject.permissionMode, role: authSubject.role,
3676
+ });
3653
3677
  const newSession = await this.sessionManager.createForkedSession(session, forkedSessionId, forkName);
3654
3678
  await forkAgent.updateSessionMetadata?.(forkedSessionId, {
3655
3679
  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,