evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- 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 +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- 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 +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- 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 +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- 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 +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -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 +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- 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 +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- 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 +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- 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 +30 -6
- 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/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- 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/message-fragments/item.md +1 -1
- 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/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
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';
|
|
@@ -83,12 +88,12 @@ import { TriggerFeedbackDispatcher } from './trigger/feedback.js';
|
|
|
83
88
|
import { TriggerRuntimeScheduler } from './trigger/scheduler.js';
|
|
84
89
|
import { TargetSessionLockRegistry } from './trigger/session-lock.js';
|
|
85
90
|
import { DaemonChannel } from './channels/daemon.js';
|
|
86
|
-
import { definitionRevision, normalizeTriggerDefinition } from './trigger/validation.js';
|
|
91
|
+
import { definitionRevision, normalizeTriggerDefinition, splitScriptCommand } from './trigger/validation.js';
|
|
87
92
|
import { applyTriggerPatch } from './trigger/patch.js';
|
|
88
93
|
import { validateModelSelectionForRole } from './core/model/model-permission.js';
|
|
89
94
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
90
95
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
91
|
-
import { ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
96
|
+
import { DAEMON_RUNTIME_EPOCH_ENV, ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
92
97
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
93
98
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
94
99
|
import { MAIN_PACKAGE_NAME } from './product.js';
|
|
@@ -785,11 +790,19 @@ async function main() {
|
|
|
785
790
|
process.exit(1);
|
|
786
791
|
}
|
|
787
792
|
}
|
|
788
|
-
// 在登记 daemon
|
|
793
|
+
// 在登记 daemon 实例前自动备份并迁移旧配置;失败才阻止启动。
|
|
789
794
|
try {
|
|
790
795
|
// Crash recovery must precede schema gates and migrations so they never
|
|
791
796
|
// inspect or migrate a partially applied Role Menu transaction.
|
|
792
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
|
+
}
|
|
793
806
|
const migration = await ensureRoleConfigV4OnStartup();
|
|
794
807
|
if (migration) {
|
|
795
808
|
const msg = `✓ Role config v4 migration applied automatically: ${migration.changedFiles} file(s)` +
|
|
@@ -834,7 +847,7 @@ async function main() {
|
|
|
834
847
|
}
|
|
835
848
|
// ── 自动迁移 ──
|
|
836
849
|
migrateIdentitiesIfNeeded();
|
|
837
|
-
//
|
|
850
|
+
// daemon.json 的旧 ecweb/serviceProxy 结构已在启动前归一化为 services[]。
|
|
838
851
|
// ── 配置体系初始化(schema 字段不相交硬约束校验)──
|
|
839
852
|
try {
|
|
840
853
|
initConfigManager();
|
|
@@ -897,9 +910,10 @@ async function main() {
|
|
|
897
910
|
// instance just before spawning us; the supervisor adopts it when healthy,
|
|
898
911
|
// otherwise starts one and keeps it alive for the daemon's lifetime.
|
|
899
912
|
let ecwebSupervisor = null;
|
|
900
|
-
|
|
913
|
+
const ecwebConfig = ecwebService(daemonCfg.services);
|
|
914
|
+
if (ecwebConfig?.enabled === true) {
|
|
901
915
|
ecwebSupervisor = createEcwebSupervisor({
|
|
902
|
-
port:
|
|
916
|
+
port: ecwebConfig.port ?? DEFAULT_ECWEB_PORT,
|
|
903
917
|
logger,
|
|
904
918
|
});
|
|
905
919
|
try {
|
|
@@ -1052,8 +1066,8 @@ async function main() {
|
|
|
1052
1066
|
agentLoader.register(new EcagentAgentPlugin());
|
|
1053
1067
|
const creationErrors = [];
|
|
1054
1068
|
const agentInstances = agentLoader.createAll(agentRegistry, {
|
|
1055
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
1056
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
1069
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
1070
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
1057
1071
|
},
|
|
1058
1072
|
}, creationErrors);
|
|
1059
1073
|
// agentMap 复合键:${aid}::${baseagent}
|
|
@@ -1197,8 +1211,12 @@ async function main() {
|
|
|
1197
1211
|
: null;
|
|
1198
1212
|
bindService?.startCleanup();
|
|
1199
1213
|
const agentDelegationRegistry = new AgentDelegationRegistry();
|
|
1214
|
+
// Replace any inherited marker from a parent/previous daemon before child
|
|
1215
|
+
// runners or in-process IPC clients are created.
|
|
1216
|
+
process.env[DAEMON_RUNTIME_EPOCH_ENV] = agentDelegationRegistry.getRuntimeEpoch();
|
|
1200
1217
|
// 创建命令处理器
|
|
1201
1218
|
const cmdHandler = new CommandHandler(sessionManager, agentMap, messageCache, eventBus, primaryRunnerKey);
|
|
1219
|
+
let agentApplicationService;
|
|
1202
1220
|
cmdHandler.setAgentDelegationRegistry(agentDelegationRegistry);
|
|
1203
1221
|
cmdHandler.setPermissionGateway(permissionGateway);
|
|
1204
1222
|
cmdHandler.setInteractionRouter(interactionRouter);
|
|
@@ -1434,10 +1452,19 @@ async function main() {
|
|
|
1434
1452
|
&& definition.feedback.target?.channelKey === 'daemon') {
|
|
1435
1453
|
return 'daemon-control feedback';
|
|
1436
1454
|
}
|
|
1455
|
+
if (definition.execution.permissionMode === 'fullaccess')
|
|
1456
|
+
return 'persistent fullaccess Trigger';
|
|
1437
1457
|
return undefined;
|
|
1438
1458
|
};
|
|
1439
|
-
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service');
|
|
1459
|
+
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon');
|
|
1440
1460
|
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1461
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1462
|
+
if (!isFullAccessEnabled())
|
|
1463
|
+
throw new Error('fullaccess is disabled by daemon.json');
|
|
1464
|
+
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
1465
|
+
throw new Error('persistent fullaccess Trigger requires DaemonOwner or fullaccess-run');
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1441
1468
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1442
1469
|
if (privilegedReason && !actor.daemonPrivileged)
|
|
1443
1470
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
@@ -1488,9 +1515,30 @@ async function main() {
|
|
|
1488
1515
|
}
|
|
1489
1516
|
};
|
|
1490
1517
|
const authorizeTriggerExecution = (definition) => {
|
|
1518
|
+
if (definition.execution.permissionMode === 'fullaccess' && !isFullAccessEnabled()) {
|
|
1519
|
+
return { allowed: false, reason: 'fullaccess is disabled by daemon.json' };
|
|
1520
|
+
}
|
|
1521
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1522
|
+
if (!definition.authorizedBy) {
|
|
1523
|
+
return { allowed: false, reason: 'fullaccess Trigger approval provenance is missing' };
|
|
1524
|
+
}
|
|
1525
|
+
const owners = new Set(loadDaemonConfig().owners ?? []);
|
|
1526
|
+
if (!owners.has(definition.authorizedBy)) {
|
|
1527
|
+
return { allowed: false, reason: 'fullaccess Trigger approver is no longer a current daemon-owner' };
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1491
1530
|
const origin = definition.origin;
|
|
1492
1531
|
if (!origin?.peerId)
|
|
1493
1532
|
return { allowed: false, reason: 'trigger origin peer is missing' };
|
|
1533
|
+
// A persistent fullaccess definition is an already-approved daemon-scope
|
|
1534
|
+
// task. Its origin remains useful for audit and feedback routing, but a
|
|
1535
|
+
// relation lookup on the origin actor would make a cross-Agent Trigger
|
|
1536
|
+
// persist successfully and then fail on every fire after creation.
|
|
1537
|
+
// Approval provenance and the feature flag above remain mandatory; only
|
|
1538
|
+
// the ordinary creator relation gate is bypassed for this mode.
|
|
1539
|
+
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1540
|
+
return { allowed: true };
|
|
1541
|
+
}
|
|
1494
1542
|
if (origin.channelKey === 'daemon') {
|
|
1495
1543
|
const subject = buildAuthSubject({
|
|
1496
1544
|
selfAid: definition.agentAid,
|
|
@@ -1557,7 +1605,8 @@ async function main() {
|
|
|
1557
1605
|
return { allowed: false, reason: operationDecision.reason };
|
|
1558
1606
|
}
|
|
1559
1607
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1560
|
-
|
|
1608
|
+
const persistedFullAccessApproval = privilegedReason === 'persistent fullaccess Trigger';
|
|
1609
|
+
if (privilegedReason && !persistedFullAccessApproval && !hasDaemonTriggerAuthority(subject)) {
|
|
1561
1610
|
return { allowed: false, reason: `${privilegedReason} requires current DaemonOwner` };
|
|
1562
1611
|
}
|
|
1563
1612
|
if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
|
|
@@ -1605,6 +1654,7 @@ async function main() {
|
|
|
1605
1654
|
management: true,
|
|
1606
1655
|
daemonOwner: false,
|
|
1607
1656
|
daemonPrivileged: true,
|
|
1657
|
+
fullAccess: false,
|
|
1608
1658
|
control: true,
|
|
1609
1659
|
role: 'none',
|
|
1610
1660
|
subject,
|
|
@@ -1643,18 +1693,20 @@ async function main() {
|
|
|
1643
1693
|
identity: roleToSessionIdentity(currentRole),
|
|
1644
1694
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1645
1695
|
fromControlChannel: false,
|
|
1696
|
+
...trustedExecutionIdentityInput(validation.grant),
|
|
1646
1697
|
});
|
|
1647
|
-
if (validation.grant.selfAid !== agentAid &&
|
|
1698
|
+
if (validation.grant.selfAid !== agentAid && subject.dataScope !== 'daemon') {
|
|
1648
1699
|
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
1649
1700
|
}
|
|
1650
|
-
if (
|
|
1701
|
+
if (subject.dataScope !== 'daemon' && !checkRoleAccess(currentRole, agentAid)) {
|
|
1651
1702
|
throw new Error('trigger actor no longer has access to this agent');
|
|
1652
1703
|
}
|
|
1653
1704
|
return {
|
|
1654
1705
|
origin: actorOrigin,
|
|
1655
|
-
management: isManagementRole(currentRole) || subject.
|
|
1706
|
+
management: isManagementRole(currentRole) || subject.dataScope === 'daemon',
|
|
1656
1707
|
daemonOwner: subject.isDaemonOwner,
|
|
1657
|
-
daemonPrivileged: subject.isDaemonOwner,
|
|
1708
|
+
daemonPrivileged: subject.isDaemonOwner || subject.dataScope === 'daemon',
|
|
1709
|
+
fullAccess: subject.processRole === 'fullaccess-run',
|
|
1658
1710
|
selfAid: validation.grant.selfAid,
|
|
1659
1711
|
control: false,
|
|
1660
1712
|
role: currentRole || 'none',
|
|
@@ -1797,6 +1849,7 @@ async function main() {
|
|
|
1797
1849
|
msgBridge.setInteractionRouter(interactionRouter);
|
|
1798
1850
|
msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
|
|
1799
1851
|
msgBridge.setAidStatsCollector(aidStatsCollector);
|
|
1852
|
+
cmdHandler.setContactRequestSubmitter((input) => msgBridge.submitAgentContactRequest(input));
|
|
1800
1853
|
bootstrapService = new BootstrapService(agentRegistry, eventBus);
|
|
1801
1854
|
msgBridge.setBootstrapService(bootstrapService);
|
|
1802
1855
|
msgBridge.setHandoffRuntime(handoffRuntime);
|
|
@@ -1818,7 +1871,14 @@ async function main() {
|
|
|
1818
1871
|
payload: summarizeOutboundPayload(payload),
|
|
1819
1872
|
});
|
|
1820
1873
|
const result = await originalSend(envelope, payload);
|
|
1821
|
-
|
|
1874
|
+
// AUN adapters return an explicit receipt. Only a receipt with a remote
|
|
1875
|
+
// message ID proves delivery; queued/failed results must not be emitted
|
|
1876
|
+
// as `message:sent`. Legacy adapters that return void remain compatible.
|
|
1877
|
+
const deliveryConfirmed = result === undefined
|
|
1878
|
+
|| (result && typeof result === 'object' && result.status === 'sent'
|
|
1879
|
+
&& typeof result.messageId === 'string'
|
|
1880
|
+
&& result.messageId.length > 0);
|
|
1881
|
+
if (shouldCountSentPayload(payload) && deliveryConfirmed) {
|
|
1822
1882
|
if ((inst.channelType || inst.adapter.channelName) !== 'aun') {
|
|
1823
1883
|
try {
|
|
1824
1884
|
const logPayload = outboundPayloadToLogText(payload);
|
|
@@ -2248,6 +2308,7 @@ async function main() {
|
|
|
2248
2308
|
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
2249
2309
|
return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
|
|
2250
2310
|
});
|
|
2311
|
+
ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
|
|
2251
2312
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
2252
2313
|
// function declaration is hoisted, while its invocation remains here so a
|
|
2253
2314
|
// failed bind still aborts before any AUN connection is attempted.
|
|
@@ -2403,8 +2464,8 @@ async function main() {
|
|
|
2403
2464
|
return;
|
|
2404
2465
|
}
|
|
2405
2466
|
if (text.toLowerCase() === '/pair') {
|
|
2406
|
-
const port =
|
|
2407
|
-
const pair = await fetchEcwebPairCode(port);
|
|
2467
|
+
const port = ecwebService(loadDaemonConfig().services)?.port ?? DEFAULT_ECWEB_PORT;
|
|
2468
|
+
const pair = await fetchEcwebPairCode(port, opts.peerId);
|
|
2408
2469
|
let reply;
|
|
2409
2470
|
if (pair) {
|
|
2410
2471
|
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
@@ -2467,11 +2528,12 @@ async function main() {
|
|
|
2467
2528
|
// ── Service Proxy:把本地服务(ecweb 等)通过控制 AID 暴露到 AUN 网络 ──
|
|
2468
2529
|
// 挂在控制 AUNChannel 上,动态解引用其 client(规避重连换 client)。
|
|
2469
2530
|
// 失败只 warn,不影响 daemon 主流程。
|
|
2470
|
-
|
|
2531
|
+
const proxyServices = (daemonCfg.services ?? []).filter((service) => service.enabled === true && service.proxy?.enabled === true);
|
|
2532
|
+
if (proxyServices.length > 0 && daemonCfg.aid) {
|
|
2471
2533
|
// 短暂延迟确保控制 channel 完全就绪
|
|
2472
2534
|
const channel = controlChannel;
|
|
2473
2535
|
const aid = daemonCfg.aid;
|
|
2474
|
-
const config =
|
|
2536
|
+
const config = proxyServices;
|
|
2475
2537
|
setTimeout(() => {
|
|
2476
2538
|
if (!channel)
|
|
2477
2539
|
return;
|
|
@@ -2778,7 +2840,7 @@ async function main() {
|
|
|
2778
2840
|
for (const runner of new Set(agentMap.values())) {
|
|
2779
2841
|
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2780
2842
|
continue;
|
|
2781
|
-
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal);
|
|
2843
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId);
|
|
2782
2844
|
if (result.applicable)
|
|
2783
2845
|
return result;
|
|
2784
2846
|
}
|
|
@@ -2802,7 +2864,7 @@ async function main() {
|
|
|
2802
2864
|
if (delegation.grant.selfAid !== selfAid) {
|
|
2803
2865
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation self agent does not match current session' };
|
|
2804
2866
|
}
|
|
2805
|
-
if (params.agent && params.agent !== selfAid) {
|
|
2867
|
+
if (params.agent && params.agent !== selfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
|
|
2806
2868
|
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2807
2869
|
}
|
|
2808
2870
|
let conversationId;
|
|
@@ -2821,8 +2883,9 @@ async function main() {
|
|
|
2821
2883
|
chatType: delegation.grant.chatType,
|
|
2822
2884
|
conversationId,
|
|
2823
2885
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2886
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
2824
2887
|
});
|
|
2825
|
-
const managementRole = isManagementRole(subject.role);
|
|
2888
|
+
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
2826
2889
|
const decision = authorizeOperation({
|
|
2827
2890
|
source: 'agent-tool',
|
|
2828
2891
|
subject,
|
|
@@ -2945,6 +3008,12 @@ async function main() {
|
|
|
2945
3008
|
if (!ch) {
|
|
2946
3009
|
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2947
3010
|
}
|
|
3011
|
+
if (params.scope === 'msg') {
|
|
3012
|
+
const admission = await evaluateOutboundContactAdmission({ selfAid: params.aid, targetAid: params.to });
|
|
3013
|
+
if (!admission.allow) {
|
|
3014
|
+
return { ok: false, error: admission.error, code: admission.code };
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
2948
3017
|
// The IPC command already carries the caller-selected transport scope.
|
|
2949
3018
|
// Never reclassify the target by parsing its AID string: a private AID may
|
|
2950
3019
|
// look group-like and a group identifier may use a future format.
|
|
@@ -3113,8 +3182,8 @@ async function main() {
|
|
|
3113
3182
|
const staged = candidate.config.enabled === false
|
|
3114
3183
|
? []
|
|
3115
3184
|
: agentLoader.createForAgent(candidate, {
|
|
3116
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3117
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3185
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3186
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3118
3187
|
},
|
|
3119
3188
|
}, creationErrors);
|
|
3120
3189
|
const onDisposeError = (instance, error) => {
|
|
@@ -3213,8 +3282,8 @@ async function main() {
|
|
|
3213
3282
|
}
|
|
3214
3283
|
const creationErrors = [];
|
|
3215
3284
|
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
3216
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3217
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3285
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3286
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3218
3287
|
},
|
|
3219
3288
|
}, creationErrors);
|
|
3220
3289
|
for (const inst of newAgentInstances) {
|
|
@@ -3381,6 +3450,41 @@ async function main() {
|
|
|
3381
3450
|
resyncInFlight = undefined;
|
|
3382
3451
|
}
|
|
3383
3452
|
};
|
|
3453
|
+
// Core-owned agent application facade. Menu/slash/managed-task callers use
|
|
3454
|
+
// this in-process service after their own authorization checks; standalone
|
|
3455
|
+
// CLI commands continue to reach these operations through IPC.
|
|
3456
|
+
const coreHotLoadAgent = async (aid) => {
|
|
3457
|
+
const hotLoad = globalThis.__evolcore_hotLoadAgent;
|
|
3458
|
+
if (typeof hotLoad !== 'function')
|
|
3459
|
+
throw new Error('Hot-load handler not initialized');
|
|
3460
|
+
await hotLoad(aid);
|
|
3461
|
+
};
|
|
3462
|
+
const coreResyncAgents = async () => {
|
|
3463
|
+
const resync = globalThis.__evolcore_resyncAgents;
|
|
3464
|
+
if (typeof resync !== 'function')
|
|
3465
|
+
throw new Error('Resync handler not initialized');
|
|
3466
|
+
return await resync();
|
|
3467
|
+
};
|
|
3468
|
+
agentApplicationService = new AgentApplicationService({
|
|
3469
|
+
registry: agentRegistry,
|
|
3470
|
+
reload: (aid, options) => reloadCoordinator.reload(aid, options),
|
|
3471
|
+
hooks: reloadHooks,
|
|
3472
|
+
hotLoad: coreHotLoadAgent,
|
|
3473
|
+
resync: coreResyncAgents,
|
|
3474
|
+
create: async (options) => {
|
|
3475
|
+
const { agentCreateNonInteractive } = await import('./cli/agent.js');
|
|
3476
|
+
return await agentCreateNonInteractive(options, { hotLoadAgent: coreHotLoadAgent });
|
|
3477
|
+
},
|
|
3478
|
+
aunAids: () => channelInstances.flatMap((inst) => {
|
|
3479
|
+
if (inst.channelType !== 'aun')
|
|
3480
|
+
return [];
|
|
3481
|
+
const state = inst.channel?.getAidState?.();
|
|
3482
|
+
return state ? [state] : [];
|
|
3483
|
+
}),
|
|
3484
|
+
aunAidStats: () => aidStatsCollector.getAllSnapshots(),
|
|
3485
|
+
});
|
|
3486
|
+
cmdHandler.setAgentApplicationService(agentApplicationService);
|
|
3487
|
+
ipcServer.setAgentApplicationService(agentApplicationService);
|
|
3384
3488
|
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
3385
3489
|
ipcServer.setAgentStatsProvider(() => {
|
|
3386
3490
|
const agents = agentRegistry.list();
|
|
@@ -3458,8 +3562,11 @@ async function main() {
|
|
|
3458
3562
|
chatType: delegation.grant.chatType,
|
|
3459
3563
|
conversationId,
|
|
3460
3564
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3565
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3461
3566
|
});
|
|
3462
|
-
if (params.targetAid
|
|
3567
|
+
if (params.targetAid
|
|
3568
|
+
&& params.targetAid !== delegation.grant.selfAid
|
|
3569
|
+
&& subject.dataScope !== 'daemon') {
|
|
3463
3570
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3464
3571
|
}
|
|
3465
3572
|
const decision = authorizeOperation({
|
|
@@ -3474,7 +3581,10 @@ async function main() {
|
|
|
3474
3581
|
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
3475
3582
|
});
|
|
3476
3583
|
return decision.allow
|
|
3477
|
-
? {
|
|
3584
|
+
? {
|
|
3585
|
+
ok: true,
|
|
3586
|
+
aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
|
|
3587
|
+
}
|
|
3478
3588
|
: { ok: false, code: decision.code, error: decision.reason };
|
|
3479
3589
|
}
|
|
3480
3590
|
ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
@@ -3539,6 +3649,7 @@ async function main() {
|
|
|
3539
3649
|
chatType: delegation.grant.chatType,
|
|
3540
3650
|
conversationId,
|
|
3541
3651
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3652
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3542
3653
|
});
|
|
3543
3654
|
const decision = authorizeOperation({
|
|
3544
3655
|
source: 'agent-tool',
|
|
@@ -3566,7 +3677,7 @@ async function main() {
|
|
|
3566
3677
|
}
|
|
3567
3678
|
const { execAgentAction, execAgentOptions, execAgentQuery } = await import('./core/command/agent-control.js');
|
|
3568
3679
|
if (operation === 'agent.list') {
|
|
3569
|
-
const result = await execAgentOptions({ options: 'all' });
|
|
3680
|
+
const result = await execAgentOptions({ options: 'all' }, agentApplicationService);
|
|
3570
3681
|
if ('error' in result)
|
|
3571
3682
|
return fail(result.code, result.error);
|
|
3572
3683
|
const agents = Array.isArray(result.data?.agents) ? result.data.agents.map((agent) => ({
|
|
@@ -3583,7 +3694,7 @@ async function main() {
|
|
|
3583
3694
|
return { ok: true, result: { ok: true, agents, redacted: true } };
|
|
3584
3695
|
}
|
|
3585
3696
|
if (operation === 'agent.show') {
|
|
3586
|
-
const result = await execAgentQuery({ aid: targetAid });
|
|
3697
|
+
const result = await execAgentQuery({ aid: targetAid }, agentApplicationService);
|
|
3587
3698
|
if ('error' in result)
|
|
3588
3699
|
return fail(result.code, result.error);
|
|
3589
3700
|
const agent = result.data ?? {};
|
|
@@ -3625,7 +3736,7 @@ async function main() {
|
|
|
3625
3736
|
return fail('INVALID_ARGS', validationError);
|
|
3626
3737
|
if (argv.includes('--dry-run'))
|
|
3627
3738
|
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3628
|
-
const
|
|
3739
|
+
const createOptions = {
|
|
3629
3740
|
aid: plan.aid,
|
|
3630
3741
|
project: plan.project,
|
|
3631
3742
|
baseagent: plan.baseagent,
|
|
@@ -3633,22 +3744,33 @@ async function main() {
|
|
|
3633
3744
|
name: plan.name,
|
|
3634
3745
|
description: plan.description,
|
|
3635
3746
|
force: argv.includes('--force'),
|
|
3636
|
-
}
|
|
3747
|
+
};
|
|
3748
|
+
const result = agentApplicationService
|
|
3749
|
+
? await agentApplicationService.create(createOptions)
|
|
3750
|
+
: await agentCreateNonInteractive(createOptions);
|
|
3637
3751
|
return !result.ok
|
|
3638
3752
|
? fail(result.code ?? 'INTERNAL', result.error)
|
|
3639
3753
|
: { ok: true, result: { ...result, configPath: '[redacted]' } };
|
|
3640
3754
|
}
|
|
3641
3755
|
if (operation === 'agent.delete') {
|
|
3642
|
-
const
|
|
3643
|
-
|
|
3756
|
+
const result = agentApplicationService
|
|
3757
|
+
? await agentApplicationService.delete(targetAid ?? '', argv.includes('--purge'))
|
|
3758
|
+
: await (async () => {
|
|
3759
|
+
const { agentDelete } = await import('./cli/agent.js');
|
|
3760
|
+
return await agentDelete(targetAid ?? '', argv.includes('--purge'));
|
|
3761
|
+
})();
|
|
3644
3762
|
return 'error' in result ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3645
3763
|
}
|
|
3646
3764
|
if (operation === 'agent.reload' && !targetAid) {
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3765
|
+
const result = agentApplicationService
|
|
3766
|
+
? await agentApplicationService.reload()
|
|
3767
|
+
: await (async () => {
|
|
3768
|
+
const { agentReload } = await import('./cli/agent.js');
|
|
3769
|
+
return await agentReload();
|
|
3770
|
+
})();
|
|
3649
3771
|
return !result.ok ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3650
3772
|
}
|
|
3651
|
-
const result = await execAgentAction(sub, { aid: targetAid ?? taskAid, force: argv.includes('--force') }, delegation.grant.actorId, eventBus);
|
|
3773
|
+
const result = await execAgentAction(sub, { aid: targetAid ?? taskAid, force: argv.includes('--force') }, delegation.grant.actorId, eventBus, agentApplicationService);
|
|
3652
3774
|
return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
|
|
3653
3775
|
});
|
|
3654
3776
|
ipcServer.setQueueOperationExecutor(async (params) => {
|
|
@@ -3707,12 +3829,51 @@ async function main() {
|
|
|
3707
3829
|
throw new Error(`agent not found: ${agentAid}`);
|
|
3708
3830
|
return agentAid;
|
|
3709
3831
|
};
|
|
3832
|
+
const auditFullAccessTriggerConfiguration = (event, definition, actor, reason) => {
|
|
3833
|
+
auditFullAccessEvent({
|
|
3834
|
+
event,
|
|
3835
|
+
source: 'trigger',
|
|
3836
|
+
actorId: actor.origin.peerId,
|
|
3837
|
+
processRole: actor.daemonOwner ? 'daemon-owner' : actor.subject.processRole,
|
|
3838
|
+
authorizedBy: definition.authorizedBy,
|
|
3839
|
+
agentAid: definition.agentAid,
|
|
3840
|
+
triggerId: definition.id,
|
|
3841
|
+
reason,
|
|
3842
|
+
});
|
|
3843
|
+
};
|
|
3844
|
+
const rejectUnapprovedFullAccessConfiguration = (definition, actor) => {
|
|
3845
|
+
// A managed Agent task may carry a daemon-owner identity for ordinary
|
|
3846
|
+
// scoped operations. That delegation is never a human approval for a
|
|
3847
|
+
// persistent host-level Trigger; only the explicit user/menu paths may
|
|
3848
|
+
// create or retain this mode.
|
|
3849
|
+
const approvalSource = actor.control ? 'daemon-service' : 'agent-delegation';
|
|
3850
|
+
if (definition.execution.permissionMode === 'fullaccess'
|
|
3851
|
+
&& !actor.fullAccess
|
|
3852
|
+
&& !canApprovePersistentFullAccessTrigger({
|
|
3853
|
+
daemonOwner: actor.daemonOwner,
|
|
3854
|
+
source: approvalSource,
|
|
3855
|
+
})) {
|
|
3856
|
+
throw new Error('persistent fullaccess Trigger requires an explicit user or management-channel action');
|
|
3857
|
+
}
|
|
3858
|
+
};
|
|
3859
|
+
const withFullAccessConfigurationProvenance = (definition, actor) => {
|
|
3860
|
+
if (definition.execution.permissionMode !== 'fullaccess') {
|
|
3861
|
+
const { authorizedBy: _authorizedBy, ...deescalated } = definition;
|
|
3862
|
+
return deescalated;
|
|
3863
|
+
}
|
|
3864
|
+
const authorizedBy = actor.fullAccess
|
|
3865
|
+
? actor.subject.authorizedBy
|
|
3866
|
+
: actor.subject.principalId ?? actor.origin.peerId;
|
|
3867
|
+
if (!authorizedBy)
|
|
3868
|
+
throw new Error('fullaccess Trigger approval provenance is unavailable');
|
|
3869
|
+
return { ...definition, authorizedBy };
|
|
3870
|
+
};
|
|
3710
3871
|
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
3711
3872
|
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
3712
3873
|
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
3713
3874
|
if (!isCrossAgentTriggerOperationAllowed({
|
|
3714
3875
|
control: actor.control,
|
|
3715
|
-
daemonOwner: actor.daemonOwner,
|
|
3876
|
+
daemonOwner: actor.daemonOwner || actor.fullAccess,
|
|
3716
3877
|
taskAgentAid: actor.selfAid,
|
|
3717
3878
|
targetAgentAid: agentAid,
|
|
3718
3879
|
targetManagement: actor.management,
|
|
@@ -3745,7 +3906,12 @@ async function main() {
|
|
|
3745
3906
|
return actor;
|
|
3746
3907
|
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
3747
3908
|
const privilegedReason = definition ? daemonPrivilegedTriggerReason(definition) : undefined;
|
|
3748
|
-
|
|
3909
|
+
const persistedFullAccessApproval = privilegedReason === 'persistent fullaccess Trigger';
|
|
3910
|
+
const deescalationOperation = operation === 'trigger.update'
|
|
3911
|
+
|| operation === 'trigger.setEnabled'
|
|
3912
|
+
|| operation === 'trigger.cancel'
|
|
3913
|
+
|| operation === 'trigger.delete';
|
|
3914
|
+
if (privilegedReason && !persistedFullAccessApproval && !actor.daemonPrivileged && !deescalationOperation) {
|
|
3749
3915
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
3750
3916
|
}
|
|
3751
3917
|
if (actor.management)
|
|
@@ -3762,7 +3928,9 @@ async function main() {
|
|
|
3762
3928
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3763
3929
|
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
3764
3930
|
const scheduler = schedulerFor(agentAid);
|
|
3765
|
-
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged
|
|
3931
|
+
const definitions = scheduler.list({ all: cmd.all === true }).filter(trigger => ((actor.daemonPrivileged
|
|
3932
|
+
|| !daemonPrivilegedTriggerReason(trigger)
|
|
3933
|
+
|| daemonPrivilegedTriggerReason(trigger) === 'persistent fullaccess Trigger')
|
|
3766
3934
|
&& (actor.management
|
|
3767
3935
|
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey))));
|
|
3768
3936
|
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
@@ -3819,11 +3987,16 @@ async function main() {
|
|
|
3819
3987
|
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
3820
3988
|
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
3821
3989
|
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
|
|
3822
|
-
|
|
3990
|
+
let definition = normalizeTriggerDefinition(materialized);
|
|
3991
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
3992
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3823
3993
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3824
3994
|
requireAgent(definition.agentAid);
|
|
3825
3995
|
validateTriggerFeedbackChannels(definition);
|
|
3826
3996
|
const trigger = schedulerFor(definition.agentAid).create(definition, cmd.files ?? [], { enable: cmd.enable });
|
|
3997
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
3998
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, 'fullaccess Trigger created through IPC');
|
|
3999
|
+
}
|
|
3827
4000
|
return { ok: true, trigger };
|
|
3828
4001
|
}
|
|
3829
4002
|
case 'trigger.update': {
|
|
@@ -3834,15 +4007,67 @@ async function main() {
|
|
|
3834
4007
|
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
3835
4008
|
if (!existing)
|
|
3836
4009
|
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
4010
|
+
const rawPatch = cmd.patch && typeof cmd.patch === 'object' && !Array.isArray(cmd.patch)
|
|
4011
|
+
? cmd.patch
|
|
4012
|
+
: {};
|
|
4013
|
+
const rawScriptFile = rawPatch.scriptFile;
|
|
4014
|
+
if (rawScriptFile !== undefined && rawScriptFile !== true) {
|
|
4015
|
+
throw new Error('scriptFile must be true');
|
|
4016
|
+
}
|
|
4017
|
+
const patchScriptFile = rawScriptFile === true;
|
|
4018
|
+
const { scriptFile: _scriptFile, ...definitionPatch } = rawPatch;
|
|
4019
|
+
const scriptFileBase64 = cmd.scriptFileBase64;
|
|
4020
|
+
if (patchScriptFile && scriptFileBase64 === undefined) {
|
|
4021
|
+
throw new Error('--script-file requires scriptFileBase64');
|
|
4022
|
+
}
|
|
4023
|
+
if (!patchScriptFile && scriptFileBase64 !== undefined) {
|
|
4024
|
+
throw new Error('scriptFileBase64 requires --script-file');
|
|
4025
|
+
}
|
|
4026
|
+
if (scriptFileBase64 !== undefined) {
|
|
4027
|
+
if (typeof scriptFileBase64 !== 'string') {
|
|
4028
|
+
throw new Error('scriptFileBase64 must be a base64 string');
|
|
4029
|
+
}
|
|
4030
|
+
if (!actor.daemonPrivileged) {
|
|
4031
|
+
throw new Error('updating a script Trigger requires DaemonOwner');
|
|
4032
|
+
}
|
|
4033
|
+
if (existing.execution.type !== 'script' || !existing.execution.script) {
|
|
4034
|
+
throw new Error('--script-file only applies to script Triggers');
|
|
4035
|
+
}
|
|
4036
|
+
if (scriptFileBase64.length > 6 * 1024 * 1024
|
|
4037
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(scriptFileBase64)) {
|
|
4038
|
+
throw new Error('scriptFileBase64 is invalid or exceeds the 4 MiB limit');
|
|
4039
|
+
}
|
|
4040
|
+
const scriptBytes = Buffer.from(scriptFileBase64, 'base64');
|
|
4041
|
+
if (scriptBytes.length > 4 * 1024 * 1024)
|
|
4042
|
+
throw new Error('script file exceeds the 4 MiB limit');
|
|
4043
|
+
}
|
|
3837
4044
|
const currentRevision = definitionRevision(existing);
|
|
3838
4045
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
3839
4046
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
3840
4047
|
}
|
|
3841
|
-
|
|
4048
|
+
let definition = Object.keys(definitionPatch).length > 0
|
|
4049
|
+
? applyTriggerPatch(existing, definitionPatch, { fromPromptFile: cmd.promptFile === true })
|
|
4050
|
+
: existing;
|
|
4051
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
4052
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3842
4053
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3843
4054
|
validateTriggerFeedbackChannels(definition);
|
|
3844
4055
|
const scheduler = schedulerFor(agentAid);
|
|
3845
|
-
const
|
|
4056
|
+
const files = scriptFileBase64 !== undefined && existing.execution.script
|
|
4057
|
+
? [{
|
|
4058
|
+
relativePath: splitScriptCommand(existing.execution.script.path).path,
|
|
4059
|
+
contentBase64: scriptFileBase64,
|
|
4060
|
+
}]
|
|
4061
|
+
: [];
|
|
4062
|
+
const trigger = scheduler.update(cmd.triggerId, definition, files);
|
|
4063
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
4064
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, existing.execution.permissionMode === 'fullaccess'
|
|
4065
|
+
? 'fullaccess Trigger updated through IPC'
|
|
4066
|
+
: 'Trigger elevated to fullaccess through IPC');
|
|
4067
|
+
}
|
|
4068
|
+
else if (existing.execution.permissionMode === 'fullaccess') {
|
|
4069
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.deescalated', existing, actor, 'Trigger permissionMode changed from fullaccess through IPC');
|
|
4070
|
+
}
|
|
3846
4071
|
return { ok: true, trigger: scheduler.listItem(trigger), revision: definitionRevision(trigger) };
|
|
3847
4072
|
}
|
|
3848
4073
|
case 'trigger.setEnabled': {
|
|
@@ -3851,8 +4076,26 @@ async function main() {
|
|
|
3851
4076
|
throw new Error('missing triggerId');
|
|
3852
4077
|
if (typeof cmd.enabled !== 'boolean')
|
|
3853
4078
|
throw new Error('missing enabled');
|
|
3854
|
-
await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
3855
|
-
const
|
|
4079
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
4080
|
+
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
4081
|
+
if (!existing)
|
|
4082
|
+
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
4083
|
+
if (cmd.enabled && existing.execution.permissionMode === 'fullaccess') {
|
|
4084
|
+
rejectUnapprovedFullAccessConfiguration(existing, actor);
|
|
4085
|
+
if (!isFullAccessEnabled())
|
|
4086
|
+
throw new Error('fullaccess is disabled by daemon.json');
|
|
4087
|
+
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
4088
|
+
throw new Error('enabling a fullaccess Trigger requires a DaemonOwner or fullaccess-run');
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
const scheduler = schedulerFor(agentAid);
|
|
4092
|
+
if (cmd.enabled && existing.execution.permissionMode === 'fullaccess') {
|
|
4093
|
+
scheduler.update(cmd.triggerId, withFullAccessConfigurationProvenance(existing, actor));
|
|
4094
|
+
}
|
|
4095
|
+
const trigger = scheduler.setEnabled(cmd.triggerId, cmd.enabled);
|
|
4096
|
+
if (cmd.enabled && trigger.execution.permissionMode === 'fullaccess') {
|
|
4097
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, 'fullaccess Trigger enabled through IPC');
|
|
4098
|
+
}
|
|
3856
4099
|
return { ok: true, trigger };
|
|
3857
4100
|
}
|
|
3858
4101
|
case 'trigger.cancel': {
|
|
@@ -3867,8 +4110,11 @@ async function main() {
|
|
|
3867
4110
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3868
4111
|
if (!cmd.triggerId)
|
|
3869
4112
|
throw new Error('missing triggerId');
|
|
3870
|
-
await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
4113
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
3871
4114
|
const trigger = schedulerFor(agentAid).delete(cmd.triggerId);
|
|
4115
|
+
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
4116
|
+
auditFullAccessTriggerConfiguration('fullaccess.trigger.deescalated', trigger, actor, 'fullaccess Trigger deleted through IPC');
|
|
4117
|
+
}
|
|
3872
4118
|
return { ok: true, trigger };
|
|
3873
4119
|
}
|
|
3874
4120
|
case 'trigger.run': {
|
|
@@ -3936,6 +4182,13 @@ async function main() {
|
|
|
3936
4182
|
}
|
|
3937
4183
|
};
|
|
3938
4184
|
try {
|
|
4185
|
+
// ECWeb is owned by the daemon. Stop it from the unified shutdown
|
|
4186
|
+
// path as well as the signal handler so an IPC-driven `ec restart`
|
|
4187
|
+
// cannot leave the old ECWeb process running after its package was
|
|
4188
|
+
// upgraded on disk.
|
|
4189
|
+
await shutdownStep('stop ECWeb supervisor', async () => {
|
|
4190
|
+
await ecwebSupervisor?.stop();
|
|
4191
|
+
});
|
|
3939
4192
|
const queueShutdown = messageQueue.shutdownGracefully();
|
|
3940
4193
|
await shutdownStep('cancel pending permissions', () => permissionGateway.cancelAllPending('daemon_restart'));
|
|
3941
4194
|
await shutdownStep('wait for trigger schedulers', async () => {
|
|
@@ -3945,6 +4198,19 @@ async function main() {
|
|
|
3945
4198
|
await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.suspendForDaemonExit()));
|
|
3946
4199
|
});
|
|
3947
4200
|
await shutdownStep('interrupt active message queue tasks', async () => { await queueShutdown; });
|
|
4201
|
+
await shutdownStep('dispose agent runners', async () => {
|
|
4202
|
+
const currentInstances = [...agentMap.entries()].map(([key, agent]) => {
|
|
4203
|
+
const split = key.lastIndexOf('::');
|
|
4204
|
+
return {
|
|
4205
|
+
evolagentName: split > 0 ? key.slice(0, split) : '<unknown>',
|
|
4206
|
+
baseagent: split > 0 ? key.slice(split + 2) : 'unknown',
|
|
4207
|
+
agent,
|
|
4208
|
+
};
|
|
4209
|
+
});
|
|
4210
|
+
await disposeAgentInstances(currentInstances, (instance, error) => {
|
|
4211
|
+
logger.warn(`[Shutdown] Failed to dispose runner ${instance.evolagentName}::${instance.baseagent}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4212
|
+
});
|
|
4213
|
+
});
|
|
3948
4214
|
eventBus.publish({
|
|
3949
4215
|
type: 'system:shutdown',
|
|
3950
4216
|
timestamp: Date.now()
|