evolcore 0.0.3 → 0.0.5

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 (148) hide show
  1. package/CHANGELOG.md +83 -0
  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 +1 -31
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +157 -5
  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/channels/aun.js +220 -112
  14. package/dist/channels/daemon.js +18 -4
  15. package/dist/channels/dingtalk.js +52 -137
  16. package/dist/channels/feishu.js +56 -4
  17. package/dist/channels/qqbot.js +23 -1
  18. package/dist/channels/wechat.js +303 -155
  19. package/dist/channels/wecom.js +383 -7
  20. package/dist/cli/aun-commands.js +11 -11
  21. package/dist/cli/daemon-commands.js +46 -15
  22. package/dist/cli/handoff-command.js +2 -1
  23. package/dist/cli/init-channel.js +185 -67
  24. package/dist/cli/init.js +49 -27
  25. package/dist/cli/trigger-command.js +92 -11
  26. package/dist/config/access-policy-domain.js +18 -0
  27. package/dist/config/access-policy.js +110 -0
  28. package/dist/config/builtin-role-templates.js +27 -14
  29. package/dist/config/builtin-roles.js +25 -6
  30. package/dist/config/config-field-policy.js +17 -5
  31. package/dist/config/config-manager.js +198 -22
  32. package/dist/config/config-operation-service.js +35 -38
  33. package/dist/config/contact-bind-code.js +274 -0
  34. package/dist/config/contact-book-store.js +173 -5
  35. package/dist/config/contact-book.js +32 -0
  36. package/dist/config/contact-operation-service.js +9 -3
  37. package/dist/config/contact-request-service.js +331 -0
  38. package/dist/config/peer-role-resolver.js +3 -1
  39. package/dist/config/schema-registry.js +49 -24
  40. package/dist/config-store.js +14 -2
  41. package/dist/core/auth/agent-delegation.js +105 -11
  42. package/dist/core/auth/authorization-audit.js +6 -0
  43. package/dist/core/auth/operation-authorizer.js +8 -0
  44. package/dist/core/auth/operation-catalog.js +51 -3
  45. package/dist/core/auth/trigger-authorization.js +15 -0
  46. package/dist/core/bootstrap-service.js +2 -9
  47. package/dist/core/channel-loader.js +6 -2
  48. package/dist/core/command/command-handler.js +10 -13
  49. package/dist/core/command/connect-menu.js +229 -26
  50. package/dist/core/command/evol-menu-version-gate.js +38 -0
  51. package/dist/core/command/group-menu.js +421 -0
  52. package/dist/core/command/menu-handler.js +269 -85
  53. package/dist/core/command/menu-protocol.js +8 -2
  54. package/dist/core/command/menu-token-store.js +102 -0
  55. package/dist/core/command/role-menu.js +16 -0
  56. package/dist/core/command/slash-gate.js +1 -1
  57. package/dist/core/command/slash-handler.js +158 -31
  58. package/dist/core/daemon-file-cache.js +28 -0
  59. package/dist/core/event-catalog.js +29 -0
  60. package/dist/core/evolagent-registry.js +4 -39
  61. package/dist/core/handoff/runtime.js +162 -37
  62. package/dist/core/handoff/store.js +46 -2
  63. package/dist/core/handoff/types.js +1 -0
  64. package/dist/core/inference/text-inference.js +16 -74
  65. package/dist/core/message/file-markers.js +7 -0
  66. package/dist/core/message/im-renderer.js +18 -14
  67. package/dist/core/message/inbound-admission.js +134 -0
  68. package/dist/core/message/message-bridge.js +616 -74
  69. package/dist/core/message/message-log.js +1 -0
  70. package/dist/core/message/message-queue.js +185 -10
  71. package/dist/core/message/peer-mode.js +7 -8
  72. package/dist/core/message/response-engine.js +381 -79
  73. package/dist/core/permission/approval-gateway.js +17 -10
  74. package/dist/core/permission/ec-command-parser.js +101 -46
  75. package/dist/core/permission/tool-policy.js +69 -24
  76. package/dist/core/protected-paths.js +36 -11
  77. package/dist/core/session/session-fs-store.js +19 -4
  78. package/dist/core/session/session-manager.js +232 -4
  79. package/dist/core/session/session-mapper.js +2 -0
  80. package/dist/core/session/session-renew.js +125 -69
  81. package/dist/core/session/session-turn-coordinator.js +5 -1
  82. package/dist/eck/kit-renderer.js +12 -1
  83. package/dist/eck/message-renderer.js +79 -1
  84. package/dist/index.js +224 -89
  85. package/dist/ipc.js +81 -4
  86. package/dist/paths.js +3 -0
  87. package/dist/response-system/config-resolver.js +29 -0
  88. package/dist/response-system/coordinator.js +8 -32
  89. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  90. package/dist/response-system/index.js +1 -0
  91. package/dist/response-system/modes/single-session/index.js +7 -4
  92. package/dist/trigger/parser.js +55 -15
  93. package/dist/trigger/patch.js +4 -1
  94. package/dist/trigger/scheduler.js +441 -39
  95. package/dist/utils/cross-platform.js +8 -2
  96. package/dist/utils/error-dict.json +7 -0
  97. package/dist/utils/evolcore-version.js +21 -0
  98. package/dist/utils/logger.js +2 -2
  99. package/dist/utils/process-introspect.js +19 -3
  100. package/dist/utils/stable-semver.js +21 -0
  101. package/dist/utils/stats.js +33 -9
  102. package/kits/docs/channels/aun.md +4 -13
  103. package/kits/docs/evolcore/INDEX.md +1 -1
  104. package/kits/docs/evolcore/config.md +47 -2
  105. package/kits/docs/evolcore/contact.md +8 -3
  106. package/kits/docs/evolcore/group-rules.md +46 -4
  107. package/kits/docs/evolcore/group.md +4 -4
  108. package/kits/docs/evolcore/msg.md +5 -5
  109. package/kits/docs/evolcore/trigger.md +25 -8
  110. package/kits/docs/path-registry.md +36 -17
  111. package/kits/eck_message_manifest.json +12 -1
  112. package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
  113. package/kits/rules/01-overview.md +17 -7
  114. package/kits/rules/02-navigation.md +38 -18
  115. package/kits/rules/03-identity.md +28 -24
  116. package/kits/rules/04-relation.md +44 -28
  117. package/kits/rules/05-venue.md +31 -15
  118. package/kits/rules/06-channel.md +11 -7
  119. package/kits/schemas/_meta.json +18 -7
  120. package/kits/schemas/agent-config.schema.7.json +303 -0
  121. package/kits/schemas/agent-config.schema.8.json +304 -0
  122. package/kits/schemas/agent-config.schema.9.json +364 -0
  123. package/kits/schemas/contact-book.schema.3.json +67 -0
  124. package/kits/schemas/daemon.schema.1.json +3 -2
  125. package/kits/schemas/daemon.schema.2.json +101 -0
  126. package/kits/schemas/daemon.schema.3.json +123 -0
  127. package/kits/schemas/defaults.schema.2.json +85 -0
  128. package/kits/schemas/defaults.schema.3.json +73 -0
  129. package/kits/schemas/relation-config.schema.6.json +46 -0
  130. package/kits/schemas/relation-config.schema.7.json +59 -0
  131. package/kits/schemas/single-session.schema.2.json +57 -0
  132. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  133. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  134. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  135. package/kits/templates/roles/admin.json +46 -0
  136. package/kits/templates/roles/member.json +4 -0
  137. package/kits/templates/roles/visitor.json +4 -0
  138. package/kits/templates/system-fragments/channel.md +12 -2
  139. package/kits/templates/system-fragments/identity.md +3 -1
  140. package/kits/templates/system-fragments/relation.md +1 -1
  141. package/kits/templates/system-fragments/session.md +6 -2
  142. package/package.json +4 -2
  143. package/MIGRATION-0.5.0.md +0 -378
  144. package/ROLE_ACCESS_CONTROL.md +0 -174
  145. package/dist/channels/contact-bind-code.js +0 -134
  146. package/dist/channels/wecom-card.js +0 -101
  147. package/dist/channels/wecom-onboarding.js +0 -82
  148. package/dist/channels/wecom-state.js +0 -191
@@ -16,7 +16,7 @@ import { ipcQuery } from '../ipc.js';
16
16
  import { cmdStart } from './daemon-commands.js';
17
17
  import * as platform from '../utils/cross-platform.js';
18
18
  import { cleanupInstances } from '../utils/instance-registry.js';
19
- import { bindContactAlias } from '../config/contact-book.js';
19
+ import { bindContactAlias, getContactSnapshot } from '../config/contact-book.js';
20
20
  import { listStaticAgentAdmins } from '../config/peer-role-resolver.js';
21
21
  import { setupRawKeyListener } from './raw-key-input.js';
22
22
  function ask(rl, question) {
@@ -384,6 +384,7 @@ export async function cmdInitFeishu() {
384
384
  let agentConfig = null;
385
385
  let choice = null;
386
386
  let contactBinding = null;
387
+ let operation = 'configure';
387
388
  try {
388
389
  aid = await pickAgentForChannel(rl);
389
390
  if (!aid)
@@ -394,16 +395,25 @@ export async function cmdInitFeishu() {
394
395
  return;
395
396
  }
396
397
  const existing = (agentConfig.channels || []).filter(c => c.type === 'feishu');
397
- choice = await pickInstanceWithinAgent(rl, 'feishu', existing);
398
+ choice = await pickInstanceWithinAgent(rl, 'feishu', existing, { confirmOverwrite: false });
398
399
  if (choice === null)
399
400
  return;
400
- contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'feishu');
401
+ if (choice.action === 'overwrite') {
402
+ operation = await pickExistingChannelOperation(rl, '飞书', choice.name);
403
+ if (operation === 'configure' && !await confirmOverwriteChannel(rl, '飞书', choice.name))
404
+ return;
405
+ }
406
+ contactBinding = await pickContactForChannelBinding(rl, agentConfig, 'feishu');
401
407
  if (!contactBinding)
402
408
  return;
403
409
  }
404
410
  finally {
405
411
  rl.close();
406
412
  }
413
+ if (operation === 'rebind-contact') {
414
+ await registerFeishuContactBind(aid, contactBindChannelKey('feishu', aid, choice.name), contactBinding);
415
+ return;
416
+ }
407
417
  console.log('\n正在获取飞书登录二维码...\n');
408
418
  let result;
409
419
  try {
@@ -444,7 +454,15 @@ export async function cmdInitFeishu() {
444
454
  console.log(` Login Open ID: ${result.openId}`);
445
455
  if (result.domain !== 'unknown')
446
456
  console.log(` Domain: ${result.domain}`);
447
- bindQrActorToContact(aid, contactBinding, 'feishu', result.openId, commitResult.channelKey);
457
+ if (result.openId) {
458
+ bindQrActorToContact(aid, contactBinding, 'feishu', result.openId, commitResult.channelKey);
459
+ }
460
+ else if (commitResult.reloaded) {
461
+ await registerFeishuContactBind(aid, commitResult.channelKey, contactBinding);
462
+ }
463
+ else {
464
+ console.log(' ⚠ 服务未运行或热重载失败,无法注册飞书绑定码;请启动服务后重新执行 ec init feishu。');
465
+ }
448
466
  // Show unified init success message
449
467
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
450
468
  console.log(generateInitSuccessMessage('feishu', contactBinding !== null));
@@ -545,6 +563,7 @@ export async function cmdInitWechat() {
545
563
  let agentConfig = null;
546
564
  let choice = null;
547
565
  let contactBinding = null;
566
+ let operation = 'configure';
548
567
  try {
549
568
  aid = await pickAgentForChannel(rl);
550
569
  if (!aid)
@@ -555,16 +574,25 @@ export async function cmdInitWechat() {
555
574
  return;
556
575
  }
557
576
  const existing = (agentConfig.channels || []).filter(c => c.type === 'wechat');
558
- choice = await pickInstanceWithinAgent(rl, 'wechat', existing);
577
+ choice = await pickInstanceWithinAgent(rl, 'wechat', existing, { confirmOverwrite: false });
559
578
  if (choice === null)
560
579
  return;
561
- contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'wechat');
580
+ if (choice.action === 'overwrite') {
581
+ operation = await pickExistingChannelOperation(rl, '微信', choice.name);
582
+ if (operation === 'configure' && !await confirmOverwriteChannel(rl, '微信', choice.name))
583
+ return;
584
+ }
585
+ contactBinding = await pickContactForChannelBinding(rl, agentConfig, 'wechat');
562
586
  if (!contactBinding)
563
587
  return;
564
588
  }
565
589
  finally {
566
590
  rl.close();
567
591
  }
592
+ if (operation === 'rebind-contact') {
593
+ await registerWechatContactBind(aid, contactBindChannelKey('wechat', aid, choice.name), contactBinding);
594
+ return;
595
+ }
568
596
  console.log('\n正在获取微信登录二维码...\n');
569
597
  const result = await runWechatQrFlow();
570
598
  if (!result) {
@@ -581,7 +609,15 @@ export async function cmdInitWechat() {
581
609
  const commitResult = await commitChannel(aid, channel, choice.action);
582
610
  if (!commitResult)
583
611
  return;
584
- bindQrActorToContact(aid, contactBinding, 'wechat', result.userId, commitResult.channelKey);
612
+ if (firstNonEmpty(result.userId)) {
613
+ bindQrActorToContact(aid, contactBinding, 'wechat', result.userId, commitResult.channelKey);
614
+ }
615
+ else if (commitResult.reloaded) {
616
+ await registerWechatContactBind(aid, commitResult.channelKey, contactBinding);
617
+ }
618
+ else {
619
+ console.log(' ⚠ 服务未运行或热重载失败,无法注册微信绑定码;请启动服务后重新执行 ec init wechat。');
620
+ }
585
621
  // Show unified init success message
586
622
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
587
623
  console.log(generateInitSuccessMessage('wechat', contactBinding !== null));
@@ -788,6 +824,7 @@ export async function cmdInitDingtalk() {
788
824
  let agentConfig = null;
789
825
  let choice = null;
790
826
  let contactBinding = null;
827
+ let operation = 'configure';
791
828
  try {
792
829
  aid = await pickAgentForChannel(rl);
793
830
  if (!aid)
@@ -798,16 +835,25 @@ export async function cmdInitDingtalk() {
798
835
  return;
799
836
  }
800
837
  const existing = (agentConfig.channels || []).filter(c => c.type === 'dingtalk');
801
- choice = await pickInstanceWithinAgent(rl, 'dingtalk', existing);
838
+ choice = await pickInstanceWithinAgent(rl, 'dingtalk', existing, { confirmOverwrite: false });
802
839
  if (choice === null)
803
840
  return;
804
- contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'dingtalk');
841
+ if (choice.action === 'overwrite') {
842
+ operation = await pickExistingChannelOperation(rl, '钉钉', choice.name);
843
+ if (operation === 'configure' && !await confirmOverwriteChannel(rl, '钉钉', choice.name))
844
+ return;
845
+ }
846
+ contactBinding = await pickContactForChannelBinding(rl, agentConfig, 'dingtalk');
805
847
  if (!contactBinding)
806
848
  return;
807
849
  }
808
850
  finally {
809
851
  rl.close();
810
852
  }
853
+ if (operation === 'rebind-contact') {
854
+ await registerDingtalkContactBind(aid, contactBindChannelKey('dingtalk', aid, choice.name), contactBinding);
855
+ return;
856
+ }
811
857
  console.log('\n正在获取钉钉登录二维码...\n');
812
858
  let result;
813
859
  try {
@@ -997,6 +1043,7 @@ export async function cmdInitQQBot() {
997
1043
  let agentConfig = null;
998
1044
  let choice = null;
999
1045
  let contactBinding = null;
1046
+ let operation = 'configure';
1000
1047
  try {
1001
1048
  aid = await pickAgentForChannel(rl);
1002
1049
  if (!aid)
@@ -1007,16 +1054,25 @@ export async function cmdInitQQBot() {
1007
1054
  return;
1008
1055
  }
1009
1056
  const existing = (agentConfig.channels || []).filter(c => c.type === 'qqbot');
1010
- choice = await pickInstanceWithinAgent(rl, 'qqbot', existing);
1057
+ choice = await pickInstanceWithinAgent(rl, 'qqbot', existing, { confirmOverwrite: false });
1011
1058
  if (choice === null)
1012
1059
  return;
1013
- contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'qqbot');
1060
+ if (choice.action === 'overwrite') {
1061
+ operation = await pickExistingChannelOperation(rl, 'QQ', choice.name);
1062
+ if (operation === 'configure' && !await confirmOverwriteChannel(rl, 'QQ', choice.name))
1063
+ return;
1064
+ }
1065
+ contactBinding = await pickContactForChannelBinding(rl, agentConfig, 'qqbot');
1014
1066
  if (!contactBinding)
1015
1067
  return;
1016
1068
  }
1017
1069
  finally {
1018
1070
  rl.close();
1019
1071
  }
1072
+ if (operation === 'rebind-contact') {
1073
+ await registerQQBotContactBind(aid, contactBindChannelKey('qqbot', aid, choice.name), contactBinding);
1074
+ return;
1075
+ }
1020
1076
  console.log('\n正在创建 QQ 机器人绑定任务...\n');
1021
1077
  let result;
1022
1078
  try {
@@ -1066,7 +1122,15 @@ export async function cmdInitQQBot() {
1066
1122
  if (!commitResult)
1067
1123
  return;
1068
1124
  console.log(` App ID: ${result.appId}`);
1069
- bindQrActorToContact(aid, contactBinding, 'qqbot', result.userOpenId, commitResult.channelKey);
1125
+ if (firstNonEmpty(result.userOpenId)) {
1126
+ bindQrActorToContact(aid, contactBinding, 'qqbot', result.userOpenId, commitResult.channelKey);
1127
+ }
1128
+ else if (commitResult.reloaded) {
1129
+ await registerQQBotContactBind(aid, commitResult.channelKey, contactBinding);
1130
+ }
1131
+ else {
1132
+ console.log(' ⚠ 服务未运行或热重载失败,无法注册 QQ 绑定码;请启动服务后重新执行 ec init qqbot。');
1133
+ }
1070
1134
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
1071
1135
  console.log(generateInitSuccessMessage('qqbot', contactBinding !== null));
1072
1136
  }
@@ -1077,6 +1141,7 @@ export async function cmdInitWecom() {
1077
1141
  let agentConfig = null;
1078
1142
  let choice = null;
1079
1143
  let contactBinding = null;
1144
+ let operation = 'configure';
1080
1145
  let botId = '';
1081
1146
  let secret = '';
1082
1147
  try {
@@ -1089,60 +1154,71 @@ export async function cmdInitWecom() {
1089
1154
  return;
1090
1155
  }
1091
1156
  const existing = (agentConfig.channels || []).filter(c => c.type === 'wecom');
1092
- choice = await pickInstanceWithinAgent(rl, 'wecom', existing);
1157
+ choice = await pickInstanceWithinAgent(rl, 'wecom', existing, { confirmOverwrite: false });
1093
1158
  if (choice === null)
1094
1159
  return;
1095
- contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'wecom');
1160
+ if (choice.action === 'overwrite') {
1161
+ operation = await pickExistingChannelOperation(rl, '企微', choice.name);
1162
+ if (operation === 'configure' && !await confirmOverwriteChannel(rl, '企微', choice.name))
1163
+ return;
1164
+ }
1165
+ contactBinding = await pickContactForChannelBinding(rl, agentConfig, 'wecom');
1096
1166
  if (!contactBinding)
1097
1167
  return;
1098
- console.log('\n企业微信 AI Bot 配置');
1099
- const method = (await ask(rl, ' 接入方式:1. 扫码创建(推荐) 2. 手工输入 [1]: ')).trim() || '1';
1100
- if (method === '1') {
1101
- try {
1102
- const { createWecomQrSession, pollWecomQrCredentials } = await import('../channels/wecom-onboarding.js');
1103
- const session = await createWecomQrSession();
1104
- const qrterm = await import('qrcode-terminal');
1105
- console.log('\n请使用企业微信扫描以下二维码:\n');
1106
- await new Promise(resolve => {
1107
- qrterm.default.generate(session.authUrl, { small: true }, (qr) => {
1108
- console.log(qr);
1109
- resolve();
1168
+ if (operation !== 'rebind-contact') {
1169
+ console.log('\n企业微信 AI Bot 配置');
1170
+ const method = (await ask(rl, ' 接入方式:1. 扫码创建(推荐) 2. 手工输入 [1]: ')).trim() || '1';
1171
+ if (method === '1') {
1172
+ try {
1173
+ const { createWecomQrSession, pollWecomQrCredentials } = await import('../channels/wecom.js');
1174
+ const session = await createWecomQrSession();
1175
+ const qrterm = await import('qrcode-terminal');
1176
+ console.log('\n请使用企业微信扫描以下二维码:\n');
1177
+ await new Promise(resolve => {
1178
+ qrterm.default.generate(session.authUrl, { small: true }, (qr) => {
1179
+ console.log(qr);
1180
+ resolve();
1181
+ });
1182
+ });
1183
+ console.log(`也可在浏览器打开:${session.browserUrl}`);
1184
+ console.log('等待扫码确认,二维码 5 分钟内有效...\n');
1185
+ const credentials = await pollWecomQrCredentials(session.scode, {
1186
+ onStatus: status => {
1187
+ if (status !== 'waiting')
1188
+ console.log(` 扫码状态: ${status}`);
1189
+ },
1110
1190
  });
1111
- });
1112
- console.log(`也可在浏览器打开:${session.browserUrl}`);
1113
- console.log('等待扫码确认,二维码 5 分钟内有效...\n');
1114
- const credentials = await pollWecomQrCredentials(session.scode, {
1115
- onStatus: status => {
1116
- if (status !== 'waiting')
1117
- console.log(` 扫码状态: ${status}`);
1118
- },
1119
- });
1120
- botId = credentials.botId;
1121
- secret = credentials.secret;
1122
- console.log(' ✓ 已取得 Bot ID 和 Secret');
1191
+ botId = credentials.botId;
1192
+ secret = credentials.secret;
1193
+ console.log(' 已取得 Bot ID 和 Secret');
1194
+ }
1195
+ catch (error) {
1196
+ console.log(` ⚠ 扫码接入失败: ${error instanceof Error ? error.message : String(error)}`);
1197
+ console.log(' 将切换到手工输入模式。\n');
1198
+ }
1123
1199
  }
1124
- catch (error) {
1125
- console.log(` ⚠ 扫码接入失败: ${error instanceof Error ? error.message : String(error)}`);
1126
- console.log(' 将切换到手工输入模式。\n');
1200
+ if (!botId || !secret) {
1201
+ console.log('请在企业微信管理后台 AI Bot 页面获取 Bot ID Secret\n');
1202
+ }
1203
+ while (!botId) {
1204
+ botId = (await ask(rl, ' Bot ID: ')).trim();
1205
+ if (!botId)
1206
+ console.log(' ⚠ 不能为空');
1207
+ }
1208
+ while (!secret) {
1209
+ secret = (await ask(rl, ' Secret: ')).trim();
1210
+ if (!secret)
1211
+ console.log(' ⚠ 不能为空');
1127
1212
  }
1128
- }
1129
- if (!botId || !secret) {
1130
- console.log('请在企业微信管理后台 → AI Bot 页面获取 Bot ID 和 Secret\n');
1131
- }
1132
- while (!botId) {
1133
- botId = (await ask(rl, ' Bot ID: ')).trim();
1134
- if (!botId)
1135
- console.log(' ⚠ 不能为空');
1136
- }
1137
- while (!secret) {
1138
- secret = (await ask(rl, ' Secret: ')).trim();
1139
- if (!secret)
1140
- console.log(' ⚠ 不能为空');
1141
1213
  }
1142
1214
  }
1143
1215
  finally {
1144
1216
  rl.close();
1145
1217
  }
1218
+ if (operation === 'rebind-contact') {
1219
+ await registerWecomContactBind(aid, contactBindChannelKey('wecom', aid, choice.name), contactBinding);
1220
+ return;
1221
+ }
1146
1222
  const channel = {
1147
1223
  type: 'wecom',
1148
1224
  name: choice.name,
@@ -1201,24 +1277,42 @@ async function pickAgentForChannel(rl) {
1201
1277
  *
1202
1278
  * If existing.length === 0 → returns { action:'add', name:'main' } directly.
1203
1279
  */
1204
- async function pickInstanceWithinAgent(rl, channelType, existing) {
1280
+ async function pickInstanceWithinAgent(rl, channelType, existing, options) {
1205
1281
  if (existing.length === 0) {
1206
1282
  return { action: 'add', name: 'main' };
1207
1283
  }
1208
1284
  const view = existing.map((c, i) => ({ ...c, name: c.name, originalIndex: i }));
1209
- return await selectInstance(rl, channelType, view);
1285
+ return await selectInstance(rl, channelType, view, options);
1210
1286
  }
1211
- async function pickOwnerAdminForChannelBinding(rl, agentConfig, channelType) {
1212
- const candidates = ownerAdminContactCandidates(agentConfig);
1213
- if (candidates.length === 0) {
1214
- console.log(`\n❌ agent ${agentConfig.aid} 尚未配置 owner/admin,无法为 ${channelType} 扫码账号建立身份映射。`);
1215
- console.log(' 请先通过 AUN 绑定 agent owner,或由 owner 在 AUN 私聊 relation 中分配 admin 后重试。');
1216
- return null;
1287
+ async function pickExistingChannelOperation(rl, displayName, channelName) {
1288
+ console.log(`\n请选择对${displayName}实例 ${channelName} 的操作:`);
1289
+ console.log(' a. 覆盖机器人配置(重新扫码或输入 App ID / App Secret)');
1290
+ console.log(' b. 重新绑定联系人(保留机器人配置,生成一次性 /bind 验证码)');
1291
+ while (true) {
1292
+ const choice = (await ask(rl, '请选择 [a/b]: ')).trim().toLowerCase();
1293
+ if (choice === 'a')
1294
+ return 'configure';
1295
+ if (choice === 'b')
1296
+ return 'rebind-contact';
1297
+ console.log('无效选择,请输入 a 或 b');
1217
1298
  }
1218
- console.log(`\n请选择 ${channelType} 扫码账号要绑定到的 owner/admin AID:`);
1299
+ }
1300
+ async function confirmOverwriteChannel(rl, displayName, channelName) {
1301
+ const confirm = (await ask(rl, `⚠️ 即将覆盖${displayName}实例 ${channelName} 的机器人配置,确认?(y/N) `)).trim().toLowerCase();
1302
+ if (confirm === 'y' || confirm === 'yes')
1303
+ return true;
1304
+ console.log('已取消');
1305
+ return false;
1306
+ }
1307
+ async function pickContactForChannelBinding(rl, agentConfig, channelType) {
1308
+ const candidates = preferredContactBindingCandidates(agentConfig);
1309
+ const contacts = getContactSnapshot(agentConfig.aid).contacts;
1310
+ console.log(`\n请选择 ${channelType} 账号要绑定到的联系人 AID:`);
1219
1311
  candidates.forEach((candidate, index) => {
1220
1312
  console.log(` ${index + 1}. ${candidate.primaryId} (${candidate.label})`);
1221
1313
  });
1314
+ const manualChoice = candidates.length + 1;
1315
+ console.log(` ${manualChoice}. 手工输入已有联系人 AID`);
1222
1316
  const defaultIndex = candidates.length === 1 ? 1 : null;
1223
1317
  const prompt = defaultIndex ? `请选择 [${defaultIndex}]: ` : '请选择: ';
1224
1318
  while (true) {
@@ -1227,14 +1321,26 @@ async function pickOwnerAdminForChannelBinding(rl, agentConfig, channelType) {
1227
1321
  if (Number.isInteger(selected) && selected >= 1 && selected <= candidates.length) {
1228
1322
  return candidates[selected - 1];
1229
1323
  }
1230
- console.log(`无效选择,请输入 1-${candidates.length}`);
1324
+ if (selected === manualChoice) {
1325
+ const primaryId = (await ask(rl, '请输入要绑定的联系人 AID: ')).trim();
1326
+ if (isValidAid(primaryId) && contacts.has(primaryId))
1327
+ return { primaryId, label: 'contact' };
1328
+ if (isValidAid(primaryId)) {
1329
+ console.log('该 AID 不是当前 Agent 的联系人,请先添加联系人或将其设为 Owner。');
1330
+ continue;
1331
+ }
1332
+ console.log('AID 格式无效,请重新输入。');
1333
+ continue;
1334
+ }
1335
+ console.log(`无效选择,请输入 1-${manualChoice}`);
1231
1336
  }
1232
1337
  }
1233
- function ownerAdminContactCandidates(agentConfig) {
1338
+ function preferredContactBindingCandidates(agentConfig) {
1339
+ const contacts = getContactSnapshot(agentConfig.aid).contacts;
1234
1340
  const labels = new Map();
1235
1341
  const add = (value, label) => {
1236
1342
  const aid = String(value || '').trim();
1237
- if (!isValidAid(aid))
1343
+ if (!isValidAid(aid) || !contacts.has(aid))
1238
1344
  return;
1239
1345
  let set = labels.get(aid);
1240
1346
  if (!set) {
@@ -1273,6 +1379,18 @@ function bindQrActorToContact(aid, binding, channelType, actorId, channelKey) {
1273
1379
  async function registerDingtalkContactBind(aid, channelName, binding) {
1274
1380
  await registerChannelContactBind('dingtalk', '钉钉', aid, channelName, binding);
1275
1381
  }
1382
+ function contactBindChannelKey(channelType, aid, channelName) {
1383
+ return `${channelType}#${aid}#${channelName}`;
1384
+ }
1385
+ async function registerFeishuContactBind(aid, channelName, binding) {
1386
+ await registerChannelContactBind('feishu', '飞书', aid, channelName, binding);
1387
+ }
1388
+ async function registerWechatContactBind(aid, channelName, binding) {
1389
+ await registerChannelContactBind('wechat', '微信', aid, channelName, binding);
1390
+ }
1391
+ async function registerQQBotContactBind(aid, channelName, binding) {
1392
+ await registerChannelContactBind('qqbot', 'QQ', aid, channelName, binding);
1393
+ }
1276
1394
  async function registerWecomContactBind(aid, channelName, binding) {
1277
1395
  await registerChannelContactBind('wecom', '企微', aid, channelName, binding);
1278
1396
  }
@@ -1292,7 +1410,7 @@ async function registerChannelContactBind(channelType, displayName, aid, channel
1292
1410
  }
1293
1411
  const expiresAt = new Date(Number(result.expiresAt)).toLocaleString();
1294
1412
  console.log(` ✓ ${displayName}身份绑定码: ${result.code}`);
1295
- console.log(` 请在 10 分钟内私聊${displayName}机器人,直接发送这 6 位数字绑定码。有效期至: ${expiresAt}`);
1413
+ console.log(` 请在 10 分钟内私聊${displayName}机器人,发送:/bind ${result.code}。有效期至: ${expiresAt}`);
1296
1414
  }
1297
1415
  function firstNonEmpty(...values) {
1298
1416
  for (const value of values) {
package/dist/cli/init.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import os from 'os';
2
3
  import path from 'path';
3
4
  import readline from 'readline';
4
5
  import { resolvePaths, ensureDataDirs } from '../paths.js';
@@ -13,6 +14,15 @@ import { WEB_CLI_BIN, WEB_PACKAGE_LATEST } from '../product.js';
13
14
  function ask(rl, question) {
14
15
  return new Promise(resolve => rl.question(question, resolve));
15
16
  }
17
+ /** 展开用户在交互式输入中使用的 home 目录简写(shell 不会替 readline 自动做这件事)。 */
18
+ export function expandLeadingTildePath(value, homeDir = os.homedir()) {
19
+ if (value === '~')
20
+ return homeDir;
21
+ if (value.startsWith('~/') || value.startsWith('~\\')) {
22
+ return path.join(homeDir, value.slice(2));
23
+ }
24
+ return value;
25
+ }
16
26
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
17
27
  function isBaseagentAvailable(baseagent) {
18
28
  if (baseagent === 'codex')
@@ -167,7 +177,7 @@ export async function cmdInit(options) {
167
177
  await runInteractive();
168
178
  }
169
179
  // ── 共享 tail(单一出口):提示创建 agent + 生成控制 AID ──
170
- await initTail();
180
+ await initTail({ invokedByStart: options?.invokedByStart });
171
181
  // ── 内部函数 ──
172
182
  async function runInteractive() {
173
183
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -194,29 +204,36 @@ export async function cmdInit(options) {
194
204
  }
195
205
  async function askProjectsDefaultPath() {
196
206
  const defaultDir = path.join(defaultProjectsRoot(p.root), 'default');
197
- const input = (await ask(rl, `项目默认目录 [${defaultDir}]: `)).trim();
198
- const resolved = input || defaultDir;
199
- if (!path.isAbsolute(resolved)) {
200
- console.log(' ⚠ 需要绝对路径,已跳过');
201
- return undefined;
202
- }
203
- if (!fs.existsSync(resolved)) {
204
- const create = (await ask(rl, ` 目录不存在,是否创建?[Y/n]: `)).trim().toLowerCase();
205
- if (create === '' || create === 'y' || create === 'yes') {
206
- try {
207
- fs.mkdirSync(resolved, { recursive: true });
207
+ while (true) {
208
+ const input = (await ask(rl, `项目默认目录 [${defaultDir}](输入 skip 跳过): `)).trim();
209
+ if (input.toLowerCase() === 'skip') {
210
+ console.log(' 已跳过项目默认目录配置');
211
+ return undefined;
212
+ }
213
+ const resolved = expandLeadingTildePath(input || defaultDir);
214
+ if (!path.isAbsolute(resolved)) {
215
+ console.log(' 请输入绝对路径或回车使用默认值;输入 skip 可跳过');
216
+ continue;
217
+ }
218
+ if (!fs.existsSync(resolved)) {
219
+ const create = (await ask(rl, ` 目录不存在,是否创建?[Y/n]: `)).trim().toLowerCase();
220
+ if (create === '' || create === 'y' || create === 'yes') {
221
+ try {
222
+ fs.mkdirSync(resolved, { recursive: true });
223
+ }
224
+ catch (e) {
225
+ console.log(` ⚠ 创建目录失败: ${e?.message || e}`);
226
+ continue;
227
+ }
228
+ console.log(` ✓ 已创建 ${resolved}`);
208
229
  }
209
- catch (e) {
210
- console.log(` 创建目录失败: ${e?.message || e}`);
211
- return undefined;
230
+ else {
231
+ console.log(' 未创建目录,请重新输入路径;输入 skip 可跳过');
232
+ continue;
212
233
  }
213
- console.log(` ✓ 已创建 ${resolved}`);
214
- }
215
- else {
216
- return undefined;
217
234
  }
235
+ return resolved;
218
236
  }
219
- return resolved;
220
237
  }
221
238
  try {
222
239
  if (defaultsExisted) {
@@ -249,10 +266,10 @@ export async function cmdInit(options) {
249
266
  }
250
267
  }
251
268
  /** 补全控制 AID + owners(可单独调用,不走 baseagent 向导)。 */
252
- export async function initTail() {
269
+ export async function initTail(options = {}) {
253
270
  // 提示创建 agent(两分支汇合后执行一次)
254
271
  const { agents } = loadAllAgents();
255
- if (agents.length === 0) {
272
+ if (agents.length === 0 && !options.invokedByStart) {
256
273
  console.log('\n提示:尚无 agent,运行以下命令创建:');
257
274
  console.log(' ec agent new <aid>.agentid.pub');
258
275
  }
@@ -368,6 +385,9 @@ export async function initTail() {
368
385
  }
369
386
  }
370
387
  }
388
+ // ec start 会接着启动 daemon,并在其 ready 后给出可用的 Agent 创建入口。
389
+ if (options.invokedByStart)
390
+ return;
371
391
  // 初始化完成总结
372
392
  console.log('\n✓ EvolCore 初始化完成');
373
393
  const finalCfg = loadDaemonConfig();
@@ -390,7 +410,7 @@ export async function initTail() {
390
410
  * Present instance selection menu when existing instances are found.
391
411
  * Returns the user's choice, or null if cancelled.
392
412
  */
393
- export async function selectInstance(rl, channelType, instances) {
413
+ export async function selectInstance(rl, channelType, instances, options = {}) {
394
414
  const typeLabel = channelType === 'feishu' ? '飞书' : channelType === 'wechat' ? '微信' : channelType === 'dingtalk' ? '钉钉' : channelType === 'qqbot' ? 'QQ机器人' : channelType === 'wecom' ? '企业微信' : channelType.toUpperCase();
395
415
  console.log(`\n发现已有 ${typeLabel} 配置:`);
396
416
  const letters = 'abcdefghijklmnopqrstuvwxyz';
@@ -427,10 +447,12 @@ export async function selectInstance(rl, channelType, instances) {
427
447
  }
428
448
  const target = instances[choiceIndex];
429
449
  console.log(`\n已选择:${target.name}`);
430
- const confirm = (await new Promise(r => rl.question(`⚠️ 即将覆盖该配置,确认?(y/N) `, r))).trim().toLowerCase();
431
- if (confirm !== 'y' && confirm !== 'yes') {
432
- console.log('已取消');
433
- return null;
450
+ if (options.confirmOverwrite !== false) {
451
+ const confirm = (await new Promise(r => rl.question(`⚠️ 即将覆盖该配置,确认?(y/N) `, r))).trim().toLowerCase();
452
+ if (confirm !== 'y' && confirm !== 'yes') {
453
+ console.log('已取消');
454
+ return null;
455
+ }
434
456
  }
435
457
  return { action: 'overwrite', index: choiceIndex, name: target.name };
436
458
  }