evolcore 0.0.19 → 0.0.21
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 +43 -1
- package/README.md +60 -9
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +383 -111
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +405 -137
- package/dist/agents/ecagent-runner.js +174 -63
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +66 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +86 -9
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +26 -11
- package/dist/aun/outbox.js +295 -68
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +1004 -273
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +81 -121
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +76 -24
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +226 -24
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +29 -16
- package/dist/config/lifecycle.js +16 -5
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +161 -12
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +155 -4
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +27 -38
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +9 -4
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +495 -72
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +465 -56
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +205 -13
- package/dist/core/permission/tool-policy.js +673 -62
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +18 -9
- package/dist/index.js +283 -65
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +10 -5
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +421 -0
- package/kits/schemas/daemon.schema.5.json +135 -0
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +119 -0
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +7 -3
- package/skills/eclink/SKILL.md +2 -0
package/dist/index.js
CHANGED
|
@@ -13,9 +13,11 @@ import { ClaudeSessionFileAdapter } from './core/session/adapters/claude-session
|
|
|
13
13
|
import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-file-adapter.js';
|
|
14
14
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
15
15
|
import { EcagentSessionFileAdapter } from './core/session/adapters/ecagent-session-file-adapter.js';
|
|
16
|
-
import { loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig } from './config-store.js';
|
|
16
|
+
import { autoMigrateIfNeeded, loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig, isFullAccessEnabled } from './config-store.js';
|
|
17
|
+
import { DEFAULT_ECWEB_PORT, ecwebService } from './config/daemon-services.js';
|
|
17
18
|
import { ConfigTarget, initConfigManager, onConfigWrite, read as readConfig, shouldFailFastForMissingOwners } from './config/config-manager.js';
|
|
18
19
|
import { ensureRoleConfigV4OnStartup, ensureRoleConfigV5OnStartup } from './config/role-migration-startup.js';
|
|
20
|
+
import { ensureAuxiliaryModelMigrationOnStartup } from './config/schema-migration.js';
|
|
19
21
|
import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
|
|
20
22
|
import { resolvePeerDisplayLabel } from './config/contact-book.js';
|
|
21
23
|
import { expirePendingContactRequests } from './config/contact-request-service.js';
|
|
@@ -42,6 +44,7 @@ import { buildEnvelope, isDeliveryTargetForChannel, replyContextFromSession } fr
|
|
|
42
44
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
43
45
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
44
46
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
47
|
+
import { evaluateOutboundContactAdmission } from './core/message/message-admission.js';
|
|
45
48
|
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
46
49
|
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
47
50
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
@@ -50,17 +53,19 @@ import { BootstrapService, completeBootstrapWithWelcome } from './core/bootstrap
|
|
|
50
53
|
import { postBootstrapWelcomeOperationId } from './core/bootstrap-messages.js';
|
|
51
54
|
import { MessageCache } from './core/message/message-cache.js';
|
|
52
55
|
import { CommandHandler, isProcessLevelOwner } from './core/command/command-handler.js';
|
|
56
|
+
import { AgentApplicationService } from './core/agent-application-service.js';
|
|
53
57
|
import { EventBus } from './core/event-bus.js';
|
|
54
58
|
import { eventPatternIncludesInternal, getEventCatalog } from './core/event-catalog.js';
|
|
55
59
|
import { StatsCollector } from './utils/stats.js';
|
|
56
60
|
import { AidStatsCollector } from './utils/stats.js';
|
|
57
61
|
import { PermissionGateway } from './core/permission/approval-gateway.js';
|
|
58
62
|
import { InteractionRouter, registerAdapterInteractions } from './core/interaction-router.js';
|
|
59
|
-
import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend } from './core/auth/agent-delegation.js';
|
|
60
|
-
import { authorizeOperation, buildAuthSubject, isCrossAgentTriggerOperationAllowed, } from './core/auth/auth-gateway.js';
|
|
63
|
+
import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend, hasTrustedFullAccessDelegation, trustedExecutionIdentityInput, } from './core/auth/agent-delegation.js';
|
|
64
|
+
import { authorizeOperation, buildAuthSubject, canApprovePersistentFullAccessTrigger, isCrossAgentTriggerOperationAllowed, } from './core/auth/auth-gateway.js';
|
|
61
65
|
import { authorizationDenialData, formatAuthorizationDenial } from './core/auth/authorization-denial.js';
|
|
66
|
+
import { auditFullAccessEvent } from './core/auth/authorization-audit.js';
|
|
62
67
|
import { parsePeerKey } from './core/relation/peer-identity.js';
|
|
63
|
-
import { isHClassPath } from './core/protected-paths.js';
|
|
68
|
+
import { isHClassPath } from './core/permission/protected-paths.js';
|
|
64
69
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
65
70
|
import { AgentLoader } from './core/baseagent-loader.js';
|
|
66
71
|
import { EvolAgentRegistry } from './core/evolagent-registry.js';
|
|
@@ -490,7 +495,6 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
490
495
|
agentName: daemonCfg.aid,
|
|
491
496
|
channelName: 'control',
|
|
492
497
|
pureIdentity: true,
|
|
493
|
-
gatewayUrl: daemonCfg.aun?.gatewayUrl,
|
|
494
498
|
defaultEncrypt: daemonCfg.aun?.defaultEncrypt,
|
|
495
499
|
aunTrace: daemonCfg.debug?.aunTrace,
|
|
496
500
|
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
@@ -734,6 +738,7 @@ async function main() {
|
|
|
734
738
|
console.error(`[EvolCore] 包路径: ${pkgRoot}`);
|
|
735
739
|
console.error(`[EvolCore] 代码时间: ${latestMtime ? fmtTime(latestMtime) : '?'}`);
|
|
736
740
|
}
|
|
741
|
+
console.error('[EvolCore] ✓ Version info printed');
|
|
737
742
|
// 过滤飞书 SDK 的 info 日志
|
|
738
743
|
const originalLog = console.log;
|
|
739
744
|
const originalInfo = console.info;
|
|
@@ -751,12 +756,16 @@ async function main() {
|
|
|
751
756
|
return;
|
|
752
757
|
originalInfo(...args);
|
|
753
758
|
};
|
|
759
|
+
console.error('[EvolCore] ✓ Console filter installed');
|
|
754
760
|
logger.info(`EvolCore v${readEvolcoreVersion()} starting... (fastaun v${readFastaunVersion()})`);
|
|
761
|
+
console.error('[EvolCore] ✓ Logger initialized');
|
|
755
762
|
// 确保数据目录存在
|
|
756
763
|
ensureDataDirs();
|
|
764
|
+
console.error('[EvolCore] ✓ Data dirs ensured');
|
|
757
765
|
// AUN keystore seed is an EvolCore application constant. A custom value can
|
|
758
766
|
// make existing identities unreadable, so fail before migrations, snapshots,
|
|
759
767
|
// instance registration, and service startup.
|
|
768
|
+
console.error('[EvolCore] ✓ Checking AUN encryption seed...');
|
|
760
769
|
try {
|
|
761
770
|
assertAunEncryptionSeedPolicy(loadDaemonConfig().aun?.encryptionSeed, process.env.AUN_ENCRYPTION_SEED);
|
|
762
771
|
}
|
|
@@ -766,6 +775,7 @@ async function main() {
|
|
|
766
775
|
console.error(msg);
|
|
767
776
|
process.exit(1);
|
|
768
777
|
}
|
|
778
|
+
console.error('[EvolCore] ✓ AUN encryption seed validated');
|
|
769
779
|
// .env 文件已在模块顶部加载,此处不再重复加载
|
|
770
780
|
// ── 单实例保护(pre-check + post-write self-check)──
|
|
771
781
|
// pre-check:发现已有活 main 直接退出,避免起任何副作用
|
|
@@ -780,11 +790,19 @@ async function main() {
|
|
|
780
790
|
process.exit(1);
|
|
781
791
|
}
|
|
782
792
|
}
|
|
783
|
-
// 在登记 daemon
|
|
793
|
+
// 在登记 daemon 实例前自动备份并迁移旧配置;失败才阻止启动。
|
|
784
794
|
try {
|
|
785
795
|
// Crash recovery must precede schema gates and migrations so they never
|
|
786
796
|
// inspect or migrate a partially applied Role Menu transaction.
|
|
787
797
|
recoverAllRoleMutationsSync();
|
|
798
|
+
autoMigrateIfNeeded();
|
|
799
|
+
const auxiliaryModelMigration = await ensureAuxiliaryModelMigrationOnStartup();
|
|
800
|
+
if (auxiliaryModelMigration) {
|
|
801
|
+
const msg = `✓ Session Renew auxiliary model migration applied automatically: ${auxiliaryModelMigration.changedFiles} file(s)`
|
|
802
|
+
+ (auxiliaryModelMigration.backup ? `; backup: ${auxiliaryModelMigration.backup}` : '');
|
|
803
|
+
logger.info(msg);
|
|
804
|
+
console.error(msg);
|
|
805
|
+
}
|
|
788
806
|
const migration = await ensureRoleConfigV4OnStartup();
|
|
789
807
|
if (migration) {
|
|
790
808
|
const msg = `✓ Role config v4 migration applied automatically: ${migration.changedFiles} file(s)` +
|
|
@@ -829,7 +847,7 @@ async function main() {
|
|
|
829
847
|
}
|
|
830
848
|
// ── 自动迁移 ──
|
|
831
849
|
migrateIdentitiesIfNeeded();
|
|
832
|
-
//
|
|
850
|
+
// daemon.json 的旧 ecweb/serviceProxy 结构已在启动前归一化为 services[]。
|
|
833
851
|
// ── 配置体系初始化(schema 字段不相交硬约束校验)──
|
|
834
852
|
try {
|
|
835
853
|
initConfigManager();
|
|
@@ -892,9 +910,10 @@ async function main() {
|
|
|
892
910
|
// instance just before spawning us; the supervisor adopts it when healthy,
|
|
893
911
|
// otherwise starts one and keeps it alive for the daemon's lifetime.
|
|
894
912
|
let ecwebSupervisor = null;
|
|
895
|
-
|
|
913
|
+
const ecwebConfig = ecwebService(daemonCfg.services);
|
|
914
|
+
if (ecwebConfig?.enabled === true) {
|
|
896
915
|
ecwebSupervisor = createEcwebSupervisor({
|
|
897
|
-
port:
|
|
916
|
+
port: ecwebConfig.port ?? DEFAULT_ECWEB_PORT,
|
|
898
917
|
logger,
|
|
899
918
|
});
|
|
900
919
|
try {
|
|
@@ -1047,8 +1066,8 @@ async function main() {
|
|
|
1047
1066
|
agentLoader.register(new EcagentAgentPlugin());
|
|
1048
1067
|
const creationErrors = [];
|
|
1049
1068
|
const agentInstances = agentLoader.createAll(agentRegistry, {
|
|
1050
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
1051
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
1069
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
1070
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
1052
1071
|
},
|
|
1053
1072
|
}, creationErrors);
|
|
1054
1073
|
// agentMap 复合键:${aid}::${baseagent}
|
|
@@ -1194,6 +1213,7 @@ async function main() {
|
|
|
1194
1213
|
const agentDelegationRegistry = new AgentDelegationRegistry();
|
|
1195
1214
|
// 创建命令处理器
|
|
1196
1215
|
const cmdHandler = new CommandHandler(sessionManager, agentMap, messageCache, eventBus, primaryRunnerKey);
|
|
1216
|
+
let agentApplicationService;
|
|
1197
1217
|
cmdHandler.setAgentDelegationRegistry(agentDelegationRegistry);
|
|
1198
1218
|
cmdHandler.setPermissionGateway(permissionGateway);
|
|
1199
1219
|
cmdHandler.setInteractionRouter(interactionRouter);
|
|
@@ -1429,10 +1449,19 @@ async function main() {
|
|
|
1429
1449
|
&& definition.feedback.target?.channelKey === 'daemon') {
|
|
1430
1450
|
return 'daemon-control feedback';
|
|
1431
1451
|
}
|
|
1452
|
+
if (definition.execution.permissionMode === 'fullaccess')
|
|
1453
|
+
return 'persistent fullaccess Trigger';
|
|
1432
1454
|
return undefined;
|
|
1433
1455
|
};
|
|
1434
|
-
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service');
|
|
1456
|
+
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon');
|
|
1435
1457
|
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1458
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1459
|
+
if (!isFullAccessEnabled())
|
|
1460
|
+
throw new Error('fullaccess is disabled by daemon.json');
|
|
1461
|
+
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
1462
|
+
throw new Error('persistent fullaccess Trigger requires DaemonOwner or fullaccess-run');
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1436
1465
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1437
1466
|
if (privilegedReason && !actor.daemonPrivileged)
|
|
1438
1467
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
@@ -1483,9 +1512,30 @@ async function main() {
|
|
|
1483
1512
|
}
|
|
1484
1513
|
};
|
|
1485
1514
|
const authorizeTriggerExecution = (definition) => {
|
|
1515
|
+
if (definition.execution.permissionMode === 'fullaccess' && !isFullAccessEnabled()) {
|
|
1516
|
+
return { allowed: false, reason: 'fullaccess is disabled by daemon.json' };
|
|
1517
|
+
}
|
|
1518
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1519
|
+
if (!definition.authorizedBy) {
|
|
1520
|
+
return { allowed: false, reason: 'fullaccess Trigger approval provenance is missing' };
|
|
1521
|
+
}
|
|
1522
|
+
const owners = new Set(loadDaemonConfig().owners ?? []);
|
|
1523
|
+
if (!owners.has(definition.authorizedBy)) {
|
|
1524
|
+
return { allowed: false, reason: 'fullaccess Trigger approver is no longer a current daemon-owner' };
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1486
1527
|
const origin = definition.origin;
|
|
1487
1528
|
if (!origin?.peerId)
|
|
1488
1529
|
return { allowed: false, reason: 'trigger origin peer is missing' };
|
|
1530
|
+
// A persistent fullaccess definition is an already-approved daemon-scope
|
|
1531
|
+
// task. Its origin remains useful for audit and feedback routing, but a
|
|
1532
|
+
// relation lookup on the origin actor would make a cross-Agent Trigger
|
|
1533
|
+
// persist successfully and then fail on every fire after creation.
|
|
1534
|
+
// Approval provenance and the feature flag above remain mandatory; only
|
|
1535
|
+
// the ordinary creator relation gate is bypassed for this mode.
|
|
1536
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1537
|
+
return { allowed: true };
|
|
1538
|
+
}
|
|
1489
1539
|
if (origin.channelKey === 'daemon') {
|
|
1490
1540
|
const subject = buildAuthSubject({
|
|
1491
1541
|
selfAid: definition.agentAid,
|
|
@@ -1552,7 +1602,8 @@ async function main() {
|
|
|
1552
1602
|
return { allowed: false, reason: operationDecision.reason };
|
|
1553
1603
|
}
|
|
1554
1604
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1555
|
-
|
|
1605
|
+
const persistedFullAccessApproval = privilegedReason === 'persistent fullaccess Trigger';
|
|
1606
|
+
if (privilegedReason && !persistedFullAccessApproval && !hasDaemonTriggerAuthority(subject)) {
|
|
1556
1607
|
return { allowed: false, reason: `${privilegedReason} requires current DaemonOwner` };
|
|
1557
1608
|
}
|
|
1558
1609
|
if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
|
|
@@ -1600,6 +1651,7 @@ async function main() {
|
|
|
1600
1651
|
management: true,
|
|
1601
1652
|
daemonOwner: false,
|
|
1602
1653
|
daemonPrivileged: true,
|
|
1654
|
+
fullAccess: false,
|
|
1603
1655
|
control: true,
|
|
1604
1656
|
role: 'none',
|
|
1605
1657
|
subject,
|
|
@@ -1638,18 +1690,20 @@ async function main() {
|
|
|
1638
1690
|
identity: roleToSessionIdentity(currentRole),
|
|
1639
1691
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1640
1692
|
fromControlChannel: false,
|
|
1693
|
+
...trustedExecutionIdentityInput(validation.grant),
|
|
1641
1694
|
});
|
|
1642
|
-
if (validation.grant.selfAid !== agentAid &&
|
|
1695
|
+
if (validation.grant.selfAid !== agentAid && subject.dataScope !== 'daemon') {
|
|
1643
1696
|
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
1644
1697
|
}
|
|
1645
|
-
if (
|
|
1698
|
+
if (subject.dataScope !== 'daemon' && !checkRoleAccess(currentRole, agentAid)) {
|
|
1646
1699
|
throw new Error('trigger actor no longer has access to this agent');
|
|
1647
1700
|
}
|
|
1648
1701
|
return {
|
|
1649
1702
|
origin: actorOrigin,
|
|
1650
|
-
management: isManagementRole(currentRole) || subject.
|
|
1703
|
+
management: isManagementRole(currentRole) || subject.dataScope === 'daemon',
|
|
1651
1704
|
daemonOwner: subject.isDaemonOwner,
|
|
1652
|
-
daemonPrivileged: subject.isDaemonOwner,
|
|
1705
|
+
daemonPrivileged: subject.isDaemonOwner || subject.dataScope === 'daemon',
|
|
1706
|
+
fullAccess: subject.processRole === 'fullaccess-run',
|
|
1653
1707
|
selfAid: validation.grant.selfAid,
|
|
1654
1708
|
control: false,
|
|
1655
1709
|
role: currentRole || 'none',
|
|
@@ -1792,6 +1846,7 @@ async function main() {
|
|
|
1792
1846
|
msgBridge.setInteractionRouter(interactionRouter);
|
|
1793
1847
|
msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
|
|
1794
1848
|
msgBridge.setAidStatsCollector(aidStatsCollector);
|
|
1849
|
+
cmdHandler.setContactRequestSubmitter((input) => msgBridge.submitAgentContactRequest(input));
|
|
1795
1850
|
bootstrapService = new BootstrapService(agentRegistry, eventBus);
|
|
1796
1851
|
msgBridge.setBootstrapService(bootstrapService);
|
|
1797
1852
|
msgBridge.setHandoffRuntime(handoffRuntime);
|
|
@@ -1813,7 +1868,14 @@ async function main() {
|
|
|
1813
1868
|
payload: summarizeOutboundPayload(payload),
|
|
1814
1869
|
});
|
|
1815
1870
|
const result = await originalSend(envelope, payload);
|
|
1816
|
-
|
|
1871
|
+
// AUN adapters return an explicit receipt. Only a receipt with a remote
|
|
1872
|
+
// message ID proves delivery; queued/failed results must not be emitted
|
|
1873
|
+
// as `message:sent`. Legacy adapters that return void remain compatible.
|
|
1874
|
+
const deliveryConfirmed = result === undefined
|
|
1875
|
+
|| (result && typeof result === 'object' && result.status === 'sent'
|
|
1876
|
+
&& typeof result.messageId === 'string'
|
|
1877
|
+
&& result.messageId.length > 0);
|
|
1878
|
+
if (shouldCountSentPayload(payload) && deliveryConfirmed) {
|
|
1817
1879
|
if ((inst.channelType || inst.adapter.channelName) !== 'aun') {
|
|
1818
1880
|
try {
|
|
1819
1881
|
const logPayload = outboundPayloadToLogText(payload);
|
|
@@ -2246,8 +2308,11 @@ async function main() {
|
|
|
2246
2308
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
2247
2309
|
// function declaration is hoisted, while its invocation remains here so a
|
|
2248
2310
|
// failed bind still aborts before any AUN connection is attempted.
|
|
2311
|
+
console.error('[EvolCore] ✓ Configuring IPC...');
|
|
2249
2312
|
configureIpc();
|
|
2313
|
+
console.error('[EvolCore] ✓ Starting IPC server...');
|
|
2250
2314
|
await ipcServer.start();
|
|
2315
|
+
console.error('[EvolCore] ✓ IPC server started');
|
|
2251
2316
|
// ── 连接所有渠道(后台首连,AUN/任意渠道故障不阻塞 daemon 主流程)──
|
|
2252
2317
|
logger.info(`🚀 EvolCore core is ready; connecting ${channelInstances.length} channel(s) in background`);
|
|
2253
2318
|
const connectAllPromise = channelLoader.connectAll(channelInstances, {
|
|
@@ -2296,7 +2361,6 @@ async function main() {
|
|
|
2296
2361
|
agentName: daemonCfg.aid,
|
|
2297
2362
|
channelName: 'control',
|
|
2298
2363
|
pureIdentity: true,
|
|
2299
|
-
gatewayUrl: daemonCfg.aun?.gatewayUrl,
|
|
2300
2364
|
defaultEncrypt: daemonCfg.aun?.defaultEncrypt,
|
|
2301
2365
|
aunTrace: daemonCfg.debug?.aunTrace,
|
|
2302
2366
|
aunSdkLog: daemonCfg.debug?.aunSdkLog,
|
|
@@ -2341,7 +2405,7 @@ async function main() {
|
|
|
2341
2405
|
...(response.name ? { name: response.name } : {}),
|
|
2342
2406
|
};
|
|
2343
2407
|
try {
|
|
2344
|
-
await controlChannel.
|
|
2408
|
+
await controlChannel.sendStructuredOrThrow(opts.channelId, timedResponse, menuResponseContext());
|
|
2345
2409
|
logMenuRequestCompleted(request, timedResponse, menuFlowContext, { delivery: 'sent' });
|
|
2346
2410
|
menuCompletionLogged = true;
|
|
2347
2411
|
}
|
|
@@ -2361,7 +2425,7 @@ async function main() {
|
|
|
2361
2425
|
// 用 sendStructured 直发 typed payload(payload.type='bind.response'),
|
|
2362
2426
|
// 不能用 sendMessage——它会把内容包成 {type:'text', text:...},App 无法识别。
|
|
2363
2427
|
// encrypted 跟随入站请求:bind.response 与 bind.request 的加密/明文对称。
|
|
2364
|
-
await controlChannel.
|
|
2428
|
+
await controlChannel.sendStructuredOrThrow(opts.channelId, response, controlReplyContext(opts));
|
|
2365
2429
|
}
|
|
2366
2430
|
return;
|
|
2367
2431
|
}
|
|
@@ -2396,8 +2460,8 @@ async function main() {
|
|
|
2396
2460
|
return;
|
|
2397
2461
|
}
|
|
2398
2462
|
if (text.toLowerCase() === '/pair') {
|
|
2399
|
-
const port =
|
|
2400
|
-
const pair = await fetchEcwebPairCode(port);
|
|
2463
|
+
const port = ecwebService(loadDaemonConfig().services)?.port ?? DEFAULT_ECWEB_PORT;
|
|
2464
|
+
const pair = await fetchEcwebPairCode(port, opts.peerId);
|
|
2401
2465
|
let reply;
|
|
2402
2466
|
if (pair) {
|
|
2403
2467
|
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
@@ -2460,11 +2524,12 @@ async function main() {
|
|
|
2460
2524
|
// ── Service Proxy:把本地服务(ecweb 等)通过控制 AID 暴露到 AUN 网络 ──
|
|
2461
2525
|
// 挂在控制 AUNChannel 上,动态解引用其 client(规避重连换 client)。
|
|
2462
2526
|
// 失败只 warn,不影响 daemon 主流程。
|
|
2463
|
-
|
|
2527
|
+
const proxyServices = (daemonCfg.services ?? []).filter((service) => service.enabled === true && service.proxy?.enabled === true);
|
|
2528
|
+
if (proxyServices.length > 0 && daemonCfg.aid) {
|
|
2464
2529
|
// 短暂延迟确保控制 channel 完全就绪
|
|
2465
2530
|
const channel = controlChannel;
|
|
2466
2531
|
const aid = daemonCfg.aid;
|
|
2467
|
-
const config =
|
|
2532
|
+
const config = proxyServices;
|
|
2468
2533
|
setTimeout(() => {
|
|
2469
2534
|
if (!channel)
|
|
2470
2535
|
return;
|
|
@@ -2795,7 +2860,7 @@ async function main() {
|
|
|
2795
2860
|
if (delegation.grant.selfAid !== selfAid) {
|
|
2796
2861
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation self agent does not match current session' };
|
|
2797
2862
|
}
|
|
2798
|
-
if (params.agent && params.agent !== selfAid) {
|
|
2863
|
+
if (params.agent && params.agent !== selfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
|
|
2799
2864
|
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2800
2865
|
}
|
|
2801
2866
|
let conversationId;
|
|
@@ -2814,8 +2879,9 @@ async function main() {
|
|
|
2814
2879
|
chatType: delegation.grant.chatType,
|
|
2815
2880
|
conversationId,
|
|
2816
2881
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2882
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
2817
2883
|
});
|
|
2818
|
-
const managementRole = isManagementRole(subject.role);
|
|
2884
|
+
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
2819
2885
|
const decision = authorizeOperation({
|
|
2820
2886
|
source: 'agent-tool',
|
|
2821
2887
|
subject,
|
|
@@ -2938,6 +3004,12 @@ async function main() {
|
|
|
2938
3004
|
if (!ch) {
|
|
2939
3005
|
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2940
3006
|
}
|
|
3007
|
+
if (params.scope === 'msg') {
|
|
3008
|
+
const admission = await evaluateOutboundContactAdmission({ selfAid: params.aid, targetAid: params.to });
|
|
3009
|
+
if (!admission.allow) {
|
|
3010
|
+
return { ok: false, error: admission.error, code: admission.code };
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
2941
3013
|
// The IPC command already carries the caller-selected transport scope.
|
|
2942
3014
|
// Never reclassify the target by parsing its AID string: a private AID may
|
|
2943
3015
|
// look group-like and a group identifier may use a future format.
|
|
@@ -3106,8 +3178,8 @@ async function main() {
|
|
|
3106
3178
|
const staged = candidate.config.enabled === false
|
|
3107
3179
|
? []
|
|
3108
3180
|
: agentLoader.createForAgent(candidate, {
|
|
3109
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3110
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3181
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3182
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3111
3183
|
},
|
|
3112
3184
|
}, creationErrors);
|
|
3113
3185
|
const onDisposeError = (instance, error) => {
|
|
@@ -3206,8 +3278,8 @@ async function main() {
|
|
|
3206
3278
|
}
|
|
3207
3279
|
const creationErrors = [];
|
|
3208
3280
|
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
3209
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3210
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3281
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3282
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3211
3283
|
},
|
|
3212
3284
|
}, creationErrors);
|
|
3213
3285
|
for (const inst of newAgentInstances) {
|
|
@@ -3374,22 +3446,61 @@ async function main() {
|
|
|
3374
3446
|
resyncInFlight = undefined;
|
|
3375
3447
|
}
|
|
3376
3448
|
};
|
|
3449
|
+
// Core-owned agent application facade. Menu/slash/managed-task callers use
|
|
3450
|
+
// this in-process service after their own authorization checks; standalone
|
|
3451
|
+
// CLI commands continue to reach these operations through IPC.
|
|
3452
|
+
const coreHotLoadAgent = async (aid) => {
|
|
3453
|
+
const hotLoad = globalThis.__evolcore_hotLoadAgent;
|
|
3454
|
+
if (typeof hotLoad !== 'function')
|
|
3455
|
+
throw new Error('Hot-load handler not initialized');
|
|
3456
|
+
await hotLoad(aid);
|
|
3457
|
+
};
|
|
3458
|
+
const coreResyncAgents = async () => {
|
|
3459
|
+
const resync = globalThis.__evolcore_resyncAgents;
|
|
3460
|
+
if (typeof resync !== 'function')
|
|
3461
|
+
throw new Error('Resync handler not initialized');
|
|
3462
|
+
return await resync();
|
|
3463
|
+
};
|
|
3464
|
+
agentApplicationService = new AgentApplicationService({
|
|
3465
|
+
registry: agentRegistry,
|
|
3466
|
+
reload: (aid, options) => reloadCoordinator.reload(aid, options),
|
|
3467
|
+
hooks: reloadHooks,
|
|
3468
|
+
hotLoad: coreHotLoadAgent,
|
|
3469
|
+
resync: coreResyncAgents,
|
|
3470
|
+
create: async (options) => {
|
|
3471
|
+
const { agentCreateNonInteractive } = await import('./cli/agent.js');
|
|
3472
|
+
return await agentCreateNonInteractive(options, { hotLoadAgent: coreHotLoadAgent });
|
|
3473
|
+
},
|
|
3474
|
+
aunAids: () => channelInstances.flatMap((inst) => {
|
|
3475
|
+
if (inst.channelType !== 'aun')
|
|
3476
|
+
return [];
|
|
3477
|
+
const state = inst.channel?.getAidState?.();
|
|
3478
|
+
return state ? [state] : [];
|
|
3479
|
+
}),
|
|
3480
|
+
aunAidStats: () => aidStatsCollector.getAllSnapshots(),
|
|
3481
|
+
});
|
|
3482
|
+
cmdHandler.setAgentApplicationService(agentApplicationService);
|
|
3483
|
+
ipcServer.setAgentApplicationService(agentApplicationService);
|
|
3377
3484
|
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
3378
|
-
ipcServer.setAgentStatsProvider(() =>
|
|
3379
|
-
const
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3485
|
+
ipcServer.setAgentStatsProvider(() => {
|
|
3486
|
+
const agents = agentRegistry.list();
|
|
3487
|
+
const snapshots = statsCollector.getSnapshots(agents.map(agent => agent.aid));
|
|
3488
|
+
return agents.map((agent) => {
|
|
3489
|
+
const snap = snapshots.get(agent.aid);
|
|
3490
|
+
return {
|
|
3491
|
+
aid: agent.aid,
|
|
3492
|
+
received: snap.lastHour.received,
|
|
3493
|
+
sent: snap.lastHour.sent,
|
|
3494
|
+
completed: snap.lastHour.completed,
|
|
3495
|
+
errors: snap.lastHour.errors,
|
|
3496
|
+
interrupts: snap.lastHour.interrupts,
|
|
3497
|
+
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
3498
|
+
processing: messageQueue.getProcessingCountByAgent(agent.aid),
|
|
3499
|
+
queued: messageQueue.getQueueLengthByAgent(agent.aid),
|
|
3500
|
+
muted: messageQueue.isAgentMuted(agent.aid),
|
|
3501
|
+
};
|
|
3502
|
+
});
|
|
3503
|
+
});
|
|
3393
3504
|
// Queue snapshot & action (for ec queue --agent CLI)
|
|
3394
3505
|
ipcServer.setQueueSnapshotProvider((params) => {
|
|
3395
3506
|
const handle = agentRegistry.get(params.agent);
|
|
@@ -3447,8 +3558,11 @@ async function main() {
|
|
|
3447
3558
|
chatType: delegation.grant.chatType,
|
|
3448
3559
|
conversationId,
|
|
3449
3560
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3561
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3450
3562
|
});
|
|
3451
|
-
if (params.targetAid
|
|
3563
|
+
if (params.targetAid
|
|
3564
|
+
&& params.targetAid !== delegation.grant.selfAid
|
|
3565
|
+
&& subject.dataScope !== 'daemon') {
|
|
3452
3566
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3453
3567
|
}
|
|
3454
3568
|
const decision = authorizeOperation({
|
|
@@ -3463,7 +3577,10 @@ async function main() {
|
|
|
3463
3577
|
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
3464
3578
|
});
|
|
3465
3579
|
return decision.allow
|
|
3466
|
-
? {
|
|
3580
|
+
? {
|
|
3581
|
+
ok: true,
|
|
3582
|
+
aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
|
|
3583
|
+
}
|
|
3467
3584
|
: { ok: false, code: decision.code, error: decision.reason };
|
|
3468
3585
|
}
|
|
3469
3586
|
ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
@@ -3528,6 +3645,7 @@ async function main() {
|
|
|
3528
3645
|
chatType: delegation.grant.chatType,
|
|
3529
3646
|
conversationId,
|
|
3530
3647
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3648
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3531
3649
|
});
|
|
3532
3650
|
const decision = authorizeOperation({
|
|
3533
3651
|
source: 'agent-tool',
|
|
@@ -3555,7 +3673,7 @@ async function main() {
|
|
|
3555
3673
|
}
|
|
3556
3674
|
const { execAgentAction, execAgentOptions, execAgentQuery } = await import('./core/command/agent-control.js');
|
|
3557
3675
|
if (operation === 'agent.list') {
|
|
3558
|
-
const result = await execAgentOptions({ options: 'all' });
|
|
3676
|
+
const result = await execAgentOptions({ options: 'all' }, agentApplicationService);
|
|
3559
3677
|
if ('error' in result)
|
|
3560
3678
|
return fail(result.code, result.error);
|
|
3561
3679
|
const agents = Array.isArray(result.data?.agents) ? result.data.agents.map((agent) => ({
|
|
@@ -3572,7 +3690,7 @@ async function main() {
|
|
|
3572
3690
|
return { ok: true, result: { ok: true, agents, redacted: true } };
|
|
3573
3691
|
}
|
|
3574
3692
|
if (operation === 'agent.show') {
|
|
3575
|
-
const result = await execAgentQuery({ aid: targetAid });
|
|
3693
|
+
const result = await execAgentQuery({ aid: targetAid }, agentApplicationService);
|
|
3576
3694
|
if ('error' in result)
|
|
3577
3695
|
return fail(result.code, result.error);
|
|
3578
3696
|
const agent = result.data ?? {};
|
|
@@ -3614,7 +3732,7 @@ async function main() {
|
|
|
3614
3732
|
return fail('INVALID_ARGS', validationError);
|
|
3615
3733
|
if (argv.includes('--dry-run'))
|
|
3616
3734
|
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3617
|
-
const
|
|
3735
|
+
const createOptions = {
|
|
3618
3736
|
aid: plan.aid,
|
|
3619
3737
|
project: plan.project,
|
|
3620
3738
|
baseagent: plan.baseagent,
|
|
@@ -3622,22 +3740,33 @@ async function main() {
|
|
|
3622
3740
|
name: plan.name,
|
|
3623
3741
|
description: plan.description,
|
|
3624
3742
|
force: argv.includes('--force'),
|
|
3625
|
-
}
|
|
3743
|
+
};
|
|
3744
|
+
const result = agentApplicationService
|
|
3745
|
+
? await agentApplicationService.create(createOptions)
|
|
3746
|
+
: await agentCreateNonInteractive(createOptions);
|
|
3626
3747
|
return !result.ok
|
|
3627
3748
|
? fail(result.code ?? 'INTERNAL', result.error)
|
|
3628
3749
|
: { ok: true, result: { ...result, configPath: '[redacted]' } };
|
|
3629
3750
|
}
|
|
3630
3751
|
if (operation === 'agent.delete') {
|
|
3631
|
-
const
|
|
3632
|
-
|
|
3752
|
+
const result = agentApplicationService
|
|
3753
|
+
? await agentApplicationService.delete(targetAid ?? '', argv.includes('--purge'))
|
|
3754
|
+
: await (async () => {
|
|
3755
|
+
const { agentDelete } = await import('./cli/agent.js');
|
|
3756
|
+
return await agentDelete(targetAid ?? '', argv.includes('--purge'));
|
|
3757
|
+
})();
|
|
3633
3758
|
return 'error' in result ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3634
3759
|
}
|
|
3635
3760
|
if (operation === 'agent.reload' && !targetAid) {
|
|
3636
|
-
const
|
|
3637
|
-
|
|
3761
|
+
const result = agentApplicationService
|
|
3762
|
+
? await agentApplicationService.reload()
|
|
3763
|
+
: await (async () => {
|
|
3764
|
+
const { agentReload } = await import('./cli/agent.js');
|
|
3765
|
+
return await agentReload();
|
|
3766
|
+
})();
|
|
3638
3767
|
return !result.ok ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3639
3768
|
}
|
|
3640
|
-
const result = await execAgentAction(sub, { aid: targetAid ?? taskAid, force: argv.includes('--force') }, delegation.grant.actorId, eventBus);
|
|
3769
|
+
const result = await execAgentAction(sub, { aid: targetAid ?? taskAid, force: argv.includes('--force') }, delegation.grant.actorId, eventBus, agentApplicationService);
|
|
3641
3770
|
return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
|
|
3642
3771
|
});
|
|
3643
3772
|
ipcServer.setQueueOperationExecutor(async (params) => {
|
|
@@ -3696,12 +3825,51 @@ async function main() {
|
|
|
3696
3825
|
throw new Error(`agent not found: ${agentAid}`);
|
|
3697
3826
|
return agentAid;
|
|
3698
3827
|
};
|
|
3828
|
+
const auditFullAccessTriggerConfiguration = (event, definition, actor, reason) => {
|
|
3829
|
+
auditFullAccessEvent({
|
|
3830
|
+
event,
|
|
3831
|
+
source: 'trigger',
|
|
3832
|
+
actorId: actor.origin.peerId,
|
|
3833
|
+
processRole: actor.daemonOwner ? 'daemon-owner' : actor.subject.processRole,
|
|
3834
|
+
authorizedBy: definition.authorizedBy,
|
|
3835
|
+
agentAid: definition.agentAid,
|
|
3836
|
+
triggerId: definition.id,
|
|
3837
|
+
reason,
|
|
3838
|
+
});
|
|
3839
|
+
};
|
|
3840
|
+
const rejectUnapprovedFullAccessConfiguration = (definition, actor) => {
|
|
3841
|
+
// A managed Agent task may carry a daemon-owner identity for ordinary
|
|
3842
|
+
// scoped operations. That delegation is never a human approval for a
|
|
3843
|
+
// persistent host-level Trigger; only the explicit user/menu paths may
|
|
3844
|
+
// create or retain this mode.
|
|
3845
|
+
const approvalSource = actor.control ? 'daemon-service' : 'agent-delegation';
|
|
3846
|
+
if (definition.execution.permissionMode === 'fullaccess'
|
|
3847
|
+
&& !actor.fullAccess
|
|
3848
|
+
&& !canApprovePersistentFullAccessTrigger({
|
|
3849
|
+
daemonOwner: actor.daemonOwner,
|
|
3850
|
+
source: approvalSource,
|
|
3851
|
+
})) {
|
|
3852
|
+
throw new Error('persistent fullaccess Trigger requires an explicit user or management-channel action');
|
|
3853
|
+
}
|
|
3854
|
+
};
|
|
3855
|
+
const withFullAccessConfigurationProvenance = (definition, actor) => {
|
|
3856
|
+
if (definition.execution.permissionMode !== 'fullaccess') {
|
|
3857
|
+
const { authorizedBy: _authorizedBy, ...deescalated } = definition;
|
|
3858
|
+
return deescalated;
|
|
3859
|
+
}
|
|
3860
|
+
const authorizedBy = actor.fullAccess
|
|
3861
|
+
? actor.subject.authorizedBy
|
|
3862
|
+
: actor.subject.principalId ?? actor.origin.peerId;
|
|
3863
|
+
if (!authorizedBy)
|
|
3864
|
+
throw new Error('fullaccess Trigger approval provenance is unavailable');
|
|
3865
|
+
return { ...definition, authorizedBy };
|
|
3866
|
+
};
|
|
3699
3867
|
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
3700
3868
|
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
3701
3869
|
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
3702
3870
|
if (!isCrossAgentTriggerOperationAllowed({
|
|
3703
3871
|
control: actor.control,
|
|
3704
|
-
daemonOwner: actor.daemonOwner,
|
|
3872
|
+
daemonOwner: actor.daemonOwner || actor.fullAccess,
|
|
3705
3873
|
taskAgentAid: actor.selfAid,
|
|
3706
3874
|
targetAgentAid: agentAid,
|
|
3707
3875
|
targetManagement: actor.management,
|
|
@@ -3734,7 +3902,12 @@ async function main() {
|
|
|
3734
3902
|
return actor;
|
|
3735
3903
|
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
3736
3904
|
const privilegedReason = definition ? daemonPrivilegedTriggerReason(definition) : undefined;
|
|
3737
|
-
|
|
3905
|
+
const persistedFullAccessApproval = privilegedReason === 'persistent fullaccess Trigger';
|
|
3906
|
+
const deescalationOperation = operation === 'trigger.update'
|
|
3907
|
+
|| operation === 'trigger.setEnabled'
|
|
3908
|
+
|| operation === 'trigger.cancel'
|
|
3909
|
+
|| operation === 'trigger.delete';
|
|
3910
|
+
if (privilegedReason && !persistedFullAccessApproval && !actor.daemonPrivileged && !deescalationOperation) {
|
|
3738
3911
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
3739
3912
|
}
|
|
3740
3913
|
if (actor.management)
|
|
@@ -3751,7 +3924,9 @@ async function main() {
|
|
|
3751
3924
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3752
3925
|
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
3753
3926
|
const scheduler = schedulerFor(agentAid);
|
|
3754
|
-
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged
|
|
3927
|
+
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged
|
|
3928
|
+
|| !daemonPrivilegedTriggerReason(trigger)
|
|
3929
|
+
|| daemonPrivilegedTriggerReason(trigger) === 'persistent fullaccess Trigger')
|
|
3755
3930
|
&& (actor.management
|
|
3756
3931
|
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey))));
|
|
3757
3932
|
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
@@ -3808,11 +3983,16 @@ async function main() {
|
|
|
3808
3983
|
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
3809
3984
|
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
3810
3985
|
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
|
|
3811
|
-
|
|
3986
|
+
let definition = normalizeTriggerDefinition(materialized);
|
|
3987
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
3988
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3812
3989
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3813
3990
|
requireAgent(definition.agentAid);
|
|
3814
3991
|
validateTriggerFeedbackChannels(definition);
|
|
3815
3992
|
const trigger = schedulerFor(definition.agentAid).create(definition, cmd.files ?? [], { enable: cmd.enable });
|
|
3993
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
3994
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, 'fullaccess Trigger created through IPC');
|
|
3995
|
+
}
|
|
3816
3996
|
return { ok: true, trigger };
|
|
3817
3997
|
}
|
|
3818
3998
|
case 'trigger.update': {
|
|
@@ -3827,11 +4007,21 @@ async function main() {
|
|
|
3827
4007
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
3828
4008
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
3829
4009
|
}
|
|
3830
|
-
|
|
4010
|
+
let definition = applyTriggerPatch(existing, cmd.patch, { fromPromptFile: cmd.promptFile === true });
|
|
4011
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
4012
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3831
4013
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3832
4014
|
validateTriggerFeedbackChannels(definition);
|
|
3833
4015
|
const scheduler = schedulerFor(agentAid);
|
|
3834
4016
|
const trigger = scheduler.update(cmd.triggerId, definition);
|
|
4017
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
4018
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, existing.execution.permissionMode === 'fullaccess'
|
|
4019
|
+
? 'fullaccess Trigger updated through IPC'
|
|
4020
|
+
: 'Trigger elevated to fullaccess through IPC');
|
|
4021
|
+
}
|
|
4022
|
+
else if (existing.execution.permissionMode === 'fullaccess') {
|
|
4023
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.deescalated', existing, actor, 'Trigger permissionMode changed from fullaccess through IPC');
|
|
4024
|
+
}
|
|
3835
4025
|
return { ok: true, trigger: scheduler.listItem(trigger), revision: definitionRevision(trigger) };
|
|
3836
4026
|
}
|
|
3837
4027
|
case 'trigger.setEnabled': {
|
|
@@ -3840,8 +4030,26 @@ async function main() {
|
|
|
3840
4030
|
throw new Error('missing triggerId');
|
|
3841
4031
|
if (typeof cmd.enabled !== 'boolean')
|
|
3842
4032
|
throw new Error('missing enabled');
|
|
3843
|
-
await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
3844
|
-
const
|
|
4033
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
4034
|
+
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
4035
|
+
if (!existing)
|
|
4036
|
+
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
4037
|
+
if (cmd.enabled && existing.execution.permissionMode === 'fullaccess') {
|
|
4038
|
+
rejectUnapprovedFullAccessConfiguration(existing, actor);
|
|
4039
|
+
if (!isFullAccessEnabled())
|
|
4040
|
+
throw new Error('fullaccess is disabled by daemon.json');
|
|
4041
|
+
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
4042
|
+
throw new Error('enabling a fullaccess Trigger requires a DaemonOwner or fullaccess-run');
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
const scheduler = schedulerFor(agentAid);
|
|
4046
|
+
if (cmd.enabled && existing.execution.permissionMode === 'fullaccess') {
|
|
4047
|
+
scheduler.update(cmd.triggerId, withFullAccessConfigurationProvenance(existing, actor));
|
|
4048
|
+
}
|
|
4049
|
+
const trigger = scheduler.setEnabled(cmd.triggerId, cmd.enabled);
|
|
4050
|
+
if (cmd.enabled && trigger.execution.permissionMode === 'fullaccess') {
|
|
4051
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, 'fullaccess Trigger enabled through IPC');
|
|
4052
|
+
}
|
|
3845
4053
|
return { ok: true, trigger };
|
|
3846
4054
|
}
|
|
3847
4055
|
case 'trigger.cancel': {
|
|
@@ -3856,8 +4064,11 @@ async function main() {
|
|
|
3856
4064
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3857
4065
|
if (!cmd.triggerId)
|
|
3858
4066
|
throw new Error('missing triggerId');
|
|
3859
|
-
await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
4067
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
3860
4068
|
const trigger = schedulerFor(agentAid).delete(cmd.triggerId);
|
|
4069
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
4070
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.deescalated', trigger, actor, 'fullaccess Trigger deleted through IPC');
|
|
4071
|
+
}
|
|
3861
4072
|
return { ok: true, trigger };
|
|
3862
4073
|
}
|
|
3863
4074
|
case 'trigger.run': {
|
|
@@ -3925,6 +4136,13 @@ async function main() {
|
|
|
3925
4136
|
}
|
|
3926
4137
|
};
|
|
3927
4138
|
try {
|
|
4139
|
+
// ECWeb is owned by the daemon. Stop it from the unified shutdown
|
|
4140
|
+
// path as well as the signal handler so an IPC-driven `ec restart`
|
|
4141
|
+
// cannot leave the old ECWeb process running after its package was
|
|
4142
|
+
// upgraded on disk.
|
|
4143
|
+
await shutdownStep('stop ECWeb supervisor', async () => {
|
|
4144
|
+
await ecwebSupervisor?.stop();
|
|
4145
|
+
});
|
|
3928
4146
|
const queueShutdown = messageQueue.shutdownGracefully();
|
|
3929
4147
|
await shutdownStep('cancel pending permissions', () => permissionGateway.cancelAllPending('daemon_restart'));
|
|
3930
4148
|
await shutdownStep('wait for trigger schedulers', async () => {
|