evolcore 0.0.3 → 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 (143) hide show
  1. package/CHANGELOG.md +64 -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/handoff-command.js +2 -1
  22. package/dist/cli/init-channel.js +185 -67
  23. package/dist/cli/init.js +7 -5
  24. package/dist/cli/trigger-command.js +92 -11
  25. package/dist/config/access-policy-domain.js +38 -0
  26. package/dist/config/access-policy.js +134 -0
  27. package/dist/config/builtin-role-templates.js +27 -14
  28. package/dist/config/builtin-roles.js +25 -6
  29. package/dist/config/config-field-policy.js +17 -5
  30. package/dist/config/config-manager.js +198 -22
  31. package/dist/config/config-operation-service.js +35 -38
  32. package/dist/config/contact-bind-code.js +274 -0
  33. package/dist/config/contact-book-store.js +173 -5
  34. package/dist/config/contact-book.js +32 -0
  35. package/dist/config/contact-operation-service.js +9 -3
  36. package/dist/config/contact-request-service.js +331 -0
  37. package/dist/config/peer-role-resolver.js +3 -1
  38. package/dist/config/schema-registry.js +49 -24
  39. package/dist/config-store.js +14 -2
  40. package/dist/core/auth/agent-delegation.js +105 -11
  41. package/dist/core/auth/authorization-audit.js +6 -0
  42. package/dist/core/auth/operation-authorizer.js +8 -0
  43. package/dist/core/auth/operation-catalog.js +51 -3
  44. package/dist/core/auth/trigger-authorization.js +15 -0
  45. package/dist/core/bootstrap-service.js +2 -9
  46. package/dist/core/channel-loader.js +6 -2
  47. package/dist/core/command/command-handler.js +10 -13
  48. package/dist/core/command/connect-menu.js +249 -24
  49. package/dist/core/command/evol-menu-version-gate.js +38 -0
  50. package/dist/core/command/group-menu.js +421 -0
  51. package/dist/core/command/menu-handler.js +269 -85
  52. package/dist/core/command/menu-protocol.js +8 -2
  53. package/dist/core/command/menu-token-store.js +102 -0
  54. package/dist/core/command/slash-gate.js +1 -1
  55. package/dist/core/command/slash-handler.js +158 -31
  56. package/dist/core/daemon-file-cache.js +28 -0
  57. package/dist/core/event-catalog.js +29 -0
  58. package/dist/core/evolagent-registry.js +4 -39
  59. package/dist/core/handoff/runtime.js +162 -37
  60. package/dist/core/handoff/store.js +46 -2
  61. package/dist/core/handoff/types.js +1 -0
  62. package/dist/core/inference/text-inference.js +16 -74
  63. package/dist/core/message/file-markers.js +7 -0
  64. package/dist/core/message/im-renderer.js +18 -14
  65. package/dist/core/message/inbound-admission.js +124 -0
  66. package/dist/core/message/message-bridge.js +616 -74
  67. package/dist/core/message/message-log.js +1 -0
  68. package/dist/core/message/message-queue.js +185 -10
  69. package/dist/core/message/peer-mode.js +7 -8
  70. package/dist/core/message/response-engine.js +381 -79
  71. package/dist/core/permission/approval-gateway.js +17 -10
  72. package/dist/core/permission/ec-command-parser.js +101 -46
  73. package/dist/core/permission/tool-policy.js +69 -24
  74. package/dist/core/protected-paths.js +36 -11
  75. package/dist/core/session/session-manager.js +25 -3
  76. package/dist/core/session/session-mapper.js +2 -0
  77. package/dist/core/session/session-renew.js +125 -69
  78. package/dist/core/session/session-turn-coordinator.js +5 -1
  79. package/dist/eck/kit-renderer.js +12 -1
  80. package/dist/eck/message-renderer.js +79 -1
  81. package/dist/index.js +224 -89
  82. package/dist/ipc.js +81 -4
  83. package/dist/paths.js +3 -0
  84. package/dist/response-system/config-resolver.js +29 -0
  85. package/dist/response-system/coordinator.js +8 -32
  86. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  87. package/dist/response-system/index.js +1 -0
  88. package/dist/response-system/modes/single-session/index.js +7 -4
  89. package/dist/trigger/parser.js +55 -15
  90. package/dist/trigger/patch.js +4 -1
  91. package/dist/trigger/scheduler.js +441 -39
  92. package/dist/utils/error-dict.json +7 -0
  93. package/dist/utils/evolcore-version.js +21 -0
  94. package/dist/utils/logger.js +2 -2
  95. package/dist/utils/stable-semver.js +21 -0
  96. package/dist/utils/stats.js +33 -9
  97. package/kits/docs/channels/aun.md +4 -13
  98. package/kits/docs/evolcore/INDEX.md +1 -1
  99. package/kits/docs/evolcore/config.md +47 -2
  100. package/kits/docs/evolcore/contact.md +7 -3
  101. package/kits/docs/evolcore/group-rules.md +46 -4
  102. package/kits/docs/evolcore/group.md +4 -4
  103. package/kits/docs/evolcore/msg.md +5 -5
  104. package/kits/docs/evolcore/trigger.md +25 -8
  105. package/kits/docs/path-registry.md +36 -17
  106. package/kits/eck_message_manifest.json +12 -1
  107. package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
  108. package/kits/rules/01-overview.md +17 -7
  109. package/kits/rules/02-navigation.md +38 -18
  110. package/kits/rules/03-identity.md +28 -24
  111. package/kits/rules/04-relation.md +44 -28
  112. package/kits/rules/05-venue.md +31 -15
  113. package/kits/rules/06-channel.md +11 -7
  114. package/kits/schemas/_meta.json +18 -7
  115. package/kits/schemas/agent-config.schema.7.json +303 -0
  116. package/kits/schemas/agent-config.schema.8.json +304 -0
  117. package/kits/schemas/agent-config.schema.9.json +374 -0
  118. package/kits/schemas/contact-book.schema.3.json +67 -0
  119. package/kits/schemas/daemon.schema.1.json +3 -2
  120. package/kits/schemas/daemon.schema.2.json +101 -0
  121. package/kits/schemas/daemon.schema.3.json +123 -0
  122. package/kits/schemas/defaults.schema.2.json +85 -0
  123. package/kits/schemas/defaults.schema.3.json +73 -0
  124. package/kits/schemas/relation-config.schema.6.json +46 -0
  125. package/kits/schemas/relation-config.schema.7.json +59 -0
  126. package/kits/schemas/single-session.schema.2.json +57 -0
  127. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  128. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  129. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  130. package/kits/templates/roles/admin.json +46 -0
  131. package/kits/templates/roles/member.json +4 -0
  132. package/kits/templates/roles/visitor.json +4 -0
  133. package/kits/templates/system-fragments/channel.md +12 -2
  134. package/kits/templates/system-fragments/identity.md +3 -1
  135. package/kits/templates/system-fragments/relation.md +1 -1
  136. package/kits/templates/system-fragments/session.md +6 -2
  137. package/package.json +4 -2
  138. package/MIGRATION-0.5.0.md +0 -378
  139. package/ROLE_ACCESS_CONTROL.md +0 -174
  140. package/dist/channels/contact-bind-code.js +0 -134
  141. package/dist/channels/wecom-card.js +0 -101
  142. package/dist/channels/wecom-onboarding.js +0 -82
  143. 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
@@ -390,7 +390,7 @@ export async function initTail() {
390
390
  * Present instance selection menu when existing instances are found.
391
391
  * Returns the user's choice, or null if cancelled.
392
392
  */
393
- export async function selectInstance(rl, channelType, instances) {
393
+ export async function selectInstance(rl, channelType, instances, options = {}) {
394
394
  const typeLabel = channelType === 'feishu' ? '飞书' : channelType === 'wechat' ? '微信' : channelType === 'dingtalk' ? '钉钉' : channelType === 'qqbot' ? 'QQ机器人' : channelType === 'wecom' ? '企业微信' : channelType.toUpperCase();
395
395
  console.log(`\n发现已有 ${typeLabel} 配置:`);
396
396
  const letters = 'abcdefghijklmnopqrstuvwxyz';
@@ -427,10 +427,12 @@ export async function selectInstance(rl, channelType, instances) {
427
427
  }
428
428
  const target = instances[choiceIndex];
429
429
  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;
430
+ if (options.confirmOverwrite !== false) {
431
+ const confirm = (await new Promise(r => rl.question(`⚠️ 即将覆盖该配置,确认?(y/N) `, r))).trim().toLowerCase();
432
+ if (confirm !== 'y' && confirm !== 'yes') {
433
+ console.log('已取消');
434
+ return null;
435
+ }
434
436
  }
435
437
  return { action: 'overwrite', index: choiceIndex, name: target.name };
436
438
  }
@@ -83,9 +83,9 @@ function printHelp() {
83
83
  list [--agent <aid>] [--all] [--json]
84
84
  show [--agent <aid>] <triggerId> [--json]
85
85
  history [--agent <aid>] [triggerId] [--limit <n>] [--json]
86
- create --file <trigger.json|trigger-dir> [--enable] [--json]
86
+ create --exec <script|trigger-session|target-session> --file <trigger.json|trigger-dir> [--enable] [--json]
87
87
  OR
88
- create [--agent <aid>] --cron <expr>|--event <pattern> --prompt <text> [--enable] [其他flag]
88
+ create [--agent <aid>] --exec <script|trigger-session|target-session> --cron <expr>|--event <pattern> (--prompt <text>|--prompt-file <path>) [--enable] [其他flag]
89
89
  set ... (create 的兼容别名)
90
90
  update [--agent <aid>] <triggerId> <参数...> [--if-revision <sha256:...>] [--json]
91
91
  enable [--agent <aid>] <triggerId>
@@ -97,8 +97,9 @@ function printHelp() {
97
97
 
98
98
  Create 参数模式支持:
99
99
  --once | --delay <时长> | --at <ISO时间> | --cron <表达式> | --every <时长> | --event <事件模式>
100
- --exec <script|trigger-session|target-session>
101
- --prompt <文本>
100
+ --exec <script|trigger-session|target-session> (必填)
101
+ --prompt <文本> | --prompt-file <路径>
102
+ Shell 双引号内的双引号、反引号和变量形式的美元符号都需在前面加反斜杠;trigger 保存的是还原后的原文
102
103
  --script-path <路径> --script-runtime <node|python|bash>
103
104
  --script-args <JSON> --script-timeout <时长>
104
105
  --feedback <target> (origin/silent 仅可通过 --file 导入)
@@ -117,12 +118,13 @@ Create 参数模式支持:
117
118
 
118
119
  Update 仅支持常用字段:
119
120
  --once | --delay | --at | --cron | --every | --event [--tz]
120
- --prompt <文本> --name <名称>
121
+ --prompt <文本> | --prompt-file <路径> --name <名称>
121
122
  --model <模型> --effort <强度> --permission <权限模式|inherit>(inherit 清除覆盖)
122
123
  --max-runs <次数> --max-duration <时长>
123
124
  --concurrency <forbid|replace|allow> --missed-policy <skip|run-once|run-all>
124
125
 
125
- 省略 --agent 时使用当前 agent AID;仅在跨身份操作或无会话上下文时需要显式指定。`);
126
+ 省略 --agent 时使用当前 agent AID;无会话上下文或跨身份操作时需要显式指定。
127
+ 任务会话中的跨身份操作仅允许目标 Agent 上具备 owner/admin 权限的调用者执行 list/show/history;创建、修改、启停、删除和运行仍只允许当前 Agent。`);
126
128
  }
127
129
  async function listTriggers(args, json) {
128
130
  const agentAid = resolveAgentAid(args);
@@ -214,10 +216,20 @@ async function showHistory(args, json) {
214
216
  }
215
217
  }
216
218
  async function createTrigger(args, json) {
217
- // Support two modes: --file (full JSON) or flags (parser-based)
218
- if (hasFlag(args, '--file')) {
219
+ if (!hasOption(args, '--exec')) {
220
+ throw new Error('创建 trigger 必须显式指定 --exec script|trigger-session|target-session');
221
+ }
222
+ const executionType = executionTypeFromFlag(args);
223
+ // --file is reserved for full trigger-definition imports.
224
+ if (hasOption(args, '--file')) {
225
+ if (hasOption(args, '--prompt-file')) {
226
+ throw new Error('--file(完整定义导入)与 --prompt-file(prompt 正文)不能同时指定');
227
+ }
219
228
  const file = requireFlag(args, '--file');
220
229
  const { definition, files } = loadDefinitionWithFiles(file);
230
+ if (definition.execution.type !== executionType) {
231
+ throw new Error(`--exec ${flagValue(args, '--exec')} 与 trigger.json 的 execution.type=${definition.execution.type} 不一致`);
232
+ }
221
233
  const res = await request({
222
234
  type: 'trigger.create',
223
235
  definition,
@@ -230,8 +242,12 @@ async function createTrigger(args, json) {
230
242
  return;
231
243
  }
232
244
  // Flag mode: use parser
245
+ const usesPromptFile = hasOption(args, '--prompt-file');
246
+ if (usesPromptFile && executionType === 'script') {
247
+ throw new Error('--exec script 不接受 --prompt-file;请使用 --script-path 和 --script-runtime');
248
+ }
233
249
  const { parseTriggerSetArgv } = await import('../trigger/parser.js');
234
- const parseResult = parseTriggerSetArgv(triggerCreateArgs(args));
250
+ const parseResult = parseTriggerSetArgv(withPromptFromFile(triggerCreateArgs(args)), { allowLongPrompt: usesPromptFile });
235
251
  if (!parseResult.ok) {
236
252
  throw new Error(parseResult.error);
237
253
  }
@@ -331,6 +347,16 @@ function sourceFromParsed(parsed) {
331
347
  return { type: 'event', eventPattern: parsed.scheduleValue };
332
348
  throw new Error(`unsupported scheduleType: ${parsed.scheduleType}`);
333
349
  }
350
+ function executionTypeFromFlag(args) {
351
+ const value = requireFlag(args, '--exec');
352
+ if (value === 'script')
353
+ return 'script';
354
+ if (value === 'trigger-session' || value === 'trigger_session')
355
+ return 'trigger_session';
356
+ if (value === 'target-session' || value === 'target_session')
357
+ return 'target_session';
358
+ throw new Error('--exec 只接受 script、trigger-session 或 target-session');
359
+ }
334
360
  function limitsFromParsed(parsed) {
335
361
  const limits = {};
336
362
  if (parsed.maxRuns !== undefined)
@@ -362,9 +388,10 @@ function shellQuoteArg(value) {
362
388
  }
363
389
  async function updateTrigger(args, json) {
364
390
  const agentAid = resolveAgentAid(args);
365
- const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision']);
391
+ const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file']);
392
+ const usesPromptFile = hasOption(args, '--prompt-file');
366
393
  const { parseTriggerUpdateArgv } = await import('../trigger/parser.js');
367
- const parsed = parseTriggerUpdateArgv(triggerId, triggerUpdateArgs(args));
394
+ const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args)), { allowLongPrompt: usesPromptFile });
368
395
  if (!parsed.ok)
369
396
  throw new Error(parsed.error);
370
397
  const expectedRevision = flagValue(args, '--if-revision');
@@ -373,6 +400,7 @@ async function updateTrigger(args, json) {
373
400
  agentAid,
374
401
  triggerId,
375
402
  patch: parsed.value,
403
+ ...(usesPromptFile ? { promptFile: true } : {}),
376
404
  ...(expectedRevision ? { expectedRevision } : {}),
377
405
  }, 30_000);
378
406
  if (json)
@@ -400,6 +428,56 @@ function triggerUpdateArgs(args) {
400
428
  }
401
429
  return out;
402
430
  }
431
+ function withPromptFromFile(args) {
432
+ const fileIndexes = [];
433
+ const filePaths = [];
434
+ for (let index = 0; index < args.length; index++) {
435
+ const arg = args[index];
436
+ if (arg === '--prompt-file') {
437
+ const value = args[index + 1];
438
+ if (!value || value.startsWith('--'))
439
+ throw new Error('missing --prompt-file');
440
+ fileIndexes.push(index, index + 1);
441
+ filePaths.push(value);
442
+ index += 1;
443
+ }
444
+ else if (arg.startsWith('--prompt-file=')) {
445
+ const value = arg.slice('--prompt-file='.length);
446
+ if (!value)
447
+ throw new Error('missing --prompt-file');
448
+ fileIndexes.push(index);
449
+ filePaths.push(value);
450
+ }
451
+ }
452
+ if (fileIndexes.length === 0)
453
+ return args;
454
+ if (filePaths.length > 1)
455
+ throw new Error('--prompt-file 只能指定一次');
456
+ if (hasOption(args, '--prompt'))
457
+ throw new Error('--prompt-file 与 --prompt 不能同时指定');
458
+ const filePath = filePaths[0];
459
+ let stat;
460
+ try {
461
+ stat = fs.statSync(filePath);
462
+ }
463
+ catch (err) {
464
+ throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
465
+ }
466
+ if (!stat.isFile())
467
+ throw new Error(`prompt 文件必须是普通文件: ${filePath}`);
468
+ let prompt;
469
+ try {
470
+ prompt = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
471
+ }
472
+ catch (err) {
473
+ throw new Error(`无法读取 prompt 文件 ${filePath}: ${err?.message || String(err)}`);
474
+ }
475
+ const fileIndexSet = new Set(fileIndexes);
476
+ return [
477
+ ...args.filter((_, index) => !fileIndexSet.has(index)),
478
+ '--prompt', prompt,
479
+ ];
480
+ }
403
481
  async function setEnabled(args, enabled, json) {
404
482
  const agentAid = resolveAgentAid(args);
405
483
  const triggerId = positional(args, 0, ['--agent', '--format']);
@@ -522,6 +600,9 @@ function flagValue(args, name) {
522
600
  function hasFlag(args, name) {
523
601
  return args.includes(name);
524
602
  }
603
+ function hasOption(args, name) {
604
+ return args.includes(name) || args.some(arg => arg.startsWith(`${name}=`));
605
+ }
525
606
  function positional(args, index, flagsWithValue) {
526
607
  const value = optionalPositional(args, index, flagsWithValue);
527
608
  if (!value)