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
package/dist/index.js CHANGED
@@ -16,9 +16,13 @@ import { loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfi
16
16
  import { ConfigTarget, initConfigManager, onConfigWrite } from './config/config-manager.js';
17
17
  import { ensureRoleConfigV4OnStartup } from './config/role-config-v4-startup.js';
18
18
  import { ensureRoleConfigV5OnStartup } from './config/role-config-v5-startup.js';
19
+ import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
20
+ import { resolvePeerDisplayLabel } from './config/contact-book.js';
21
+ import { expirePendingContactRequests } from './config/contact-request-service.js';
19
22
  import { shouldFailFastForMissingOwners } from './config/owner-policy.js';
20
23
  import { isManagementRole } from './config/builtin-roles.js';
21
24
  import { checkRoleAccess, getFirstStaticAgentOwner, resolvePeerRoleDetail, roleToSessionIdentity } from './config/peer-role-resolver.js';
25
+ import { isCrossAgentTriggerOperationAllowed, isCrossAgentTriggerReadOperation } from './core/auth/trigger-authorization.js';
22
26
  import { ensureStartupConfigSnapshot, isConfigSnapshotsEnabled, retentionCleanup, readCurrent, readWVersion, diffWorkingVsVersion, paramDiff, incrementSuccessCount } from './config/snapshot.js';
23
27
  import { appendBootLog, selfDiagnose } from './config/boot-log.js';
24
28
  import { CONFIG_SCHEMA_VERSION } from './types.js';
@@ -26,19 +30,22 @@ import { SessionManager } from './core/session/session-manager.js';
26
30
  import { ClaudeAgentPlugin } from './agents/claude-runner.js';
27
31
  import { CodexAgentPlugin } from './agents/codex-runner.js';
28
32
  import { GeminiAgentPlugin } from './agents/gemini-runner.js';
29
- import { FeishuChannelPlugin } from './channels/feishu.js';
30
- import { WechatChannelPlugin } from './channels/wechat.js';
33
+ import { FeishuChannelPlugin, registerPendingFeishuContactBind } from './channels/feishu.js';
34
+ import { WechatChannelPlugin, registerPendingWechatContactBind } from './channels/wechat.js';
31
35
  import { AUN_HANDOFF_MARKER_FIELD, AUNChannel, AUNChannelPlugin } from './channels/aun.js';
36
+ import { assertAunEncryptionSeedPolicy } from './aun/aid/encryption-seed-policy.js';
32
37
  import { startServiceProxy } from './aun/service-proxy.js';
33
38
  import { BindService } from './utils/aid-bind.js';
34
39
  import { DingtalkChannelPlugin, registerPendingDingtalkContactBind } from './channels/dingtalk.js';
35
- import { QQBotChannelPlugin } from './channels/qqbot.js';
36
- import { WecomChannelPlugin } from './channels/wecom.js';
40
+ import { QQBotChannelPlugin, registerPendingQQBotContactBind } from './channels/qqbot.js';
41
+ import { WecomChannelPlugin, registerPendingWecomContactBind } from './channels/wecom.js';
37
42
  import { buildEnvelope } from './core/message/message-utils.js';
38
43
  import { ResponseEngine } from './core/message/response-engine.js';
39
44
  import { MessageQueue } from './core/message/message-queue.js';
40
45
  import { MessageBridge } from './core/message/message-bridge.js';
41
46
  import { MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
47
+ import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata } from './core/command/evol-menu-version-gate.js';
48
+ import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
42
49
  import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
43
50
  import { HandoffRuntime } from './core/handoff/runtime.js';
44
51
  import { BootstrapService } from './core/bootstrap-service.js';
@@ -344,7 +351,7 @@ function removeLegacyAgentUpgradeCheck(manager) {
344
351
  export async function sendSystemPayload(adapter, envelope, payload) {
345
352
  await adapter.send(envelope, payload);
346
353
  }
347
- async function runBindBootstrapDaemon(daemonCfg, defaults) {
354
+ async function runBindBootstrapDaemon(daemonCfg) {
348
355
  logger.warn('[bind-bootstrap] starting control AID + IPC only');
349
356
  const bindService = new BindService({
350
357
  receiverAid: daemonCfg.aid,
@@ -358,8 +365,8 @@ async function runBindBootstrapDaemon(daemonCfg, defaults) {
358
365
  agentName: daemonCfg.aid,
359
366
  channelName: 'control',
360
367
  pureIdentity: true,
361
- aunTrace: daemonCfg.debug?.aunTrace ?? defaults.debug?.aunTrace,
362
- aunSdkLog: daemonCfg.debug?.aunSdkLog ?? defaults.debug?.aunSdkLog,
368
+ aunTrace: daemonCfg.debug?.aunTrace,
369
+ aunSdkLog: daemonCfg.debug?.aunSdkLog,
363
370
  });
364
371
  try {
365
372
  await controlChannel.connect();
@@ -416,13 +423,7 @@ async function runBindBootstrapDaemon(daemonCfg, defaults) {
416
423
  process.on('exit', () => removeAll());
417
424
  }
418
425
  export function readEvolcoreVersion() {
419
- try {
420
- const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
421
- return pkg.version || 'unknown';
422
- }
423
- catch {
424
- return 'unknown';
425
- }
426
+ return readInstalledEvolcoreVersion();
426
427
  }
427
428
  function detectAvailableBaseagentsForBind() {
428
429
  const out = [];
@@ -582,6 +583,18 @@ async function main() {
582
583
  logger.info(`EvolCore v${readEvolcoreVersion()} starting... (fastaun v${readFastaunVersion()})`);
583
584
  // 确保数据目录存在
584
585
  ensureDataDirs();
586
+ // AUN keystore seed is an EvolCore application constant. A custom value can
587
+ // make existing identities unreadable, so fail before migrations, snapshots,
588
+ // instance registration, and service startup.
589
+ try {
590
+ assertAunEncryptionSeedPolicy(loadDaemonConfig().aun?.encryptionSeed, process.env.AUN_ENCRYPTION_SEED);
591
+ }
592
+ catch (error) {
593
+ const msg = `❌ ${error instanceof Error ? error.message : String(error)}`;
594
+ logger.error(msg);
595
+ console.error(msg);
596
+ process.exit(1);
597
+ }
585
598
  // .env 文件已在模块顶部加载,此处不再重复加载
586
599
  // ── 单实例保护(pre-check + post-write self-check)──
587
600
  // pre-check:发现已有活 main 直接退出,避免起任何副作用
@@ -598,6 +611,9 @@ async function main() {
598
611
  }
599
612
  // 在登记 daemon 实例前自动备份并迁移旧角色配置;失败才阻止启动。
600
613
  try {
614
+ // Crash recovery must precede schema gates and migrations so they never
615
+ // inspect or migrate a partially applied Role Menu transaction.
616
+ recoverAllRoleMutationsSync();
601
617
  const migration = await ensureRoleConfigV4OnStartup();
602
618
  if (migration) {
603
619
  const msg = `✓ Role config v4 migration applied automatically: ${migration.changedFiles} file(s)` +
@@ -612,6 +628,13 @@ async function main() {
612
628
  logger.info(msg);
613
629
  console.error(msg);
614
630
  }
631
+ const contactMigration = await ensureContactBookV2OnStartup();
632
+ if (contactMigration) {
633
+ const msg = `✓ Contact book v2 migration applied automatically: ${contactMigration.changedFiles} file(s)` +
634
+ (contactMigration.backup ? `; backup: ${contactMigration.backup}` : '');
635
+ logger.info(msg);
636
+ console.error(msg);
637
+ }
615
638
  }
616
639
  catch (e) {
617
640
  const msg = `❌ ${e instanceof Error ? e.message : String(e)}`;
@@ -639,7 +662,6 @@ async function main() {
639
662
  // ── 配置体系初始化(schema 字段不相交硬约束校验)──
640
663
  try {
641
664
  initConfigManager();
642
- recoverAllRoleMutationsSync();
643
665
  }
644
666
  catch (e) {
645
667
  const msg = `❌ 配置 schema 校验失败: ${e instanceof Error ? e.message : String(e)}`;
@@ -670,6 +692,9 @@ async function main() {
670
692
  }
671
693
  }
672
694
  const daemonCfg = loadDaemonConfig();
695
+ if (daemonCfg.debug?.logLevel) {
696
+ setLogLevel(daemonCfg.debug.logLevel);
697
+ }
673
698
  if (initializeEckSnapshotsConfig(daemonCfg)) {
674
699
  logger.warn(`[ECK] Debug snapshots enabled at ${eckDebugDir()}; files may contain sensitive context and local paths.`);
675
700
  }
@@ -691,12 +716,9 @@ async function main() {
691
716
  logger.warn('[startup] daemon.json.owners 未配置:进程级 menu 操作(/system /agent)将一律拒绝。' +
692
717
  '如需远程管理,请在 daemon.json 配置 owners: [<你的 AID>]');
693
718
  }
694
- // 应用配置中的日志级别(优先于环境变量)
695
- // logLevel 现在不在新结构中——若要保留,将来可加 defaults.debug.logLevel
696
- // 阶段 2c 暂跳过
697
719
  const paths = resolvePaths();
698
720
  if (bindBootstrapMode && daemonCfg.aid && loadAllAgents().agents.length === 0) {
699
- await runBindBootstrapDaemon(daemonCfg, defaults);
721
+ await runBindBootstrapDaemon(daemonCfg);
700
722
  return;
701
723
  }
702
724
  // ── EvolAgent Registry:加载 agents/<aid>/config.json ──
@@ -744,14 +766,11 @@ async function main() {
744
766
  agentRuntimeError = 'No runnable self-agent (all are error/disabled).';
745
767
  logger.warn(`[startup] ${agentRuntimeError} Control Plane will remain available.`);
746
768
  }
747
- // 进程级设置:idleMonitor 属于 daemon.json;debug 继续沿用 defaults 的现有行为。
769
+ // 进程级设置独立来自 daemon.json,不进入 Agent 覆盖链。
748
770
  const globalSettings = {
749
771
  idleMonitor: daemonCfg.idleMonitor,
750
- debug: defaults.debug,
772
+ debug: daemonCfg.debug,
751
773
  };
752
- if (globalSettings.debug?.logLevel) {
753
- setLogLevel(globalSettings.debug.logLevel);
754
- }
755
774
  // 启动期 anthropic 凭证校验已移除:runner 创建时由 AgentLoader 错误处理
756
775
  logger.info('✓ Config loaded');
757
776
  // Store for IPC access (T10 will wire this)
@@ -870,7 +889,9 @@ async function main() {
870
889
  const permissionGateway = new PermissionGateway();
871
890
  permissionGateway.setEventBus(eventBus);
872
891
  onConfigWrite(({ target, selector }) => {
873
- if ((target !== ConfigTarget.Agent && target !== ConfigTarget.Contact) || !selector.self)
892
+ if ((target !== ConfigTarget.Agent
893
+ && target !== ConfigTarget.Contact
894
+ && target !== ConfigTarget.Relation) || !selector.self)
874
895
  return;
875
896
  const cancelled = permissionGateway.revalidatePendingApprovals(selector.self);
876
897
  if (cancelled > 0) {
@@ -906,7 +927,7 @@ async function main() {
906
927
  messageCache.cleanupExpired();
907
928
  }, 60 * 60 * 1000);
908
929
  // 渠道插件系统
909
- const channelLoader = new ChannelLoader();
930
+ const channelLoader = new ChannelLoader(daemonCfg.debug);
910
931
  channelLoader.register(new FeishuChannelPlugin());
911
932
  channelLoader.register(new WechatChannelPlugin());
912
933
  channelLoader.register(new AUNChannelPlugin());
@@ -928,6 +949,14 @@ async function main() {
928
949
  }
929
950
  const channelInstances = evolagentInstances;
930
951
  logger.info(`✓ Created ${channelInstances.length} channel instance(s)`);
952
+ const contactRequestExpiryTimer = setInterval(() => {
953
+ for (const agent of agentRegistry.runnableAgents()) {
954
+ void expirePendingContactRequests(agent.aid).catch(error => {
955
+ logger.warn(`[contact-request] Pending expiry sweep failed: self=${agent.aid} error=${error instanceof Error ? error.message : String(error)}`);
956
+ });
957
+ }
958
+ }, 60_000);
959
+ contactRequestExpiryTimer.unref?.();
931
960
  const bindService = daemonCfg.aid
932
961
  ? new BindService({
933
962
  receiverAid: daemonCfg.aid,
@@ -990,6 +1019,10 @@ async function main() {
990
1019
  await responseEngine.interruptSession(sessionKey, reason);
991
1020
  });
992
1021
  messageQueue.setEventBus(eventBus);
1022
+ // Restore persisted input before Trigger schedulers start. Trigger-owned work
1023
+ // is deliberately dropped here and reclaimed from active.json by its scheduler.
1024
+ messageQueue.restorePersisted(false);
1025
+ const droppedTriggerSessionIds = new Set(messageQueue.consumeDroppedTriggerSessionIds());
993
1026
  // 进程退出时立即刷盘队列状态(防止 debounce 期间的消息丢失)
994
1027
  onShutdown(() => messageQueue.persistQueuesImmediate());
995
1028
  // 回填 messageQueue 引用
@@ -1061,6 +1094,43 @@ async function main() {
1061
1094
  baseagent: primaryAgent?.baseagent || 'codex',
1062
1095
  projectPath: primaryAgent?.projectPath || process.cwd(),
1063
1096
  };
1097
+ const triggerBaseagentsForAgent = (agentAid) => {
1098
+ const ownerName = agentRegistry.get(agentAid)?.name
1099
+ ?? (agentAid === daemonTriggerOwner.aid ? primaryAgent?.name : undefined);
1100
+ if (!ownerName)
1101
+ return [];
1102
+ const prefix = `${ownerName}::`;
1103
+ return [...agentMap.keys()]
1104
+ .filter(key => key.startsWith(prefix))
1105
+ .map(key => key.slice(prefix.length));
1106
+ };
1107
+ const materializeTriggerCreateBaseagent = (input, agentAid) => {
1108
+ const rawExecution = input.execution;
1109
+ if (!rawExecution || typeof rawExecution !== 'object' || Array.isArray(rawExecution))
1110
+ return input;
1111
+ const execution = rawExecution;
1112
+ if (execution.type !== 'trigger_session') {
1113
+ if (execution.baseagent !== undefined) {
1114
+ throw new Error('execution.baseagent is only allowed when execution.type=trigger_session');
1115
+ }
1116
+ return input;
1117
+ }
1118
+ if (execution.baseagent !== undefined
1119
+ && (typeof execution.baseagent !== 'string' || !execution.baseagent.trim())) {
1120
+ throw new Error('execution.baseagent must be a non-empty string');
1121
+ }
1122
+ const baseagent = typeof execution.baseagent === 'string'
1123
+ ? execution.baseagent.trim()
1124
+ : agentRegistry.get(agentAid)?.baseagent
1125
+ ?? (agentAid === daemonTriggerOwner.aid ? daemonTriggerOwner.baseagent : undefined);
1126
+ if (!baseagent)
1127
+ throw new Error('unable to resolve Trigger session baseagent');
1128
+ const available = triggerBaseagentsForAgent(agentAid);
1129
+ if (!available.includes(baseagent)) {
1130
+ throw new Error(`Trigger baseagent unavailable for ${agentAid}: ${baseagent} (available: ${available.join(', ') || 'none'})`);
1131
+ }
1132
+ return { ...input, execution: { ...execution, baseagent } };
1133
+ };
1064
1134
  const getTriggerChannel = (agentAid, channelKey) => {
1065
1135
  if (agentAid === daemonTriggerOwner.aid && channelKey === daemonChannel.channelKey) {
1066
1136
  return {
@@ -1117,7 +1187,7 @@ async function main() {
1117
1187
  const agent = agentRegistry.get(definition.agentAid);
1118
1188
  const modelDecision = validateModelSelectionForRole({
1119
1189
  role: actor.role,
1120
- baseagent: agent?.baseagent,
1190
+ baseagent: definition.execution.baseagent ?? agent?.baseagent,
1121
1191
  requestedModel: definition.execution.model,
1122
1192
  selfAid: definition.agentAid,
1123
1193
  });
@@ -1125,7 +1195,9 @@ async function main() {
1125
1195
  throw new Error(modelDecision.message || 'trigger model is not allowed by current role');
1126
1196
  }
1127
1197
  if (definition.execution.effort) {
1128
- const baseagent = agentRegistry.get(definition.agentAid)?.baseagent || 'claude';
1198
+ const baseagent = definition.execution.baseagent
1199
+ ?? agentRegistry.get(definition.agentAid)?.baseagent
1200
+ ?? 'claude';
1129
1201
  const effortDecision = validateRuntimeStringFieldOverride({
1130
1202
  selfAid: definition.agentAid,
1131
1203
  role: actor.role,
@@ -1230,7 +1302,7 @@ async function main() {
1230
1302
  }
1231
1303
  return { allowed: true };
1232
1304
  };
1233
- const authenticatedTriggerActor = async (agentAid, actorSessionId, delegationToken, controlToken) => {
1305
+ const authenticatedTriggerActor = async (agentAid, actorSessionId, delegationToken, delegationCommandHash, controlToken) => {
1234
1306
  if (typeof actorSessionId !== 'string' || !actorSessionId) {
1235
1307
  const providedToken = typeof controlToken === 'string' ? Buffer.from(controlToken) : undefined;
1236
1308
  const expectedToken = Buffer.from(triggerControlToken);
@@ -1266,12 +1338,9 @@ async function main() {
1266
1338
  subject,
1267
1339
  };
1268
1340
  }
1269
- const validation = agentDelegationRegistry.validate(typeof delegationToken === 'string' ? delegationToken : undefined, actorSessionId);
1341
+ const validation = agentDelegationRegistry.validate(typeof delegationToken === 'string' ? delegationToken : undefined, actorSessionId, typeof delegationCommandHash === 'string' ? delegationCommandHash : undefined);
1270
1342
  if (!validation.ok)
1271
1343
  throw new Error(validation.reason);
1272
- if (validation.grant.selfAid !== agentAid) {
1273
- throw new Error('trigger agent does not match authenticated task agent');
1274
- }
1275
1344
  const actorSession = await sessionManager.getSessionById(actorSessionId);
1276
1345
  const actorOrigin = actorSession ? originFromActorSession(actorSession, validation.grant.actorId, validation.grant.channel, validation.grant.chatType) : undefined;
1277
1346
  if (!actorSession || !actorOrigin?.peerId)
@@ -1280,7 +1349,9 @@ async function main() {
1280
1349
  throw new Error('trigger delegation actor does not match session peer');
1281
1350
  }
1282
1351
  const roleDetail = resolvePeerRoleDetail({
1283
- selfAid: validation.grant.selfAid,
1352
+ // A cross-agent audit must be authorized by the target agent's role
1353
+ // configuration, not by the task agent that carried the delegation.
1354
+ selfAid: agentAid,
1284
1355
  channelKey: validation.grant.channel,
1285
1356
  channelType: validation.grant.channelType,
1286
1357
  chatType: 'private',
@@ -1288,11 +1359,11 @@ async function main() {
1288
1359
  conversationId: validation.grant.actorId,
1289
1360
  });
1290
1361
  const currentRole = roleDetail.effectiveRole;
1291
- if (!checkRoleAccess(currentRole, validation.grant.selfAid)) {
1362
+ if (!checkRoleAccess(currentRole, agentAid)) {
1292
1363
  throw new Error('trigger actor no longer has access to this agent');
1293
1364
  }
1294
1365
  const subject = buildAuthSubject({
1295
- selfAid: validation.grant.selfAid,
1366
+ selfAid: agentAid,
1296
1367
  actorId: validation.grant.actorId,
1297
1368
  channel: validation.grant.channel,
1298
1369
  channelType: validation.grant.channelType,
@@ -1385,18 +1456,36 @@ async function main() {
1385
1456
  }
1386
1457
  };
1387
1458
  cmdHandler.setTriggerSchedulerResolver((agentAid) => triggerSchedulers.get(agentAid));
1388
- const startedTriggerAgents = new Set();
1459
+ const triggerSchedulerStarts = new Map();
1389
1460
  const ensureTriggerSchedulerStarted = async (agent) => {
1390
- if (startedTriggerAgents.has(agent.aid))
1391
- return;
1392
- startedTriggerAgents.add(agent.aid);
1393
- await startTriggerScheduler(agent);
1461
+ const existing = triggerSchedulerStarts.get(agent.aid);
1462
+ if (existing)
1463
+ return await existing;
1464
+ const starting = startTriggerScheduler(agent);
1465
+ triggerSchedulerStarts.set(agent.aid, starting);
1466
+ try {
1467
+ await starting;
1468
+ }
1469
+ catch (error) {
1470
+ if (triggerSchedulerStarts.get(agent.aid) === starting)
1471
+ triggerSchedulerStarts.delete(agent.aid);
1472
+ throw error;
1473
+ }
1394
1474
  };
1395
1475
  const ensureDaemonTriggerSchedulerStarted = async () => {
1396
- if (startedTriggerAgents.has(daemonTriggerOwner.aid))
1397
- return;
1398
- startedTriggerAgents.add(daemonTriggerOwner.aid);
1399
- await startTriggerScheduler(daemonTriggerOwner, { seedUpgradeCheck: true });
1476
+ const existing = triggerSchedulerStarts.get(daemonTriggerOwner.aid);
1477
+ if (existing)
1478
+ return await existing;
1479
+ const starting = startTriggerScheduler(daemonTriggerOwner, { seedUpgradeCheck: true });
1480
+ triggerSchedulerStarts.set(daemonTriggerOwner.aid, starting);
1481
+ try {
1482
+ await starting;
1483
+ }
1484
+ catch (error) {
1485
+ if (triggerSchedulerStarts.get(daemonTriggerOwner.aid) === starting)
1486
+ triggerSchedulerStarts.delete(daemonTriggerOwner.aid);
1487
+ throw error;
1488
+ }
1400
1489
  };
1401
1490
  // 默认策略
1402
1491
  const defaultPolicy = {
@@ -1417,8 +1506,15 @@ async function main() {
1417
1506
  const defaultProjectPath = primaryAgent?.projectPath
1418
1507
  ?? defaults.projects?.defaultPath
1419
1508
  ?? path.join(paths.root, 'projects', 'default');
1509
+ const isContactBindChannelReady = (channelKey) => {
1510
+ const inst = channelInstances.find(candidate => candidate.adapter.channelKey === channelKey);
1511
+ return !!inst && inst.channel.getStatus?.().connected === true;
1512
+ };
1420
1513
  const msgBridge = new MessageBridge(defaultProjectPath, sessionManager, processor, messageQueue, cmdHandler, eventBus, primaryAgent?.config.debounce, () => processLevelOwners);
1421
1514
  msgBridge.setAgentRegistry(agentRegistry);
1515
+ msgBridge.setInteractionRouter(interactionRouter);
1516
+ msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
1517
+ msgBridge.setAidStatsCollector(aidStatsCollector);
1422
1518
  const bootstrapService = new BootstrapService(agentRegistry, eventBus);
1423
1519
  msgBridge.setBootstrapService(bootstrapService);
1424
1520
  msgBridge.setHandoffRuntime(handoffRuntime);
@@ -1445,7 +1541,7 @@ async function main() {
1445
1541
  const isGroup = session.chatType === 'group';
1446
1542
  const target = isGroup
1447
1543
  ? (session.metadata?.groupId || session.channelId)
1448
- : (session.metadata?.peerId || envelope.channelId);
1544
+ : (envelope.replyContext?.peerId || session.metadata?.peerId || envelope.channelId);
1449
1545
  appendMessageLog(chatDir, buildOutboundEntry({
1450
1546
  from: owningAgent?.aid ?? envelope.agentName ?? session.selfAID ?? 'self',
1451
1547
  to: String(target || envelope.channelId),
@@ -1463,6 +1559,16 @@ async function main() {
1463
1559
  source: normalizeMessageSource(envelope.replyContext?.metadata?.source),
1464
1560
  chatmode: envelope.chatmode,
1465
1561
  }));
1562
+ // Per-AID 统计(预览行/tooltip 数据源)。AUN 在 channel 层自行记账,此处只补非 AUN。
1563
+ const statsAid = owningAgent?.aid ?? envelope.agentName ?? session.selfAID;
1564
+ if (statsAid) {
1565
+ aidStatsCollector.recordOutbound(statsAid, String(target || envelope.channelId), Buffer.byteLength(logPayload.text, 'utf-8'), logPayload.text, false, undefined, envelope.chatmode, 'send', {
1566
+ channelType: inst.channelType || inst.adapter.channelName,
1567
+ // 与入向同一套展示名解析:联系人簿 displayName 优先,兜底原生 ID
1568
+ peerLabel: resolvePeerDisplayLabel(statsAid, inst.channelType || inst.adapter.channelName, String(target || envelope.channelId), inst.adapter.channelKey) || session.metadata?.peerName || String(target || envelope.channelId),
1569
+ routeId: envelope.channelId,
1570
+ });
1571
+ }
1466
1572
  }
1467
1573
  }
1468
1574
  catch (err) {
@@ -1612,7 +1718,7 @@ async function main() {
1612
1718
  const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey) ?? agentRegistry.resolveByChannel(name);
1613
1719
  if (!agent)
1614
1720
  return;
1615
- if (!agent.config.debug?.upmsg)
1721
+ if (!daemonCfg.debug?.upmsg)
1616
1722
  return;
1617
1723
  const ownerAid = getFirstStaticAgentOwner(agent.aid);
1618
1724
  if (!ownerAid)
@@ -1787,15 +1893,15 @@ async function main() {
1787
1893
  // Trigger scheduler 与渠道连接解耦:cron 定时任务独立于渠道可用性运行
1788
1894
  // (触发时若渠道未连,发送侧自行排队/重试)。markChannelConnected 里的
1789
1895
  // ensureTriggerSchedulerStarted 仅作"尽早启动"优化,此处保证即使渠道首连
1790
- // 全部失败(如 gateway 宕机),trigger 仍无条件启动。Set 去重,不会重复。
1791
- ensureDaemonTriggerSchedulerStarted().catch((e) => {
1792
- logger.warn(`[startup] daemon trigger scheduler start failed for ${daemonTriggerOwner.aid}: ${e}`);
1793
- });
1794
- for (const agent of triggerStartupAgents) {
1795
- ensureTriggerSchedulerStarted(agent).catch((e) => {
1896
+ // 全部失败(如 gateway 宕机),trigger 仍无条件启动。Promise 去重,不会重复。
1897
+ await Promise.all([
1898
+ ensureDaemonTriggerSchedulerStarted().catch((e) => {
1899
+ logger.warn(`[startup] daemon trigger scheduler start failed for ${daemonTriggerOwner.aid}: ${e}`);
1900
+ }),
1901
+ ...triggerStartupAgents.map(agent => ensureTriggerSchedulerStarted(agent).catch((e) => {
1796
1902
  logger.warn(`[startup] trigger scheduler start failed for ${agent.aid}: ${e}`);
1797
- });
1798
- }
1903
+ })),
1904
+ ]);
1799
1905
  // ── 控制 AID(daemon 进程身份):pureIdentity 接入 AUN,独立于 evolagent ──
1800
1906
  // 证书缺失检测/生成在 CLI 侧(ec start)完成。daemon 是后台进程无终端,
1801
1907
  // 这里只做兜底:证书缺失时 warn 并继续(AUNChannel 内部后台重连),绝不阻塞。
@@ -1813,8 +1919,8 @@ async function main() {
1813
1919
  agentName: daemonCfg.aid,
1814
1920
  channelName: 'control',
1815
1921
  pureIdentity: true,
1816
- aunTrace: daemonCfg.debug?.aunTrace ?? defaults.debug?.aunTrace,
1817
- aunSdkLog: daemonCfg.debug?.aunSdkLog ?? defaults.debug?.aunSdkLog,
1922
+ aunTrace: daemonCfg.debug?.aunTrace,
1923
+ aunSdkLog: daemonCfg.debug?.aunSdkLog,
1818
1924
  });
1819
1925
  // connect() 失败不置空实例:AUNChannel 内部有无限重连(SDK auto_reconnect +
1820
1926
  // scheduleReconnect),首连失败后台会自愈;保留实例供 status 显示 disconnected。
@@ -1884,13 +1990,16 @@ async function main() {
1884
1990
  const menuControl = parseMenuControl(text);
1885
1991
  const isRoleMenu = menuControl.isMenu
1886
1992
  && (menuControl.name === 'role' || menuControl.request.cmd === '/role');
1993
+ const menuResponseContext = () => ({
1994
+ metadata: evolMenuResponseTransportMetadata(opts.encrypted),
1995
+ });
1887
1996
  if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
1888
1997
  logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
1889
1998
  return;
1890
1999
  }
1891
2000
  if (menuControl.isMenu && !isRoleMenu && !isProcessLevelOwner(opts.peerId, processLevelOwners)) {
1892
2001
  const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: 'Current identity cannot access the control channel' });
1893
- await controlChannel.sendStructured(opts.channelId, response, { metadata: { encrypted: opts.encrypted } });
2002
+ await controlChannel.sendStructured(opts.channelId, response, menuResponseContext());
1894
2003
  return;
1895
2004
  }
1896
2005
  if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, processLevelOwners)) {
@@ -1913,9 +2022,17 @@ async function main() {
1913
2022
  }
1914
2023
  // menu.* JSON 路由:owner 已在上方校验,转交 execMenuForControl(fromControlChannel=true)
1915
2024
  if (menuControl.isMenu) {
2025
+ const versionError = evaluateEvolMenuVersionGate({
2026
+ encrypted: opts.encrypted,
2027
+ protectedHeaders: opts.protectedHeaders,
2028
+ });
2029
+ if (versionError) {
2030
+ await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError), menuResponseContext());
2031
+ return;
2032
+ }
1916
2033
  const validationError = validateMenuRequest(menuControl.request);
1917
2034
  if (validationError) {
1918
- await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError), { metadata: { encrypted: opts.encrypted } });
2035
+ await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError), menuResponseContext());
1919
2036
  return;
1920
2037
  }
1921
2038
  const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId));
@@ -1924,7 +2041,7 @@ async function main() {
1924
2041
  : deduped.value;
1925
2042
  // 同 bind.response:sendStructured 直发 typed payload(payload.type='menu.response'),
1926
2043
  // 不能用 sendMessage(会包成 {type:'text',...});encrypted 跟随入站请求保持对称。
1927
- await controlChannel.sendStructured(opts.channelId, response, { metadata: { encrypted: opts.encrypted } });
2044
+ await controlChannel.sendStructured(opts.channelId, response, menuResponseContext());
1928
2045
  return;
1929
2046
  }
1930
2047
  // owner 发的其他内容:提示可用指令
@@ -1991,9 +2108,7 @@ async function main() {
1991
2108
  });
1992
2109
  }
1993
2110
  });
1994
- // 先恢复消息队列。若某个 session 有原始 active/pending 消息,下面的泛化 resume 会跳过它。
1995
- messageQueue.restorePersisted(false);
1996
- const droppedTriggerSessionIds = new Set(messageQueue.consumeDroppedTriggerSessionIds());
2111
+ // Trigger scheduler 已在队列恢复之后启动;这里只协调其余持久化工作。
1997
2112
  // runnableAgents() only returns pre-start `stopped` agents. Reuse the stable
1998
2113
  // startup snapshot because channel connection may already mark them running.
1999
2114
  const recoveryAids = triggerStartupAgents.map(agent => agent.aid);
@@ -2029,8 +2144,13 @@ async function main() {
2029
2144
  logger.info(`[Resume] Found ${pendingSessions.length} pending session(s) from before restart`);
2030
2145
  for (const session of pendingSessions) {
2031
2146
  if (droppedTriggerSessionIds.has(session.id)) {
2032
- logger.info(`[Resume] session ${session.id}: dropped stale Trigger execution, clearing processing without resume`);
2033
- sessionManager.clearProcessing(session.id);
2147
+ if (messageQueue.isProcessing(session.id) || messageQueue.getQueueLength(session.id) > 0) {
2148
+ logger.info(`[Resume] session ${session.id}: Trigger scheduler already reclaimed execution, skipping generic resume`);
2149
+ }
2150
+ else {
2151
+ logger.info(`[Resume] session ${session.id}: dropped stale Trigger execution was not reclaimable, clearing processing`);
2152
+ sessionManager.clearProcessing(session.id);
2153
+ }
2034
2154
  continue;
2035
2155
  }
2036
2156
  if (messageQueue.isProcessing(session.id) || messageQueue.getQueueLength(session.id) > 0) {
@@ -2121,8 +2241,8 @@ async function main() {
2121
2241
  ? { aid: daemonCfg.aid, connected: controlChannel?.getAidState().status === 'connected' }
2122
2242
  : undefined,
2123
2243
  };
2124
- }, async (cmd, sessionId, delegationToken) => {
2125
- const delegation = agentDelegationRegistry.validate(delegationToken, sessionId);
2244
+ }, async (cmd, sessionId, delegationToken, delegationCommandHash) => {
2245
+ const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
2126
2246
  if (!delegation.ok)
2127
2247
  return { ok: false, code: delegation.code, error: delegation.reason };
2128
2248
  return cmdHandler.handleCtl(cmd, sessionId);
@@ -2131,9 +2251,27 @@ async function main() {
2131
2251
  ipcServer.setAgentRegistry(agentRegistry);
2132
2252
  ipcServer.setDingtalkContactBindExecutor({
2133
2253
  register: (cmd) => registerPendingDingtalkContactBind(cmd),
2254
+ isChannelReady: isContactBindChannelReady,
2255
+ });
2256
+ ipcServer.setFeishuContactBindExecutor({
2257
+ register: (cmd) => registerPendingFeishuContactBind(cmd),
2258
+ isChannelReady: isContactBindChannelReady,
2259
+ });
2260
+ ipcServer.setQQBotContactBindExecutor({
2261
+ register: (cmd) => registerPendingQQBotContactBind(cmd),
2262
+ isChannelReady: isContactBindChannelReady,
2263
+ });
2264
+ ipcServer.setWecomContactBindExecutor({
2265
+ register: (cmd) => registerPendingWecomContactBind(cmd),
2266
+ isChannelReady: isContactBindChannelReady,
2267
+ });
2268
+ ipcServer.setWechatContactBindExecutor({
2269
+ register: (cmd) => registerPendingWechatContactBind(cmd),
2270
+ isChannelReady: isContactBindChannelReady,
2134
2271
  });
2135
2272
  ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
2136
- ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken));
2273
+ ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
2274
+ ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
2137
2275
  cmdHandler.setDaemonStatusProvider(() => {
2138
2276
  const aidState = controlChannel?.getAidState?.();
2139
2277
  return {
@@ -2259,6 +2397,7 @@ async function main() {
2259
2397
  : undefined;
2260
2398
  const delegation = authorizeDelegatedAunMsgSend(agentDelegationRegistry, {
2261
2399
  delegationToken: params.delegationToken,
2400
+ delegationCommandHash: params.delegationCommandHash,
2262
2401
  sessionId: params.originSessionId,
2263
2402
  messageId: params.originMessageId,
2264
2403
  aid: params.aid,
@@ -2481,6 +2620,10 @@ async function main() {
2481
2620
  };
2482
2621
  // Full resync handler: scan disk, load new agents, unload removed/disabled, reload changed
2483
2622
  globalThis.__evolcore_resyncAgents = async () => {
2623
+ // 先清 kit 缓存:'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload
2624
+ // 策略,平时不查盘。放在扫盘与 reload 之前,本轮上下线的 agent 才能读到磁盘上的
2625
+ // 最新版本——放在末尾的话,这一轮全用旧值,改动要等下一次 resync 才生效。
2626
+ invalidateKitCache();
2484
2627
  const { loadAllAgents: scanAgents, loadDefaults: readDefaults } = await import('./config-store.js');
2485
2628
  const { resolveEffective } = await import('./config/config-manager.js');
2486
2629
  const freshDefaults = readDefaults();
@@ -2502,6 +2645,7 @@ async function main() {
2502
2645
  }
2503
2646
  await triggerSchedulers.get(aid)?.stop();
2504
2647
  triggerSchedulers.delete(aid);
2648
+ triggerSchedulerStarts.delete(aid);
2505
2649
  // 断开所有 channels
2506
2650
  for (const chName of agent.channelInstanceNames()) {
2507
2651
  const inst = channelInstances.find(i => i.adapter.channelName === chName);
@@ -2554,8 +2698,7 @@ async function main() {
2554
2698
  results.push(`⚠ ${cfg.aid}: ${e?.message || e}`);
2555
2699
  }
2556
2700
  }
2557
- // 重建 channel index + 清除 kit 缓存
2558
- invalidateKitCache();
2701
+ // 重建 channel indexkit 缓存已在本轮开头清过)
2559
2702
  agentRegistry.channelIndex.clear();
2560
2703
  agentRegistry.buildChannelIndex();
2561
2704
  logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
@@ -2627,8 +2770,21 @@ async function main() {
2627
2770
  return agentAid;
2628
2771
  };
2629
2772
  const authorizeTrigger = async (agentAid, operation, triggerId) => {
2630
- const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.controlToken);
2631
- if (!actor.control && actor.selfAid !== agentAid) {
2773
+ const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
2774
+ const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
2775
+ if (!isCrossAgentTriggerOperationAllowed({
2776
+ control: actor.control,
2777
+ taskAgentAid: actor.selfAid,
2778
+ targetAgentAid: agentAid,
2779
+ targetManagement: actor.management,
2780
+ operation,
2781
+ })) {
2782
+ if (isCrossAgent && !actor.management) {
2783
+ throw new Error('cross-agent trigger audit requires owner/admin access on the target agent');
2784
+ }
2785
+ if (isCrossAgent && !isCrossAgentTriggerReadOperation(operation)) {
2786
+ throw new Error('cross-agent trigger operations are limited to list, show, and history');
2787
+ }
2632
2788
  throw new Error('trigger agent does not match authenticated task agent');
2633
2789
  }
2634
2790
  const operationDecision = authorizeOperation({
@@ -2708,7 +2864,8 @@ async function main() {
2708
2864
  }
2709
2865
  const agentAid = requireAgent(rawDefinition.agentAid);
2710
2866
  const actor = await authorizeTrigger(agentAid, 'trigger.create');
2711
- const definition = normalizeTriggerDefinition({ ...rawDefinition, origin: actor.origin });
2867
+ const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
2868
+ const definition = normalizeTriggerDefinition(materialized);
2712
2869
  validateTriggerDefinitionForActor(definition, actor);
2713
2870
  requireAgent(definition.agentAid);
2714
2871
  validateTriggerFeedbackChannels(definition);
@@ -2727,7 +2884,7 @@ async function main() {
2727
2884
  if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
2728
2885
  throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
2729
2886
  }
2730
- const definition = applyTriggerPatch(existing, cmd.patch);
2887
+ const definition = applyTriggerPatch(existing, cmd.patch, { fromPromptFile: cmd.promptFile === true });
2731
2888
  validateTriggerDefinitionForActor(definition, actor);
2732
2889
  validateTriggerFeedbackChannels(definition);
2733
2890
  const scheduler = schedulerFor(agentAid);
@@ -2797,7 +2954,11 @@ async function main() {
2797
2954
  // 显式触发更可控。
2798
2955
  // 优雅关闭
2799
2956
  let shutdownSignal = 'unknown';
2957
+ let shutdownInProgress = false;
2800
2958
  const shutdown = async (signal) => {
2959
+ if (shutdownInProgress)
2960
+ return;
2961
+ shutdownInProgress = true;
2801
2962
  if (signal)
2802
2963
  shutdownSignal = signal;
2803
2964
  const pid = process.pid;
@@ -2806,30 +2967,60 @@ async function main() {
2806
2967
  ipcServer.stopCpuTracking();
2807
2968
  ipcServer.stop();
2808
2969
  bindService?.stopCleanup();
2809
- await permissionGateway.cancelAllPending('daemon_restart');
2810
- await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.waitForIdle(5_000)));
2811
- await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.stop()));
2812
- eventBus.publish({
2813
- type: 'system:shutdown',
2814
- timestamp: Date.now()
2815
- });
2816
- // 断开插件系统的渠道
2817
- await channelLoader.disconnectAll(channelInstances);
2818
- for (const inst of channelInstances) {
2819
- const type = inst.channelType || inst.adapter.channelName;
2820
- eventBus.publish({ type: 'channel:disconnected', channel: type, channelName: inst.adapter.channelName, reason: 'shutdown' });
2970
+ // 必须在第一个 await 之前同步冻结 active batch、发布 task:interrupted,
2971
+ // 否则 handler continuation 可能先提交完成并清掉待恢复的主消息。
2972
+ const shutdownStep = async (label, operation) => {
2973
+ try {
2974
+ await operation();
2975
+ }
2976
+ catch (error) {
2977
+ logger.error(`[Shutdown] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
2978
+ }
2979
+ };
2980
+ try {
2981
+ const queueShutdown = messageQueue.shutdownGracefully();
2982
+ await shutdownStep('cancel pending permissions', () => permissionGateway.cancelAllPending('daemon_restart'));
2983
+ await shutdownStep('wait for trigger schedulers', async () => {
2984
+ await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.waitForIdle(5_000)));
2985
+ });
2986
+ await shutdownStep('suspend trigger schedulers', async () => {
2987
+ await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.suspendForDaemonExit()));
2988
+ });
2989
+ await shutdownStep('interrupt active message queue tasks', async () => { await queueShutdown; });
2990
+ eventBus.publish({
2991
+ type: 'system:shutdown',
2992
+ timestamp: Date.now()
2993
+ });
2994
+ // 断开插件系统的渠道
2995
+ await shutdownStep('disconnect channels', () => channelLoader.disconnectAll(channelInstances));
2996
+ for (const inst of channelInstances) {
2997
+ const type = inst.channelType || inst.adapter.channelName;
2998
+ eventBus.publish({ type: 'channel:disconnected', channel: type, channelName: inst.adapter.channelName, reason: 'shutdown' });
2999
+ }
3000
+ // 断开控制 AID(daemon 进程身份)
3001
+ if (controlChannel) {
3002
+ await shutdownStep('disconnect control AID', () => controlChannel.disconnect());
3003
+ }
2821
3004
  }
2822
- // 断开控制 AID(daemon 进程身份)
2823
- if (controlChannel) {
3005
+ catch (error) {
3006
+ logger.error(`[Shutdown] Unexpected shutdown failure: ${error instanceof Error ? error.stack || error.message : String(error)}`);
3007
+ }
3008
+ finally {
2824
3009
  try {
2825
- await controlChannel.disconnect();
3010
+ sessionManager.close();
2826
3011
  }
2827
- catch { /* ignore */ }
3012
+ catch (error) {
3013
+ logger.error(`[Shutdown] session manager close failed: ${error instanceof Error ? error.message : String(error)}`);
3014
+ }
3015
+ try {
3016
+ removeAll();
3017
+ }
3018
+ catch (error) {
3019
+ logger.error(`[Shutdown] instance cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
3020
+ }
3021
+ logger.info('✓ Shutdown complete');
3022
+ process.exit(0);
2828
3023
  }
2829
- sessionManager.close();
2830
- removeAll();
2831
- logger.info('✓ Shutdown complete');
2832
- process.exit(0);
2833
3024
  };
2834
3025
  process.on('SIGINT', () => shutdown('SIGINT'));
2835
3026
  process.on('SIGTERM', () => shutdown('SIGTERM'));