evolcore 0.0.9 → 0.0.11
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 +29 -0
- package/README.md +3 -3
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +123 -42
- package/dist/agents/codex-app-server-client.js +33 -9
- package/dist/agents/codex-runner.js +58 -8
- package/dist/agents/ecagent-runner.js +17 -2
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +131 -128
- package/dist/cli/agent-command.js +16 -9
- package/dist/cli/agent.js +82 -19
- package/dist/cli/daemon-commands.js +21 -2
- package/dist/cli/index.js +76 -61
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/gateway-config.js +26 -10
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/operation-authorizer.js +32 -147
- package/dist/core/auth/operation-catalog.js +80 -0
- package/dist/core/bootstrap-messages.js +50 -0
- package/dist/core/bootstrap-service.js +85 -10
- package/dist/core/channel-loader.js +23 -6
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/menu-handler.js +67 -76
- package/dist/core/command/slash-handler.js +4 -4
- package/dist/core/data-migration.js +79 -27
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +8 -3
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/message-bridge.js +1 -1
- package/dist/core/message/message-log.js +22 -0
- package/dist/core/message/message-queue.js +19 -4
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +231 -93
- package/dist/core/protected-paths.js +10 -7
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/index.js +262 -84
- package/dist/ipc.js +29 -11
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/rules/01-overview.md +9 -0
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/templates/roles/admin.json +5 -0
- package/kits/templates/roles/member.json +17 -0
- package/kits/templates/roles/visitor.json +8 -0
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +2 -1
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-f
|
|
|
14
14
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
15
15
|
import { EcagentSessionFileAdapter } from './core/session/adapters/ecagent-session-file-adapter.js';
|
|
16
16
|
import { loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig } from './config-store.js';
|
|
17
|
-
import { ConfigTarget, initConfigManager, onConfigWrite } from './config/config-manager.js';
|
|
17
|
+
import { ConfigTarget, initConfigManager, onConfigWrite, read as readConfig } from './config/config-manager.js';
|
|
18
18
|
import { ensureRoleConfigV4OnStartup } from './config/role-config-v4-startup.js';
|
|
19
19
|
import { ensureRoleConfigV5OnStartup } from './config/role-config-v5-startup.js';
|
|
20
20
|
import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
|
|
@@ -50,7 +50,7 @@ import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata } from '
|
|
|
50
50
|
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
51
51
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
52
52
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
53
|
-
import { BootstrapService } from './core/bootstrap-service.js';
|
|
53
|
+
import { BootstrapService, completeBootstrapWithWelcome } from './core/bootstrap-service.js';
|
|
54
54
|
import { MessageCache } from './core/message/message-cache.js';
|
|
55
55
|
import { CommandHandler, isProcessLevelOwner } from './core/command/command-handler.js';
|
|
56
56
|
import { EventBus } from './core/event-bus.js';
|
|
@@ -66,6 +66,8 @@ import { isHClassPath } from './core/protected-paths.js';
|
|
|
66
66
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
67
67
|
import { AgentLoader } from './core/baseagent-loader.js';
|
|
68
68
|
import { EvolAgentRegistry } from './core/evolagent-registry.js';
|
|
69
|
+
import { createRunnerReloadTransaction, disposeAgentInstances } from './core/runner-reload-transaction.js';
|
|
70
|
+
import { AgentReloadBusyError, AgentReloadCoordinator } from './core/agent-reload-coordinator.js';
|
|
69
71
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
70
72
|
import { IpcServer } from './ipc.js';
|
|
71
73
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
@@ -974,6 +976,7 @@ async function main() {
|
|
|
974
976
|
}
|
|
975
977
|
const channelInstances = evolagentInstances;
|
|
976
978
|
logger.info(`✓ Created ${channelInstances.length} channel instance(s)`);
|
|
979
|
+
let bootstrapService;
|
|
977
980
|
const contactRequestExpiryTimer = setInterval(() => {
|
|
978
981
|
for (const agent of agentRegistry.runnableAgents()) {
|
|
979
982
|
void expirePendingContactRequests(agent.aid).catch(error => {
|
|
@@ -988,6 +991,35 @@ async function main() {
|
|
|
988
991
|
getAvailableBaseagents: detectAvailableBaseagentsForBind,
|
|
989
992
|
getUptimeSeconds: () => Math.floor(process.uptime()),
|
|
990
993
|
onDaemonOwnersUpdated: (owners) => { processLevelOwners = owners; },
|
|
994
|
+
onAgentOwnerUpdated: (agentAid, ownerAid) => {
|
|
995
|
+
const service = bootstrapService;
|
|
996
|
+
if (!service)
|
|
997
|
+
return;
|
|
998
|
+
const instance = channelInstances.find(candidate => {
|
|
999
|
+
const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
|
|
1000
|
+
?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
|
|
1001
|
+
return owner?.aid === agentAid;
|
|
1002
|
+
});
|
|
1003
|
+
if (!instance)
|
|
1004
|
+
return;
|
|
1005
|
+
eventBus.publish({
|
|
1006
|
+
type: 'channel:owner-bound',
|
|
1007
|
+
channel: instance.channelType || instance.adapter.channelName,
|
|
1008
|
+
channelName: instance.adapter.channelName,
|
|
1009
|
+
userId: ownerAid,
|
|
1010
|
+
});
|
|
1011
|
+
void service.tryStartBootstrap({
|
|
1012
|
+
adapter: instance.adapter,
|
|
1013
|
+
channelKey: instance.adapter.channelKey,
|
|
1014
|
+
channelType: instance.channelType,
|
|
1015
|
+
agentAid,
|
|
1016
|
+
recipientId: ownerAid,
|
|
1017
|
+
channelId: instance.channelType === 'aun' ? ownerAid : undefined,
|
|
1018
|
+
source: 'owner-bound',
|
|
1019
|
+
}).catch((error) => {
|
|
1020
|
+
logger.warn(`[Bootstrap] Owner-bound start failed for ${agentAid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1021
|
+
});
|
|
1022
|
+
},
|
|
991
1023
|
})
|
|
992
1024
|
: null;
|
|
993
1025
|
bindService?.startCleanup();
|
|
@@ -1541,7 +1573,7 @@ async function main() {
|
|
|
1541
1573
|
msgBridge.setInteractionRouter(interactionRouter);
|
|
1542
1574
|
msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
|
|
1543
1575
|
msgBridge.setAidStatsCollector(aidStatsCollector);
|
|
1544
|
-
|
|
1576
|
+
bootstrapService = new BootstrapService(agentRegistry, eventBus);
|
|
1545
1577
|
msgBridge.setBootstrapService(bootstrapService);
|
|
1546
1578
|
msgBridge.setHandoffRuntime(handoffRuntime);
|
|
1547
1579
|
// ── Channel instance registration (shared by startup and hot-load) ──
|
|
@@ -2312,6 +2344,17 @@ async function main() {
|
|
|
2312
2344
|
function configureIpc() {
|
|
2313
2345
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2314
2346
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2347
|
+
ipcServer.setBootstrapCompleteExecutor(async (aid) => {
|
|
2348
|
+
const aunInstance = channelInstances.find(candidate => {
|
|
2349
|
+
if (candidate.channelType !== 'aun')
|
|
2350
|
+
return false;
|
|
2351
|
+
const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
|
|
2352
|
+
?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
|
|
2353
|
+
return owner?.aid === aid;
|
|
2354
|
+
});
|
|
2355
|
+
const welcome = aunInstance?.channel;
|
|
2356
|
+
return completeBootstrapWithWelcome(bootstrapService, aid, welcome);
|
|
2357
|
+
});
|
|
2315
2358
|
ipcServer.setDingtalkContactBindExecutor({
|
|
2316
2359
|
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
2317
2360
|
isChannelReady: isContactBindChannelReady,
|
|
@@ -2600,16 +2643,17 @@ async function main() {
|
|
|
2600
2643
|
});
|
|
2601
2644
|
});
|
|
2602
2645
|
// ── Reload hooks: enable agentRegistry.reload() to drain/disconnect/restart channels ──
|
|
2646
|
+
const unregisterRuntimeChannel = (channelName) => {
|
|
2647
|
+
markChannelDisconnected(channelName);
|
|
2648
|
+
processor.unregisterChannel(channelName);
|
|
2649
|
+
cmdHandler.unregisterChannel(channelName);
|
|
2650
|
+
msgBridge.removeChannel(channelName);
|
|
2651
|
+
};
|
|
2603
2652
|
const reloadHooks = buildReloadHooks({
|
|
2604
2653
|
channelLoader,
|
|
2605
2654
|
channelInstances,
|
|
2606
2655
|
registerChannelInstance,
|
|
2607
|
-
unregisterChannelInstance:
|
|
2608
|
-
markChannelDisconnected(channelName);
|
|
2609
|
-
processor.unregisterChannel(channelName);
|
|
2610
|
-
cmdHandler.unregisterChannel(channelName);
|
|
2611
|
-
msgBridge.removeChannel(channelName);
|
|
2612
|
-
},
|
|
2656
|
+
unregisterChannelInstance: unregisterRuntimeChannel,
|
|
2613
2657
|
onChannelStarted: (inst) => {
|
|
2614
2658
|
// startChannel 重建渠道时重新注入 AidStatsCollector(与 hot-load 路径对齐)
|
|
2615
2659
|
if (inst.channelType === 'aun') {
|
|
@@ -2619,9 +2663,112 @@ async function main() {
|
|
|
2619
2663
|
}
|
|
2620
2664
|
},
|
|
2621
2665
|
onChannelConnected: markChannelConnected,
|
|
2666
|
+
onAgentReloaded: async (agent) => {
|
|
2667
|
+
const instance = channelInstances.find(candidate => {
|
|
2668
|
+
const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
|
|
2669
|
+
?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
|
|
2670
|
+
return owner?.aid === agent.aid;
|
|
2671
|
+
});
|
|
2672
|
+
if (!instance)
|
|
2673
|
+
return;
|
|
2674
|
+
await bootstrapService.tryStartBootstrap({
|
|
2675
|
+
adapter: instance.adapter,
|
|
2676
|
+
channelKey: instance.adapter.channelKey,
|
|
2677
|
+
channelType: instance.channelType,
|
|
2678
|
+
agentAid: agent.aid,
|
|
2679
|
+
source: 'owner-bound',
|
|
2680
|
+
});
|
|
2681
|
+
},
|
|
2622
2682
|
messageQueue,
|
|
2623
2683
|
handoffRuntime,
|
|
2684
|
+
stageAgentRunners: async (candidate) => {
|
|
2685
|
+
const creationErrors = [];
|
|
2686
|
+
const staged = candidate.config.enabled === false
|
|
2687
|
+
? []
|
|
2688
|
+
: agentLoader.createForAgent(candidate, {
|
|
2689
|
+
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
2690
|
+
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
2691
|
+
},
|
|
2692
|
+
}, creationErrors);
|
|
2693
|
+
const onDisposeError = (instance, error) => {
|
|
2694
|
+
logger.warn(`[Reload] Failed to dispose runner ${instance.evolagentName}::${instance.baseagent}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2695
|
+
};
|
|
2696
|
+
if (creationErrors.length > 0) {
|
|
2697
|
+
await disposeAgentInstances(staged, onDisposeError);
|
|
2698
|
+
throw new Error(creationErrors.map(error => `${error.baseagent}: ${error.message}`).join('; '));
|
|
2699
|
+
}
|
|
2700
|
+
if (candidate.config.enabled !== false && staged.length === 0 && candidate.config.active_baseagent) {
|
|
2701
|
+
throw new Error(`No runner could be created for ${candidate.aid}::${candidate.config.active_baseagent}`);
|
|
2702
|
+
}
|
|
2703
|
+
for (const instance of staged) {
|
|
2704
|
+
instance.agent.setPermissionGateway?.(permissionGateway);
|
|
2705
|
+
instance.agent.setCompactStartCallback?.((sessionId) => {
|
|
2706
|
+
processor.handleCompactStart(sessionId);
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
return createRunnerReloadTransaction({
|
|
2710
|
+
aid: candidate.aid,
|
|
2711
|
+
agentMap,
|
|
2712
|
+
staged,
|
|
2713
|
+
onDisposeError,
|
|
2714
|
+
});
|
|
2715
|
+
},
|
|
2624
2716
|
});
|
|
2717
|
+
const reloadOwnedMutes = new Set();
|
|
2718
|
+
const reloadCoordinator = new AgentReloadCoordinator({
|
|
2719
|
+
registry: agentRegistry,
|
|
2720
|
+
hooks: reloadHooks,
|
|
2721
|
+
getBusyCount: (aid) => {
|
|
2722
|
+
const agentName = agentRegistry.get(aid)?.name;
|
|
2723
|
+
if (!agentName)
|
|
2724
|
+
return 0;
|
|
2725
|
+
return messageQueue.getProcessingCountByAgent(agentName)
|
|
2726
|
+
+ messageQueue.getQueueLengthByAgent(agentName);
|
|
2727
|
+
},
|
|
2728
|
+
beforeReload: invalidateKitCache,
|
|
2729
|
+
forceReload: async (aid) => {
|
|
2730
|
+
const agentName = agentRegistry.get(aid)?.name;
|
|
2731
|
+
if (!agentName)
|
|
2732
|
+
return;
|
|
2733
|
+
messageQueue.clearByAgent(agentName);
|
|
2734
|
+
await messageQueue.interruptByAgentAndWait(agentName);
|
|
2735
|
+
const deadline = Date.now() + 5_000;
|
|
2736
|
+
while (messageQueue.getProcessingCountByAgent(agentName) > 0) {
|
|
2737
|
+
if (Date.now() >= deadline) {
|
|
2738
|
+
throw new Error(`Timed out waiting for Agent "${aid}" tasks to stop`);
|
|
2739
|
+
}
|
|
2740
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
acquireWorkGate: (aid) => {
|
|
2744
|
+
const agentName = agentRegistry.get(aid)?.name;
|
|
2745
|
+
if (!agentName)
|
|
2746
|
+
return;
|
|
2747
|
+
const inheritedReloadMute = reloadOwnedMutes.delete(agentName);
|
|
2748
|
+
const alreadyMuted = messageQueue.isAgentMuted(agentName);
|
|
2749
|
+
const ownsMute = inheritedReloadMute || !alreadyMuted;
|
|
2750
|
+
if (!alreadyMuted)
|
|
2751
|
+
messageQueue.muteAgent(agentName);
|
|
2752
|
+
return () => {
|
|
2753
|
+
const current = agentRegistry.get(aid);
|
|
2754
|
+
if (!current) {
|
|
2755
|
+
messageQueue.clearByAgent(agentName);
|
|
2756
|
+
if (ownsMute)
|
|
2757
|
+
messageQueue.unmuteAgent(agentName);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (current.status === 'disabled') {
|
|
2761
|
+
messageQueue.clearByAgent(agentName);
|
|
2762
|
+
if (ownsMute)
|
|
2763
|
+
reloadOwnedMutes.add(agentName);
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
if (ownsMute)
|
|
2767
|
+
messageQueue.unmuteAgent(agentName);
|
|
2768
|
+
};
|
|
2769
|
+
},
|
|
2770
|
+
});
|
|
2771
|
+
ipcServer.setAgentReloadExecutor((aid, options) => reloadCoordinator.reload(aid, options));
|
|
2625
2772
|
// Make reload hooks accessible to IPC handler & ctl handler (both run in this process)
|
|
2626
2773
|
globalThis.__evolcore_reloadHooks = reloadHooks;
|
|
2627
2774
|
// Hot-load handler: dynamically add a new agent at runtime
|
|
@@ -2691,90 +2838,121 @@ async function main() {
|
|
|
2691
2838
|
}
|
|
2692
2839
|
};
|
|
2693
2840
|
// Full resync handler: scan disk, load new agents, unload removed/disabled, reload changed
|
|
2841
|
+
let resyncInFlight;
|
|
2694
2842
|
globalThis.__evolcore_resyncAgents = async () => {
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2843
|
+
if (resyncInFlight)
|
|
2844
|
+
return resyncInFlight;
|
|
2845
|
+
const operation = (async () => {
|
|
2846
|
+
// 先清 kit 缓存:'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload
|
|
2847
|
+
// 策略,平时不查盘。放在扫盘与 reload 之前,本轮上下线的 agent 才能读到磁盘上的
|
|
2848
|
+
// 最新版本——放在末尾的话,这一轮全用旧值,改动要等下一次 resync 才生效。
|
|
2849
|
+
invalidateKitCache();
|
|
2850
|
+
const { loadAllAgents: scanAgents } = await import('./config-store.js');
|
|
2851
|
+
const { agents: diskAgents } = scanAgents();
|
|
2852
|
+
const results = [];
|
|
2853
|
+
// 1. 下线:运行时有但磁盘上没有 / disabled 的
|
|
2854
|
+
for (const [aid] of [...agentRegistry.agents.entries()]) {
|
|
2855
|
+
const diskCfg = diskAgents.find(a => a.aid === aid);
|
|
2856
|
+
if (!diskCfg || diskCfg.enabled === false) {
|
|
2857
|
+
try {
|
|
2858
|
+
const offlined = await reloadCoordinator.runExclusive(aid, async () => {
|
|
2859
|
+
const latestDiskCfg = readConfig(ConfigTarget.Agent, { self: aid }, { cache: false, expand: true });
|
|
2860
|
+
if (latestDiskCfg && latestDiskCfg.enabled !== false)
|
|
2861
|
+
return false;
|
|
2862
|
+
const runtimeAgent = agentRegistry.get(aid);
|
|
2863
|
+
if (!runtimeAgent)
|
|
2864
|
+
return false;
|
|
2865
|
+
let removed = false;
|
|
2866
|
+
handoffRuntime.pauseAgent(aid);
|
|
2867
|
+
try {
|
|
2868
|
+
await handoffRuntime.drainAgent(aid);
|
|
2869
|
+
await triggerSchedulers.get(aid)?.stop();
|
|
2870
|
+
triggerSchedulers.delete(aid);
|
|
2871
|
+
triggerSchedulerStarts.delete(aid);
|
|
2872
|
+
// 断开所有 channels
|
|
2873
|
+
for (const chName of runtimeAgent.channelInstanceNames()) {
|
|
2874
|
+
const inst = channelInstances.find(i => i.adapter.channelName === chName);
|
|
2875
|
+
if (inst) {
|
|
2876
|
+
try {
|
|
2877
|
+
await inst.disconnect();
|
|
2878
|
+
}
|
|
2879
|
+
catch { }
|
|
2880
|
+
const idx = channelInstances.indexOf(inst);
|
|
2881
|
+
if (idx >= 0)
|
|
2882
|
+
channelInstances.splice(idx, 1);
|
|
2883
|
+
}
|
|
2884
|
+
unregisterRuntimeChannel(chName);
|
|
2885
|
+
}
|
|
2886
|
+
agentRegistry.agents.delete(aid);
|
|
2887
|
+
messageQueue.clearByAgent(runtimeAgent.name);
|
|
2888
|
+
removed = true;
|
|
2889
|
+
return true;
|
|
2890
|
+
}
|
|
2891
|
+
finally {
|
|
2892
|
+
if (!removed)
|
|
2893
|
+
handoffRuntime.resumeAgent(aid);
|
|
2894
|
+
}
|
|
2895
|
+
});
|
|
2896
|
+
if (offlined)
|
|
2897
|
+
results.push(`- ${aid} (offline)`);
|
|
2898
|
+
}
|
|
2899
|
+
catch (error) {
|
|
2900
|
+
if (error instanceof AgentReloadBusyError) {
|
|
2901
|
+
results.push(`⏸ ${aid} (busy: ${error.busyCount} task(s); reload skipped)`);
|
|
2902
|
+
}
|
|
2903
|
+
else {
|
|
2904
|
+
results.push(`⚠ ${aid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2727
2905
|
}
|
|
2728
|
-
catch { }
|
|
2729
|
-
markChannelDisconnected(inst.adapter.channelName);
|
|
2730
|
-
const idx = channelInstances.indexOf(inst);
|
|
2731
|
-
if (idx >= 0)
|
|
2732
|
-
channelInstances.splice(idx, 1);
|
|
2733
2906
|
}
|
|
2907
|
+
continue;
|
|
2734
2908
|
}
|
|
2735
|
-
agentRegistry.agents.delete(aid);
|
|
2736
|
-
results.push(`- ${aid} (offline)`);
|
|
2737
|
-
continue;
|
|
2738
2909
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2910
|
+
// 2. 新增:磁盘上有但运行时没有的
|
|
2911
|
+
for (const cfg of diskAgents) {
|
|
2912
|
+
if (cfg.enabled === false)
|
|
2913
|
+
continue;
|
|
2914
|
+
if (agentRegistry.agents.has(cfg.aid))
|
|
2915
|
+
continue;
|
|
2916
|
+
try {
|
|
2917
|
+
await globalThis.__evolcore_hotLoadAgent(cfg.aid);
|
|
2918
|
+
results.push(`+ ${cfg.aid} (online)`);
|
|
2919
|
+
}
|
|
2920
|
+
catch (e) {
|
|
2921
|
+
results.push(`✗ ${cfg.aid}: ${e?.message || e}`);
|
|
2922
|
+
}
|
|
2749
2923
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2924
|
+
// 3. 已有的:重新 reload(config 可能改了)
|
|
2925
|
+
for (const cfg of diskAgents) {
|
|
2926
|
+
if (cfg.enabled === false)
|
|
2927
|
+
continue;
|
|
2928
|
+
if (!agentRegistry.agents.has(cfg.aid))
|
|
2929
|
+
continue;
|
|
2930
|
+
// 只有磁盘上存在且运行时也存在的才 reload
|
|
2931
|
+
try {
|
|
2932
|
+
await reloadCoordinator.reload(cfg.aid);
|
|
2933
|
+
const runtimeAgent = agentRegistry.get(cfg.aid);
|
|
2934
|
+
if (runtimeAgent)
|
|
2935
|
+
await startTriggerScheduler(runtimeAgent);
|
|
2936
|
+
results.push(`↻ ${cfg.aid} (reloaded)`);
|
|
2937
|
+
}
|
|
2938
|
+
catch (e) {
|
|
2939
|
+
results.push(`⚠ ${cfg.aid}: ${e?.message || e}`);
|
|
2940
|
+
}
|
|
2752
2941
|
}
|
|
2942
|
+
// 重建 channel index(kit 缓存已在本轮开头清过)
|
|
2943
|
+
agentRegistry.channelIndex.clear();
|
|
2944
|
+
agentRegistry.buildChannelIndex();
|
|
2945
|
+
logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
|
|
2946
|
+
return results;
|
|
2947
|
+
})();
|
|
2948
|
+
resyncInFlight = operation;
|
|
2949
|
+
try {
|
|
2950
|
+
return await operation;
|
|
2753
2951
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
if (cfg.enabled === false)
|
|
2758
|
-
continue;
|
|
2759
|
-
if (!agentRegistry.agents.has(cfg.aid))
|
|
2760
|
-
continue;
|
|
2761
|
-
// 只有磁盘上存在且运行时也存在的才 reload
|
|
2762
|
-
try {
|
|
2763
|
-
await agentRegistry.reload(cfg.aid, hooks);
|
|
2764
|
-
const runtimeAgent = agentRegistry.get(cfg.aid);
|
|
2765
|
-
if (runtimeAgent)
|
|
2766
|
-
await startTriggerScheduler(runtimeAgent);
|
|
2767
|
-
results.push(`↻ ${cfg.aid} (reloaded)`);
|
|
2768
|
-
}
|
|
2769
|
-
catch (e) {
|
|
2770
|
-
results.push(`⚠ ${cfg.aid}: ${e?.message || e}`);
|
|
2771
|
-
}
|
|
2952
|
+
finally {
|
|
2953
|
+
if (resyncInFlight === operation)
|
|
2954
|
+
resyncInFlight = undefined;
|
|
2772
2955
|
}
|
|
2773
|
-
// 重建 channel index(kit 缓存已在本轮开头清过)
|
|
2774
|
-
agentRegistry.channelIndex.clear();
|
|
2775
|
-
agentRegistry.buildChannelIndex();
|
|
2776
|
-
logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
|
|
2777
|
-
return results;
|
|
2778
2956
|
};
|
|
2779
2957
|
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
2780
2958
|
ipcServer.setAgentStatsProvider(() => agentRegistry.list().map((agent) => {
|
package/dist/ipc.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { logger } from './utils/logger.js';
|
|
6
6
|
import { fileCache } from './core/daemon-file-cache.js';
|
|
7
7
|
import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
|
|
8
|
+
import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
|
|
8
9
|
import { resolvePaths } from './paths.js';
|
|
9
10
|
import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
|
|
10
11
|
import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
|
|
@@ -39,6 +40,8 @@ export class IpcServer {
|
|
|
39
40
|
queueSnapshotProvider;
|
|
40
41
|
queueActionExecutor;
|
|
41
42
|
triggerExecutor;
|
|
43
|
+
bootstrapCompleteExecutor;
|
|
44
|
+
agentReloadExecutor;
|
|
42
45
|
taskRuntimeContextProvider;
|
|
43
46
|
aunMsgSender;
|
|
44
47
|
handoffReturnExecutor;
|
|
@@ -85,6 +88,9 @@ export class IpcServer {
|
|
|
85
88
|
setContactOperationExecutor(executor) {
|
|
86
89
|
this.contactOperationExecutor = executor;
|
|
87
90
|
}
|
|
91
|
+
setAgentReloadExecutor(executor) {
|
|
92
|
+
this.agentReloadExecutor = executor;
|
|
93
|
+
}
|
|
88
94
|
/** Inject AUN AID state aggregator for aun-aids IPC handler */
|
|
89
95
|
setAunAidProvider(provider) {
|
|
90
96
|
this.aunAidProvider = provider;
|
|
@@ -117,6 +123,10 @@ export class IpcServer {
|
|
|
117
123
|
setTriggerExecutor(executor) {
|
|
118
124
|
this.triggerExecutor = executor;
|
|
119
125
|
}
|
|
126
|
+
/** Complete an agent bootstrap in the daemon so disk and runtime state change together. */
|
|
127
|
+
setBootstrapCompleteExecutor(executor) {
|
|
128
|
+
this.bootstrapCompleteExecutor = executor;
|
|
129
|
+
}
|
|
120
130
|
/** Inject active task runtime-context provider for in-task CLI routing. */
|
|
121
131
|
setTaskRuntimeContextProvider(provider) {
|
|
122
132
|
this.taskRuntimeContextProvider = provider;
|
|
@@ -325,7 +335,6 @@ export class IpcServer {
|
|
|
325
335
|
bindType: cmd.bindType,
|
|
326
336
|
targetAid: cmd.targetAid,
|
|
327
337
|
agentName: cmd.agentName,
|
|
328
|
-
ownerMode: cmd.ownerMode,
|
|
329
338
|
ttlMs: cmd.ttlMs,
|
|
330
339
|
});
|
|
331
340
|
}
|
|
@@ -668,29 +677,38 @@ export class IpcServer {
|
|
|
668
677
|
return { ok: false, error: `Agent "${name}" found but info missing (race?)` };
|
|
669
678
|
return { ok: true, agent: info };
|
|
670
679
|
}
|
|
680
|
+
case 'evolagent.bootstrapComplete': {
|
|
681
|
+
const aid = cmd.aid;
|
|
682
|
+
if (!aid || typeof aid !== 'string')
|
|
683
|
+
return { ok: false, error: 'missing aid' };
|
|
684
|
+
if (!this.bootstrapCompleteExecutor)
|
|
685
|
+
return { ok: false, error: 'bootstrap complete executor not configured' };
|
|
686
|
+
try {
|
|
687
|
+
return await this.bootstrapCompleteExecutor(aid);
|
|
688
|
+
}
|
|
689
|
+
catch (e) {
|
|
690
|
+
return { ok: false, error: e?.message || String(e) };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
671
693
|
case 'evolagent.reload': {
|
|
672
694
|
if (!this.agentRegistry)
|
|
673
695
|
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
674
696
|
const name = cmd.name;
|
|
675
697
|
if (!name || typeof name !== 'string')
|
|
676
698
|
return { ok: false, error: 'missing name' };
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return { ok: false, error: 'Reload hooks not initialized' };
|
|
699
|
+
if (!this.agentReloadExecutor)
|
|
700
|
+
return { ok: false, error: 'Agent reload coordinator not initialized' };
|
|
680
701
|
try {
|
|
681
702
|
const a = this.agentRegistry.get(name);
|
|
682
703
|
if (!a)
|
|
683
704
|
return { ok: false, error: `Agent "${name}" not found` };
|
|
684
|
-
|
|
685
|
-
return { ok: false, error: 'EvolAgentRegistry.reload not available' };
|
|
686
|
-
// 'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload 策略,平时不查盘:
|
|
687
|
-
// reload 是它唯一的刷新时机,必须先清再 reload,否则重载出来的 agent 仍用旧模板。
|
|
688
|
-
const { invalidateKitCache } = await import('./eck/kit-renderer.js');
|
|
689
|
-
invalidateKitCache();
|
|
690
|
-
await this.agentRegistry.reload(name, hooks);
|
|
705
|
+
await this.agentReloadExecutor(name, { force: cmd.force === true });
|
|
691
706
|
return { ok: true, result: `Agent "${name}" reloaded` };
|
|
692
707
|
}
|
|
693
708
|
catch (e) {
|
|
709
|
+
if (e instanceof AgentReloadBusyError) {
|
|
710
|
+
return { ok: false, code: e.code, busyCount: e.busyCount, error: e.message };
|
|
711
|
+
}
|
|
694
712
|
return { ok: false, error: e?.message || String(e) };
|
|
695
713
|
}
|
|
696
714
|
}
|
package/dist/utils/aid-bind.js
CHANGED
|
@@ -45,7 +45,6 @@ export class BindService {
|
|
|
45
45
|
}
|
|
46
46
|
const ttlMs = clampTtl(req.ttlMs);
|
|
47
47
|
const expiresAt = Date.now() + ttlMs;
|
|
48
|
-
const ownerMode = req.ownerMode === 'replace' ? 'replace' : 'append';
|
|
49
48
|
const task = {
|
|
50
49
|
taskId,
|
|
51
50
|
bindType: req.bindType,
|
|
@@ -56,7 +55,6 @@ export class BindService {
|
|
|
56
55
|
createdAt: Date.now(),
|
|
57
56
|
expiresAt,
|
|
58
57
|
status: 'pending',
|
|
59
|
-
ownerMode,
|
|
60
58
|
};
|
|
61
59
|
this.tasks.set(taskId, task);
|
|
62
60
|
this.taskIdsByTokenHash.set(tokenHash, taskId);
|
|
@@ -209,9 +207,7 @@ export class BindService {
|
|
|
209
207
|
if (task.bindType === 'daemon') {
|
|
210
208
|
const cfg = loadDaemonConfig();
|
|
211
209
|
const current = cfg.owners ?? [];
|
|
212
|
-
const owners =
|
|
213
|
-
? [ownerAid]
|
|
214
|
-
: [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
210
|
+
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
215
211
|
saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
|
|
216
212
|
this.opts.onDaemonOwnersUpdated?.(owners);
|
|
217
213
|
return;
|
|
@@ -220,10 +216,9 @@ export class BindService {
|
|
|
220
216
|
if (!agent)
|
|
221
217
|
throw new Error(`agent not found: ${task.targetAid}`);
|
|
222
218
|
const current = agent.owners ?? [];
|
|
223
|
-
const owners =
|
|
224
|
-
? [ownerAid]
|
|
225
|
-
: [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
219
|
+
const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
|
|
226
220
|
saveAgent({ ...agent, owners });
|
|
221
|
+
this.opts.onAgentOwnerUpdated?.(task.targetAid, ownerAid);
|
|
227
222
|
}
|
|
228
223
|
cleanup() {
|
|
229
224
|
const now = Date.now();
|
package/dist/utils/log-writer.js
CHANGED
|
@@ -231,19 +231,14 @@ export class LogWriter {
|
|
|
231
231
|
return `${day}-${pad(d.getHours())}`;
|
|
232
232
|
}
|
|
233
233
|
cleanupOldArchives() {
|
|
234
|
-
LogWriter.cleanupArchivesIn(this.logDir, this.retentionMs);
|
|
234
|
+
LogWriter.cleanupArchivesIn(this.logDir, this.baseName, this.retentionMs);
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
|
-
*
|
|
237
|
+
* 清理当前日志类型的归档文件,不能跨 baseName 清理。
|
|
238
238
|
*
|
|
239
239
|
* 命名约定:`<baseName>-YYYYMMDD-HH.log`(hourly)或 `<baseName>-YYYYMMDD.log`(daily),
|
|
240
|
-
* 其中 baseName 由字母/数字/连字符组成。
|
|
241
|
-
*
|
|
242
|
-
* 这条规则跨 baseName 统一——只要文件按这个 pattern 命名就认为受 LogWriter 体系管辖。
|
|
243
|
-
* 这样 conditional 启用的 LogWriter(如 aun trace 关闭时)不会留下永久无人清的归档:
|
|
244
|
-
* 任意 LogWriter 实例化都会顺便清掉它们。
|
|
245
240
|
*/
|
|
246
|
-
static cleanupArchivesIn(logDir, retentionMs) {
|
|
241
|
+
static cleanupArchivesIn(logDir, baseName, retentionMs) {
|
|
247
242
|
if (retentionMs <= 0)
|
|
248
243
|
return;
|
|
249
244
|
const cutoff = Date.now() - retentionMs;
|
|
@@ -254,9 +249,10 @@ export class LogWriter {
|
|
|
254
249
|
catch {
|
|
255
250
|
return;
|
|
256
251
|
}
|
|
257
|
-
const
|
|
252
|
+
const escapedBaseName = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
253
|
+
const archivePattern = new RegExp(`^${escapedBaseName}-\\d{8}(?:-\\d{2})?\\.log$`);
|
|
258
254
|
for (const name of entries) {
|
|
259
|
-
if (!
|
|
255
|
+
if (!archivePattern.test(name))
|
|
260
256
|
continue;
|
|
261
257
|
const full = path.join(logDir, name);
|
|
262
258
|
try {
|
package/dist/utils/logger.js
CHANGED
|
@@ -24,15 +24,15 @@ function getWriters() {
|
|
|
24
24
|
closeWriters(writers);
|
|
25
25
|
writers = {
|
|
26
26
|
logDir,
|
|
27
|
-
main: new LogWriter({ baseName: 'daemon', logDir, rotation: 'hourly', retention: { hours:
|
|
27
|
+
main: new LogWriter({ baseName: 'daemon', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
28
28
|
message: config.messageLog
|
|
29
|
-
? new LogWriter({ baseName: 'messages', logDir, rotation: 'hourly', retention: { hours:
|
|
29
|
+
? new LogWriter({ baseName: 'messages', logDir, rotation: 'hourly', retention: { hours: 48 } })
|
|
30
30
|
: null,
|
|
31
31
|
event: config.eventLog
|
|
32
|
-
? new LogWriter({ baseName: 'events', logDir, rotation: 'hourly', retention: { hours:
|
|
32
|
+
? new LogWriter({ baseName: 'events', logDir, rotation: 'hourly', retention: { hours: 48 } })
|
|
33
33
|
: null,
|
|
34
|
-
channelIn: new LogWriter({ baseName: 'channel-in', logDir, rotation: 'hourly', retention: { hours:
|
|
35
|
-
channelOut: new LogWriter({ baseName: 'channel-out', logDir, rotation: 'hourly', retention: { hours:
|
|
34
|
+
channelIn: new LogWriter({ baseName: 'channel-in', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
35
|
+
channelOut: new LogWriter({ baseName: 'channel-out', logDir, rotation: 'hourly', retention: { hours: 48 } }),
|
|
36
36
|
};
|
|
37
37
|
return writers;
|
|
38
38
|
}
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
私聊场景下收发消息的命令集。触发词:回复/发消息/拉取/撤回/查在线。
|
|
4
4
|
|
|
5
|
+
## Agent 会话中的 Shell 调用边界
|
|
6
|
+
|
|
7
|
+
在 EvolCore 托管会话中,每次工具调用只能执行一条完整的 `ec` 命令。禁止把
|
|
8
|
+
`command -v`、`ec aid`、`&&`、`||`、`;`、`|`、重定向、子 shell、`bash -lc` 或其它脚本
|
|
9
|
+
和 `ec msg` 拼在一起。不要做单独的 CLI 探测;直接调用本页的单条命令,读取结构化结果后
|
|
10
|
+
再决定下一步。工具审批被拒绝时应报告原始错误并停止,不要通过追加命令或提权参数重试。
|
|
11
|
+
|
|
5
12
|
以自己的 AID 为发送者(`<from>`),对端 AID 为 `<to>`。
|
|
6
13
|
|
|
7
14
|
## 发送消息
|
|
@@ -29,6 +36,8 @@ ec msg send <from> <to> --link <url> [--title "<title>"] [--description "<desc>"
|
|
|
29
36
|
ec msg send <from> <to> --payload '<json>'
|
|
30
37
|
```
|
|
31
38
|
|
|
39
|
+
每一行都必须单独执行,且是该次 shell 调用中的唯一命令。
|
|
40
|
+
|
|
32
41
|
发送相关选项:
|
|
33
42
|
- `--text-from-file <path>` — 从文件读取文本(UTF-8),用于超长消息或避免 Shell 转义
|
|
34
43
|
- `--encrypt` — 端到端加密
|
|
@@ -39,6 +48,10 @@ ec msg send <from> <to> --payload '<json>'
|
|
|
39
48
|
- `--text <说>` — 附件说明文字(仅 `--file`)
|
|
40
49
|
- `--transcript <text>` — 语音转写(仅 `--as voice`)
|
|
41
50
|
|
|
51
|
+
在 EvolCore 会话内,发送还会经过当前 session 的 self/peer/role 鉴权。`visitor` 或 `member`
|
|
52
|
+
默认只允许回复当前对端;从一个 agent 会话转发给第三个 agent 属于 relay 操作,可能因
|
|
53
|
+
`ownPeerOnly` 被拒绝。没有成功 JSON 回执(含 `message_id`/`status`)不得声称已发送。
|
|
54
|
+
|
|
42
55
|
成功输出:`✓ 已发送 <message_id> seq=<n> status=<status>`
|
|
43
56
|
|
|
44
57
|
> 正文写法:含空格/换行/特殊字符时用引号包起来;纯短词可不包,但多空格会被压成一个。
|