evolcore 0.0.20 → 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 +22 -0
- package/README.md +58 -9
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +379 -108
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +402 -135
- 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 +182 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +409 -88
- 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 +69 -115
- package/dist/cli/init.js +27 -15
- 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 +135 -17
- 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 +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +150 -2
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-service.js +6 -2
- 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 +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 +4 -0
- 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 +486 -68
- 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 +410 -54
- 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 +159 -1
- package/dist/core/permission/tool-policy.js +575 -21
- 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 +1 -1
- package/dist/index.js +253 -46
- 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/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/schemas/_meta.json +4 -2
- package/kits/schemas/agent-config.schema.11.json +13 -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/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -208,9 +208,16 @@ export class SessionTurnCoordinator {
|
|
|
208
208
|
return true;
|
|
209
209
|
}
|
|
210
210
|
async persist(session, state) {
|
|
211
|
-
const
|
|
212
|
-
session.metadata = metadata;
|
|
213
|
-
this.states.set(session.id,
|
|
214
|
-
|
|
211
|
+
const next = cloneTurnMetadata(state);
|
|
212
|
+
session.metadata = { ...(session.metadata ?? {}), turnState: next };
|
|
213
|
+
this.states.set(session.id, next);
|
|
214
|
+
if (typeof this.sessionManager.updateTurnState === 'function') {
|
|
215
|
+
await this.sessionManager.updateTurnState(session.id, next);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Compatibility for minimal test doubles and pre-patch adapters. Real
|
|
219
|
+
// SessionManager instances always expose updateTurnState.
|
|
220
|
+
await this.sessionManager.updateSession(session.id, { metadata: { turnState: next } });
|
|
221
|
+
}
|
|
215
222
|
}
|
|
216
223
|
}
|
package/dist/eck/kit-renderer.js
CHANGED
|
@@ -254,7 +254,7 @@ const PARAM_DESCRIPTIONS = {
|
|
|
254
254
|
proactiveSendTargetLabel: 'proactive 发送目标标签(对方/群里)',
|
|
255
255
|
readonly: '是否只读模式',
|
|
256
256
|
evolcoreMode: 'evolcore 运行模式(dev=源码仓库可直接修改 | install=全局安装包只读)',
|
|
257
|
-
baseAgent: 'base agent 规范值(claude/codex/gemini/
|
|
257
|
+
baseAgent: 'base agent 规范值(claude/codex/gemini/ecagent)',
|
|
258
258
|
baseAgentName: 'base agent 显示名',
|
|
259
259
|
baseAgentModel: 'base agent 引擎底座模型(evolcore 作用域无配置时的兜底)',
|
|
260
260
|
effectiveModel: '当前实际生效模型(关系级 > agent级 > 全局 优先级解析结果)',
|
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';
|
|
@@ -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}
|
|
@@ -1199,6 +1213,7 @@ async function main() {
|
|
|
1199
1213
|
const agentDelegationRegistry = new AgentDelegationRegistry();
|
|
1200
1214
|
// 创建命令处理器
|
|
1201
1215
|
const cmdHandler = new CommandHandler(sessionManager, agentMap, messageCache, eventBus, primaryRunnerKey);
|
|
1216
|
+
let agentApplicationService;
|
|
1202
1217
|
cmdHandler.setAgentDelegationRegistry(agentDelegationRegistry);
|
|
1203
1218
|
cmdHandler.setPermissionGateway(permissionGateway);
|
|
1204
1219
|
cmdHandler.setInteractionRouter(interactionRouter);
|
|
@@ -1434,10 +1449,19 @@ async function main() {
|
|
|
1434
1449
|
&& definition.feedback.target?.channelKey === 'daemon') {
|
|
1435
1450
|
return 'daemon-control feedback';
|
|
1436
1451
|
}
|
|
1452
|
+
if (definition.execution.permissionMode === 'fullaccess')
|
|
1453
|
+
return 'persistent fullaccess Trigger';
|
|
1437
1454
|
return undefined;
|
|
1438
1455
|
};
|
|
1439
|
-
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service');
|
|
1456
|
+
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon');
|
|
1440
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
|
+
}
|
|
1441
1465
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1442
1466
|
if (privilegedReason && !actor.daemonPrivileged)
|
|
1443
1467
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
@@ -1488,9 +1512,30 @@ async function main() {
|
|
|
1488
1512
|
}
|
|
1489
1513
|
};
|
|
1490
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
|
+
}
|
|
1491
1527
|
const origin = definition.origin;
|
|
1492
1528
|
if (!origin?.peerId)
|
|
1493
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
|
+
}
|
|
1494
1539
|
if (origin.channelKey === 'daemon') {
|
|
1495
1540
|
const subject = buildAuthSubject({
|
|
1496
1541
|
selfAid: definition.agentAid,
|
|
@@ -1557,7 +1602,8 @@ async function main() {
|
|
|
1557
1602
|
return { allowed: false, reason: operationDecision.reason };
|
|
1558
1603
|
}
|
|
1559
1604
|
const privilegedReason = daemonPrivilegedTriggerReason(definition);
|
|
1560
|
-
|
|
1605
|
+
const persistedFullAccessApproval = privilegedReason === 'persistent fullaccess Trigger';
|
|
1606
|
+
if (privilegedReason && !persistedFullAccessApproval && !hasDaemonTriggerAuthority(subject)) {
|
|
1561
1607
|
return { allowed: false, reason: `${privilegedReason} requires current DaemonOwner` };
|
|
1562
1608
|
}
|
|
1563
1609
|
if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
|
|
@@ -1605,6 +1651,7 @@ async function main() {
|
|
|
1605
1651
|
management: true,
|
|
1606
1652
|
daemonOwner: false,
|
|
1607
1653
|
daemonPrivileged: true,
|
|
1654
|
+
fullAccess: false,
|
|
1608
1655
|
control: true,
|
|
1609
1656
|
role: 'none',
|
|
1610
1657
|
subject,
|
|
@@ -1643,18 +1690,20 @@ async function main() {
|
|
|
1643
1690
|
identity: roleToSessionIdentity(currentRole),
|
|
1644
1691
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
1645
1692
|
fromControlChannel: false,
|
|
1693
|
+
...trustedExecutionIdentityInput(validation.grant),
|
|
1646
1694
|
});
|
|
1647
|
-
if (validation.grant.selfAid !== agentAid &&
|
|
1695
|
+
if (validation.grant.selfAid !== agentAid && subject.dataScope !== 'daemon') {
|
|
1648
1696
|
throw new Error('cross-agent Trigger operations require DaemonOwner');
|
|
1649
1697
|
}
|
|
1650
|
-
if (
|
|
1698
|
+
if (subject.dataScope !== 'daemon' && !checkRoleAccess(currentRole, agentAid)) {
|
|
1651
1699
|
throw new Error('trigger actor no longer has access to this agent');
|
|
1652
1700
|
}
|
|
1653
1701
|
return {
|
|
1654
1702
|
origin: actorOrigin,
|
|
1655
|
-
management: isManagementRole(currentRole) || subject.
|
|
1703
|
+
management: isManagementRole(currentRole) || subject.dataScope === 'daemon',
|
|
1656
1704
|
daemonOwner: subject.isDaemonOwner,
|
|
1657
|
-
daemonPrivileged: subject.isDaemonOwner,
|
|
1705
|
+
daemonPrivileged: subject.isDaemonOwner || subject.dataScope === 'daemon',
|
|
1706
|
+
fullAccess: subject.processRole === 'fullaccess-run',
|
|
1658
1707
|
selfAid: validation.grant.selfAid,
|
|
1659
1708
|
control: false,
|
|
1660
1709
|
role: currentRole || 'none',
|
|
@@ -1797,6 +1846,7 @@ async function main() {
|
|
|
1797
1846
|
msgBridge.setInteractionRouter(interactionRouter);
|
|
1798
1847
|
msgBridge.setContactBindRuntimeChecker(isContactBindChannelReady);
|
|
1799
1848
|
msgBridge.setAidStatsCollector(aidStatsCollector);
|
|
1849
|
+
cmdHandler.setContactRequestSubmitter((input) => msgBridge.submitAgentContactRequest(input));
|
|
1800
1850
|
bootstrapService = new BootstrapService(agentRegistry, eventBus);
|
|
1801
1851
|
msgBridge.setBootstrapService(bootstrapService);
|
|
1802
1852
|
msgBridge.setHandoffRuntime(handoffRuntime);
|
|
@@ -1818,7 +1868,14 @@ async function main() {
|
|
|
1818
1868
|
payload: summarizeOutboundPayload(payload),
|
|
1819
1869
|
});
|
|
1820
1870
|
const result = await originalSend(envelope, payload);
|
|
1821
|
-
|
|
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) {
|
|
1822
1879
|
if ((inst.channelType || inst.adapter.channelName) !== 'aun') {
|
|
1823
1880
|
try {
|
|
1824
1881
|
const logPayload = outboundPayloadToLogText(payload);
|
|
@@ -2403,8 +2460,8 @@ async function main() {
|
|
|
2403
2460
|
return;
|
|
2404
2461
|
}
|
|
2405
2462
|
if (text.toLowerCase() === '/pair') {
|
|
2406
|
-
const port =
|
|
2407
|
-
const pair = await fetchEcwebPairCode(port);
|
|
2463
|
+
const port = ecwebService(loadDaemonConfig().services)?.port ?? DEFAULT_ECWEB_PORT;
|
|
2464
|
+
const pair = await fetchEcwebPairCode(port, opts.peerId);
|
|
2408
2465
|
let reply;
|
|
2409
2466
|
if (pair) {
|
|
2410
2467
|
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
@@ -2467,11 +2524,12 @@ async function main() {
|
|
|
2467
2524
|
// ── Service Proxy:把本地服务(ecweb 等)通过控制 AID 暴露到 AUN 网络 ──
|
|
2468
2525
|
// 挂在控制 AUNChannel 上,动态解引用其 client(规避重连换 client)。
|
|
2469
2526
|
// 失败只 warn,不影响 daemon 主流程。
|
|
2470
|
-
|
|
2527
|
+
const proxyServices = (daemonCfg.services ?? []).filter((service) => service.enabled === true && service.proxy?.enabled === true);
|
|
2528
|
+
if (proxyServices.length > 0 && daemonCfg.aid) {
|
|
2471
2529
|
// 短暂延迟确保控制 channel 完全就绪
|
|
2472
2530
|
const channel = controlChannel;
|
|
2473
2531
|
const aid = daemonCfg.aid;
|
|
2474
|
-
const config =
|
|
2532
|
+
const config = proxyServices;
|
|
2475
2533
|
setTimeout(() => {
|
|
2476
2534
|
if (!channel)
|
|
2477
2535
|
return;
|
|
@@ -2802,7 +2860,7 @@ async function main() {
|
|
|
2802
2860
|
if (delegation.grant.selfAid !== selfAid) {
|
|
2803
2861
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation self agent does not match current session' };
|
|
2804
2862
|
}
|
|
2805
|
-
if (params.agent && params.agent !== selfAid) {
|
|
2863
|
+
if (params.agent && params.agent !== selfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
|
|
2806
2864
|
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2807
2865
|
}
|
|
2808
2866
|
let conversationId;
|
|
@@ -2821,8 +2879,9 @@ async function main() {
|
|
|
2821
2879
|
chatType: delegation.grant.chatType,
|
|
2822
2880
|
conversationId,
|
|
2823
2881
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2882
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
2824
2883
|
});
|
|
2825
|
-
const managementRole = isManagementRole(subject.role);
|
|
2884
|
+
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
2826
2885
|
const decision = authorizeOperation({
|
|
2827
2886
|
source: 'agent-tool',
|
|
2828
2887
|
subject,
|
|
@@ -2945,6 +3004,12 @@ async function main() {
|
|
|
2945
3004
|
if (!ch) {
|
|
2946
3005
|
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2947
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
|
+
}
|
|
2948
3013
|
// The IPC command already carries the caller-selected transport scope.
|
|
2949
3014
|
// Never reclassify the target by parsing its AID string: a private AID may
|
|
2950
3015
|
// look group-like and a group identifier may use a future format.
|
|
@@ -3113,8 +3178,8 @@ async function main() {
|
|
|
3113
3178
|
const staged = candidate.config.enabled === false
|
|
3114
3179
|
? []
|
|
3115
3180
|
: agentLoader.createForAgent(candidate, {
|
|
3116
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3117
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3181
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3182
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3118
3183
|
},
|
|
3119
3184
|
}, creationErrors);
|
|
3120
3185
|
const onDisposeError = (instance, error) => {
|
|
@@ -3213,8 +3278,8 @@ async function main() {
|
|
|
3213
3278
|
}
|
|
3214
3279
|
const creationErrors = [];
|
|
3215
3280
|
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
3216
|
-
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
3217
|
-
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
3281
|
+
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3282
|
+
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3218
3283
|
},
|
|
3219
3284
|
}, creationErrors);
|
|
3220
3285
|
for (const inst of newAgentInstances) {
|
|
@@ -3381,6 +3446,41 @@ async function main() {
|
|
|
3381
3446
|
resyncInFlight = undefined;
|
|
3382
3447
|
}
|
|
3383
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);
|
|
3384
3484
|
ipcServer.setStatsProvider(() => statsCollector.getSnapshot());
|
|
3385
3485
|
ipcServer.setAgentStatsProvider(() => {
|
|
3386
3486
|
const agents = agentRegistry.list();
|
|
@@ -3458,8 +3558,11 @@ async function main() {
|
|
|
3458
3558
|
chatType: delegation.grant.chatType,
|
|
3459
3559
|
conversationId,
|
|
3460
3560
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3561
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3461
3562
|
});
|
|
3462
|
-
if (params.targetAid
|
|
3563
|
+
if (params.targetAid
|
|
3564
|
+
&& params.targetAid !== delegation.grant.selfAid
|
|
3565
|
+
&& subject.dataScope !== 'daemon') {
|
|
3463
3566
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3464
3567
|
}
|
|
3465
3568
|
const decision = authorizeOperation({
|
|
@@ -3474,7 +3577,10 @@ async function main() {
|
|
|
3474
3577
|
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
3475
3578
|
});
|
|
3476
3579
|
return decision.allow
|
|
3477
|
-
? {
|
|
3580
|
+
? {
|
|
3581
|
+
ok: true,
|
|
3582
|
+
aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
|
|
3583
|
+
}
|
|
3478
3584
|
: { ok: false, code: decision.code, error: decision.reason };
|
|
3479
3585
|
}
|
|
3480
3586
|
ipcServer.setAgentOperationExecutor(async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
@@ -3539,6 +3645,7 @@ async function main() {
|
|
|
3539
3645
|
chatType: delegation.grant.chatType,
|
|
3540
3646
|
conversationId,
|
|
3541
3647
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
3648
|
+
...trustedExecutionIdentityInput(delegation.grant),
|
|
3542
3649
|
});
|
|
3543
3650
|
const decision = authorizeOperation({
|
|
3544
3651
|
source: 'agent-tool',
|
|
@@ -3566,7 +3673,7 @@ async function main() {
|
|
|
3566
3673
|
}
|
|
3567
3674
|
const { execAgentAction, execAgentOptions, execAgentQuery } = await import('./core/command/agent-control.js');
|
|
3568
3675
|
if (operation === 'agent.list') {
|
|
3569
|
-
const result = await execAgentOptions({ options: 'all' });
|
|
3676
|
+
const result = await execAgentOptions({ options: 'all' }, agentApplicationService);
|
|
3570
3677
|
if ('error' in result)
|
|
3571
3678
|
return fail(result.code, result.error);
|
|
3572
3679
|
const agents = Array.isArray(result.data?.agents) ? result.data.agents.map((agent) => ({
|
|
@@ -3583,7 +3690,7 @@ async function main() {
|
|
|
3583
3690
|
return { ok: true, result: { ok: true, agents, redacted: true } };
|
|
3584
3691
|
}
|
|
3585
3692
|
if (operation === 'agent.show') {
|
|
3586
|
-
const result = await execAgentQuery({ aid: targetAid });
|
|
3693
|
+
const result = await execAgentQuery({ aid: targetAid }, agentApplicationService);
|
|
3587
3694
|
if ('error' in result)
|
|
3588
3695
|
return fail(result.code, result.error);
|
|
3589
3696
|
const agent = result.data ?? {};
|
|
@@ -3625,7 +3732,7 @@ async function main() {
|
|
|
3625
3732
|
return fail('INVALID_ARGS', validationError);
|
|
3626
3733
|
if (argv.includes('--dry-run'))
|
|
3627
3734
|
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3628
|
-
const
|
|
3735
|
+
const createOptions = {
|
|
3629
3736
|
aid: plan.aid,
|
|
3630
3737
|
project: plan.project,
|
|
3631
3738
|
baseagent: plan.baseagent,
|
|
@@ -3633,22 +3740,33 @@ async function main() {
|
|
|
3633
3740
|
name: plan.name,
|
|
3634
3741
|
description: plan.description,
|
|
3635
3742
|
force: argv.includes('--force'),
|
|
3636
|
-
}
|
|
3743
|
+
};
|
|
3744
|
+
const result = agentApplicationService
|
|
3745
|
+
? await agentApplicationService.create(createOptions)
|
|
3746
|
+
: await agentCreateNonInteractive(createOptions);
|
|
3637
3747
|
return !result.ok
|
|
3638
3748
|
? fail(result.code ?? 'INTERNAL', result.error)
|
|
3639
3749
|
: { ok: true, result: { ...result, configPath: '[redacted]' } };
|
|
3640
3750
|
}
|
|
3641
3751
|
if (operation === 'agent.delete') {
|
|
3642
|
-
const
|
|
3643
|
-
|
|
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
|
+
})();
|
|
3644
3758
|
return 'error' in result ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3645
3759
|
}
|
|
3646
3760
|
if (operation === 'agent.reload' && !targetAid) {
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3761
|
+
const result = agentApplicationService
|
|
3762
|
+
? await agentApplicationService.reload()
|
|
3763
|
+
: await (async () => {
|
|
3764
|
+
const { agentReload } = await import('./cli/agent.js');
|
|
3765
|
+
return await agentReload();
|
|
3766
|
+
})();
|
|
3649
3767
|
return !result.ok ? fail(result.code ?? 'INTERNAL', result.error) : { ok: true, result };
|
|
3650
3768
|
}
|
|
3651
|
-
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);
|
|
3652
3770
|
return 'error' in result ? fail(result.code, result.error) : { ok: true, result: { ok: true, ...(result.data ?? {}) } };
|
|
3653
3771
|
});
|
|
3654
3772
|
ipcServer.setQueueOperationExecutor(async (params) => {
|
|
@@ -3707,12 +3825,51 @@ async function main() {
|
|
|
3707
3825
|
throw new Error(`agent not found: ${agentAid}`);
|
|
3708
3826
|
return agentAid;
|
|
3709
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
|
+
};
|
|
3710
3867
|
const authorizeTrigger = async (agentAid, operation, triggerId) => {
|
|
3711
3868
|
const actor = await authenticatedTriggerActor(agentAid, cmd.actorSessionId, cmd.delegationToken, cmd.delegationCommandHash, cmd.controlToken);
|
|
3712
3869
|
const isCrossAgent = !actor.control && actor.selfAid !== agentAid;
|
|
3713
3870
|
if (!isCrossAgentTriggerOperationAllowed({
|
|
3714
3871
|
control: actor.control,
|
|
3715
|
-
daemonOwner: actor.daemonOwner,
|
|
3872
|
+
daemonOwner: actor.daemonOwner || actor.fullAccess,
|
|
3716
3873
|
taskAgentAid: actor.selfAid,
|
|
3717
3874
|
targetAgentAid: agentAid,
|
|
3718
3875
|
targetManagement: actor.management,
|
|
@@ -3745,7 +3902,12 @@ async function main() {
|
|
|
3745
3902
|
return actor;
|
|
3746
3903
|
const definition = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === triggerId);
|
|
3747
3904
|
const privilegedReason = definition ? daemonPrivilegedTriggerReason(definition) : undefined;
|
|
3748
|
-
|
|
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) {
|
|
3749
3911
|
throw new Error(`${privilegedReason} requires DaemonOwner`);
|
|
3750
3912
|
}
|
|
3751
3913
|
if (actor.management)
|
|
@@ -3762,7 +3924,9 @@ async function main() {
|
|
|
3762
3924
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3763
3925
|
const actor = await authorizeTrigger(agentAid, 'trigger.list');
|
|
3764
3926
|
const scheduler = schedulerFor(agentAid);
|
|
3765
|
-
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')
|
|
3766
3930
|
&& (actor.management
|
|
3767
3931
|
|| (trigger.origin?.peerId === actor.origin?.peerId && trigger.origin?.channelKey === actor.origin?.channelKey))));
|
|
3768
3932
|
return { ok: true, triggers: scheduler.listItems(definitions) };
|
|
@@ -3819,11 +3983,16 @@ async function main() {
|
|
|
3819
3983
|
const agentAid = requireAgent(rawDefinition.agentAid);
|
|
3820
3984
|
const actor = await authorizeTrigger(agentAid, 'trigger.create');
|
|
3821
3985
|
const materialized = materializeTriggerCreateBaseagent({ ...rawDefinition, origin: actor.origin }, agentAid);
|
|
3822
|
-
|
|
3986
|
+
let definition = normalizeTriggerDefinition(materialized);
|
|
3987
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
3988
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3823
3989
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3824
3990
|
requireAgent(definition.agentAid);
|
|
3825
3991
|
validateTriggerFeedbackChannels(definition);
|
|
3826
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
|
+
}
|
|
3827
3996
|
return { ok: true, trigger };
|
|
3828
3997
|
}
|
|
3829
3998
|
case 'trigger.update': {
|
|
@@ -3838,11 +4007,21 @@ async function main() {
|
|
|
3838
4007
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
3839
4008
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
3840
4009
|
}
|
|
3841
|
-
|
|
4010
|
+
let definition = applyTriggerPatch(existing, cmd.patch, { fromPromptFile: cmd.promptFile === true });
|
|
4011
|
+
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
4012
|
+
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
3842
4013
|
validateTriggerDefinitionForActor(definition, actor);
|
|
3843
4014
|
validateTriggerFeedbackChannels(definition);
|
|
3844
4015
|
const scheduler = schedulerFor(agentAid);
|
|
3845
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
|
+
}
|
|
3846
4025
|
return { ok: true, trigger: scheduler.listItem(trigger), revision: definitionRevision(trigger) };
|
|
3847
4026
|
}
|
|
3848
4027
|
case 'trigger.setEnabled': {
|
|
@@ -3851,8 +4030,26 @@ async function main() {
|
|
|
3851
4030
|
throw new Error('missing triggerId');
|
|
3852
4031
|
if (typeof cmd.enabled !== 'boolean')
|
|
3853
4032
|
throw new Error('missing enabled');
|
|
3854
|
-
await authorizeTrigger(agentAid, 'trigger.setEnabled', cmd.triggerId);
|
|
3855
|
-
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
|
+
}
|
|
3856
4053
|
return { ok: true, trigger };
|
|
3857
4054
|
}
|
|
3858
4055
|
case 'trigger.cancel': {
|
|
@@ -3867,8 +4064,11 @@ async function main() {
|
|
|
3867
4064
|
const agentAid = requireAgent(cmd.agentAid);
|
|
3868
4065
|
if (!cmd.triggerId)
|
|
3869
4066
|
throw new Error('missing triggerId');
|
|
3870
|
-
await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
4067
|
+
const actor = await authorizeTrigger(agentAid, 'trigger.delete', cmd.triggerId);
|
|
3871
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
|
+
}
|
|
3872
4072
|
return { ok: true, trigger };
|
|
3873
4073
|
}
|
|
3874
4074
|
case 'trigger.run': {
|
|
@@ -3936,6 +4136,13 @@ async function main() {
|
|
|
3936
4136
|
}
|
|
3937
4137
|
};
|
|
3938
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
|
+
});
|
|
3939
4146
|
const queueShutdown = messageQueue.shutdownGracefully();
|
|
3940
4147
|
await shutdownStep('cancel pending permissions', () => permissionGateway.cancelAllPending('daemon_restart'));
|
|
3941
4148
|
await shutdownStep('wait for trigger schedulers', async () => {
|