evolcore 0.0.7 → 0.0.8
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 +19 -0
- package/README.md +3 -3
- package/dist/agents/baseagent.js +6 -5
- package/dist/agents/claude-runner.js +1 -0
- package/dist/agents/codex-app-server-client.js +6 -2
- package/dist/agents/codex-runner.js +8 -3
- package/dist/aun/aid/control-aid.js +40 -27
- package/dist/aun/aid/domain.js +23 -0
- package/dist/channels/aun.js +26 -21
- package/dist/cli/bench.js +4 -3
- package/dist/cli/daemon-commands.js +189 -64
- package/dist/cli/data-command.js +62 -35
- package/dist/cli/init-channel.js +1 -1
- package/dist/cli/init.js +9 -13
- package/dist/cli/restart-monitor.js +102 -22
- package/dist/config/config-manager.js +34 -3
- package/dist/config/gateway-config.js +80 -35
- package/dist/config-store.js +15 -3
- package/dist/core/baseagent-loader.js +5 -3
- package/dist/core/capability/providers/codex-capability-provider.js +2 -2
- package/dist/core/channel-loader.js +10 -1
- package/dist/core/command/menu-handler.js +17 -6
- package/dist/core/data-migration.js +517 -24
- package/dist/core/protected-paths.js +12 -1
- package/dist/index.js +752 -700
- package/dist/ipc.js +2 -0
- package/dist/utils/codex-cli.js +39 -0
- package/dist/utils/cross-platform.js +147 -18
- package/dist/utils/instance-registry.js +43 -6
- package/dist/utils/process-introspect.js +7 -3
- package/kits/rules/01-overview.md +3 -2
- package/kits/schemas/_meta.json +2 -1
- package/kits/schemas/daemon.schema.4.json +132 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -88,6 +88,7 @@ import { validateModelSelectionForRole } from './core/model/model-permission.js'
|
|
|
88
88
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
89
89
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
90
90
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
91
|
+
import { MAIN_PACKAGE_NAME } from './product.js';
|
|
91
92
|
import fs from 'fs';
|
|
92
93
|
import crypto from 'crypto';
|
|
93
94
|
import { fileURLToPath } from 'url';
|
|
@@ -277,7 +278,7 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
277
278
|
'fi',
|
|
278
279
|
'',
|
|
279
280
|
'local_version="$(node -e \'try { console.log(require(process.argv[1]).version || "") } catch { process.exit(0) }\' "$PACKAGE_JSON")"',
|
|
280
|
-
|
|
281
|
+
`remote_version="$(npm view ${MAIN_PACKAGE_NAME} version 2>/dev/null || true)"`,
|
|
281
282
|
'',
|
|
282
283
|
'if [ -z "$remote_version" ]; then',
|
|
283
284
|
' json "error" "failed to check evolcore latest version"',
|
|
@@ -367,6 +368,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
367
368
|
agentName: daemonCfg.aid,
|
|
368
369
|
channelName: 'control',
|
|
369
370
|
pureIdentity: true,
|
|
371
|
+
gatewayUrl: daemonCfg.aun?.gatewayUrl,
|
|
370
372
|
aunTrace: daemonCfg.debug?.aunTrace,
|
|
371
373
|
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
372
374
|
});
|
|
@@ -397,7 +399,20 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
397
399
|
channels: {},
|
|
398
400
|
channelsByType: {},
|
|
399
401
|
queue: { pending: 0, processing: 0 },
|
|
400
|
-
controlAid:
|
|
402
|
+
controlAid: (() => {
|
|
403
|
+
const aidState = controlChannel?.getAidState();
|
|
404
|
+
return {
|
|
405
|
+
aid: daemonCfg.aid,
|
|
406
|
+
connected: aidState?.status === 'connected',
|
|
407
|
+
...(aidState ? {
|
|
408
|
+
status: aidState.status,
|
|
409
|
+
gatewayUrl: aidState.gatewayUrl,
|
|
410
|
+
reconnectCount: aidState.reconnectCount,
|
|
411
|
+
lastConnectedAt: aidState.lastConnectedAt,
|
|
412
|
+
lastError: aidState.lastError,
|
|
413
|
+
} : {}),
|
|
414
|
+
};
|
|
415
|
+
})(),
|
|
401
416
|
}));
|
|
402
417
|
ipcServer.setBindExecutor({
|
|
403
418
|
begin: (cmd) => bindService.begin(cmd),
|
|
@@ -859,11 +874,12 @@ async function main() {
|
|
|
859
874
|
agentLoader.register(new CodexAgentPlugin());
|
|
860
875
|
agentLoader.register(new GeminiAgentPlugin());
|
|
861
876
|
agentLoader.register(new EcagentAgentPlugin());
|
|
877
|
+
const creationErrors = [];
|
|
862
878
|
const agentInstances = agentLoader.createAll(agentRegistry, {
|
|
863
879
|
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
864
880
|
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
865
881
|
},
|
|
866
|
-
});
|
|
882
|
+
}, creationErrors);
|
|
867
883
|
// agentMap 复合键:${aid}::${baseagent}
|
|
868
884
|
const agentMap = new Map();
|
|
869
885
|
for (const inst of agentInstances) {
|
|
@@ -871,10 +887,18 @@ async function main() {
|
|
|
871
887
|
}
|
|
872
888
|
const primaryBaseagent = primaryAgent?.baseagent ?? 'claude';
|
|
873
889
|
let primaryRunnerKey = primaryAgent ? `${primaryAgent.aid}::${primaryBaseagent}` : '<empty>::claude';
|
|
874
|
-
const agentRunner =
|
|
890
|
+
const agentRunner = primaryAgent
|
|
891
|
+
? agentMap.get(primaryRunnerKey)
|
|
892
|
+
: agentInstances[0]?.agent;
|
|
875
893
|
if (primaryAgent && !agentRunner) {
|
|
876
894
|
agentRuntimeState = 'error';
|
|
877
|
-
|
|
895
|
+
const primaryCreationErrors = creationErrors.filter(error => error.evolagentName === primaryAgent.name);
|
|
896
|
+
const creationDetail = primaryCreationErrors.length > 0
|
|
897
|
+
? primaryCreationErrors.map(({ baseagent, message }) => `${baseagent}: ${message}`).join('; ')
|
|
898
|
+
: `active_baseagent=${primaryAgent.config.active_baseagent ?? '(unset)'}, configured=${Object.keys(primaryAgent.config.baseagents ?? {}).join(',') || '(none)'}`;
|
|
899
|
+
agentRuntimeError = primaryCreationErrors.length > 0
|
|
900
|
+
? creationDetail
|
|
901
|
+
: `No agent runner created for ${primaryAgent.name}: ${creationDetail}`;
|
|
878
902
|
primaryAgent.status = 'error';
|
|
879
903
|
primaryAgent.error = agentRuntimeError;
|
|
880
904
|
logger.error(agentRuntimeError);
|
|
@@ -1871,6 +1895,78 @@ async function main() {
|
|
|
1871
1895
|
if (inst)
|
|
1872
1896
|
connectedChannels.delete(inst.adapter.channelKey);
|
|
1873
1897
|
};
|
|
1898
|
+
// Bind the per-HOME IPC endpoint before any AUN connection is attempted.
|
|
1899
|
+
// A failed bind (for example, a stale Windows named pipe) must abort startup
|
|
1900
|
+
// without creating a second AUN session that can kick the old daemon.
|
|
1901
|
+
let controlChannel;
|
|
1902
|
+
const ipcServer = new IpcServer(resolvePaths().socket, () => {
|
|
1903
|
+
const channels = {};
|
|
1904
|
+
const channelsByType = {};
|
|
1905
|
+
for (const inst of channelInstances) {
|
|
1906
|
+
const name = inst.adapter.channelName;
|
|
1907
|
+
const status = inst.channel.getStatus?.() ?? { connected: true };
|
|
1908
|
+
const channelType = inst.channelType || name;
|
|
1909
|
+
channels[name] = { ...status, channelType };
|
|
1910
|
+
if (!channelsByType[channelType])
|
|
1911
|
+
channelsByType[channelType] = [];
|
|
1912
|
+
channelsByType[channelType].push(name);
|
|
1913
|
+
}
|
|
1914
|
+
const snap = statsCollector.getSnapshot();
|
|
1915
|
+
const agentListForStatus = agentRegistry.list();
|
|
1916
|
+
return {
|
|
1917
|
+
pid: process.pid,
|
|
1918
|
+
uptime: snap.uptimeMs,
|
|
1919
|
+
controlPlane: {
|
|
1920
|
+
ready: true,
|
|
1921
|
+
owned: processLevelOwners.length > 0,
|
|
1922
|
+
},
|
|
1923
|
+
agentRuntime: {
|
|
1924
|
+
state: agentRuntimeState,
|
|
1925
|
+
runnableAgents: agentListForStatus.filter((a) => a.status !== 'error' && a.status !== 'disabled').length,
|
|
1926
|
+
runningAgents: agentListForStatus.filter((a) => a.status === 'running').length,
|
|
1927
|
+
...(agentRuntimeError ? { error: agentRuntimeError } : {}),
|
|
1928
|
+
},
|
|
1929
|
+
channels,
|
|
1930
|
+
channelsByType,
|
|
1931
|
+
queue: {
|
|
1932
|
+
pending: messageQueue.getGlobalQueueLength(),
|
|
1933
|
+
processing: messageQueue.getGlobalProcessingCount(),
|
|
1934
|
+
},
|
|
1935
|
+
stats: {
|
|
1936
|
+
received: snap.lastHour.received,
|
|
1937
|
+
sent: snap.lastHour.sent,
|
|
1938
|
+
completed: snap.lastHour.completed,
|
|
1939
|
+
errors: snap.lastHour.errors,
|
|
1940
|
+
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
1941
|
+
},
|
|
1942
|
+
controlAid: (() => {
|
|
1943
|
+
if (!daemonCfg.aid)
|
|
1944
|
+
return undefined;
|
|
1945
|
+
const aidState = controlChannel?.getAidState();
|
|
1946
|
+
return {
|
|
1947
|
+
aid: daemonCfg.aid,
|
|
1948
|
+
connected: aidState?.status === 'connected',
|
|
1949
|
+
...(aidState ? {
|
|
1950
|
+
status: aidState.status,
|
|
1951
|
+
gatewayUrl: aidState.gatewayUrl,
|
|
1952
|
+
reconnectCount: aidState.reconnectCount,
|
|
1953
|
+
lastConnectedAt: aidState.lastConnectedAt,
|
|
1954
|
+
lastError: aidState.lastError,
|
|
1955
|
+
} : {}),
|
|
1956
|
+
};
|
|
1957
|
+
})(),
|
|
1958
|
+
};
|
|
1959
|
+
}, async (cmd, sessionId, delegationToken, delegationCommandHash) => {
|
|
1960
|
+
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
1961
|
+
if (!delegation.ok)
|
|
1962
|
+
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
1963
|
+
return cmdHandler.handleCtl(cmd, sessionId);
|
|
1964
|
+
});
|
|
1965
|
+
// Register every IPC executor/provider before exposing the endpoint. The
|
|
1966
|
+
// function declaration is hoisted, while its invocation remains here so a
|
|
1967
|
+
// failed bind still aborts before any AUN connection is attempted.
|
|
1968
|
+
configureIpc();
|
|
1969
|
+
await ipcServer.start();
|
|
1874
1970
|
// ── 连接所有渠道(后台首连,AUN/任意渠道故障不阻塞 daemon 主流程)──
|
|
1875
1971
|
logger.info(`🚀 EvolCore core is ready; connecting ${channelInstances.length} channel(s) in background`);
|
|
1876
1972
|
const connectAllPromise = channelLoader.connectAll(channelInstances, {
|
|
@@ -1913,13 +2009,13 @@ async function main() {
|
|
|
1913
2009
|
logger.warn(`控制 AID 证书缺失:${daemonCfg.aid}(AUN 控制通道后台重连;如需重建运行 ec init)`);
|
|
1914
2010
|
}
|
|
1915
2011
|
}
|
|
1916
|
-
let controlChannel;
|
|
1917
2012
|
if (daemonCfg.aid) {
|
|
1918
2013
|
controlChannel = new AUNChannel({
|
|
1919
2014
|
aid: daemonCfg.aid,
|
|
1920
2015
|
agentName: daemonCfg.aid,
|
|
1921
2016
|
channelName: 'control',
|
|
1922
2017
|
pureIdentity: true,
|
|
2018
|
+
gatewayUrl: daemonCfg.aun?.gatewayUrl,
|
|
1923
2019
|
aunTrace: daemonCfg.debug?.aunTrace,
|
|
1924
2020
|
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
1925
2021
|
});
|
|
@@ -2199,756 +2295,712 @@ async function main() {
|
|
|
2199
2295
|
});
|
|
2200
2296
|
}
|
|
2201
2297
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2298
|
+
function configureIpc() {
|
|
2299
|
+
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2300
|
+
ipcServer.setAgentRegistry(agentRegistry);
|
|
2301
|
+
ipcServer.setDingtalkContactBindExecutor({
|
|
2302
|
+
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
2303
|
+
isChannelReady: isContactBindChannelReady,
|
|
2304
|
+
});
|
|
2305
|
+
ipcServer.setFeishuContactBindExecutor({
|
|
2306
|
+
register: (cmd) => registerPendingFeishuContactBind(cmd),
|
|
2307
|
+
isChannelReady: isContactBindChannelReady,
|
|
2308
|
+
});
|
|
2309
|
+
ipcServer.setQQBotContactBindExecutor({
|
|
2310
|
+
register: (cmd) => registerPendingQQBotContactBind(cmd),
|
|
2311
|
+
isChannelReady: isContactBindChannelReady,
|
|
2312
|
+
});
|
|
2313
|
+
ipcServer.setWecomContactBindExecutor({
|
|
2314
|
+
register: (cmd) => registerPendingWecomContactBind(cmd),
|
|
2315
|
+
isChannelReady: isContactBindChannelReady,
|
|
2316
|
+
});
|
|
2317
|
+
ipcServer.setWechatContactBindExecutor({
|
|
2318
|
+
register: (cmd) => registerPendingWechatContactBind(cmd),
|
|
2319
|
+
isChannelReady: isContactBindChannelReady,
|
|
2320
|
+
});
|
|
2321
|
+
ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
|
|
2322
|
+
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2323
|
+
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2324
|
+
cmdHandler.setDaemonStatusProvider(() => {
|
|
2325
|
+
const aidState = controlChannel?.getAidState?.();
|
|
2326
|
+
return {
|
|
2327
|
+
aid: daemonCfg.aid ?? null,
|
|
2328
|
+
aun: aidState ? {
|
|
2329
|
+
connected: aidState.status === 'connected',
|
|
2330
|
+
status: aidState.status,
|
|
2331
|
+
reconnectCount: aidState.reconnectCount ?? 0,
|
|
2332
|
+
flapCount: aidState.flapCount ?? 0,
|
|
2333
|
+
...(aidState.lastError ? { lastError: String(aidState.lastError).slice(0, 80) } : {}),
|
|
2334
|
+
...(aidState.kickDetail?.reason ? { kickReason: String(aidState.kickDetail.reason).slice(0, 80) } : {}),
|
|
2335
|
+
} : {
|
|
2336
|
+
connected: false,
|
|
2337
|
+
status: daemonCfg.aid ? 'disconnected' : 'disabled',
|
|
2338
|
+
},
|
|
2339
|
+
};
|
|
2340
|
+
});
|
|
2341
|
+
if (bindService) {
|
|
2342
|
+
ipcServer.setBindExecutor({
|
|
2343
|
+
begin: (cmd) => bindService.begin(cmd),
|
|
2344
|
+
status: (taskId) => bindService.status(taskId),
|
|
2345
|
+
cancel: (taskId) => bindService.cancel(taskId),
|
|
2346
|
+
});
|
|
2214
2347
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
sent: snap.lastHour.sent,
|
|
2239
|
-
completed: snap.lastHour.completed,
|
|
2240
|
-
errors: snap.lastHour.errors,
|
|
2241
|
-
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
2242
|
-
},
|
|
2243
|
-
controlAid: daemonCfg.aid
|
|
2244
|
-
? { aid: daemonCfg.aid, connected: controlChannel?.getAidState().status === 'connected' }
|
|
2245
|
-
: undefined,
|
|
2246
|
-
};
|
|
2247
|
-
}, async (cmd, sessionId, delegationToken, delegationCommandHash) => {
|
|
2248
|
-
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
2249
|
-
if (!delegation.ok)
|
|
2250
|
-
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
2251
|
-
return cmdHandler.handleCtl(cmd, sessionId);
|
|
2252
|
-
});
|
|
2253
|
-
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2254
|
-
ipcServer.setAgentRegistry(agentRegistry);
|
|
2255
|
-
ipcServer.setDingtalkContactBindExecutor({
|
|
2256
|
-
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
2257
|
-
isChannelReady: isContactBindChannelReady,
|
|
2258
|
-
});
|
|
2259
|
-
ipcServer.setFeishuContactBindExecutor({
|
|
2260
|
-
register: (cmd) => registerPendingFeishuContactBind(cmd),
|
|
2261
|
-
isChannelReady: isContactBindChannelReady,
|
|
2262
|
-
});
|
|
2263
|
-
ipcServer.setQQBotContactBindExecutor({
|
|
2264
|
-
register: (cmd) => registerPendingQQBotContactBind(cmd),
|
|
2265
|
-
isChannelReady: isContactBindChannelReady,
|
|
2266
|
-
});
|
|
2267
|
-
ipcServer.setWecomContactBindExecutor({
|
|
2268
|
-
register: (cmd) => registerPendingWecomContactBind(cmd),
|
|
2269
|
-
isChannelReady: isContactBindChannelReady,
|
|
2270
|
-
});
|
|
2271
|
-
ipcServer.setWechatContactBindExecutor({
|
|
2272
|
-
register: (cmd) => registerPendingWechatContactBind(cmd),
|
|
2273
|
-
isChannelReady: isContactBindChannelReady,
|
|
2274
|
-
});
|
|
2275
|
-
ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
|
|
2276
|
-
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2277
|
-
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2278
|
-
cmdHandler.setDaemonStatusProvider(() => {
|
|
2279
|
-
const aidState = controlChannel?.getAidState?.();
|
|
2280
|
-
return {
|
|
2281
|
-
aid: daemonCfg.aid ?? null,
|
|
2282
|
-
aun: aidState ? {
|
|
2283
|
-
connected: aidState.status === 'connected',
|
|
2284
|
-
status: aidState.status,
|
|
2285
|
-
reconnectCount: aidState.reconnectCount ?? 0,
|
|
2286
|
-
flapCount: aidState.flapCount ?? 0,
|
|
2287
|
-
...(aidState.lastError ? { lastError: String(aidState.lastError).slice(0, 80) } : {}),
|
|
2288
|
-
...(aidState.kickDetail?.reason ? { kickReason: String(aidState.kickDetail.reason).slice(0, 80) } : {}),
|
|
2289
|
-
} : {
|
|
2290
|
-
connected: false,
|
|
2291
|
-
status: daemonCfg.aid ? 'disconnected' : 'disabled',
|
|
2292
|
-
},
|
|
2293
|
-
};
|
|
2294
|
-
});
|
|
2295
|
-
if (bindService) {
|
|
2296
|
-
ipcServer.setBindExecutor({
|
|
2297
|
-
begin: (cmd) => bindService.begin(cmd),
|
|
2298
|
-
status: (taskId) => bindService.status(taskId),
|
|
2299
|
-
cancel: (taskId) => bindService.cancel(taskId),
|
|
2348
|
+
// 注入 AUN AID 状态聚合器:遍历所有 aun 类型 channel,调 getAidState() 收集
|
|
2349
|
+
ipcServer.setAunAidProvider(() => {
|
|
2350
|
+
const out = [];
|
|
2351
|
+
for (const inst of channelInstances) {
|
|
2352
|
+
if (inst.channelType !== 'aun')
|
|
2353
|
+
continue;
|
|
2354
|
+
const ch = inst.channel;
|
|
2355
|
+
if (typeof ch?.getAidState === 'function') {
|
|
2356
|
+
try {
|
|
2357
|
+
const aidState = ch.getAidState();
|
|
2358
|
+
// 增强:添加队列状态
|
|
2359
|
+
const agentName = aidState.agentName || aidState.aid;
|
|
2360
|
+
const processing = messageQueue.getProcessingCountByAgent(agentName);
|
|
2361
|
+
const queued = messageQueue.getQueueLengthByAgent(agentName);
|
|
2362
|
+
out.push({
|
|
2363
|
+
...aidState,
|
|
2364
|
+
queueStatus: { processing, queued }
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
catch { /* ignore */ }
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return out;
|
|
2300
2371
|
});
|
|
2301
|
-
|
|
2302
|
-
// 注入 AUN AID 状态聚合器:遍历所有 aun 类型 channel,调 getAidState() 收集
|
|
2303
|
-
ipcServer.setAunAidProvider(() => {
|
|
2304
|
-
const out = [];
|
|
2372
|
+
// 注入 Per-AID 统计收集器到所有 AUN channel 实例
|
|
2305
2373
|
for (const inst of channelInstances) {
|
|
2306
2374
|
if (inst.channelType !== 'aun')
|
|
2307
2375
|
continue;
|
|
2308
2376
|
const ch = inst.channel;
|
|
2309
|
-
if (typeof ch?.
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
if (inst.channelType !== 'aun')
|
|
2329
|
-
continue;
|
|
2330
|
-
const ch = inst.channel;
|
|
2331
|
-
if (typeof ch?.setAidStatsCollector === 'function') {
|
|
2332
|
-
ch.setAidStatsCollector(aidStatsCollector);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
// 注入 Per-AID 统计 IPC provider
|
|
2336
|
-
aidStatsCollector.setQueueStatsProvider((agentName) => ({
|
|
2337
|
-
processing: messageQueue.getProcessingCountByAgent(agentName),
|
|
2338
|
-
queued: messageQueue.getQueueLengthByAgent(agentName),
|
|
2339
|
-
muted: messageQueue.isAgentMuted(agentName),
|
|
2340
|
-
}));
|
|
2341
|
-
ipcServer.setAunAidStatsProvider(() => aidStatsCollector.getAllSnapshots());
|
|
2342
|
-
ipcServer.setAunAidStatsRecorder((params) => {
|
|
2343
|
-
aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode, 'send');
|
|
2344
|
-
});
|
|
2345
|
-
ipcServer.setTaskRuntimeContextProvider(({ sessionId }) => responseEngine.getTaskRuntimeContext(sessionId));
|
|
2346
|
-
ipcServer.setHandoffReturnExecutor((params) => responseEngine.returnHandoffResult(params));
|
|
2347
|
-
ipcServer.setHandoffStatusExecutor(async (params) => {
|
|
2348
|
-
if (!params.sessionId)
|
|
2349
|
-
return { ok: false, code: 'HANDOFF_CALL_SESSION_REQUIRED', error: 'current session is required' };
|
|
2350
|
-
const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
|
|
2351
|
-
const session = await sessionManager.getSessionById(params.sessionId);
|
|
2352
|
-
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
2353
|
-
if (!selfAid)
|
|
2354
|
-
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2355
|
-
return handoffRuntime.status(selfAid, params.handoffId)
|
|
2356
|
-
?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2357
|
-
});
|
|
2358
|
-
const resolveHandoffQueryAid = async (params) => {
|
|
2359
|
-
if (params.sessionId) {
|
|
2377
|
+
if (typeof ch?.setAidStatsCollector === 'function') {
|
|
2378
|
+
ch.setAidStatsCollector(aidStatsCollector);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
// 注入 Per-AID 统计 IPC provider
|
|
2382
|
+
aidStatsCollector.setQueueStatsProvider((agentName) => ({
|
|
2383
|
+
processing: messageQueue.getProcessingCountByAgent(agentName),
|
|
2384
|
+
queued: messageQueue.getQueueLengthByAgent(agentName),
|
|
2385
|
+
muted: messageQueue.isAgentMuted(agentName),
|
|
2386
|
+
}));
|
|
2387
|
+
ipcServer.setAunAidStatsProvider(() => aidStatsCollector.getAllSnapshots());
|
|
2388
|
+
ipcServer.setAunAidStatsRecorder((params) => {
|
|
2389
|
+
aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode, 'send');
|
|
2390
|
+
});
|
|
2391
|
+
ipcServer.setTaskRuntimeContextProvider(({ sessionId }) => responseEngine.getTaskRuntimeContext(sessionId));
|
|
2392
|
+
ipcServer.setHandoffReturnExecutor((params) => responseEngine.returnHandoffResult(params));
|
|
2393
|
+
ipcServer.setHandoffStatusExecutor(async (params) => {
|
|
2394
|
+
if (!params.sessionId)
|
|
2395
|
+
return { ok: false, code: 'HANDOFF_CALL_SESSION_REQUIRED', error: 'current session is required' };
|
|
2360
2396
|
const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
|
|
2361
2397
|
const session = await sessionManager.getSessionById(params.sessionId);
|
|
2362
2398
|
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
2363
|
-
if (!selfAid)
|
|
2364
|
-
return { ok: false, code: '
|
|
2399
|
+
if (!selfAid)
|
|
2400
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2401
|
+
return handoffRuntime.status(selfAid, params.handoffId)
|
|
2402
|
+
?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2403
|
+
});
|
|
2404
|
+
const resolveHandoffQueryAid = async (params) => {
|
|
2405
|
+
if (params.sessionId) {
|
|
2406
|
+
const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
|
|
2407
|
+
const session = await sessionManager.getSessionById(params.sessionId);
|
|
2408
|
+
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
2409
|
+
if (!selfAid) {
|
|
2410
|
+
return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is invalid' };
|
|
2411
|
+
}
|
|
2412
|
+
if (params.agent && params.agent !== selfAid) {
|
|
2413
|
+
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2414
|
+
}
|
|
2415
|
+
return { ok: true, aid: selfAid };
|
|
2365
2416
|
}
|
|
2366
|
-
if (params.agent
|
|
2367
|
-
return { ok: false, code: '
|
|
2417
|
+
if (!params.agent) {
|
|
2418
|
+
return { ok: false, code: 'HANDOFF_AGENT_REQUIRED', error: '--agent is required outside a task context' };
|
|
2368
2419
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
return { ok:
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
selfAid: scope.aid,
|
|
2385
|
-
state: params.state,
|
|
2386
|
-
sessionId: params.filterSessionId,
|
|
2387
|
-
limit: params.limit,
|
|
2420
|
+
if (!agentRegistry.get(params.agent)) {
|
|
2421
|
+
return { ok: false, code: 'HANDOFF_AGENT_NOT_FOUND', error: `agent not found: ${params.agent}` };
|
|
2422
|
+
}
|
|
2423
|
+
return { ok: true, aid: params.agent };
|
|
2424
|
+
};
|
|
2425
|
+
ipcServer.setHandoffListExecutor(async (params) => {
|
|
2426
|
+
const scope = await resolveHandoffQueryAid(params);
|
|
2427
|
+
if (!scope.ok)
|
|
2428
|
+
return scope;
|
|
2429
|
+
return handoffRuntime.listHandoffs({
|
|
2430
|
+
selfAid: scope.aid,
|
|
2431
|
+
state: params.state,
|
|
2432
|
+
sessionId: params.filterSessionId,
|
|
2433
|
+
limit: params.limit,
|
|
2434
|
+
});
|
|
2388
2435
|
});
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
return scope
|
|
2394
|
-
|
|
2395
|
-
?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2396
|
-
});
|
|
2397
|
-
ipcServer.setAunMsgSender(async (params) => {
|
|
2398
|
-
const runtimeCausation = params.originSessionId
|
|
2399
|
-
? responseEngine.getTaskRuntimeContext(params.originSessionId)?.causation
|
|
2400
|
-
: undefined;
|
|
2401
|
-
const delegation = authorizeDelegatedAunMsgSend(agentDelegationRegistry, {
|
|
2402
|
-
delegationToken: params.delegationToken,
|
|
2403
|
-
delegationCommandHash: params.delegationCommandHash,
|
|
2404
|
-
sessionId: params.originSessionId,
|
|
2405
|
-
messageId: params.originMessageId,
|
|
2406
|
-
aid: params.aid,
|
|
2407
|
-
to: params.to,
|
|
2408
|
-
scope: params.scope,
|
|
2409
|
-
action: params.file ? 'file' : 'send',
|
|
2436
|
+
ipcServer.setHandoffTraceExecutor(async (params) => {
|
|
2437
|
+
const scope = await resolveHandoffQueryAid(params);
|
|
2438
|
+
if (!scope.ok)
|
|
2439
|
+
return scope;
|
|
2440
|
+
return handoffRuntime.traceHandoff(scope.aid, params.handoffId, params.limit)
|
|
2441
|
+
?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2410
2442
|
});
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2443
|
+
ipcServer.setAunMsgSender(async (params) => {
|
|
2444
|
+
const runtimeCausation = params.originSessionId
|
|
2445
|
+
? responseEngine.getTaskRuntimeContext(params.originSessionId)?.causation
|
|
2446
|
+
: undefined;
|
|
2447
|
+
const delegation = authorizeDelegatedAunMsgSend(agentDelegationRegistry, {
|
|
2448
|
+
delegationToken: params.delegationToken,
|
|
2449
|
+
delegationCommandHash: params.delegationCommandHash,
|
|
2450
|
+
sessionId: params.originSessionId,
|
|
2451
|
+
messageId: params.originMessageId,
|
|
2452
|
+
aid: params.aid,
|
|
2453
|
+
to: params.to,
|
|
2454
|
+
scope: params.scope,
|
|
2455
|
+
action: params.file ? 'file' : 'send',
|
|
2456
|
+
});
|
|
2457
|
+
if (!delegation.ok) {
|
|
2458
|
+
return { ok: false, error: delegation.reason, code: delegation.code };
|
|
2459
|
+
}
|
|
2460
|
+
const inst = channelInstances.find((candidate) => {
|
|
2461
|
+
if (candidate.channelType !== 'aun')
|
|
2462
|
+
return false;
|
|
2463
|
+
const ch = candidate.channel;
|
|
2464
|
+
try {
|
|
2465
|
+
const aidState = typeof ch?.getAidState === 'function' ? ch.getAidState() : null;
|
|
2466
|
+
if (aidState?.aid === params.aid)
|
|
2467
|
+
return true;
|
|
2468
|
+
if (typeof ch?.getAid === 'function' && ch.getAid() === params.aid)
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
catch { /* ignore */ }
|
|
2416
2472
|
return false;
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
return true;
|
|
2422
|
-
if (typeof ch?.getAid === 'function' && ch.getAid() === params.aid)
|
|
2423
|
-
return true;
|
|
2473
|
+
});
|
|
2474
|
+
const ch = inst?.channel;
|
|
2475
|
+
if (!ch) {
|
|
2476
|
+
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2424
2477
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2478
|
+
const targetIsGroup = typeof ch.isGroupId === 'function' && ch.isGroupId(params.to);
|
|
2479
|
+
if ((params.scope === 'group') !== targetIsGroup) {
|
|
2480
|
+
return { ok: false, error: 'AUN target does not match the delegated send scope', code: 'UNSUPPORTED_TARGET' };
|
|
2481
|
+
}
|
|
2482
|
+
let payload = params.payload;
|
|
2483
|
+
if (params.file) {
|
|
2484
|
+
if (isHClassPath(params.file.filePath)) {
|
|
2485
|
+
return {
|
|
2486
|
+
ok: false,
|
|
2487
|
+
error: 'H-class protected files cannot be uploaded by an agent task',
|
|
2488
|
+
code: 'H_CLASS_PROTECTED',
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
if (typeof ch.buildDaemonFilePayload !== 'function') {
|
|
2492
|
+
return { ok: false, error: 'AUN channel does not support daemon file uploads', code: 'AUN_FILE_UNSUPPORTED' };
|
|
2493
|
+
}
|
|
2494
|
+
try {
|
|
2495
|
+
payload = await ch.buildDaemonFilePayload(params.file);
|
|
2496
|
+
}
|
|
2497
|
+
catch (error) {
|
|
2498
|
+
return {
|
|
2499
|
+
ok: false,
|
|
2500
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2501
|
+
code: error?.code ?? 'AUN_FILE_UPLOAD_FAILED',
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2444
2504
|
}
|
|
2445
|
-
if (
|
|
2446
|
-
return { ok: false, error: '
|
|
2505
|
+
if (!payload) {
|
|
2506
|
+
return { ok: false, error: 'message payload is required', code: 'INVALID_PAYLOAD' };
|
|
2447
2507
|
}
|
|
2448
|
-
|
|
2449
|
-
payload =
|
|
2508
|
+
if (params.thread && typeof payload.thread_id !== 'string') {
|
|
2509
|
+
payload = { ...payload, thread_id: params.thread };
|
|
2450
2510
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
ok: false,
|
|
2454
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2455
|
-
code: error?.code ?? 'AUN_FILE_UPLOAD_FAILED',
|
|
2456
|
-
};
|
|
2511
|
+
if (params.scope === 'group' && params.mentions?.length) {
|
|
2512
|
+
payload = { ...payload, mentions: params.mentions };
|
|
2457
2513
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2514
|
+
let targetSessionId;
|
|
2515
|
+
if (params.originSessionId && params.originMessageId) {
|
|
2516
|
+
try {
|
|
2517
|
+
const created = await handoffRuntime.createOutbound({
|
|
2518
|
+
selfAid: params.aid,
|
|
2519
|
+
to: params.to,
|
|
2520
|
+
originSessionId: params.originSessionId,
|
|
2521
|
+
originMessageId: params.originMessageId,
|
|
2522
|
+
payload,
|
|
2523
|
+
encrypt: params.encrypt === true,
|
|
2524
|
+
thread: params.thread,
|
|
2525
|
+
targetChatType: params.scope === 'group' ? 'group' : 'private',
|
|
2526
|
+
explicitReturnPolicy: params.returnPolicy,
|
|
2527
|
+
originAuthorization: {
|
|
2528
|
+
actorId: delegation.grant.actorId,
|
|
2529
|
+
channelKey: delegation.grant.channel,
|
|
2530
|
+
channelType: delegation.grant.channelType,
|
|
2531
|
+
chatType: delegation.grant.chatType,
|
|
2532
|
+
peerKey: delegation.grant.peerKey,
|
|
2533
|
+
},
|
|
2534
|
+
causation: runtimeCausation,
|
|
2535
|
+
});
|
|
2536
|
+
targetSessionId = created.targetSession.id;
|
|
2537
|
+
if (created.crossSession && created.handoff) {
|
|
2538
|
+
return {
|
|
2539
|
+
ok: true,
|
|
2540
|
+
status: created.handoff.state === 'target_sent' ? 'delivered' : 'queued',
|
|
2541
|
+
message_id: created.handoff.target_message_id ?? undefined,
|
|
2542
|
+
handoff_id: created.handoff.handoff_id,
|
|
2543
|
+
target_session_id: created.targetSession.id,
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
if (params.scope === 'msg' && !payload.ref_message_id) {
|
|
2547
|
+
payload.ref_message_id = params.originMessageId;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
catch (error) {
|
|
2551
|
+
const code = error?.code;
|
|
2552
|
+
const handoffId = error?.handoffId
|
|
2553
|
+
?? error?.blockingHandoffId;
|
|
2492
2554
|
return {
|
|
2493
|
-
ok:
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
handoff_id:
|
|
2497
|
-
target_session_id: created.targetSession.id,
|
|
2555
|
+
ok: false,
|
|
2556
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2557
|
+
code,
|
|
2558
|
+
...(handoffId ? { handoff_id: handoffId } : {}),
|
|
2498
2559
|
};
|
|
2499
2560
|
}
|
|
2500
|
-
if (params.scope === 'msg' && !payload.ref_message_id) {
|
|
2501
|
-
payload.ref_message_id = params.originMessageId;
|
|
2502
|
-
}
|
|
2503
2561
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
return {
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2562
|
+
if (params.scope === 'group') {
|
|
2563
|
+
if (typeof ch.sendDaemonGroupMsg !== 'function') {
|
|
2564
|
+
return { ok: false, error: 'AUN channel does not support daemon group sends', code: 'AUN_GROUP_UNSUPPORTED' };
|
|
2565
|
+
}
|
|
2566
|
+
return await ch.sendDaemonGroupMsg({
|
|
2567
|
+
groupId: params.to,
|
|
2568
|
+
payload,
|
|
2569
|
+
mentions: params.mentions,
|
|
2570
|
+
encrypt: params.encrypt,
|
|
2571
|
+
log: params.log ? { ...params.log, sessionId: targetSessionId ?? params.log.sessionId } : undefined,
|
|
2572
|
+
});
|
|
2514
2573
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
if (typeof ch.sendDaemonGroupMsg !== 'function') {
|
|
2518
|
-
return { ok: false, error: 'AUN channel does not support daemon group sends', code: 'AUN_GROUP_UNSUPPORTED' };
|
|
2574
|
+
if (typeof ch.sendDaemonMsg !== 'function') {
|
|
2575
|
+
return { ok: false, error: 'AUN channel does not support daemon private sends', code: 'AUN_MSG_UNSUPPORTED' };
|
|
2519
2576
|
}
|
|
2520
|
-
return await ch.
|
|
2521
|
-
|
|
2577
|
+
return await ch.sendDaemonMsg({
|
|
2578
|
+
to: params.to,
|
|
2522
2579
|
payload,
|
|
2523
|
-
mentions: params.mentions,
|
|
2524
2580
|
encrypt: params.encrypt,
|
|
2581
|
+
causation: runtimeCausation,
|
|
2525
2582
|
log: params.log ? { ...params.log, sessionId: targetSessionId ?? params.log.sessionId } : undefined,
|
|
2526
2583
|
});
|
|
2527
|
-
}
|
|
2528
|
-
if (typeof ch.sendDaemonMsg !== 'function') {
|
|
2529
|
-
return { ok: false, error: 'AUN channel does not support daemon private sends', code: 'AUN_MSG_UNSUPPORTED' };
|
|
2530
|
-
}
|
|
2531
|
-
return await ch.sendDaemonMsg({
|
|
2532
|
-
to: params.to,
|
|
2533
|
-
payload,
|
|
2534
|
-
encrypt: params.encrypt,
|
|
2535
|
-
causation: runtimeCausation,
|
|
2536
|
-
log: params.log ? { ...params.log, sessionId: targetSessionId ?? params.log.sessionId } : undefined,
|
|
2537
2584
|
});
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
// startChannel 重建渠道时重新注入 AidStatsCollector(与 hot-load 路径对齐)
|
|
2552
|
-
if (inst.channelType === 'aun') {
|
|
2553
|
-
const ch = inst.channel;
|
|
2554
|
-
if (typeof ch?.setAidStatsCollector === 'function')
|
|
2555
|
-
ch.setAidStatsCollector(aidStatsCollector);
|
|
2556
|
-
}
|
|
2557
|
-
},
|
|
2558
|
-
onChannelConnected: markChannelConnected,
|
|
2559
|
-
messageQueue,
|
|
2560
|
-
handoffRuntime,
|
|
2561
|
-
});
|
|
2562
|
-
// Make reload hooks accessible to IPC handler & ctl handler (both run in this process)
|
|
2563
|
-
globalThis.__evolcore_reloadHooks = reloadHooks;
|
|
2564
|
-
// Hot-load handler: dynamically add a new agent at runtime
|
|
2565
|
-
globalThis.__evolcore_hotLoadAgent = async (aid) => {
|
|
2566
|
-
handoffRuntime.pauseAgent(aid);
|
|
2567
|
-
let resumed = false;
|
|
2568
|
-
try {
|
|
2569
|
-
agentRuntimeState = 'starting';
|
|
2570
|
-
agentRuntimeError = undefined;
|
|
2571
|
-
const agent = agentRegistry.loadNewAgent(aid);
|
|
2572
|
-
if (!agent) {
|
|
2573
|
-
agentRuntimeState = agentRegistry.runnableAgents().length > 0 ? 'running' : 'error';
|
|
2574
|
-
agentRuntimeError = `Failed to load agent ${aid}`;
|
|
2575
|
-
throw new Error(agentRuntimeError);
|
|
2576
|
-
}
|
|
2577
|
-
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
2578
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
2579
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
2580
|
-
},
|
|
2581
|
-
});
|
|
2582
|
-
for (const inst of newAgentInstances) {
|
|
2583
|
-
agentMap.set(`${inst.evolagentName}::${inst.baseagent}`, inst.agent);
|
|
2584
|
-
inst.agent.setPermissionGateway?.(permissionGateway);
|
|
2585
|
-
inst.agent.setCompactStartCallback?.((sessionId) => {
|
|
2586
|
-
processor.handleCompactStart(sessionId);
|
|
2587
|
-
});
|
|
2588
|
-
}
|
|
2589
|
-
if (newAgentInstances.length === 0) {
|
|
2590
|
-
agent.status = 'error';
|
|
2591
|
-
agent.error = 'No baseagent runner created for hot-loaded agent';
|
|
2592
|
-
agentRuntimeState = 'error';
|
|
2593
|
-
agentRuntimeError = agent.error;
|
|
2594
|
-
throw new Error(agent.error);
|
|
2595
|
-
}
|
|
2596
|
-
// 创建 channels
|
|
2597
|
-
const instances = await channelLoader.createForAgent(agent);
|
|
2598
|
-
for (const inst of instances) {
|
|
2599
|
-
registerChannelInstance(inst);
|
|
2585
|
+
// ── Reload hooks: enable agentRegistry.reload() to drain/disconnect/restart channels ──
|
|
2586
|
+
const reloadHooks = buildReloadHooks({
|
|
2587
|
+
channelLoader,
|
|
2588
|
+
channelInstances,
|
|
2589
|
+
registerChannelInstance,
|
|
2590
|
+
unregisterChannelInstance: (channelName) => {
|
|
2591
|
+
markChannelDisconnected(channelName);
|
|
2592
|
+
processor.unregisterChannel(channelName);
|
|
2593
|
+
cmdHandler.unregisterChannel(channelName);
|
|
2594
|
+
msgBridge.removeChannel(channelName);
|
|
2595
|
+
},
|
|
2596
|
+
onChannelStarted: (inst) => {
|
|
2597
|
+
// startChannel 重建渠道时重新注入 AidStatsCollector(与 hot-load 路径对齐)
|
|
2600
2598
|
if (inst.channelType === 'aun') {
|
|
2601
2599
|
const ch = inst.channel;
|
|
2602
2600
|
if (typeof ch?.setAidStatsCollector === 'function')
|
|
2603
2601
|
ch.setAidStatsCollector(aidStatsCollector);
|
|
2604
2602
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
// Full resync handler: scan disk, load new agents, unload removed/disabled, reload changed
|
|
2625
|
-
globalThis.__evolcore_resyncAgents = async () => {
|
|
2626
|
-
// 先清 kit 缓存:'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload
|
|
2627
|
-
// 策略,平时不查盘。放在扫盘与 reload 之前,本轮上下线的 agent 才能读到磁盘上的
|
|
2628
|
-
// 最新版本——放在末尾的话,这一轮全用旧值,改动要等下一次 resync 才生效。
|
|
2629
|
-
invalidateKitCache();
|
|
2630
|
-
const { loadAllAgents: scanAgents, loadDefaults: readDefaults } = await import('./config-store.js');
|
|
2631
|
-
const { resolveEffective } = await import('./config/config-manager.js');
|
|
2632
|
-
const freshDefaults = readDefaults();
|
|
2633
|
-
const { agents: diskAgents } = scanAgents();
|
|
2634
|
-
const diskAidSet = new Set(diskAgents.map(a => a.aid));
|
|
2635
|
-
const results = [];
|
|
2636
|
-
// 1. 下线:运行时有但磁盘上没有 / disabled 的
|
|
2637
|
-
for (const [aid, agent] of [...agentRegistry.agents.entries()]) {
|
|
2638
|
-
const diskCfg = diskAgents.find(a => a.aid === aid);
|
|
2639
|
-
if (!diskCfg || diskCfg.enabled === false) {
|
|
2640
|
-
handoffRuntime.pauseAgent(aid);
|
|
2641
|
-
try {
|
|
2642
|
-
await handoffRuntime.drainAgent(aid);
|
|
2603
|
+
},
|
|
2604
|
+
onChannelConnected: markChannelConnected,
|
|
2605
|
+
messageQueue,
|
|
2606
|
+
handoffRuntime,
|
|
2607
|
+
});
|
|
2608
|
+
// Make reload hooks accessible to IPC handler & ctl handler (both run in this process)
|
|
2609
|
+
globalThis.__evolcore_reloadHooks = reloadHooks;
|
|
2610
|
+
// Hot-load handler: dynamically add a new agent at runtime
|
|
2611
|
+
globalThis.__evolcore_hotLoadAgent = async (aid) => {
|
|
2612
|
+
handoffRuntime.pauseAgent(aid);
|
|
2613
|
+
let resumed = false;
|
|
2614
|
+
try {
|
|
2615
|
+
agentRuntimeState = 'starting';
|
|
2616
|
+
agentRuntimeError = undefined;
|
|
2617
|
+
const agent = agentRegistry.loadNewAgent(aid);
|
|
2618
|
+
if (!agent) {
|
|
2619
|
+
agentRuntimeState = agentRegistry.runnableAgents().length > 0 ? 'running' : 'error';
|
|
2620
|
+
agentRuntimeError = `Failed to load agent ${aid}`;
|
|
2621
|
+
throw new Error(agentRuntimeError);
|
|
2643
2622
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2623
|
+
const creationErrors = [];
|
|
2624
|
+
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
2625
|
+
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
2626
|
+
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
2627
|
+
},
|
|
2628
|
+
}, creationErrors);
|
|
2629
|
+
for (const inst of newAgentInstances) {
|
|
2630
|
+
agentMap.set(`${inst.evolagentName}::${inst.baseagent}`, inst.agent);
|
|
2631
|
+
inst.agent.setPermissionGateway?.(permissionGateway);
|
|
2632
|
+
inst.agent.setCompactStartCallback?.((sessionId) => {
|
|
2633
|
+
processor.handleCompactStart(sessionId);
|
|
2634
|
+
});
|
|
2648
2635
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2636
|
+
if (newAgentInstances.length === 0) {
|
|
2637
|
+
const creationDetail = creationErrors.length > 0
|
|
2638
|
+
? creationErrors.map(({ baseagent, message }) => `${baseagent}: ${message}`).join('; ')
|
|
2639
|
+
: `active_baseagent=${agent.config.active_baseagent ?? '(unset)'}, configured=${Object.keys(agent.config.baseagents ?? {}).join(',') || '(none)'}`;
|
|
2640
|
+
agent.status = 'error';
|
|
2641
|
+
agent.error = creationErrors.length > 0
|
|
2642
|
+
? creationDetail
|
|
2643
|
+
: `No baseagent runner created for hot-loaded agent: ${creationDetail}`;
|
|
2644
|
+
agentRuntimeState = 'error';
|
|
2645
|
+
agentRuntimeError = agent.error;
|
|
2646
|
+
throw new Error(agent.error);
|
|
2647
|
+
}
|
|
2648
|
+
// 创建 channels
|
|
2649
|
+
const instances = await channelLoader.createForAgent(agent);
|
|
2650
|
+
for (const inst of instances) {
|
|
2651
|
+
registerChannelInstance(inst);
|
|
2652
|
+
if (inst.channelType === 'aun') {
|
|
2653
|
+
const ch = inst.channel;
|
|
2654
|
+
if (typeof ch?.setAidStatsCollector === 'function')
|
|
2655
|
+
ch.setAidStatsCollector(aidStatsCollector);
|
|
2664
2656
|
}
|
|
2657
|
+
agent.channels.set(inst.adapter.channelKey, inst.adapter);
|
|
2658
|
+
channelInstances.push(inst);
|
|
2665
2659
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
continue;
|
|
2677
|
-
try {
|
|
2678
|
-
await globalThis.__evolcore_hotLoadAgent(cfg.aid);
|
|
2679
|
-
results.push(`+ ${cfg.aid} (online)`);
|
|
2680
|
-
}
|
|
2681
|
-
catch (e) {
|
|
2682
|
-
results.push(`✗ ${cfg.aid}: ${e?.message || e}`);
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
// 3. 已有的:重新 reload(config 可能改了)
|
|
2686
|
-
const hooks = globalThis.__evolcore_reloadHooks;
|
|
2687
|
-
for (const cfg of diskAgents) {
|
|
2688
|
-
if (cfg.enabled === false)
|
|
2689
|
-
continue;
|
|
2690
|
-
if (!agentRegistry.agents.has(cfg.aid))
|
|
2691
|
-
continue;
|
|
2692
|
-
// 只有磁盘上存在且运行时也存在的才 reload
|
|
2693
|
-
try {
|
|
2694
|
-
await agentRegistry.reload(cfg.aid, hooks);
|
|
2695
|
-
const runtimeAgent = agentRegistry.get(cfg.aid);
|
|
2696
|
-
if (runtimeAgent)
|
|
2697
|
-
await startTriggerScheduler(runtimeAgent);
|
|
2698
|
-
results.push(`↻ ${cfg.aid} (reloaded)`);
|
|
2660
|
+
agent.status = 'running';
|
|
2661
|
+
// 连接
|
|
2662
|
+
await channelLoader.connectAll(instances, { onConnected: markChannelConnected });
|
|
2663
|
+
await handoffRuntime.recover([aid]);
|
|
2664
|
+
await ensureTriggerSchedulerStarted(agent);
|
|
2665
|
+
handoffRuntime.resumeAgent(aid);
|
|
2666
|
+
resumed = true;
|
|
2667
|
+
agentRuntimeState = 'running';
|
|
2668
|
+
agentRuntimeError = undefined;
|
|
2669
|
+
logger.info(`[HotLoad] ✓ Agent ${aid} online with ${instances.length} channel(s)`);
|
|
2699
2670
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2671
|
+
finally {
|
|
2672
|
+
if (!resumed)
|
|
2673
|
+
handoffRuntime.resumeAgent(aid);
|
|
2702
2674
|
}
|
|
2703
|
-
}
|
|
2704
|
-
// 重建 channel index(kit 缓存已在本轮开头清过)
|
|
2705
|
-
agentRegistry.channelIndex.clear();
|
|
2706
|
-
agentRegistry.buildChannelIndex();
|
|
2707
|
-
logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
|
|
2708
|
-
return results;
|
|
2709
|
-
};
|
|
2710
|
-
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
2711
|
-
ipcServer.setAgentStatsProvider(() => agentRegistry.list().map((agent) => {
|
|
2712
|
-
const snap = statsCollector.getSnapshot(agent.aid);
|
|
2713
|
-
return {
|
|
2714
|
-
aid: agent.aid,
|
|
2715
|
-
received: snap.lastHour.received,
|
|
2716
|
-
sent: snap.lastHour.sent,
|
|
2717
|
-
completed: snap.lastHour.completed,
|
|
2718
|
-
errors: snap.lastHour.errors,
|
|
2719
|
-
interrupts: snap.lastHour.interrupts,
|
|
2720
|
-
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
2721
|
-
processing: messageQueue.getProcessingCountByAgent(agent.aid),
|
|
2722
|
-
queued: messageQueue.getQueueLengthByAgent(agent.aid),
|
|
2723
|
-
muted: messageQueue.isAgentMuted(agent.aid),
|
|
2724
2675
|
};
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2676
|
+
// Full resync handler: scan disk, load new agents, unload removed/disabled, reload changed
|
|
2677
|
+
globalThis.__evolcore_resyncAgents = async () => {
|
|
2678
|
+
// 先清 kit 缓存:'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload
|
|
2679
|
+
// 策略,平时不查盘。放在扫盘与 reload 之前,本轮上下线的 agent 才能读到磁盘上的
|
|
2680
|
+
// 最新版本——放在末尾的话,这一轮全用旧值,改动要等下一次 resync 才生效。
|
|
2681
|
+
invalidateKitCache();
|
|
2682
|
+
const { loadAllAgents: scanAgents, loadDefaults: readDefaults } = await import('./config-store.js');
|
|
2683
|
+
const { resolveEffective } = await import('./config/config-manager.js');
|
|
2684
|
+
const freshDefaults = readDefaults();
|
|
2685
|
+
const { agents: diskAgents } = scanAgents();
|
|
2686
|
+
const diskAidSet = new Set(diskAgents.map(a => a.aid));
|
|
2687
|
+
const results = [];
|
|
2688
|
+
// 1. 下线:运行时有但磁盘上没有 / disabled 的
|
|
2689
|
+
for (const [aid, agent] of [...agentRegistry.agents.entries()]) {
|
|
2690
|
+
const diskCfg = diskAgents.find(a => a.aid === aid);
|
|
2691
|
+
if (!diskCfg || diskCfg.enabled === false) {
|
|
2692
|
+
handoffRuntime.pauseAgent(aid);
|
|
2693
|
+
try {
|
|
2694
|
+
await handoffRuntime.drainAgent(aid);
|
|
2695
|
+
}
|
|
2696
|
+
catch (error) {
|
|
2697
|
+
handoffRuntime.resumeAgent(aid);
|
|
2698
|
+
results.push(`⚠ ${aid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
await triggerSchedulers.get(aid)?.stop();
|
|
2702
|
+
triggerSchedulers.delete(aid);
|
|
2703
|
+
triggerSchedulerStarts.delete(aid);
|
|
2704
|
+
// 断开所有 channels
|
|
2705
|
+
for (const chName of agent.channelInstanceNames()) {
|
|
2706
|
+
const inst = channelInstances.find(i => i.adapter.channelName === chName);
|
|
2707
|
+
if (inst) {
|
|
2708
|
+
try {
|
|
2709
|
+
await inst.disconnect();
|
|
2710
|
+
}
|
|
2711
|
+
catch { }
|
|
2712
|
+
markChannelDisconnected(inst.adapter.channelName);
|
|
2713
|
+
const idx = channelInstances.indexOf(inst);
|
|
2714
|
+
if (idx >= 0)
|
|
2715
|
+
channelInstances.splice(idx, 1);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
agentRegistry.agents.delete(aid);
|
|
2719
|
+
results.push(`- ${aid} (offline)`);
|
|
2720
|
+
continue;
|
|
2754
2721
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
};
|
|
2766
|
-
const requireAgent = (agentAid) => {
|
|
2767
|
-
if (typeof agentAid !== 'string' || !agentAid)
|
|
2768
|
-
throw new Error('missing agentAid');
|
|
2769
|
-
if (agentAid === daemonTriggerOwner.aid)
|
|
2770
|
-
return agentAid;
|
|
2771
|
-
if (!agentRegistry.get(agentAid))
|
|
2772
|
-
throw new Error(`agent not found: ${agentAid}`);
|
|
2773
|
-
return agentAid;
|
|
2774
|
-
};
|
|
2775
|
-
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
2776
|
-
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
2777
|
-
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
2778
|
-
if (!isCrossAgentTriggerOperationAllowed({
|
|
2779
|
-
control: actor.control,
|
|
2780
|
-
taskAgentAid: actor.selfAid,
|
|
2781
|
-
targetAgentAid: agentAid,
|
|
2782
|
-
targetManagement: actor.management,
|
|
2783
|
-
operation,
|
|
2784
|
-
})) {
|
|
2785
|
-
if (isCrossAgent && !actor.management) {
|
|
2786
|
-
throw new Error('cross-agent trigger audit requires owner/admin access on the target agent');
|
|
2722
|
+
}
|
|
2723
|
+
// 2. 新增:磁盘上有但运行时没有的
|
|
2724
|
+
for (const cfg of diskAgents) {
|
|
2725
|
+
if (cfg.enabled === false)
|
|
2726
|
+
continue;
|
|
2727
|
+
if (agentRegistry.agents.has(cfg.aid))
|
|
2728
|
+
continue;
|
|
2729
|
+
try {
|
|
2730
|
+
await globalThis.__evolcore_hotLoadAgent(cfg.aid);
|
|
2731
|
+
results.push(`+ ${cfg.aid} (online)`);
|
|
2787
2732
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2733
|
+
catch (e) {
|
|
2734
|
+
results.push(`✗ ${cfg.aid}: ${e?.message || e}`);
|
|
2790
2735
|
}
|
|
2791
|
-
throw new Error('trigger agent does not match authenticated task agent');
|
|
2792
2736
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2737
|
+
// 3. 已有的:重新 reload(config 可能改了)
|
|
2738
|
+
const hooks = globalThis.__evolcore_reloadHooks;
|
|
2739
|
+
for (const cfg of diskAgents) {
|
|
2740
|
+
if (cfg.enabled === false)
|
|
2741
|
+
continue;
|
|
2742
|
+
if (!agentRegistry.agents.has(cfg.aid))
|
|
2743
|
+
continue;
|
|
2744
|
+
// 只有磁盘上存在且运行时也存在的才 reload
|
|
2745
|
+
try {
|
|
2746
|
+
await agentRegistry.reload(cfg.aid, hooks);
|
|
2747
|
+
const runtimeAgent = agentRegistry.get(cfg.aid);
|
|
2748
|
+
if (runtimeAgent)
|
|
2749
|
+
await startTriggerScheduler(runtimeAgent);
|
|
2750
|
+
results.push(`↻ ${cfg.aid} (reloaded)`);
|
|
2751
|
+
}
|
|
2752
|
+
catch (e) {
|
|
2753
|
+
results.push(`⚠ ${cfg.aid}: ${e?.message || e}`);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
// 重建 channel index(kit 缓存已在本轮开头清过)
|
|
2757
|
+
agentRegistry.channelIndex.clear();
|
|
2758
|
+
agentRegistry.buildChannelIndex();
|
|
2759
|
+
logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
|
|
2760
|
+
return results;
|
|
2761
|
+
};
|
|
2762
|
+
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
2763
|
+
ipcServer.setAgentStatsProvider(() => agentRegistry.list().map((agent) => {
|
|
2764
|
+
const snap = statsCollector.getSnapshot(agent.aid);
|
|
2765
|
+
return {
|
|
2766
|
+
aid: agent.aid,
|
|
2767
|
+
received: snap.lastHour.received,
|
|
2768
|
+
sent: snap.lastHour.sent,
|
|
2769
|
+
completed: snap.lastHour.completed,
|
|
2770
|
+
errors: snap.lastHour.errors,
|
|
2771
|
+
interrupts: snap.lastHour.interrupts,
|
|
2772
|
+
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
2773
|
+
processing: messageQueue.getProcessingCountByAgent(agent.aid),
|
|
2774
|
+
queued: messageQueue.getQueueLengthByAgent(agent.aid),
|
|
2775
|
+
muted: messageQueue.isAgentMuted(agent.aid),
|
|
2776
|
+
};
|
|
2777
|
+
}));
|
|
2778
|
+
// Queue snapshot & action (for ec queue --agent CLI)
|
|
2779
|
+
ipcServer.setQueueSnapshotProvider((params) => {
|
|
2780
|
+
const handle = agentRegistry.get(params.agent);
|
|
2781
|
+
const agentName = handle?.name;
|
|
2782
|
+
if (!agentName)
|
|
2783
|
+
return [];
|
|
2784
|
+
return messageQueue.getQueueItemsByAgent(agentName);
|
|
2785
|
+
});
|
|
2786
|
+
ipcServer.setQueueActionExecutor(async (params) => {
|
|
2787
|
+
const handle = agentRegistry.get(params.agent);
|
|
2788
|
+
const agentName = handle?.name;
|
|
2789
|
+
if (!agentName)
|
|
2790
|
+
return { ok: false, error: `agent not found: ${params.agent}` };
|
|
2791
|
+
switch (params.action) {
|
|
2792
|
+
case 'clear':
|
|
2793
|
+
return { ok: true, cleared: messageQueue.clearByAgent(agentName) };
|
|
2794
|
+
case 'cancel':
|
|
2795
|
+
if (!params.messageId)
|
|
2796
|
+
return { ok: false, error: 'missing messageId' };
|
|
2797
|
+
return { ok: true, cancelled: messageQueue.cancelMessageById(agentName, params.messageId) };
|
|
2798
|
+
case 'interrupt':
|
|
2799
|
+
if (!params.sessionKey)
|
|
2800
|
+
return { ok: false, error: 'missing sessionKey' };
|
|
2801
|
+
{
|
|
2802
|
+
const sessionId = messageQueue.findSessionIdBySessionKey(params.sessionKey);
|
|
2803
|
+
if (!sessionId)
|
|
2804
|
+
return { ok: false, error: `session not found: ${params.sessionKey}` };
|
|
2805
|
+
return { ok: true, interrupted: await messageQueue.interruptBySession(sessionId) };
|
|
2806
|
+
}
|
|
2807
|
+
default:
|
|
2808
|
+
return { ok: false, error: `unknown action: ${params.action}` };
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
ipcServer.setTriggerExecutor(async (cmd) => {
|
|
2812
|
+
const schedulerFor = (agentAid) => {
|
|
2813
|
+
const scheduler = triggerSchedulers.get(agentAid);
|
|
2814
|
+
if (!scheduler)
|
|
2815
|
+
throw new Error(`trigger scheduler not found for agent: ${agentAid}`);
|
|
2816
|
+
return scheduler;
|
|
2817
|
+
};
|
|
2818
|
+
const requireAgent = (agentAid) => {
|
|
2819
|
+
if (typeof agentAid !== 'string' || !agentAid)
|
|
2820
|
+
throw new Error('missing agentAid');
|
|
2821
|
+
if (agentAid === daemonTriggerOwner.aid)
|
|
2822
|
+
return agentAid;
|
|
2823
|
+
if (!agentRegistry.get(agentAid))
|
|
2824
|
+
throw new Error(`agent not found: ${agentAid}`);
|
|
2825
|
+
return agentAid;
|
|
2826
|
+
};
|
|
2827
|
+
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
2828
|
+
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
2829
|
+
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
2830
|
+
if (!isCrossAgentTriggerOperationAllowed({
|
|
2831
|
+
control: actor.control,
|
|
2832
|
+
taskAgentAid: actor.selfAid,
|
|
2833
|
+
targetAgentAid: agentAid,
|
|
2834
|
+
targetManagement: actor.management,
|
|
2797
2835
|
operation,
|
|
2798
|
-
|
|
2836
|
+
})) {
|
|
2837
|
+
if (isCrossAgent && !actor.management) {
|
|
2838
|
+
throw new Error('cross-agent trigger audit requires owner/admin access on the target agent');
|
|
2839
|
+
}
|
|
2840
|
+
if (isCrossAgent && !isCrossAgentTriggerReadOperation(operation)) {
|
|
2841
|
+
throw new Error('cross-agent trigger operations are limited to list, show, and history');
|
|
2842
|
+
}
|
|
2843
|
+
throw new Error('trigger agent does not match authenticated task agent');
|
|
2844
|
+
}
|
|
2845
|
+
const operationDecision = authorizeOperation({
|
|
2799
2846
|
source: actor.control ? 'control' : 'agent-tool',
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2847
|
+
subject: actor.subject,
|
|
2848
|
+
intent: {
|
|
2849
|
+
operation,
|
|
2850
|
+
scope: 'relation',
|
|
2851
|
+
source: actor.control ? 'control' : 'agent-tool',
|
|
2852
|
+
args: {
|
|
2853
|
+
self: agentAid,
|
|
2854
|
+
peer: actor.origin.peerId,
|
|
2855
|
+
peerKey: actor.subject.peerKey,
|
|
2856
|
+
...(triggerId ? { triggerId } : {}),
|
|
2857
|
+
},
|
|
2805
2858
|
},
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2859
|
+
});
|
|
2860
|
+
if (!operationDecision.allow)
|
|
2861
|
+
throw new Error(operationDecision.reason);
|
|
2862
|
+
if (actor.management)
|
|
2863
|
+
return actor;
|
|
2864
|
+
if (!triggerId)
|
|
2865
|
+
return actor;
|
|
2866
|
+
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
2867
|
+
if (!definition
|
|
2868
|
+
|| definition.origin?.peerId !== actor.origin?.peerId
|
|
2869
|
+
|| definition.origin?.channelKey !== actor.origin?.channelKey) {
|
|
2870
|
+
throw new Error('trigger not found or access denied');
|
|
2871
|
+
}
|
|
2813
2872
|
return actor;
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
case 'trigger.list': {
|
|
2824
|
-
const agentAid = requireAgent(cmd.agentAid);
|
|
2825
|
-
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
2826
|
-
const scheduler = schedulerFor(agentAid);
|
|
2827
|
-
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => actor.management
|
|
2828
|
-
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey));
|
|
2829
|
-
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
2830
|
-
}
|
|
2831
|
-
case 'trigger.show': {
|
|
2832
|
-
const agentAid = requireAgent(cmd.agentAid);
|
|
2833
|
-
if (!cmd.triggerId)
|
|
2834
|
-
throw new Error('missing triggerId');
|
|
2835
|
-
await authorizeTrigger(agentAid, 'trigger.show', cmd.triggerId);
|
|
2836
|
-
return {
|
|
2837
|
-
ok: true,
|
|
2838
|
-
...schedulerFor(agentAid).show(cmd.triggerId, {
|
|
2839
|
-
includeScriptPreview: cmd.includeScriptPreview !== false,
|
|
2840
|
-
}),
|
|
2841
|
-
};
|
|
2842
|
-
}
|
|
2843
|
-
case 'trigger.history': {
|
|
2844
|
-
const agentAid = requireAgent(cmd.agentAid);
|
|
2845
|
-
const limit = cmd.limit === undefined ? 100 : Number(cmd.limit);
|
|
2846
|
-
if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000)
|
|
2847
|
-
throw new Error('invalid history limit');
|
|
2848
|
-
if (cmd.triggerId !== undefined && (typeof cmd.triggerId !== 'string' || !cmd.triggerId)) {
|
|
2849
|
-
throw new Error('invalid triggerId');
|
|
2873
|
+
};
|
|
2874
|
+
switch (cmd.type) {
|
|
2875
|
+
case 'trigger.list': {
|
|
2876
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2877
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
2878
|
+
const scheduler = schedulerFor(agentAid);
|
|
2879
|
+
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => actor.management
|
|
2880
|
+
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey));
|
|
2881
|
+
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
2850
2882
|
}
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2883
|
+
case 'trigger.show': {
|
|
2884
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2885
|
+
if (!cmd.triggerId)
|
|
2886
|
+
throw new Error('missing triggerId');
|
|
2887
|
+
await authorizeTrigger(agentAid, 'trigger.show', cmd.triggerId);
|
|
2888
|
+
return {
|
|
2889
|
+
ok: true,
|
|
2890
|
+
...schedulerFor(agentAid).show(cmd.triggerId, {
|
|
2891
|
+
includeScriptPreview: cmd.includeScriptPreview !== false,
|
|
2892
|
+
}),
|
|
2893
|
+
};
|
|
2855
2894
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2895
|
+
case 'trigger.history': {
|
|
2896
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2897
|
+
const limit = cmd.limit === undefined ? 100 : Number(cmd.limit);
|
|
2898
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > 10_000)
|
|
2899
|
+
throw new Error('invalid history limit');
|
|
2900
|
+
if (cmd.triggerId !== undefined && (typeof cmd.triggerId !== 'string' || !cmd.triggerId)) {
|
|
2901
|
+
throw new Error('invalid triggerId');
|
|
2902
|
+
}
|
|
2903
|
+
if (cmd.triggerId)
|
|
2904
|
+
await authorizeTrigger(agentAid, 'trigger.history', cmd.triggerId);
|
|
2905
|
+
else if (!(await authorizeTrigger(agentAid, 'trigger.history')).management) {
|
|
2906
|
+
throw new Error('triggerId is required for non-management history access');
|
|
2907
|
+
}
|
|
2908
|
+
return { ok: true, events: schedulerFor(agentAid).history(cmd.triggerId, limit) };
|
|
2867
2909
|
}
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
validateTriggerDefinitionForActor(definition, actor);
|
|
2873
|
-
requireAgent(definition.agentAid);
|
|
2874
|
-
validateTriggerFeedbackChannels(definition);
|
|
2875
|
-
const trigger = schedulerFor(definition.agentAid).create(definition, cmd.files ?? [], { enable: cmd.enable });
|
|
2876
|
-
return { ok: true, trigger };
|
|
2877
|
-
}
|
|
2878
|
-
case 'trigger.update': {
|
|
2879
|
-
const agentAid = requireAgent(cmd.agentAid);
|
|
2880
|
-
if (!cmd.triggerId)
|
|
2881
|
-
throw new Error('missing triggerId');
|
|
2882
|
-
const actor = await authorizeTrigger(agentAid, 'trigger.update', cmd.triggerId);
|
|
2883
|
-
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
2884
|
-
if (!existing)
|
|
2885
|
-
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
2886
|
-
const currentRevision = definitionRevision(existing);
|
|
2887
|
-
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
2888
|
-
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
2910
|
+
case 'trigger.eventCatalog': {
|
|
2911
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2912
|
+
await authorizeTrigger(agentAid, 'trigger.eventCatalog');
|
|
2913
|
+
return { ok: true, ...getEventCatalog({ includeInternal: cmd.includeInternal === true }) };
|
|
2889
2914
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2915
|
+
case 'trigger.create': {
|
|
2916
|
+
const rawDefinition = cmd.definition;
|
|
2917
|
+
if (!rawDefinition || typeof rawDefinition !== 'object' || Array.isArray(rawDefinition)) {
|
|
2918
|
+
throw new Error('trigger definition must be an object');
|
|
2919
|
+
}
|
|
2920
|
+
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
2921
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
2922
|
+
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
|
|
2923
|
+
const definition = normalizeTriggerDefinition(materialized);
|
|
2924
|
+
validateTriggerDefinitionForActor(definition, actor);
|
|
2925
|
+
requireAgent(definition.agentAid);
|
|
2926
|
+
validateTriggerFeedbackChannels(definition);
|
|
2927
|
+
const trigger = schedulerFor(definition.agentAid).create(definition, cmd.files ?? [], { enable: cmd.enable });
|
|
2928
|
+
return { ok: true, trigger };
|
|
2929
|
+
}
|
|
2930
|
+
case 'trigger.update': {
|
|
2931
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2932
|
+
if (!cmd.triggerId)
|
|
2933
|
+
throw new Error('missing triggerId');
|
|
2934
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.update', cmd.triggerId);
|
|
2935
|
+
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
2936
|
+
if (!existing)
|
|
2937
|
+
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
2938
|
+
const currentRevision = definitionRevision(existing);
|
|
2939
|
+
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
2940
|
+
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
2941
|
+
}
|
|
2942
|
+
const definition = applyTriggerPatch(existing, cmd.patch, { fromPromptFile: cmd.promptFile === true });
|
|
2943
|
+
validateTriggerDefinitionForActor(definition, actor);
|
|
2944
|
+
validateTriggerFeedbackChannels(definition);
|
|
2945
|
+
const scheduler = schedulerFor(agentAid);
|
|
2946
|
+
const trigger = scheduler.update(cmd.triggerId, definition);
|
|
2947
|
+
return { ok: true, trigger: scheduler.listItem(trigger), revision: definitionRevision(trigger) };
|
|
2948
|
+
}
|
|
2949
|
+
case 'trigger.setEnabled': {
|
|
2950
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2951
|
+
if (!cmd.triggerId)
|
|
2952
|
+
throw new Error('missing triggerId');
|
|
2953
|
+
if (typeof cmd.enabled !== 'boolean')
|
|
2954
|
+
throw new Error('missing enabled');
|
|
2955
|
+
await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
2956
|
+
const trigger = schedulerFor(agentAid).setEnabled(cmd.triggerId, cmd.enabled);
|
|
2957
|
+
return { ok: true, trigger };
|
|
2958
|
+
}
|
|
2959
|
+
case 'trigger.cancel': {
|
|
2960
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2961
|
+
if (!cmd.triggerId)
|
|
2962
|
+
throw new Error('missing triggerId');
|
|
2963
|
+
await authorizeTrigger(agentAid, 'trigger.cancel', cmd.triggerId);
|
|
2964
|
+
const trigger = schedulerFor(agentAid).cancel(cmd.triggerId);
|
|
2965
|
+
return { ok: true, trigger };
|
|
2966
|
+
}
|
|
2967
|
+
case 'trigger.delete': {
|
|
2968
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2969
|
+
if (!cmd.triggerId)
|
|
2970
|
+
throw new Error('missing triggerId');
|
|
2971
|
+
await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
2972
|
+
const trigger = schedulerFor(agentAid).delete(cmd.triggerId);
|
|
2973
|
+
return { ok: true, trigger };
|
|
2974
|
+
}
|
|
2975
|
+
case 'trigger.run': {
|
|
2976
|
+
const agentAid = requireAgent(cmd.agentAid);
|
|
2977
|
+
if (!cmd.triggerId)
|
|
2978
|
+
throw new Error('missing triggerId');
|
|
2979
|
+
await authorizeTrigger(agentAid, 'trigger.run', cmd.triggerId);
|
|
2980
|
+
const result = await schedulerFor(agentAid).run(cmd.triggerId, {
|
|
2981
|
+
dryRun: cmd.dryRun === true,
|
|
2982
|
+
...(cmd.eventPayload !== undefined ? { eventPayload: cmd.eventPayload } : {}),
|
|
2983
|
+
});
|
|
2984
|
+
return {
|
|
2985
|
+
ok: result.ok,
|
|
2986
|
+
result,
|
|
2987
|
+
runId: result.runId,
|
|
2988
|
+
triggerId: result.triggerId,
|
|
2989
|
+
status: result.status,
|
|
2990
|
+
reason: result.reason,
|
|
2991
|
+
conflictRunId: result.conflictRunId,
|
|
2992
|
+
error: result.error,
|
|
2993
|
+
audit: result.audit,
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
default:
|
|
2997
|
+
return { ok: false, error: `unknown trigger command: ${cmd.type}` };
|
|
2943
2998
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
}
|
|
2947
|
-
});
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
2948
3001
|
ipcServer.startCpuTracking();
|
|
2949
|
-
//
|
|
2950
|
-
|
|
2951
|
-
// 写入 ready 信号(Control Plane 已可通过 IPC 查询;channel 连接不阻塞启动判定)
|
|
3002
|
+
// IPC was bound before channel connection. At this point all handlers are
|
|
3003
|
+
// registered, so the ready signal can safely expose the control plane.
|
|
2952
3004
|
const readySignalPath = resolvePaths().readySignal;
|
|
2953
3005
|
fs.writeFileSync(readySignalPath, String(Date.now()));
|
|
2954
3006
|
logger.info(`✓ Ready signal written: ${readySignalPath}`);
|