evolcore 0.0.17 → 0.0.19
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 +51 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +43 -24
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +82 -14
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/data-migration.js +10 -4
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +66 -8
- package/dist/core/message/response-engine.js +147 -10
- package/dist/core/permission/ec-command-parser.js +148 -22
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +19 -7
- package/dist/index.js +357 -48
- package/dist/ipc.js +81 -5
- package/dist/paths.js +0 -3
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/docs/path-registry.md +1 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ import { buildEnvelope, isDeliveryTargetForChannel, replyContextFromSession } fr
|
|
|
42
42
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
43
43
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
44
44
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
45
|
-
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
|
|
45
|
+
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
46
46
|
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
47
47
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
48
48
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
@@ -51,13 +51,14 @@ import { postBootstrapWelcomeOperationId } from './core/bootstrap-messages.js';
|
|
|
51
51
|
import { MessageCache } from './core/message/message-cache.js';
|
|
52
52
|
import { CommandHandler, isProcessLevelOwner } from './core/command/command-handler.js';
|
|
53
53
|
import { EventBus } from './core/event-bus.js';
|
|
54
|
-
import { getEventCatalog } from './core/event-catalog.js';
|
|
54
|
+
import { eventPatternIncludesInternal, getEventCatalog } from './core/event-catalog.js';
|
|
55
55
|
import { StatsCollector } from './utils/stats.js';
|
|
56
56
|
import { AidStatsCollector } from './utils/stats.js';
|
|
57
57
|
import { PermissionGateway } from './core/permission/approval-gateway.js';
|
|
58
58
|
import { InteractionRouter, registerAdapterInteractions } from './core/interaction-router.js';
|
|
59
59
|
import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend } from './core/auth/agent-delegation.js';
|
|
60
|
-
import { authorizeOperation, buildAuthSubject, isCrossAgentTriggerOperationAllowed,
|
|
60
|
+
import { authorizeOperation, buildAuthSubject, isCrossAgentTriggerOperationAllowed, } from './core/auth/auth-gateway.js';
|
|
61
|
+
import { authorizationDenialData, formatAuthorizationDenial } from './core/auth/authorization-denial.js';
|
|
61
62
|
import { parsePeerKey } from './core/relation/peer-identity.js';
|
|
62
63
|
import { isHClassPath } from './core/protected-paths.js';
|
|
63
64
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
@@ -481,6 +482,9 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
481
482
|
});
|
|
482
483
|
bindService.startCleanup();
|
|
483
484
|
let controlChannel;
|
|
485
|
+
let shutdown;
|
|
486
|
+
let pendingShutdownReason;
|
|
487
|
+
let shutdownInProgress = false;
|
|
484
488
|
controlChannel = new AUNChannel({
|
|
485
489
|
aid: daemonCfg.aid,
|
|
486
490
|
agentName: daemonCfg.aid,
|
|
@@ -535,12 +539,21 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
535
539
|
gatewayUrl: aidState.gatewayUrl,
|
|
536
540
|
reconnectCount: aidState.reconnectCount,
|
|
537
541
|
lastConnectedAt: aidState.lastConnectedAt,
|
|
542
|
+
lastTransportEventAt: aidState.lastTransportEventAt,
|
|
543
|
+
lastInboundAt: aidState.lastInboundAt,
|
|
538
544
|
lastError: aidState.lastError,
|
|
539
545
|
} : {}),
|
|
540
546
|
};
|
|
541
547
|
})(),
|
|
542
548
|
}));
|
|
543
549
|
ipcServer.setControlToken(controlToken);
|
|
550
|
+
ipcServer.setShutdownExecutor(async (reason) => {
|
|
551
|
+
if (!shutdown) {
|
|
552
|
+
pendingShutdownReason = reason;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
await shutdown(reason);
|
|
556
|
+
});
|
|
544
557
|
ipcServer.setBindExecutor({
|
|
545
558
|
begin: (cmd) => bindService.begin(cmd),
|
|
546
559
|
status: (taskId) => bindService.status(taskId),
|
|
@@ -549,9 +562,16 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
549
562
|
await ipcServer.start();
|
|
550
563
|
fs.writeFileSync(resolvePaths().readySignal, String(Date.now()));
|
|
551
564
|
logger.info(`✓ Bind bootstrap ready signal written: ${resolvePaths().readySignal}`);
|
|
552
|
-
|
|
565
|
+
shutdown = async (signal) => {
|
|
566
|
+
if (shutdownInProgress)
|
|
567
|
+
return;
|
|
568
|
+
shutdownInProgress = true;
|
|
553
569
|
logger.info(`[bind-bootstrap] shutting down${signal ? ` (${signal})` : ''}`);
|
|
554
570
|
ipcServer.stop();
|
|
571
|
+
try {
|
|
572
|
+
fs.unlinkSync(resolvePaths().readySignal);
|
|
573
|
+
}
|
|
574
|
+
catch { }
|
|
555
575
|
bindService.stopCleanup();
|
|
556
576
|
if (controlChannel) {
|
|
557
577
|
try {
|
|
@@ -562,6 +582,10 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
562
582
|
removeAll();
|
|
563
583
|
process.exit(0);
|
|
564
584
|
};
|
|
585
|
+
if (pendingShutdownReason) {
|
|
586
|
+
const reason = pendingShutdownReason;
|
|
587
|
+
setImmediate(() => { void shutdown?.(reason); });
|
|
588
|
+
}
|
|
565
589
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
566
590
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
567
591
|
process.on('exit', () => removeAll());
|
|
@@ -1390,11 +1414,29 @@ async function main() {
|
|
|
1390
1414
|
throw Object.assign(new Error('AUN trigger feedback target requires a valid explicit delivery route'), { code: 'AUN_OUTBOUND_ROUTE_REQUIRED' });
|
|
1391
1415
|
}
|
|
1392
1416
|
};
|
|
1393
|
-
const
|
|
1394
|
-
if (definition.
|
|
1395
|
-
|
|
1417
|
+
const daemonPrivilegedTriggerReason = (definition) => {
|
|
1418
|
+
if (definition.origin?.channelKey === 'daemon' || definition.origin?.channelType === 'daemon') {
|
|
1419
|
+
return 'daemon/control-origin Trigger';
|
|
1396
1420
|
}
|
|
1397
|
-
if (definition.execution.
|
|
1421
|
+
if (definition.execution.type === 'script')
|
|
1422
|
+
return 'script Trigger';
|
|
1423
|
+
if (definition.execution.permissionMode === 'bypass')
|
|
1424
|
+
return 'persistent bypass Trigger';
|
|
1425
|
+
if (definition.source.type === 'event' && eventPatternIncludesInternal(definition.source.eventPattern)) {
|
|
1426
|
+
return 'internal-event Trigger';
|
|
1427
|
+
}
|
|
1428
|
+
if (definition.feedback.strategy === 'target'
|
|
1429
|
+
&& definition.feedback.target?.channelKey === 'daemon') {
|
|
1430
|
+
return 'daemon-control feedback';
|
|
1431
|
+
}
|
|
1432
|
+
return undefined;
|
|
1433
|
+
};
|
|
1434
|
+
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service');
|
|
1435
|
+
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1436
|
+
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1437
|
+
if (privilegedReason && !actor.daemonPrivileged)
|
|
1438
|
+
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
1439
|
+
if (definition.execution.permissionMode && !actor.daemonPrivileged) {
|
|
1398
1440
|
const permissionDecision = constrainRuntimePermissionMode({
|
|
1399
1441
|
selfAid: definition.agentAid,
|
|
1400
1442
|
role: actor.role,
|
|
@@ -1404,7 +1446,7 @@ async function main() {
|
|
|
1404
1446
|
throw new Error(`trigger permissionMode ${definition.execution.permissionMode} exceeds current role ${actor.role}`);
|
|
1405
1447
|
}
|
|
1406
1448
|
}
|
|
1407
|
-
if (definition.execution.model) {
|
|
1449
|
+
if (definition.execution.model && !actor.daemonPrivileged) {
|
|
1408
1450
|
const agent = agentRegistry.get(definition.agentAid);
|
|
1409
1451
|
const modelDecision = validateModelSelectionForRole({
|
|
1410
1452
|
role: actor.role,
|
|
@@ -1415,7 +1457,7 @@ async function main() {
|
|
|
1415
1457
|
if (!modelDecision.ok)
|
|
1416
1458
|
throw new Error(modelDecision.message || 'trigger model is not allowed by current role');
|
|
1417
1459
|
}
|
|
1418
|
-
if (definition.execution.effort) {
|
|
1460
|
+
if (definition.execution.effort && !actor.daemonPrivileged) {
|
|
1419
1461
|
const baseagent = definition.execution.baseagent
|
|
1420
1462
|
?? agentRegistry.get(definition.agentAid)?.baseagent
|
|
1421
1463
|
?? 'claude';
|
|
@@ -1453,7 +1495,7 @@ async function main() {
|
|
|
1453
1495
|
channelId: origin.channelId,
|
|
1454
1496
|
chatType: 'private',
|
|
1455
1497
|
conversationId: origin.peerId,
|
|
1456
|
-
|
|
1498
|
+
trustedProcessRole: 'daemon-service',
|
|
1457
1499
|
fromControlChannel: true,
|
|
1458
1500
|
});
|
|
1459
1501
|
const decision = authorizeOperation({
|
|
@@ -1493,6 +1535,7 @@ async function main() {
|
|
|
1493
1535
|
conversationId: origin.peerId,
|
|
1494
1536
|
roleDetail,
|
|
1495
1537
|
identity: roleToSessionIdentity(role),
|
|
1538
|
+
processOwners: loadDaemonConfig().owners ?? [],
|
|
1496
1539
|
fromControlChannel: false,
|
|
1497
1540
|
});
|
|
1498
1541
|
const operationDecision = authorizeOperation({
|
|
@@ -1508,8 +1551,9 @@ async function main() {
|
|
|
1508
1551
|
if (!operationDecision.allow) {
|
|
1509
1552
|
return { allowed: false, reason: operationDecision.reason };
|
|
1510
1553
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1554
|
+
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1555
|
+
if (privilegedReason && !hasDaemonTriggerAuthority(subject)) {
|
|
1556
|
+
return { allowed: false, reason: `${privilegedReason} requires current DaemonOwner` };
|
|
1513
1557
|
}
|
|
1514
1558
|
if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
|
|
1515
1559
|
const target = definition.feedback.target;
|
|
@@ -1548,14 +1592,16 @@ async function main() {
|
|
|
1548
1592
|
channelId: origin.channelId,
|
|
1549
1593
|
chatType: 'private',
|
|
1550
1594
|
conversationId: origin.peerId,
|
|
1551
|
-
|
|
1595
|
+
trustedProcessRole: 'daemon-service',
|
|
1552
1596
|
fromControlChannel: true,
|
|
1553
1597
|
});
|
|
1554
1598
|
return {
|
|
1555
1599
|
origin,
|
|
1556
1600
|
management: true,
|
|
1601
|
+
daemonOwner: false,
|
|
1602
|
+
daemonPrivileged: true,
|
|
1557
1603
|
control: true,
|
|
1558
|
-
role: '
|
|
1604
|
+
role: 'none',
|
|
1559
1605
|
subject,
|
|
1560
1606
|
};
|
|
1561
1607
|
}
|
|
@@ -1580,9 +1626,6 @@ async function main() {
|
|
|
1580
1626
|
conversationId: validation.grant.actorId,
|
|
1581
1627
|
});
|
|
1582
1628
|
const currentRole = roleDetail.effectiveRole;
|
|
1583
|
-
if (!checkRoleAccess(currentRole, agentAid)) {
|
|
1584
|
-
throw new Error('trigger actor no longer has access to this agent');
|
|
1585
|
-
}
|
|
1586
1629
|
const subject = buildAuthSubject({
|
|
1587
1630
|
selfAid: agentAid,
|
|
1588
1631
|
actorId: validation.grant.actorId,
|
|
@@ -1593,11 +1636,20 @@ async function main() {
|
|
|
1593
1636
|
conversationId: validation.grant.actorId,
|
|
1594
1637
|
roleDetail,
|
|
1595
1638
|
identity: roleToSessionIdentity(currentRole),
|
|
1639
|
+
processOwners: loadDaemonConfig().owners ?? [],
|
|
1596
1640
|
fromControlChannel: false,
|
|
1597
1641
|
});
|
|
1642
|
+
if (validation.grant.selfAid !== agentAid && !subject.isDaemonOwner) {
|
|
1643
|
+
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
1644
|
+
}
|
|
1645
|
+
if (!subject.isDaemonOwner && !checkRoleAccess(currentRole, agentAid)) {
|
|
1646
|
+
throw new Error('trigger actor no longer has access to this agent');
|
|
1647
|
+
}
|
|
1598
1648
|
return {
|
|
1599
1649
|
origin: actorOrigin,
|
|
1600
|
-
management: isManagementRole(currentRole),
|
|
1650
|
+
management: isManagementRole(currentRole) || subject.isDaemonOwner,
|
|
1651
|
+
daemonOwner: subject.isDaemonOwner,
|
|
1652
|
+
daemonPrivileged: subject.isDaemonOwner,
|
|
1601
1653
|
selfAid: validation.grant.selfAid,
|
|
1602
1654
|
control: false,
|
|
1603
1655
|
role: currentRole || 'none',
|
|
@@ -2124,6 +2176,8 @@ async function main() {
|
|
|
2124
2176
|
// A failed bind (for example, a stale Windows named pipe) must abort startup
|
|
2125
2177
|
// without creating a second AUN session that can kick the old daemon.
|
|
2126
2178
|
let controlChannel;
|
|
2179
|
+
let shutdown;
|
|
2180
|
+
let pendingShutdownReason;
|
|
2127
2181
|
const ipcServer = new IpcServer(resolvePaths().socket, () => {
|
|
2128
2182
|
const channels = {};
|
|
2129
2183
|
const channelsByType = {};
|
|
@@ -2176,6 +2230,8 @@ async function main() {
|
|
|
2176
2230
|
gatewayUrl: aidState.gatewayUrl,
|
|
2177
2231
|
reconnectCount: aidState.reconnectCount,
|
|
2178
2232
|
lastConnectedAt: aidState.lastConnectedAt,
|
|
2233
|
+
lastTransportEventAt: aidState.lastTransportEventAt,
|
|
2234
|
+
lastInboundAt: aidState.lastInboundAt,
|
|
2179
2235
|
lastError: aidState.lastError,
|
|
2180
2236
|
} : {}),
|
|
2181
2237
|
};
|
|
@@ -2259,7 +2315,15 @@ async function main() {
|
|
|
2259
2315
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
2260
2316
|
// 发送方身份由 AUN X.509 证书链验证,非 owner 完全静默。
|
|
2261
2317
|
controlChannel.onMessage(async (opts) => {
|
|
2318
|
+
let activeMenuRequest;
|
|
2319
|
+
let menuCompletionLogged = false;
|
|
2320
|
+
const menuFlowContext = {
|
|
2321
|
+
source: 'control-aun',
|
|
2322
|
+
selfAid: daemonCfg.aid,
|
|
2323
|
+
messageId: opts.messageId,
|
|
2324
|
+
};
|
|
2262
2325
|
try {
|
|
2326
|
+
const menuTiming = normalizeMenuResponseTiming(opts);
|
|
2263
2327
|
const text = (opts.content || '').trim();
|
|
2264
2328
|
let parsed;
|
|
2265
2329
|
try {
|
|
@@ -2269,6 +2333,28 @@ async function main() {
|
|
|
2269
2333
|
parsed = null;
|
|
2270
2334
|
}
|
|
2271
2335
|
const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
|
|
2336
|
+
const sendControlMenuResponse = async (response) => {
|
|
2337
|
+
const timedResponse = withMenuProcessingTime(response, menuTiming);
|
|
2338
|
+
const request = activeMenuRequest ?? {
|
|
2339
|
+
type: 'menu.unknown',
|
|
2340
|
+
id: response.id,
|
|
2341
|
+
...(response.name ? { name: response.name } : {}),
|
|
2342
|
+
};
|
|
2343
|
+
try {
|
|
2344
|
+
await controlChannel.sendStructured(opts.channelId, timedResponse, menuResponseContext());
|
|
2345
|
+
logMenuRequestCompleted(request, timedResponse, menuFlowContext, { delivery: 'sent' });
|
|
2346
|
+
menuCompletionLogged = true;
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
logMenuRequestCompleted(request, timedResponse, menuFlowContext, {
|
|
2350
|
+
delivery: 'failed',
|
|
2351
|
+
reason: 'transport-error',
|
|
2352
|
+
transportError: error,
|
|
2353
|
+
});
|
|
2354
|
+
menuCompletionLogged = true;
|
|
2355
|
+
throw error;
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
2272
2358
|
if (bindService && parsed?.type === 'bind.request') {
|
|
2273
2359
|
const response = await bindService.handleRequest(parsed, opts.peerId);
|
|
2274
2360
|
if (response) {
|
|
@@ -2280,18 +2366,32 @@ async function main() {
|
|
|
2280
2366
|
return;
|
|
2281
2367
|
}
|
|
2282
2368
|
const menuControl = parseMenuControl(text);
|
|
2369
|
+
if (menuControl.isMenu) {
|
|
2370
|
+
activeMenuRequest = menuControl.request;
|
|
2371
|
+
logMenuRequestReceived(menuControl.request, menuFlowContext);
|
|
2372
|
+
}
|
|
2283
2373
|
const isRoleMenu = menuControl.isMenu
|
|
2284
2374
|
&& (menuControl.name === 'role' || menuControl.request.cmd === '/role');
|
|
2375
|
+
const currentProcessOwners = loadDaemonConfig().owners ?? [];
|
|
2285
2376
|
if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
|
|
2286
2377
|
logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
|
|
2378
|
+
logMenuRequestCompleted(menuControl.request, undefined, menuFlowContext, {
|
|
2379
|
+
delivery: 'dropped',
|
|
2380
|
+
reason: 'missing-id',
|
|
2381
|
+
});
|
|
2382
|
+
menuCompletionLogged = true;
|
|
2287
2383
|
return;
|
|
2288
2384
|
}
|
|
2289
|
-
if (menuControl.isMenu && !isRoleMenu && !isProcessLevelOwner(opts.peerId,
|
|
2290
|
-
const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, {
|
|
2291
|
-
|
|
2385
|
+
if (menuControl.isMenu && !isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2386
|
+
const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, {
|
|
2387
|
+
code: 'NOT_ALLOWED',
|
|
2388
|
+
message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
2389
|
+
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
2390
|
+
});
|
|
2391
|
+
await sendControlMenuResponse(response);
|
|
2292
2392
|
return;
|
|
2293
2393
|
}
|
|
2294
|
-
if (!isRoleMenu && !isProcessLevelOwner(opts.peerId,
|
|
2394
|
+
if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2295
2395
|
logger.debug(`控制 AID 收到非 owner 消息,忽略: from=${opts.peerId}`);
|
|
2296
2396
|
return;
|
|
2297
2397
|
}
|
|
@@ -2316,12 +2416,12 @@ async function main() {
|
|
|
2316
2416
|
protectedHeaders: opts.protectedHeaders,
|
|
2317
2417
|
});
|
|
2318
2418
|
if (versionError) {
|
|
2319
|
-
await
|
|
2419
|
+
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError));
|
|
2320
2420
|
return;
|
|
2321
2421
|
}
|
|
2322
2422
|
const validationError = validateMenuRequest(menuControl.request);
|
|
2323
2423
|
if (validationError) {
|
|
2324
|
-
await
|
|
2424
|
+
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError));
|
|
2325
2425
|
return;
|
|
2326
2426
|
}
|
|
2327
2427
|
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId));
|
|
@@ -2340,13 +2440,20 @@ async function main() {
|
|
|
2340
2440
|
: deduped.value;
|
|
2341
2441
|
// 同 bind.response:sendStructured 直发 typed payload(payload.type='menu.response'),
|
|
2342
2442
|
// 不能用 sendMessage(会包成 {type:'text',...});encrypted 跟随入站请求保持对称。
|
|
2343
|
-
await
|
|
2443
|
+
await sendControlMenuResponse(response);
|
|
2344
2444
|
return;
|
|
2345
2445
|
}
|
|
2346
2446
|
// owner 发的其他内容:提示可用指令
|
|
2347
2447
|
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)', controlReplyContext(opts));
|
|
2348
2448
|
}
|
|
2349
2449
|
catch (e) {
|
|
2450
|
+
if (activeMenuRequest && !menuCompletionLogged) {
|
|
2451
|
+
logMenuRequestCompleted(activeMenuRequest, undefined, menuFlowContext, {
|
|
2452
|
+
delivery: 'failed',
|
|
2453
|
+
reason: 'handler-error',
|
|
2454
|
+
transportError: e,
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2350
2457
|
logger.warn(`控制 AID 消息处理失败: ${e?.message || e}`);
|
|
2351
2458
|
}
|
|
2352
2459
|
});
|
|
@@ -2502,6 +2609,13 @@ async function main() {
|
|
|
2502
2609
|
}
|
|
2503
2610
|
}
|
|
2504
2611
|
function configureIpc() {
|
|
2612
|
+
ipcServer.setShutdownExecutor(async (reason) => {
|
|
2613
|
+
if (!shutdown) {
|
|
2614
|
+
pendingShutdownReason = reason;
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
await shutdown(reason);
|
|
2618
|
+
});
|
|
2505
2619
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2506
2620
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2507
2621
|
ipcServer.setControlToken(triggerControlToken);
|
|
@@ -2657,7 +2771,7 @@ async function main() {
|
|
|
2657
2771
|
for (const runner of new Set(agentMap.values())) {
|
|
2658
2772
|
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2659
2773
|
continue;
|
|
2660
|
-
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput);
|
|
2774
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal);
|
|
2661
2775
|
if (result.applicable)
|
|
2662
2776
|
return result;
|
|
2663
2777
|
}
|
|
@@ -2699,7 +2813,7 @@ async function main() {
|
|
|
2699
2813
|
channelId: conversationId,
|
|
2700
2814
|
chatType: delegation.grant.chatType,
|
|
2701
2815
|
conversationId,
|
|
2702
|
-
processOwners:
|
|
2816
|
+
processOwners: loadDaemonConfig().owners ?? [],
|
|
2703
2817
|
});
|
|
2704
2818
|
const managementRole = isManagementRole(subject.role);
|
|
2705
2819
|
const decision = authorizeOperation({
|
|
@@ -3317,9 +3431,6 @@ async function main() {
|
|
|
3317
3431
|
if (!session || session.selfAID !== delegation.grant.selfAid) {
|
|
3318
3432
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation does not match the current session' };
|
|
3319
3433
|
}
|
|
3320
|
-
if (params.targetAid && params.targetAid !== delegation.grant.selfAid) {
|
|
3321
|
-
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3322
|
-
}
|
|
3323
3434
|
let conversationId;
|
|
3324
3435
|
try {
|
|
3325
3436
|
conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
|
|
@@ -3335,10 +3446,10 @@ async function main() {
|
|
|
3335
3446
|
channelId: conversationId,
|
|
3336
3447
|
chatType: delegation.grant.chatType,
|
|
3337
3448
|
conversationId,
|
|
3338
|
-
processOwners:
|
|
3449
|
+
processOwners: loadDaemonConfig().owners ?? [],
|
|
3339
3450
|
});
|
|
3340
|
-
if (!
|
|
3341
|
-
return { ok: false, code: '
|
|
3451
|
+
if (params.targetAid && params.targetAid !== delegation.grant.selfAid && !subject.isDaemonOwner) {
|
|
3452
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3342
3453
|
}
|
|
3343
3454
|
const decision = authorizeOperation({
|
|
3344
3455
|
source: 'agent-tool',
|
|
@@ -3355,6 +3466,180 @@ async function main() {
|
|
|
3355
3466
|
? { ok: true, aid: delegation.grant.selfAid }
|
|
3356
3467
|
: { ok: false, code: decision.code, error: decision.reason };
|
|
3357
3468
|
}
|
|
3469
|
+
ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
3470
|
+
const fail = (code, error, reasonCode) => ({
|
|
3471
|
+
ok: false,
|
|
3472
|
+
code,
|
|
3473
|
+
error,
|
|
3474
|
+
...(reasonCode ? { data: authorizationDenialData(reasonCode) } : {}),
|
|
3475
|
+
});
|
|
3476
|
+
if (!Array.isArray(argv) || argv[0] !== 'agent')
|
|
3477
|
+
return fail('INVALID_REQUEST', 'agent.op requires an agent command');
|
|
3478
|
+
const sub = argv[1];
|
|
3479
|
+
if (!sub)
|
|
3480
|
+
return fail('INVALID_ARGS', 'missing agent subcommand');
|
|
3481
|
+
const value = (flag) => {
|
|
3482
|
+
const index = argv.indexOf(flag);
|
|
3483
|
+
return index >= 0 && index + 1 < argv.length && !argv[index + 1].startsWith('--') ? argv[index + 1] : undefined;
|
|
3484
|
+
};
|
|
3485
|
+
const taskSession = await sessionManager.getSessionById(sessionId);
|
|
3486
|
+
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
3487
|
+
if (!delegation.ok)
|
|
3488
|
+
return fail(delegation.code, delegation.reason);
|
|
3489
|
+
if (!taskSession || taskSession.selfAID !== delegation.grant.selfAid) {
|
|
3490
|
+
return fail('INVALID_DELEGATION', 'delegation does not match the current session');
|
|
3491
|
+
}
|
|
3492
|
+
const taskAid = delegation.grant.selfAid;
|
|
3493
|
+
const targetAid = sub === 'list'
|
|
3494
|
+
? undefined
|
|
3495
|
+
: sub === 'show'
|
|
3496
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : taskAid)
|
|
3497
|
+
: sub === 'reload'
|
|
3498
|
+
? (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined)
|
|
3499
|
+
: (argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined);
|
|
3500
|
+
const operation = sub === 'list' ? 'agent.list'
|
|
3501
|
+
: sub === 'show' ? 'agent.show'
|
|
3502
|
+
: sub === 'new' ? 'agent.create'
|
|
3503
|
+
: sub === 'enable' ? 'agent.enable'
|
|
3504
|
+
: sub === 'disable' ? 'agent.disable'
|
|
3505
|
+
: sub === 'reload' ? 'agent.reload'
|
|
3506
|
+
: sub === 'delete' ? 'agent.delete' : undefined;
|
|
3507
|
+
if (!operation)
|
|
3508
|
+
return fail('INVALID_ARGS', `unsupported managed agent command: ${sub}`);
|
|
3509
|
+
if (sub === 'new' && !argv.includes('--non-interactive')) {
|
|
3510
|
+
return fail('MANAGED_NON_INTERACTIVE_REQUIRED', 'interactive Agent creation is unavailable in a managed task', 'MANAGED_NON_INTERACTIVE_REQUIRED');
|
|
3511
|
+
}
|
|
3512
|
+
if (sub === 'delete' && argv.includes('--purge') && !argv.includes('--confirm-purge')) {
|
|
3513
|
+
return fail('PURGE_CONFIRMATION_REQUIRED', 'agent delete --purge requires explicit --confirm-purge', 'PURGE_CONFIRMATION_REQUIRED');
|
|
3514
|
+
}
|
|
3515
|
+
let conversationId;
|
|
3516
|
+
try {
|
|
3517
|
+
conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
|
|
3518
|
+
}
|
|
3519
|
+
catch {
|
|
3520
|
+
return fail('INVALID_DELEGATION', 'delegation contains an invalid relation key');
|
|
3521
|
+
}
|
|
3522
|
+
const subject = buildAuthSubject({
|
|
3523
|
+
selfAid: taskAid,
|
|
3524
|
+
actorId: delegation.grant.actorId,
|
|
3525
|
+
channel: delegation.grant.channel,
|
|
3526
|
+
channelType: delegation.grant.channelType,
|
|
3527
|
+
channelId: conversationId,
|
|
3528
|
+
chatType: delegation.grant.chatType,
|
|
3529
|
+
conversationId,
|
|
3530
|
+
processOwners: loadDaemonConfig().owners ?? [],
|
|
3531
|
+
});
|
|
3532
|
+
const decision = authorizeOperation({
|
|
3533
|
+
source: 'agent-tool',
|
|
3534
|
+
subject,
|
|
3535
|
+
intent: {
|
|
3536
|
+
operation,
|
|
3537
|
+
scope: operation === 'agent.show'
|
|
3538
|
+
? 'agent'
|
|
3539
|
+
: operation === 'agent.reload' && targetAid
|
|
3540
|
+
? 'agent'
|
|
3541
|
+
: 'control',
|
|
3542
|
+
source: 'agent-tool',
|
|
3543
|
+
args: {
|
|
3544
|
+
self: taskAid,
|
|
3545
|
+
aid: targetAid ?? taskAid,
|
|
3546
|
+
taskAgentAid: taskAid,
|
|
3547
|
+
targetAgentAid: targetAid ?? taskAid,
|
|
3548
|
+
},
|
|
3549
|
+
},
|
|
3550
|
+
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
3551
|
+
});
|
|
3552
|
+
if (!decision.allow) {
|
|
3553
|
+
const reasonCode = decision.command?.reasonCode;
|
|
3554
|
+
return fail(decision.code, decision.reason, reasonCode);
|
|
3555
|
+
}
|
|
3556
|
+
const { execAgentAction, execAgentOptions, execAgentQuery } = await import('./core/command/agent-control.js');
|
|
3557
|
+
if (operation === 'agent.list') {
|
|
3558
|
+
const result = await execAgentOptions({ options: 'all' });
|
|
3559
|
+
if ('error' in result)
|
|
3560
|
+
return fail(result.code, result.error);
|
|
3561
|
+
const agents = Array.isArray(result.data?.agents) ? result.data.agents.map((agent) => ({
|
|
3562
|
+
aid: agent.aid,
|
|
3563
|
+
name: agent.name,
|
|
3564
|
+
personalName: agent.personalName ?? null,
|
|
3565
|
+
status: agent.status,
|
|
3566
|
+
channels: Array.isArray(agent.channels) ? agent.channels : [],
|
|
3567
|
+
projectPath: null,
|
|
3568
|
+
baseagent: agent.baseagent ?? null,
|
|
3569
|
+
model: agent.model ?? null,
|
|
3570
|
+
lastActivity: agent.lastActivity ?? null,
|
|
3571
|
+
})) : [];
|
|
3572
|
+
return { ok: true, result: { ok: true, agents, redacted: true } };
|
|
3573
|
+
}
|
|
3574
|
+
if (operation === 'agent.show') {
|
|
3575
|
+
const result = await execAgentQuery({ aid: targetAid });
|
|
3576
|
+
if ('error' in result)
|
|
3577
|
+
return fail(result.code, result.error);
|
|
3578
|
+
const agent = result.data ?? {};
|
|
3579
|
+
return { ok: true, result: {
|
|
3580
|
+
ok: true,
|
|
3581
|
+
aid: agent.aid,
|
|
3582
|
+
status: agent.status,
|
|
3583
|
+
identity: agent.identity ?? { name: null, description: null },
|
|
3584
|
+
config: {
|
|
3585
|
+
baseagent: agent.config?.baseagent ?? null,
|
|
3586
|
+
model: agent.config?.model ?? null,
|
|
3587
|
+
effort: agent.config?.effort ?? null,
|
|
3588
|
+
chatmode: agent.config?.chatmode ?? null,
|
|
3589
|
+
channels: Array.isArray(agent.config?.channels) ? agent.config.channels : [],
|
|
3590
|
+
},
|
|
3591
|
+
connection: agent.connection ?? null,
|
|
3592
|
+
sessions: agent.sessions ?? { active: 0, last_activity: null },
|
|
3593
|
+
paths: { config: null, agent_md: null, project: null, data: null },
|
|
3594
|
+
redacted: true,
|
|
3595
|
+
...(agent.createProgress ? { createProgress: agent.createProgress } : {}),
|
|
3596
|
+
} };
|
|
3597
|
+
}
|
|
3598
|
+
if (operation === 'agent.create') {
|
|
3599
|
+
const aid = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
3600
|
+
if (!aid)
|
|
3601
|
+
return fail('INVALID_ARGS', 'ec agent new requires an AID');
|
|
3602
|
+
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./cli/agent.js');
|
|
3603
|
+
const plan = resolveAgentCreatePlan({
|
|
3604
|
+
aid,
|
|
3605
|
+
project: value('--project'),
|
|
3606
|
+
baseagent: value('--baseagent'),
|
|
3607
|
+
owner: value('--owner'),
|
|
3608
|
+
name: value('--name'),
|
|
3609
|
+
description: value('--description'),
|
|
3610
|
+
force: argv.includes('--force'),
|
|
3611
|
+
});
|
|
3612
|
+
const validationError = validateAgentCreatePlan(plan);
|
|
3613
|
+
if (validationError)
|
|
3614
|
+
return fail('INVALID_ARGS', validationError);
|
|
3615
|
+
if (argv.includes('--dry-run'))
|
|
3616
|
+
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3617
|
+
const result = await agentCreateNonInteractive({
|
|
3618
|
+
aid: plan.aid,
|
|
3619
|
+
project: plan.project,
|
|
3620
|
+
baseagent: plan.baseagent,
|
|
3621
|
+
owner: plan.owner,
|
|
3622
|
+
name: plan.name,
|
|
3623
|
+
description: plan.description,
|
|
3624
|
+
force: argv.includes('--force'),
|
|
3625
|
+
});
|
|
3626
|
+
return !result.ok
|
|
3627
|
+
? fail(result.code ?? 'INTERNAL', result.error)
|
|
3628
|
+
: { ok: true, result: { ...result, configPath: '[redacted]' } };
|
|
3629
|
+
}
|
|
3630
|
+
if (operation === 'agent.delete') {
|
|
3631
|
+
const { agentDelete } = await import('./cli/agent.js');
|
|
3632
|
+
const result = await agentDelete(targetAid ?? '', argv.includes('--purge'));
|
|
3633
|
+
return 'error' in result ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3634
|
+
}
|
|
3635
|
+
if (operation === 'agent.reload' && !targetAid) {
|
|
3636
|
+
const { agentReload } = await import('./cli/agent.js');
|
|
3637
|
+
const result = await agentReload();
|
|
3638
|
+
return !result.ok ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3639
|
+
}
|
|
3640
|
+
const result = await execAgentAction(sub, { aid: targetAid ?? taskAid, force: argv.includes('--force') }, delegation.grant.actorId, eventBus);
|
|
3641
|
+
return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
|
|
3642
|
+
});
|
|
3358
3643
|
ipcServer.setQueueOperationExecutor(async (params) => {
|
|
3359
3644
|
const operation = `ec.queue.${params.action ?? 'read'}`;
|
|
3360
3645
|
const authorization = await authorizeManagedAgentOperation(params, operation);
|
|
@@ -3416,17 +3701,14 @@ async function main() {
|
|
|
3416
3701
|
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
3417
3702
|
if (!isCrossAgentTriggerOperationAllowed({
|
|
3418
3703
|
control: actor.control,
|
|
3704
|
+
daemonOwner: actor.daemonOwner,
|
|
3419
3705
|
taskAgentAid: actor.selfAid,
|
|
3420
3706
|
targetAgentAid: agentAid,
|
|
3421
3707
|
targetManagement: actor.management,
|
|
3422
3708
|
operation,
|
|
3423
3709
|
})) {
|
|
3424
|
-
if (isCrossAgent
|
|
3425
|
-
throw new Error('cross-agent
|
|
3426
|
-
}
|
|
3427
|
-
if (isCrossAgent && !isCrossAgentTriggerReadOperation(operation)) {
|
|
3428
|
-
throw new Error('cross-agent trigger operations are limited to list, show, and history');
|
|
3429
|
-
}
|
|
3710
|
+
if (isCrossAgent)
|
|
3711
|
+
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
3430
3712
|
throw new Error('trigger agent does not match authenticated task agent');
|
|
3431
3713
|
}
|
|
3432
3714
|
const operationDecision = authorizeOperation({
|
|
@@ -3438,6 +3720,8 @@ async function main() {
|
|
|
3438
3720
|
source: actor.control ? 'control' : 'agent-tool',
|
|
3439
3721
|
args: {
|
|
3440
3722
|
self: agentAid,
|
|
3723
|
+
taskAgentAid: actor.selfAid,
|
|
3724
|
+
targetAgentAid: agentAid,
|
|
3441
3725
|
peer: actor.origin.peerId,
|
|
3442
3726
|
peerKey: actor.subject.peerKey,
|
|
3443
3727
|
...(triggerId ? { triggerId } : {}),
|
|
@@ -3446,11 +3730,15 @@ async function main() {
|
|
|
3446
3730
|
});
|
|
3447
3731
|
if (!operationDecision.allow)
|
|
3448
3732
|
throw new Error(operationDecision.reason);
|
|
3449
|
-
if (actor.management)
|
|
3450
|
-
return actor;
|
|
3451
3733
|
if (!triggerId)
|
|
3452
3734
|
return actor;
|
|
3453
3735
|
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
3736
|
+
const privilegedReason = definition ? daemonPrivilegedTriggerReason(definition) : undefined;
|
|
3737
|
+
if (privilegedReason && !actor.daemonPrivileged) {
|
|
3738
|
+
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
3739
|
+
}
|
|
3740
|
+
if (actor.management)
|
|
3741
|
+
return actor;
|
|
3454
3742
|
if (!definition
|
|
3455
3743
|
|| definition.origin?.peerId !== actor.origin?.peerId
|
|
3456
3744
|
|| definition.origin?.channelKey !== actor.origin?.channelKey) {
|
|
@@ -3463,8 +3751,9 @@ async function main() {
|
|
|
3463
3751
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3464
3752
|
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
3465
3753
|
const scheduler = schedulerFor(agentAid);
|
|
3466
|
-
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => actor.
|
|
3467
|
-
|
|
3754
|
+
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged || !daemonPrivilegedTriggerReason(trigger))
|
|
3755
|
+
&& (actor.management
|
|
3756
|
+
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey))));
|
|
3468
3757
|
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
3469
3758
|
}
|
|
3470
3759
|
case 'trigger.show': {
|
|
@@ -3487,16 +3776,28 @@ async function main() {
|
|
|
3487
3776
|
if (cmd.triggerId !== undefined && (typeof cmd.triggerId !== 'string' || !cmd.triggerId)) {
|
|
3488
3777
|
throw new Error('invalid triggerId');
|
|
3489
3778
|
}
|
|
3779
|
+
let actor;
|
|
3490
3780
|
if (cmd.triggerId)
|
|
3491
3781
|
await authorizeTrigger(agentAid, 'trigger.history', cmd.triggerId);
|
|
3492
|
-
else
|
|
3782
|
+
else
|
|
3783
|
+
actor = await authorizeTrigger(agentAid, 'trigger.history');
|
|
3784
|
+
if (!cmd.triggerId && !actor?.management) {
|
|
3493
3785
|
throw new Error('triggerId is required for non-management history access');
|
|
3494
3786
|
}
|
|
3495
|
-
|
|
3787
|
+
const events = schedulerFor(agentAid).history(cmd.triggerId, limit);
|
|
3788
|
+
if (cmd.triggerId || actor?.daemonPrivileged)
|
|
3789
|
+
return { ok: true, events };
|
|
3790
|
+
const privilegedIds = new Set(schedulerFor(agentAid).list({ all: true })
|
|
3791
|
+
.filter(trigger => !!daemonPrivilegedTriggerReason(trigger))
|
|
3792
|
+
.map(trigger => trigger.id));
|
|
3793
|
+
return { ok: true, events: events.filter((event) => !privilegedIds.has(String(event.triggerId ?? ''))) };
|
|
3496
3794
|
}
|
|
3497
3795
|
case 'trigger.eventCatalog': {
|
|
3498
3796
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3499
|
-
await authorizeTrigger(agentAid, 'trigger.eventCatalog');
|
|
3797
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.eventCatalog');
|
|
3798
|
+
if (cmd.includeInternal === true && !actor.daemonPrivileged) {
|
|
3799
|
+
throw new Error('internal Trigger event catalog requires DaemonOwner');
|
|
3800
|
+
}
|
|
3500
3801
|
return { ok: true, ...getEventCatalog({ includeInternal: cmd.includeInternal === true }) };
|
|
3501
3802
|
}
|
|
3502
3803
|
case 'trigger.create': {
|
|
@@ -3597,7 +3898,7 @@ async function main() {
|
|
|
3597
3898
|
// 优雅关闭
|
|
3598
3899
|
let shutdownSignal = 'unknown';
|
|
3599
3900
|
let shutdownInProgress = false;
|
|
3600
|
-
|
|
3901
|
+
shutdown = async (signal) => {
|
|
3601
3902
|
if (shutdownInProgress)
|
|
3602
3903
|
return;
|
|
3603
3904
|
shutdownInProgress = true;
|
|
@@ -3608,6 +3909,10 @@ async function main() {
|
|
|
3608
3909
|
logger.info(`\n\nShutting down gracefully... (signal=${shutdownSignal}, pid=${pid}, ppid=${ppid})`);
|
|
3609
3910
|
ipcServer.stopCpuTracking();
|
|
3610
3911
|
ipcServer.stop();
|
|
3912
|
+
try {
|
|
3913
|
+
fs.unlinkSync(readySignalPath);
|
|
3914
|
+
}
|
|
3915
|
+
catch { }
|
|
3611
3916
|
bindService?.stopCleanup();
|
|
3612
3917
|
// 必须在第一个 await 之前同步冻结 active batch、发布 task:interrupted,
|
|
3613
3918
|
// 否则 handler continuation 可能先提交完成并清掉待恢复的主消息。
|
|
@@ -3664,6 +3969,10 @@ async function main() {
|
|
|
3664
3969
|
process.exit(0);
|
|
3665
3970
|
}
|
|
3666
3971
|
};
|
|
3972
|
+
if (pendingShutdownReason) {
|
|
3973
|
+
const reason = pendingShutdownReason;
|
|
3974
|
+
setImmediate(() => { void shutdown?.(reason); });
|
|
3975
|
+
}
|
|
3667
3976
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
3668
3977
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
3669
3978
|
// 全局错误处理:防止未捕获的 WebSocket 错误导致进程崩溃
|