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/index.js
CHANGED
|
@@ -17,9 +17,12 @@ import { ConfigTarget, initConfigManager, onConfigWrite } from './config/config-
|
|
|
17
17
|
import { ensureRoleConfigV4OnStartup } from './config/role-config-v4-startup.js';
|
|
18
18
|
import { ensureRoleConfigV5OnStartup } from './config/role-config-v5-startup.js';
|
|
19
19
|
import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
|
|
20
|
+
import { resolvePeerDisplayLabel } from './config/contact-book.js';
|
|
21
|
+
import { expirePendingContactRequests } from './config/contact-request-service.js';
|
|
20
22
|
import { shouldFailFastForMissingOwners } from './config/owner-policy.js';
|
|
21
23
|
import { isManagementRole } from './config/builtin-roles.js';
|
|
22
24
|
import { checkRoleAccess, getFirstStaticAgentOwner, resolvePeerRoleDetail, roleToSessionIdentity } from './config/peer-role-resolver.js';
|
|
25
|
+
import { isCrossAgentTriggerOperationAllowed, isCrossAgentTriggerReadOperation } from './core/auth/trigger-authorization.js';
|
|
23
26
|
import { ensureStartupConfigSnapshot, isConfigSnapshotsEnabled, retentionCleanup, readCurrent, readWVersion, diffWorkingVsVersion, paramDiff, incrementSuccessCount } from './config/snapshot.js';
|
|
24
27
|
import { appendBootLog, selfDiagnose } from './config/boot-log.js';
|
|
25
28
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
@@ -27,19 +30,22 @@ import { SessionManager } from './core/session/session-manager.js';
|
|
|
27
30
|
import { ClaudeAgentPlugin } from './agents/claude-runner.js';
|
|
28
31
|
import { CodexAgentPlugin } from './agents/codex-runner.js';
|
|
29
32
|
import { GeminiAgentPlugin } from './agents/gemini-runner.js';
|
|
30
|
-
import { FeishuChannelPlugin } from './channels/feishu.js';
|
|
31
|
-
import { WechatChannelPlugin } from './channels/wechat.js';
|
|
33
|
+
import { FeishuChannelPlugin, registerPendingFeishuContactBind } from './channels/feishu.js';
|
|
34
|
+
import { WechatChannelPlugin, registerPendingWechatContactBind } from './channels/wechat.js';
|
|
32
35
|
import { AUN_HANDOFF_MARKER_FIELD, AUNChannel, AUNChannelPlugin } from './channels/aun.js';
|
|
36
|
+
import { assertAunEncryptionSeedPolicy } from './aun/aid/encryption-seed-policy.js';
|
|
33
37
|
import { startServiceProxy } from './aun/service-proxy.js';
|
|
34
38
|
import { BindService } from './utils/aid-bind.js';
|
|
35
39
|
import { DingtalkChannelPlugin, registerPendingDingtalkContactBind } from './channels/dingtalk.js';
|
|
36
|
-
import { QQBotChannelPlugin } from './channels/qqbot.js';
|
|
40
|
+
import { QQBotChannelPlugin, registerPendingQQBotContactBind } from './channels/qqbot.js';
|
|
37
41
|
import { WecomChannelPlugin, registerPendingWecomContactBind } from './channels/wecom.js';
|
|
38
42
|
import { buildEnvelope } from './core/message/message-utils.js';
|
|
39
43
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
40
44
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
41
45
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
42
46
|
import { MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
|
|
47
|
+
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata } from './core/command/evol-menu-version-gate.js';
|
|
48
|
+
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
43
49
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
44
50
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
45
51
|
import { BootstrapService } from './core/bootstrap-service.js';
|
|
@@ -345,7 +351,7 @@ function removeLegacyAgentUpgradeCheck(manager) {
|
|
|
345
351
|
export async function sendSystemPayload(adapter, envelope, payload) {
|
|
346
352
|
await adapter.send(envelope, payload);
|
|
347
353
|
}
|
|
348
|
-
async function runBindBootstrapDaemon(daemonCfg
|
|
354
|
+
async function runBindBootstrapDaemon(daemonCfg) {
|
|
349
355
|
logger.warn('[bind-bootstrap] starting control AID + IPC only');
|
|
350
356
|
const bindService = new BindService({
|
|
351
357
|
receiverAid: daemonCfg.aid,
|
|
@@ -359,8 +365,8 @@ async function runBindBootstrapDaemon(daemonCfg, defaults) {
|
|
|
359
365
|
agentName: daemonCfg.aid,
|
|
360
366
|
channelName: 'control',
|
|
361
367
|
pureIdentity: true,
|
|
362
|
-
aunTrace: daemonCfg.debug?.aunTrace
|
|
363
|
-
aunSdkLog: daemonCfg.debug?.aunSdkLog
|
|
368
|
+
aunTrace: daemonCfg.debug?.aunTrace,
|
|
369
|
+
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
364
370
|
});
|
|
365
371
|
try {
|
|
366
372
|
await controlChannel.connect();
|
|
@@ -417,13 +423,7 @@ async function runBindBootstrapDaemon(daemonCfg, defaults) {
|
|
|
417
423
|
process.on('exit', () => removeAll());
|
|
418
424
|
}
|
|
419
425
|
export function readEvolcoreVersion() {
|
|
420
|
-
|
|
421
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
|
|
422
|
-
return pkg.version || 'unknown';
|
|
423
|
-
}
|
|
424
|
-
catch {
|
|
425
|
-
return 'unknown';
|
|
426
|
-
}
|
|
426
|
+
return readInstalledEvolcoreVersion();
|
|
427
427
|
}
|
|
428
428
|
function detectAvailableBaseagentsForBind() {
|
|
429
429
|
const out = [];
|
|
@@ -583,6 +583,18 @@ async function main() {
|
|
|
583
583
|
logger.info(`EvolCore v${readEvolcoreVersion()} starting... (fastaun v${readFastaunVersion()})`);
|
|
584
584
|
// 确保数据目录存在
|
|
585
585
|
ensureDataDirs();
|
|
586
|
+
// AUN keystore seed is an EvolCore application constant. A custom value can
|
|
587
|
+
// make existing identities unreadable, so fail before migrations, snapshots,
|
|
588
|
+
// instance registration, and service startup.
|
|
589
|
+
try {
|
|
590
|
+
assertAunEncryptionSeedPolicy(loadDaemonConfig().aun?.encryptionSeed, process.env.AUN_ENCRYPTION_SEED);
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
const msg = `❌ ${error instanceof Error ? error.message : String(error)}`;
|
|
594
|
+
logger.error(msg);
|
|
595
|
+
console.error(msg);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
586
598
|
// .env 文件已在模块顶部加载,此处不再重复加载
|
|
587
599
|
// ── 单实例保护(pre-check + post-write self-check)──
|
|
588
600
|
// pre-check:发现已有活 main 直接退出,避免起任何副作用
|
|
@@ -680,6 +692,9 @@ async function main() {
|
|
|
680
692
|
}
|
|
681
693
|
}
|
|
682
694
|
const daemonCfg = loadDaemonConfig();
|
|
695
|
+
if (daemonCfg.debug?.logLevel) {
|
|
696
|
+
setLogLevel(daemonCfg.debug.logLevel);
|
|
697
|
+
}
|
|
683
698
|
if (initializeEckSnapshotsConfig(daemonCfg)) {
|
|
684
699
|
logger.warn(`[ECK] Debug snapshots enabled at ${eckDebugDir()}; files may contain sensitive context and local paths.`);
|
|
685
700
|
}
|
|
@@ -701,12 +716,9 @@ async function main() {
|
|
|
701
716
|
logger.warn('[startup] daemon.json.owners 未配置:进程级 menu 操作(/system /agent)将一律拒绝。' +
|
|
702
717
|
'如需远程管理,请在 daemon.json 配置 owners: [<你的 AID>]');
|
|
703
718
|
}
|
|
704
|
-
// 应用配置中的日志级别(优先于环境变量)
|
|
705
|
-
// logLevel 现在不在新结构中——若要保留,将来可加 defaults.debug.logLevel
|
|
706
|
-
// 阶段 2c 暂跳过
|
|
707
719
|
const paths = resolvePaths();
|
|
708
720
|
if (bindBootstrapMode && daemonCfg.aid && loadAllAgents().agents.length === 0) {
|
|
709
|
-
await runBindBootstrapDaemon(daemonCfg
|
|
721
|
+
await runBindBootstrapDaemon(daemonCfg);
|
|
710
722
|
return;
|
|
711
723
|
}
|
|
712
724
|
// ── EvolAgent Registry:加载 agents/<aid>/config.json ──
|
|
@@ -754,14 +766,11 @@ async function main() {
|
|
|
754
766
|
agentRuntimeError = 'No runnable self-agent (all are error/disabled).';
|
|
755
767
|
logger.warn(`[startup] ${agentRuntimeError} Control Plane will remain available.`);
|
|
756
768
|
}
|
|
757
|
-
//
|
|
769
|
+
// 进程级设置独立来自 daemon.json,不进入 Agent 覆盖链。
|
|
758
770
|
const globalSettings = {
|
|
759
771
|
idleMonitor: daemonCfg.idleMonitor,
|
|
760
|
-
debug:
|
|
772
|
+
debug: daemonCfg.debug,
|
|
761
773
|
};
|
|
762
|
-
if (globalSettings.debug?.logLevel) {
|
|
763
|
-
setLogLevel(globalSettings.debug.logLevel);
|
|
764
|
-
}
|
|
765
774
|
// 启动期 anthropic 凭证校验已移除:runner 创建时由 AgentLoader 错误处理
|
|
766
775
|
logger.info('✓ Config loaded');
|
|
767
776
|
// Store for IPC access (T10 will wire this)
|
|
@@ -918,7 +927,7 @@ async function main() {
|
|
|
918
927
|
messageCache.cleanupExpired();
|
|
919
928
|
}, 60 * 60 * 1000);
|
|
920
929
|
// 渠道插件系统
|
|
921
|
-
const channelLoader = new ChannelLoader();
|
|
930
|
+
const channelLoader = new ChannelLoader(daemonCfg.debug);
|
|
922
931
|
channelLoader.register(new FeishuChannelPlugin());
|
|
923
932
|
channelLoader.register(new WechatChannelPlugin());
|
|
924
933
|
channelLoader.register(new AUNChannelPlugin());
|
|
@@ -940,6 +949,14 @@ async function main() {
|
|
|
940
949
|
}
|
|
941
950
|
const channelInstances = evolagentInstances;
|
|
942
951
|
logger.info(`✓ Created ${channelInstances.length} channel instance(s)`);
|
|
952
|
+
const contactRequestExpiryTimer = setInterval(() => {
|
|
953
|
+
for (const agent of agentRegistry.runnableAgents()) {
|
|
954
|
+
void expirePendingContactRequests(agent.aid).catch(error => {
|
|
955
|
+
logger.warn(`[contact-request] Pending expiry sweep failed: self=${agent.aid} error=${error instanceof Error ? error.message : String(error)}`);
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}, 60_000);
|
|
959
|
+
contactRequestExpiryTimer.unref?.();
|
|
943
960
|
const bindService = daemonCfg.aid
|
|
944
961
|
? new BindService({
|
|
945
962
|
receiverAid: daemonCfg.aid,
|
|
@@ -1002,6 +1019,10 @@ async function main() {
|
|
|
1002
1019
|
await responseEngine.interruptSession(sessionKey, reason);
|
|
1003
1020
|
});
|
|
1004
1021
|
messageQueue.setEventBus(eventBus);
|
|
1022
|
+
// Restore persisted input before Trigger schedulers start. Trigger-owned work
|
|
1023
|
+
// is deliberately dropped here and reclaimed from active.json by its scheduler.
|
|
1024
|
+
messageQueue.restorePersisted(false);
|
|
1025
|
+
const droppedTriggerSessionIds = new Set(messageQueue.consumeDroppedTriggerSessionIds());
|
|
1005
1026
|
// 进程退出时立即刷盘队列状态(防止 debounce 期间的消息丢失)
|
|
1006
1027
|
onShutdown(() => messageQueue.persistQueuesImmediate());
|
|
1007
1028
|
// 回填 messageQueue 引用
|
|
@@ -1281,7 +1302,7 @@ async function main() {
|
|
|
1281
1302
|
}
|
|
1282
1303
|
return { allowed: true };
|
|
1283
1304
|
};
|
|
1284
|
-
const authenticatedTriggerActor = async (agentAid, actorSessionId, delegationToken, controlToken) => {
|
|
1305
|
+
const authenticatedTriggerActor = async (agentAid, actorSessionId, delegationToken, delegationCommandHash, controlToken) => {
|
|
1285
1306
|
if (typeof actorSessionId !== 'string' || !actorSessionId) {
|
|
1286
1307
|
const providedToken = typeof controlToken === 'string' ? Buffer.from(controlToken) : undefined;
|
|
1287
1308
|
const expectedToken = Buffer.from(triggerControlToken);
|
|
@@ -1317,12 +1338,9 @@ async function main() {
|
|
|
1317
1338
|
subject,
|
|
1318
1339
|
};
|
|
1319
1340
|
}
|
|
1320
|
-
const validation = agentDelegationRegistry.validate(typeof delegationToken === 'string' ? delegationToken : undefined, actorSessionId);
|
|
1341
|
+
const validation = agentDelegationRegistry.validate(typeof delegationToken === 'string' ? delegationToken : undefined, actorSessionId, typeof delegationCommandHash === 'string' ? delegationCommandHash : undefined);
|
|
1321
1342
|
if (!validation.ok)
|
|
1322
1343
|
throw new Error(validation.reason);
|
|
1323
|
-
if (validation.grant.selfAid !== agentAid) {
|
|
1324
|
-
throw new Error('trigger agent does not match authenticated task agent');
|
|
1325
|
-
}
|
|
1326
1344
|
const actorSession = await sessionManager.getSessionById(actorSessionId);
|
|
1327
1345
|
const actorOrigin = actorSession ? originFromActorSession(actorSession, validation.grant.actorId, validation.grant.channel, validation.grant.chatType) : undefined;
|
|
1328
1346
|
if (!actorSession || !actorOrigin?.peerId)
|
|
@@ -1331,7 +1349,9 @@ async function main() {
|
|
|
1331
1349
|
throw new Error('trigger delegation actor does not match session peer');
|
|
1332
1350
|
}
|
|
1333
1351
|
const roleDetail = resolvePeerRoleDetail({
|
|
1334
|
-
|
|
1352
|
+
// A cross-agent audit must be authorized by the target agent's role
|
|
1353
|
+
// configuration, not by the task agent that carried the delegation.
|
|
1354
|
+
selfAid: agentAid,
|
|
1335
1355
|
channelKey: validation.grant.channel,
|
|
1336
1356
|
channelType: validation.grant.channelType,
|
|
1337
1357
|
chatType: 'private',
|
|
@@ -1339,11 +1359,11 @@ async function main() {
|
|
|
1339
1359
|
conversationId: validation.grant.actorId,
|
|
1340
1360
|
});
|
|
1341
1361
|
const currentRole = roleDetail.effectiveRole;
|
|
1342
|
-
if (!checkRoleAccess(currentRole,
|
|
1362
|
+
if (!checkRoleAccess(currentRole, agentAid)) {
|
|
1343
1363
|
throw new Error('trigger actor no longer has access to this agent');
|
|
1344
1364
|
}
|
|
1345
1365
|
const subject = buildAuthSubject({
|
|
1346
|
-
selfAid:
|
|
1366
|
+
selfAid: agentAid,
|
|
1347
1367
|
actorId: validation.grant.actorId,
|
|
1348
1368
|
channel: validation.grant.channel,
|
|
1349
1369
|
channelType: validation.grant.channelType,
|
|
@@ -1436,18 +1456,36 @@ async function main() {
|
|
|
1436
1456
|
}
|
|
1437
1457
|
};
|
|
1438
1458
|
cmdHandler.setTriggerSchedulerResolver((agentAid) => triggerSchedulers.get(agentAid));
|
|
1439
|
-
const
|
|
1459
|
+
const triggerSchedulerStarts = new Map();
|
|
1440
1460
|
const ensureTriggerSchedulerStarted = async (agent) => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1461
|
+
const existing = triggerSchedulerStarts.get(agent.aid);
|
|
1462
|
+
if (existing)
|
|
1463
|
+
return await existing;
|
|
1464
|
+
const starting = startTriggerScheduler(agent);
|
|
1465
|
+
triggerSchedulerStarts.set(agent.aid, starting);
|
|
1466
|
+
try {
|
|
1467
|
+
await starting;
|
|
1468
|
+
}
|
|
1469
|
+
catch (error) {
|
|
1470
|
+
if (triggerSchedulerStarts.get(agent.aid) === starting)
|
|
1471
|
+
triggerSchedulerStarts.delete(agent.aid);
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1445
1474
|
};
|
|
1446
1475
|
const ensureDaemonTriggerSchedulerStarted = async () => {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1476
|
+
const existing = triggerSchedulerStarts.get(daemonTriggerOwner.aid);
|
|
1477
|
+
if (existing)
|
|
1478
|
+
return await existing;
|
|
1479
|
+
const starting = startTriggerScheduler(daemonTriggerOwner, { seedUpgradeCheck: true });
|
|
1480
|
+
triggerSchedulerStarts.set(daemonTriggerOwner.aid, starting);
|
|
1481
|
+
try {
|
|
1482
|
+
await starting;
|
|
1483
|
+
}
|
|
1484
|
+
catch (error) {
|
|
1485
|
+
if (triggerSchedulerStarts.get(daemonTriggerOwner.aid) === starting)
|
|
1486
|
+
triggerSchedulerStarts.delete(daemonTriggerOwner.aid);
|
|
1487
|
+
throw error;
|
|
1488
|
+
}
|
|
1451
1489
|
};
|
|
1452
1490
|
// 默认策略
|
|
1453
1491
|
const defaultPolicy = {
|
|
@@ -1468,8 +1506,15 @@ async function main() {
|
|
|
1468
1506
|
const defaultProjectPath = primaryAgent?.projectPath
|
|
1469
1507
|
?? defaults.projects?.defaultPath
|
|
1470
1508
|
?? path.join(paths.root, 'projects', 'default');
|
|
1509
|
+
const isContactBindChannelReady = (channelKey) => {
|
|
1510
|
+
const inst = channelInstances.find(candidate => candidate.adapter.channelKey === channelKey);
|
|
1511
|
+
return !!inst && inst.channel.getStatus?.().connected === true;
|
|
1512
|
+
};
|
|
1471
1513
|
const msgBridge = new MessageBridge(defaultProjectPath, sessionManager, processor, messageQueue, cmdHandler, eventBus, primaryAgent?.config.debounce, () => processLevelOwners);
|
|
1472
1514
|
msgBridge.setAgentRegistry(agentRegistry);
|
|
1515
|
+
msgBridge.setInteractionRouter(interactionRouter);
|
|
1516
|
+
msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
|
|
1517
|
+
msgBridge.setAidStatsCollector(aidStatsCollector);
|
|
1473
1518
|
const bootstrapService = new BootstrapService(agentRegistry, eventBus);
|
|
1474
1519
|
msgBridge.setBootstrapService(bootstrapService);
|
|
1475
1520
|
msgBridge.setHandoffRuntime(handoffRuntime);
|
|
@@ -1496,7 +1541,7 @@ async function main() {
|
|
|
1496
1541
|
const isGroup = session.chatType === 'group';
|
|
1497
1542
|
const target = isGroup
|
|
1498
1543
|
? (session.metadata?.groupId || session.channelId)
|
|
1499
|
-
: (session.metadata?.peerId || envelope.channelId);
|
|
1544
|
+
: (envelope.replyContext?.peerId || session.metadata?.peerId || envelope.channelId);
|
|
1500
1545
|
appendMessageLog(chatDir, buildOutboundEntry({
|
|
1501
1546
|
from: owningAgent?.aid ?? envelope.agentName ?? session.selfAID ?? 'self',
|
|
1502
1547
|
to: String(target || envelope.channelId),
|
|
@@ -1514,6 +1559,16 @@ async function main() {
|
|
|
1514
1559
|
source: normalizeMessageSource(envelope.replyContext?.metadata?.source),
|
|
1515
1560
|
chatmode: envelope.chatmode,
|
|
1516
1561
|
}));
|
|
1562
|
+
// Per-AID 统计(预览行/tooltip 数据源)。AUN 在 channel 层自行记账,此处只补非 AUN。
|
|
1563
|
+
const statsAid = owningAgent?.aid ?? envelope.agentName ?? session.selfAID;
|
|
1564
|
+
if (statsAid) {
|
|
1565
|
+
aidStatsCollector.recordOutbound(statsAid, String(target || envelope.channelId), Buffer.byteLength(logPayload.text, 'utf-8'), logPayload.text, false, undefined, envelope.chatmode, 'send', {
|
|
1566
|
+
channelType: inst.channelType || inst.adapter.channelName,
|
|
1567
|
+
// 与入向同一套展示名解析:联系人簿 displayName 优先,兜底原生 ID
|
|
1568
|
+
peerLabel: resolvePeerDisplayLabel(statsAid, inst.channelType || inst.adapter.channelName, String(target || envelope.channelId), inst.adapter.channelKey) || session.metadata?.peerName || String(target || envelope.channelId),
|
|
1569
|
+
routeId: envelope.channelId,
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1517
1572
|
}
|
|
1518
1573
|
}
|
|
1519
1574
|
catch (err) {
|
|
@@ -1663,7 +1718,7 @@ async function main() {
|
|
|
1663
1718
|
const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey) ?? agentRegistry.resolveByChannel(name);
|
|
1664
1719
|
if (!agent)
|
|
1665
1720
|
return;
|
|
1666
|
-
if (!
|
|
1721
|
+
if (!daemonCfg.debug?.upmsg)
|
|
1667
1722
|
return;
|
|
1668
1723
|
const ownerAid = getFirstStaticAgentOwner(agent.aid);
|
|
1669
1724
|
if (!ownerAid)
|
|
@@ -1838,15 +1893,15 @@ async function main() {
|
|
|
1838
1893
|
// Trigger scheduler 与渠道连接解耦:cron 定时任务独立于渠道可用性运行
|
|
1839
1894
|
// (触发时若渠道未连,发送侧自行排队/重试)。markChannelConnected 里的
|
|
1840
1895
|
// ensureTriggerSchedulerStarted 仅作"尽早启动"优化,此处保证即使渠道首连
|
|
1841
|
-
// 全部失败(如 gateway 宕机),trigger 仍无条件启动。
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
ensureTriggerSchedulerStarted(agent).catch((e) => {
|
|
1896
|
+
// 全部失败(如 gateway 宕机),trigger 仍无条件启动。Promise 去重,不会重复。
|
|
1897
|
+
await Promise.all([
|
|
1898
|
+
ensureDaemonTriggerSchedulerStarted().catch((e) => {
|
|
1899
|
+
logger.warn(`[startup] daemon trigger scheduler start failed for ${daemonTriggerOwner.aid}: ${e}`);
|
|
1900
|
+
}),
|
|
1901
|
+
...triggerStartupAgents.map(agent => ensureTriggerSchedulerStarted(agent).catch((e) => {
|
|
1847
1902
|
logger.warn(`[startup] trigger scheduler start failed for ${agent.aid}: ${e}`);
|
|
1848
|
-
})
|
|
1849
|
-
|
|
1903
|
+
})),
|
|
1904
|
+
]);
|
|
1850
1905
|
// ── 控制 AID(daemon 进程身份):pureIdentity 接入 AUN,独立于 evolagent ──
|
|
1851
1906
|
// 证书缺失检测/生成在 CLI 侧(ec start)完成。daemon 是后台进程无终端,
|
|
1852
1907
|
// 这里只做兜底:证书缺失时 warn 并继续(AUNChannel 内部后台重连),绝不阻塞。
|
|
@@ -1864,8 +1919,8 @@ async function main() {
|
|
|
1864
1919
|
agentName: daemonCfg.aid,
|
|
1865
1920
|
channelName: 'control',
|
|
1866
1921
|
pureIdentity: true,
|
|
1867
|
-
aunTrace: daemonCfg.debug?.aunTrace
|
|
1868
|
-
aunSdkLog: daemonCfg.debug?.aunSdkLog
|
|
1922
|
+
aunTrace: daemonCfg.debug?.aunTrace,
|
|
1923
|
+
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
1869
1924
|
});
|
|
1870
1925
|
// connect() 失败不置空实例:AUNChannel 内部有无限重连(SDK auto_reconnect +
|
|
1871
1926
|
// scheduleReconnect),首连失败后台会自愈;保留实例供 status 显示 disconnected。
|
|
@@ -1935,13 +1990,16 @@ async function main() {
|
|
|
1935
1990
|
const menuControl = parseMenuControl(text);
|
|
1936
1991
|
const isRoleMenu = menuControl.isMenu
|
|
1937
1992
|
&& (menuControl.name === 'role' || menuControl.request.cmd === '/role');
|
|
1993
|
+
const menuResponseContext = () => ({
|
|
1994
|
+
metadata: evolMenuResponseTransportMetadata(opts.encrypted),
|
|
1995
|
+
});
|
|
1938
1996
|
if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
|
|
1939
1997
|
logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
|
|
1940
1998
|
return;
|
|
1941
1999
|
}
|
|
1942
2000
|
if (menuControl.isMenu && !isRoleMenu && !isProcessLevelOwner(opts.peerId, processLevelOwners)) {
|
|
1943
2001
|
const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: 'Current identity cannot access the control channel' });
|
|
1944
|
-
await controlChannel.sendStructured(opts.channelId, response,
|
|
2002
|
+
await controlChannel.sendStructured(opts.channelId, response, menuResponseContext());
|
|
1945
2003
|
return;
|
|
1946
2004
|
}
|
|
1947
2005
|
if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, processLevelOwners)) {
|
|
@@ -1964,9 +2022,17 @@ async function main() {
|
|
|
1964
2022
|
}
|
|
1965
2023
|
// menu.* JSON 路由:owner 已在上方校验,转交 execMenuForControl(fromControlChannel=true)
|
|
1966
2024
|
if (menuControl.isMenu) {
|
|
2025
|
+
const versionError = evaluateEvolMenuVersionGate({
|
|
2026
|
+
encrypted: opts.encrypted,
|
|
2027
|
+
protectedHeaders: opts.protectedHeaders,
|
|
2028
|
+
});
|
|
2029
|
+
if (versionError) {
|
|
2030
|
+
await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError), menuResponseContext());
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
1967
2033
|
const validationError = validateMenuRequest(menuControl.request);
|
|
1968
2034
|
if (validationError) {
|
|
1969
|
-
await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError),
|
|
2035
|
+
await controlChannel.sendStructured(opts.channelId, menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError), menuResponseContext());
|
|
1970
2036
|
return;
|
|
1971
2037
|
}
|
|
1972
2038
|
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId));
|
|
@@ -1975,7 +2041,7 @@ async function main() {
|
|
|
1975
2041
|
: deduped.value;
|
|
1976
2042
|
// 同 bind.response:sendStructured 直发 typed payload(payload.type='menu.response'),
|
|
1977
2043
|
// 不能用 sendMessage(会包成 {type:'text',...});encrypted 跟随入站请求保持对称。
|
|
1978
|
-
await controlChannel.sendStructured(opts.channelId, response,
|
|
2044
|
+
await controlChannel.sendStructured(opts.channelId, response, menuResponseContext());
|
|
1979
2045
|
return;
|
|
1980
2046
|
}
|
|
1981
2047
|
// owner 发的其他内容:提示可用指令
|
|
@@ -2042,9 +2108,7 @@ async function main() {
|
|
|
2042
2108
|
});
|
|
2043
2109
|
}
|
|
2044
2110
|
});
|
|
2045
|
-
//
|
|
2046
|
-
messageQueue.restorePersisted(false);
|
|
2047
|
-
const droppedTriggerSessionIds = new Set(messageQueue.consumeDroppedTriggerSessionIds());
|
|
2111
|
+
// Trigger scheduler 已在队列恢复之后启动;这里只协调其余持久化工作。
|
|
2048
2112
|
// runnableAgents() only returns pre-start `stopped` agents. Reuse the stable
|
|
2049
2113
|
// startup snapshot because channel connection may already mark them running.
|
|
2050
2114
|
const recoveryAids = triggerStartupAgents.map(agent => agent.aid);
|
|
@@ -2080,8 +2144,13 @@ async function main() {
|
|
|
2080
2144
|
logger.info(`[Resume] Found ${pendingSessions.length} pending session(s) from before restart`);
|
|
2081
2145
|
for (const session of pendingSessions) {
|
|
2082
2146
|
if (droppedTriggerSessionIds.has(session.id)) {
|
|
2083
|
-
|
|
2084
|
-
|
|
2147
|
+
if (messageQueue.isProcessing(session.id) || messageQueue.getQueueLength(session.id) > 0) {
|
|
2148
|
+
logger.info(`[Resume] session ${session.id}: Trigger scheduler already reclaimed execution, skipping generic resume`);
|
|
2149
|
+
}
|
|
2150
|
+
else {
|
|
2151
|
+
logger.info(`[Resume] session ${session.id}: dropped stale Trigger execution was not reclaimable, clearing processing`);
|
|
2152
|
+
sessionManager.clearProcessing(session.id);
|
|
2153
|
+
}
|
|
2085
2154
|
continue;
|
|
2086
2155
|
}
|
|
2087
2156
|
if (messageQueue.isProcessing(session.id) || messageQueue.getQueueLength(session.id) > 0) {
|
|
@@ -2172,8 +2241,8 @@ async function main() {
|
|
|
2172
2241
|
? { aid: daemonCfg.aid, connected: controlChannel?.getAidState().status === 'connected' }
|
|
2173
2242
|
: undefined,
|
|
2174
2243
|
};
|
|
2175
|
-
}, async (cmd, sessionId, delegationToken) => {
|
|
2176
|
-
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId);
|
|
2244
|
+
}, async (cmd, sessionId, delegationToken, delegationCommandHash) => {
|
|
2245
|
+
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
2177
2246
|
if (!delegation.ok)
|
|
2178
2247
|
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
2179
2248
|
return cmdHandler.handleCtl(cmd, sessionId);
|
|
@@ -2182,13 +2251,27 @@ async function main() {
|
|
|
2182
2251
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2183
2252
|
ipcServer.setDingtalkContactBindExecutor({
|
|
2184
2253
|
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
2254
|
+
isChannelReady: isContactBindChannelReady,
|
|
2255
|
+
});
|
|
2256
|
+
ipcServer.setFeishuContactBindExecutor({
|
|
2257
|
+
register: (cmd) => registerPendingFeishuContactBind(cmd),
|
|
2258
|
+
isChannelReady: isContactBindChannelReady,
|
|
2259
|
+
});
|
|
2260
|
+
ipcServer.setQQBotContactBindExecutor({
|
|
2261
|
+
register: (cmd) => registerPendingQQBotContactBind(cmd),
|
|
2262
|
+
isChannelReady: isContactBindChannelReady,
|
|
2185
2263
|
});
|
|
2186
2264
|
ipcServer.setWecomContactBindExecutor({
|
|
2187
2265
|
register: (cmd) => registerPendingWecomContactBind(cmd),
|
|
2266
|
+
isChannelReady: isContactBindChannelReady,
|
|
2267
|
+
});
|
|
2268
|
+
ipcServer.setWechatContactBindExecutor({
|
|
2269
|
+
register: (cmd) => registerPendingWechatContactBind(cmd),
|
|
2270
|
+
isChannelReady: isContactBindChannelReady,
|
|
2188
2271
|
});
|
|
2189
2272
|
ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
|
|
2190
|
-
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken));
|
|
2191
|
-
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken));
|
|
2273
|
+
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2274
|
+
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2192
2275
|
cmdHandler.setDaemonStatusProvider(() => {
|
|
2193
2276
|
const aidState = controlChannel?.getAidState?.();
|
|
2194
2277
|
return {
|
|
@@ -2314,6 +2397,7 @@ async function main() {
|
|
|
2314
2397
|
: undefined;
|
|
2315
2398
|
const delegation = authorizeDelegatedAunMsgSend(agentDelegationRegistry, {
|
|
2316
2399
|
delegationToken: params.delegationToken,
|
|
2400
|
+
delegationCommandHash: params.delegationCommandHash,
|
|
2317
2401
|
sessionId: params.originSessionId,
|
|
2318
2402
|
messageId: params.originMessageId,
|
|
2319
2403
|
aid: params.aid,
|
|
@@ -2536,6 +2620,10 @@ async function main() {
|
|
|
2536
2620
|
};
|
|
2537
2621
|
// Full resync handler: scan disk, load new agents, unload removed/disabled, reload changed
|
|
2538
2622
|
globalThis.__evolcore_resyncAgents = async () => {
|
|
2623
|
+
// 先清 kit 缓存:'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload
|
|
2624
|
+
// 策略,平时不查盘。放在扫盘与 reload 之前,本轮上下线的 agent 才能读到磁盘上的
|
|
2625
|
+
// 最新版本——放在末尾的话,这一轮全用旧值,改动要等下一次 resync 才生效。
|
|
2626
|
+
invalidateKitCache();
|
|
2539
2627
|
const { loadAllAgents: scanAgents, loadDefaults: readDefaults } = await import('./config-store.js');
|
|
2540
2628
|
const { resolveEffective } = await import('./config/config-manager.js');
|
|
2541
2629
|
const freshDefaults = readDefaults();
|
|
@@ -2557,6 +2645,7 @@ async function main() {
|
|
|
2557
2645
|
}
|
|
2558
2646
|
await triggerSchedulers.get(aid)?.stop();
|
|
2559
2647
|
triggerSchedulers.delete(aid);
|
|
2648
|
+
triggerSchedulerStarts.delete(aid);
|
|
2560
2649
|
// 断开所有 channels
|
|
2561
2650
|
for (const chName of agent.channelInstanceNames()) {
|
|
2562
2651
|
const inst = channelInstances.find(i => i.adapter.channelName === chName);
|
|
@@ -2609,8 +2698,7 @@ async function main() {
|
|
|
2609
2698
|
results.push(`⚠ ${cfg.aid}: ${e?.message || e}`);
|
|
2610
2699
|
}
|
|
2611
2700
|
}
|
|
2612
|
-
// 重建 channel index
|
|
2613
|
-
invalidateKitCache();
|
|
2701
|
+
// 重建 channel index(kit 缓存已在本轮开头清过)
|
|
2614
2702
|
agentRegistry.channelIndex.clear();
|
|
2615
2703
|
agentRegistry.buildChannelIndex();
|
|
2616
2704
|
logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
|
|
@@ -2682,8 +2770,21 @@ async function main() {
|
|
|
2682
2770
|
return agentAid;
|
|
2683
2771
|
};
|
|
2684
2772
|
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
2685
|
-
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.controlToken);
|
|
2686
|
-
|
|
2773
|
+
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
2774
|
+
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
2775
|
+
if (!isCrossAgentTriggerOperationAllowed({
|
|
2776
|
+
control: actor.control,
|
|
2777
|
+
taskAgentAid: actor.selfAid,
|
|
2778
|
+
targetAgentAid: agentAid,
|
|
2779
|
+
targetManagement: actor.management,
|
|
2780
|
+
operation,
|
|
2781
|
+
})) {
|
|
2782
|
+
if (isCrossAgent && !actor.management) {
|
|
2783
|
+
throw new Error('cross-agent trigger audit requires owner/admin access on the target agent');
|
|
2784
|
+
}
|
|
2785
|
+
if (isCrossAgent && !isCrossAgentTriggerReadOperation(operation)) {
|
|
2786
|
+
throw new Error('cross-agent trigger operations are limited to list, show, and history');
|
|
2787
|
+
}
|
|
2687
2788
|
throw new Error('trigger agent does not match authenticated task agent');
|
|
2688
2789
|
}
|
|
2689
2790
|
const operationDecision = authorizeOperation({
|
|
@@ -2783,7 +2884,7 @@ async function main() {
|
|
|
2783
2884
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
2784
2885
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
2785
2886
|
}
|
|
2786
|
-
const definition = applyTriggerPatch(existing, cmd.patch);
|
|
2887
|
+
const definition = applyTriggerPatch(existing, cmd.patch, { fromPromptFile: cmd.promptFile === true });
|
|
2787
2888
|
validateTriggerDefinitionForActor(definition, actor);
|
|
2788
2889
|
validateTriggerFeedbackChannels(definition);
|
|
2789
2890
|
const scheduler = schedulerFor(agentAid);
|
|
@@ -2853,7 +2954,11 @@ async function main() {
|
|
|
2853
2954
|
// 显式触发更可控。
|
|
2854
2955
|
// 优雅关闭
|
|
2855
2956
|
let shutdownSignal = 'unknown';
|
|
2957
|
+
let shutdownInProgress = false;
|
|
2856
2958
|
const shutdown = async (signal) => {
|
|
2959
|
+
if (shutdownInProgress)
|
|
2960
|
+
return;
|
|
2961
|
+
shutdownInProgress = true;
|
|
2857
2962
|
if (signal)
|
|
2858
2963
|
shutdownSignal = signal;
|
|
2859
2964
|
const pid = process.pid;
|
|
@@ -2862,30 +2967,60 @@ async function main() {
|
|
|
2862
2967
|
ipcServer.stopCpuTracking();
|
|
2863
2968
|
ipcServer.stop();
|
|
2864
2969
|
bindService?.stopCleanup();
|
|
2865
|
-
await
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2970
|
+
// 必须在第一个 await 之前同步冻结 active batch、发布 task:interrupted,
|
|
2971
|
+
// 否则 handler continuation 可能先提交完成并清掉待恢复的主消息。
|
|
2972
|
+
const shutdownStep = async (label, operation) => {
|
|
2973
|
+
try {
|
|
2974
|
+
await operation();
|
|
2975
|
+
}
|
|
2976
|
+
catch (error) {
|
|
2977
|
+
logger.error(`[Shutdown] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
try {
|
|
2981
|
+
const queueShutdown = messageQueue.shutdownGracefully();
|
|
2982
|
+
await shutdownStep('cancel pending permissions', () => permissionGateway.cancelAllPending('daemon_restart'));
|
|
2983
|
+
await shutdownStep('wait for trigger schedulers', async () => {
|
|
2984
|
+
await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.waitForIdle(5_000)));
|
|
2985
|
+
});
|
|
2986
|
+
await shutdownStep('suspend trigger schedulers', async () => {
|
|
2987
|
+
await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.suspendForDaemonExit()));
|
|
2988
|
+
});
|
|
2989
|
+
await shutdownStep('interrupt active message queue tasks', async () => { await queueShutdown; });
|
|
2990
|
+
eventBus.publish({
|
|
2991
|
+
type: 'system:shutdown',
|
|
2992
|
+
timestamp: Date.now()
|
|
2993
|
+
});
|
|
2994
|
+
// 断开插件系统的渠道
|
|
2995
|
+
await shutdownStep('disconnect channels', () => channelLoader.disconnectAll(channelInstances));
|
|
2996
|
+
for (const inst of channelInstances) {
|
|
2997
|
+
const type = inst.channelType || inst.adapter.channelName;
|
|
2998
|
+
eventBus.publish({ type: 'channel:disconnected', channel: type, channelName: inst.adapter.channelName, reason: 'shutdown' });
|
|
2999
|
+
}
|
|
3000
|
+
// 断开控制 AID(daemon 进程身份)
|
|
3001
|
+
if (controlChannel) {
|
|
3002
|
+
await shutdownStep('disconnect control AID', () => controlChannel.disconnect());
|
|
3003
|
+
}
|
|
2877
3004
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
3005
|
+
catch (error) {
|
|
3006
|
+
logger.error(`[Shutdown] Unexpected shutdown failure: ${error instanceof Error ? error.stack || error.message : String(error)}`);
|
|
3007
|
+
}
|
|
3008
|
+
finally {
|
|
2880
3009
|
try {
|
|
2881
|
-
|
|
3010
|
+
sessionManager.close();
|
|
2882
3011
|
}
|
|
2883
|
-
catch {
|
|
3012
|
+
catch (error) {
|
|
3013
|
+
logger.error(`[Shutdown] session manager close failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3014
|
+
}
|
|
3015
|
+
try {
|
|
3016
|
+
removeAll();
|
|
3017
|
+
}
|
|
3018
|
+
catch (error) {
|
|
3019
|
+
logger.error(`[Shutdown] instance cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3020
|
+
}
|
|
3021
|
+
logger.info('✓ Shutdown complete');
|
|
3022
|
+
process.exit(0);
|
|
2884
3023
|
}
|
|
2885
|
-
sessionManager.close();
|
|
2886
|
-
removeAll();
|
|
2887
|
-
logger.info('✓ Shutdown complete');
|
|
2888
|
-
process.exit(0);
|
|
2889
3024
|
};
|
|
2890
3025
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
2891
3026
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|