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.
- package/CHANGELOG.md +64 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +7 -5
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +249 -24
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-manager.js +25 -3
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +7 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
package/dist/cli/init-channel.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
-
|
|
1125
|
-
console.log(
|
|
1126
|
-
|
|
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
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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)
|