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