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.
Files changed (147) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +58 -9
  3. package/bin/codex-managed-hook.mjs +3 -0
  4. package/bin/install-codex-managed-hooks.mjs +3 -1
  5. package/dist/agents/baseagent.js +10 -6
  6. package/dist/agents/claude-runner.js +393 -108
  7. package/dist/agents/codex-app-server-client.js +41 -7
  8. package/dist/agents/codex-runner.js +1292 -220
  9. package/dist/agents/ecagent-runner.js +171 -61
  10. package/dist/agents/gemini-runner.js +130 -30
  11. package/dist/agents/request-identity.js +25 -0
  12. package/dist/agents/runner-types.js +19 -0
  13. package/dist/aun/aid/agentmd.js +59 -2
  14. package/dist/aun/aid/identity.js +4 -1
  15. package/dist/aun/aid/index.js +1 -1
  16. package/dist/aun/msg/group.js +72 -6
  17. package/dist/aun/msg/history.js +213 -36
  18. package/dist/aun/msg/managed-operation.js +58 -9
  19. package/dist/aun/msg/p2p.js +5 -0
  20. package/dist/aun/outbox.js +189 -80
  21. package/dist/aun/service-proxy.js +43 -25
  22. package/dist/channels/aun.js +618 -123
  23. package/dist/channels/daemon.js +6 -1
  24. package/dist/cli/agent-command.js +4 -3
  25. package/dist/cli/agent.js +66 -56
  26. package/dist/cli/aun-commands.js +177 -42
  27. package/dist/cli/command-log.js +10 -11
  28. package/dist/cli/contact.js +1 -0
  29. package/dist/cli/daemon-commands.js +98 -123
  30. package/dist/cli/init.js +27 -15
  31. package/dist/cli/task-context.js +50 -0
  32. package/dist/cli/trigger-command.js +14 -5
  33. package/dist/cli/watch-logs.js +10 -3
  34. package/dist/config/builtin-roles.js +1 -0
  35. package/dist/config/config-field-policy.js +19 -5
  36. package/dist/config/config-manager.js +167 -22
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/contact-operation-service.js +32 -1
  39. package/dist/config/contact-request-service.js +44 -0
  40. package/dist/config/daemon-services.js +186 -0
  41. package/dist/config/gateway-config.js +20 -9
  42. package/dist/config/role-service.js +54 -3
  43. package/dist/config/schema-migration.js +550 -0
  44. package/dist/config-store.js +151 -9
  45. package/dist/core/agent-application-service.js +279 -0
  46. package/dist/core/audit/log-integrity.js +102 -0
  47. package/dist/core/auth/agent-delegation.js +43 -1
  48. package/dist/core/auth/auth-gateway.js +41 -4
  49. package/dist/core/auth/authorization-audit.js +216 -8
  50. package/dist/core/auth/operation-authorizer.js +41 -1
  51. package/dist/core/auth/operation-catalog.js +9 -1
  52. package/dist/core/bootstrap-messages.js +8 -0
  53. package/dist/core/bootstrap-service.js +99 -27
  54. package/dist/core/causation/aun-association.js +7 -4
  55. package/dist/core/command/agent-control.js +56 -16
  56. package/dist/core/command/command-handler.js +311 -44
  57. package/dist/core/command/connect-menu.js +3 -4
  58. package/dist/core/command/group-menu.js +5 -7
  59. package/dist/core/command/menu-handler.js +288 -80
  60. package/dist/core/command/menu-protocol.js +1 -1
  61. package/dist/core/command/role-menu.js +21 -11
  62. package/dist/core/command/slash-gate.js +85 -18
  63. package/dist/core/command/slash-handler.js +377 -36
  64. package/dist/core/data-migration.js +11 -1
  65. package/dist/core/event-catalog.js +37 -0
  66. package/dist/core/evolagent.js +4 -0
  67. package/dist/core/handoff/dispatcher.js +4 -0
  68. package/dist/core/handoff/runtime.js +33 -3
  69. package/dist/core/handoff/store.js +32 -9
  70. package/dist/core/inference/text-inference.js +7 -15
  71. package/dist/core/message/im-renderer.js +90 -87
  72. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  73. package/dist/core/message/message-bridge.js +184 -12
  74. package/dist/core/message/message-log.js +47 -7
  75. package/dist/core/message/message-queue.js +227 -16
  76. package/dist/core/message/message-utils.js +12 -5
  77. package/dist/core/message/response-engine.js +658 -109
  78. package/dist/core/message/send-receipt.js +1 -0
  79. package/dist/core/message/stream-debouncer.js +9 -2
  80. package/dist/core/model/model-catalog.js +23 -15
  81. package/dist/core/model/model-diagnostics.js +28 -10
  82. package/dist/core/permission/approval-gateway.js +180 -6
  83. package/dist/core/permission/ec-command-parser.js +627 -69
  84. package/dist/core/permission/mode.js +18 -3
  85. package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
  86. package/dist/core/permission/readonly-shell-query.js +263 -9
  87. package/dist/core/permission/sandbox-runtime.js +159 -1
  88. package/dist/core/permission/tool-error-code.js +12 -0
  89. package/dist/core/permission/tool-policy.js +618 -23
  90. package/dist/core/session/session-fs-store.js +154 -5
  91. package/dist/core/session/session-manager.js +329 -30
  92. package/dist/core/session/session-renew.js +37 -13
  93. package/dist/core/session/session-turn-coordinator.js +16 -5
  94. package/dist/eck/kit-renderer.js +1 -1
  95. package/dist/index.js +316 -50
  96. package/dist/ipc.js +459 -29
  97. package/dist/paths.js +82 -7
  98. package/dist/response-system/context-builder.js +1 -7
  99. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  100. package/dist/stats/price-resolver.js +4 -0
  101. package/dist/trigger/anomaly-store.js +1 -0
  102. package/dist/trigger/feedback.js +70 -7
  103. package/dist/trigger/history.js +79 -4
  104. package/dist/trigger/legacy-session-history.js +2 -2
  105. package/dist/trigger/parser.js +13 -3
  106. package/dist/trigger/scheduler.js +20 -3
  107. package/dist/trigger/validation.js +6 -1
  108. package/dist/utils/atomic-write.js +27 -0
  109. package/dist/utils/ecweb-utils.js +16 -2
  110. package/dist/utils/error-utils.js +4 -1
  111. package/dist/utils/logger.js +30 -6
  112. package/dist/utils/process-tree-stats.js +24 -4
  113. package/dist/utils/process-tree-worker.js +31 -0
  114. package/dist/utils/project-path.js +1 -2
  115. package/dist/utils/tool-summary.js +59 -0
  116. package/dist/utils/windows-shell-trust.js +201 -0
  117. package/kits/docs/INDEX.md +1 -1
  118. package/kits/docs/evolcore/INDEX.md +3 -3
  119. package/kits/docs/evolcore/agent-create.md +146 -0
  120. package/kits/docs/evolcore/agent.md +6 -0
  121. package/kits/docs/evolcore/contact.md +7 -1
  122. package/kits/docs/evolcore/group-collaboration.md +251 -0
  123. package/kits/docs/evolcore/group-rules.md +1 -19
  124. package/kits/docs/evolcore/group.md +3 -1
  125. package/kits/docs/evolcore/msg.md +16 -0
  126. package/kits/docs/evolcore/trigger.md +6 -3
  127. package/kits/docs/prompt-loading-architecture.md +6 -0
  128. package/kits/eck_message_manifest.json +6 -6
  129. package/kits/schemas/_meta.json +7 -4
  130. package/kits/schemas/agent-config.schema.11.json +13 -0
  131. package/kits/schemas/agent-config.schema.12.json +427 -0
  132. package/kits/schemas/daemon.schema.5.json +0 -1
  133. package/kits/schemas/daemon.schema.6.json +131 -0
  134. package/kits/schemas/defaults.schema.5.json +15 -3
  135. package/kits/schemas/migrations/README.md +3 -1
  136. package/kits/schemas/relation-config.schema.8.json +13 -0
  137. package/kits/schemas/role-config.schema.1.json +1 -2
  138. package/kits/schemas/single-session.schema.3.json +32 -0
  139. package/kits/templates/message-fragments/item.md +1 -1
  140. package/kits/templates/roles/admin.json +1 -0
  141. package/kits/templates/roles/member.json +1 -0
  142. package/kits/templates/roles/visitor.json +1 -0
  143. package/kits/templates/system-fragments/bootstrap.md +2 -1
  144. package/kits/templates/system-fragments/commands.md +2 -2
  145. package/package.json +6 -3
  146. package/skills/eclink/SKILL.md +2 -0
  147. package/dist/config/aun-gateway-config.js +0 -2
@@ -1,8 +1,8 @@
1
1
  import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
2
2
  import { renderCommandCardAsText } from '../interaction-router.js';
3
- import { buildEnvelope, deliveryTargetFromSession, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
3
+ import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
4
4
  import { resolvePaths, getPackageRoot } from '../../paths.js';
5
- import { loadDaemonConfig } from '../../config-store.js';
5
+ import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
6
6
  import { logger } from '../../utils/logger.js';
7
7
  import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
8
8
  import { isManagementRole } from '../../config/builtin-roles.js';
@@ -19,12 +19,13 @@ import { buildSessionTurnList } from '../session/session-turns.js';
19
19
  import { resolveConfigCommand } from '../../config/resolved-config-op.js';
20
20
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
21
21
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
22
- import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
22
+ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
23
23
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
24
+ import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
24
25
  import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
25
26
  import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
26
27
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
27
- import { isHClassPath, isLClassPath, isSameOrDescendant } from '../protected-paths.js';
28
+ import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
28
29
  import { validateModelSelectionForRole } from '../model/model-permission.js';
29
30
  import { getModelInfo } from '../model/model-catalog.js';
30
31
  import { constrainRuntimePermissionMode, resolveRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
@@ -79,6 +80,20 @@ function writeUserSettings(updates) {
79
80
  function isAdminRole(role) {
80
81
  return isManagementRole(role);
81
82
  }
83
+ /**
84
+ * A cross-channel history selector is intentionally narrower than ordinary
85
+ * managed reads. The persisted session, rather than a caller-controlled
86
+ * runtime hint, proves that this command belongs to a daemon Trigger.
87
+ */
88
+ function isTrustedDaemonTriggerSession(session, selfAid) {
89
+ const channelId = String(session.channelId || '');
90
+ return session.channel === 'daemon'
91
+ && session.channelType === 'daemon'
92
+ && channelId.startsWith('trigger:')
93
+ && session.selfAID?.replace(/^@/, '') === selfAid
94
+ && session.metadata?.channelKey === 'daemon'
95
+ && session.metadata?.peerId === channelId;
96
+ }
82
97
  export class CommandHandler {
83
98
  sessionManager;
84
99
  messageCache;
@@ -95,12 +110,14 @@ export class CommandHandler {
95
110
  agentMap;
96
111
  primaryRunnerKey;
97
112
  agentRegistry;
113
+ agentApplicationService;
98
114
  triggerSchedulerResolver;
99
115
  triggerSchedulerFallback;
100
116
  triggerSchedulerFallbackAid;
101
117
  daemonStatusProvider;
102
118
  agentDelegationRegistry;
103
119
  handoffRuntime;
120
+ contactRequestSubmitter;
104
121
  /**
105
122
  * Get the runner for a (channel, baseagent) pair.
106
123
  *
@@ -195,6 +212,10 @@ export class CommandHandler {
195
212
  setAgentRegistry(registry) {
196
213
  this.agentRegistry = registry;
197
214
  }
215
+ /** Inject the Core-owned agent application facade after runtime hooks exist. */
216
+ setAgentApplicationService(service) {
217
+ this.agentApplicationService = service;
218
+ }
198
219
  setTriggerSchedulerResolver(resolver) {
199
220
  this.triggerSchedulerResolver = resolver;
200
221
  }
@@ -342,9 +363,39 @@ export class CommandHandler {
342
363
  triggerPermissionFromParsed(parsed) {
343
364
  return parsed.permissionMode ?? undefined;
344
365
  }
345
- validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid) {
366
+ /**
367
+ * Persist fullaccess approval provenance as the canonical process owner,
368
+ * rather than the transport-specific peer id. A Feishu/other-channel
369
+ * alias may authenticate as a daemon-owner while its raw peer id is not an
370
+ * entry in daemon.json.owners; storing that alias would make the Trigger
371
+ * fail its next scheduler-time owner check.
372
+ */
373
+ canonicalDaemonOwnerId(selfAid, channel, peerId, chatType) {
374
+ const fallback = String(peerId || '').trim();
375
+ try {
376
+ const detail = resolvePeerRoleDetail({
377
+ selfAid,
378
+ channelKey: channel,
379
+ channelType: this.resolveChannelType(channel),
380
+ chatType: chatType === 'group' ? 'group' : 'private',
381
+ actorId: fallback,
382
+ conversationId: fallback,
383
+ });
384
+ const principal = detail.actor.principalId?.trim();
385
+ return principal || fallback;
386
+ }
387
+ catch {
388
+ return fallback;
389
+ }
390
+ }
391
+ validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
346
392
  if (!mode)
347
393
  return undefined;
394
+ if (mode === 'fullaccess') {
395
+ if (!isFullAccessEnabled())
396
+ return 'fullaccess 功能未启用';
397
+ return isDaemonOwner ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
398
+ }
348
399
  if (!role) {
349
400
  if (mode === 'bypass' && !isAdmin)
350
401
  return '无权限:只有 owner/admin 可以创建或修改 bypass trigger';
@@ -355,6 +406,19 @@ export class CommandHandler {
355
406
  return `无权限:角色 ${role} 不能使用 ${mode} trigger permissionMode`;
356
407
  return undefined;
357
408
  }
409
+ auditFullAccessTriggerConfiguration(input) {
410
+ auditFullAccessEvent({
411
+ event: input.event,
412
+ source: 'trigger',
413
+ actorId: input.actorId,
414
+ processRole: input.isDaemonOwner ? 'daemon-owner' : 'none',
415
+ agentAid: input.definition.agentAid,
416
+ messageId: input.messageId,
417
+ triggerId: input.definition.id,
418
+ authorizedBy: input.definition.authorizedBy,
419
+ reason: input.reason,
420
+ });
421
+ }
358
422
  triggerRoleContext(selfAid, channel, peerId, chatType) {
359
423
  const owning = this.getOwningAgent(channel);
360
424
  if (!owning || owning.aid !== selfAid)
@@ -404,7 +468,7 @@ export class CommandHandler {
404
468
  .find(definition => (definition.id === nameOrId || definition.name === nameOrId)
405
469
  && this.canAccessTriggerDefinition(definition, peerId, channel, isAdmin));
406
470
  }
407
- async buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
471
+ async buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
408
472
  const now = Date.now();
409
473
  const id = `trig_${now}_${crypto.randomBytes(4).toString('hex')}`;
410
474
  const name = parsed.name ?? `trigger-${now.toString(36)}`;
@@ -475,9 +539,12 @@ export class CommandHandler {
475
539
  } : undefined;
476
540
  const permissionMode = this.triggerPermissionFromParsed(parsed);
477
541
  const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
478
- const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid);
542
+ const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
479
543
  if (permissionError)
480
544
  return { error: permissionError };
545
+ if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
546
+ return { error: '当前认证通道不能批准持久 fullaccess Trigger' };
547
+ }
481
548
  if (parsed.baseagent !== undefined) {
482
549
  if (typeof parsed.baseagent !== 'string' || !parsed.baseagent.trim()) {
483
550
  return { error: 'Trigger baseagent 不能为空' };
@@ -501,6 +568,9 @@ export class CommandHandler {
501
568
  };
502
569
  }
503
570
  }
571
+ const authorizedBy = permissionMode === 'fullaccess' && isDaemonOwner
572
+ ? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
573
+ : undefined;
504
574
  const definition = {
505
575
  $schema_version: 3.1,
506
576
  id,
@@ -509,6 +579,7 @@ export class CommandHandler {
509
579
  name,
510
580
  createdAt: now,
511
581
  updatedAt: now,
582
+ ...(authorizedBy ? { authorizedBy } : {}),
512
583
  origin,
513
584
  source,
514
585
  execution: {
@@ -665,8 +736,8 @@ export class CommandHandler {
665
736
  replyContext: opts.replyCtx,
666
737
  });
667
738
  const fallbackText = renderCommandCardAsText(card);
668
- const messageId = await sendInteractionPayload(adapter, envelope, opts.interaction, fallbackText, opts.replyCtx);
669
- if (messageId)
739
+ const receipt = await sendInteractionPayload(adapter, envelope, opts.interaction, fallbackText, opts.replyCtx);
740
+ if (isInteractionSendAccepted(receipt))
670
741
  return null;
671
742
  }
672
743
  catch (e) {
@@ -711,6 +782,10 @@ export class CommandHandler {
711
782
  setInteractionRouter(router) {
712
783
  this.interactionRouter = router;
713
784
  }
785
+ /** Inject the daemon-owned contact request/card workflow for Agent self-service. */
786
+ setContactRequestSubmitter(submitter) {
787
+ this.contactRequestSubmitter = submitter;
788
+ }
714
789
  setStatsCollector(collector) {
715
790
  this.statsCollector = collector;
716
791
  }
@@ -1044,6 +1119,27 @@ export class CommandHandler {
1044
1119
  async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1045
1120
  try {
1046
1121
  const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1122
+ // Lifecycle commands are control-plane operations. Their replies must
1123
+ // use notification payloads so other agents in a group do not treat the
1124
+ // text as ordinary model input. Normalize guard-path replies here too,
1125
+ // since those return before slash-handler command-specific branches.
1126
+ if (/^\/(?:new|renew)(?:\s|$)/i.test(content.trim())
1127
+ && result && typeof result === 'object'
1128
+ && (result.kind === 'command.result' || result.kind === 'command.error')) {
1129
+ const isError = result.kind === 'command.error';
1130
+ return isError
1131
+ ? {
1132
+ kind: 'system.error',
1133
+ text: result.text,
1134
+ subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.error`,
1135
+ recoverable: true,
1136
+ }
1137
+ : {
1138
+ kind: 'system.notice',
1139
+ text: result.text,
1140
+ subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.result`,
1141
+ };
1142
+ }
1047
1143
  return result;
1048
1144
  }
1049
1145
  catch (error) {
@@ -1057,7 +1153,7 @@ export class CommandHandler {
1057
1153
  async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1058
1154
  return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1059
1155
  }
1060
- async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
1156
+ async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1061
1157
  if (content === '/trigger') {
1062
1158
  const visible = scheduler
1063
1159
  .list()
@@ -1110,11 +1206,35 @@ export class CommandHandler {
1110
1206
  const prefix = sub.startsWith('delete ') ? 'delete ' : sub.startsWith('remove ') ? 'remove ' : 'rm ';
1111
1207
  const { definition } = extractAndFindTrigger(sub, prefix, 'delete');
1112
1208
  const deleted = scheduler.delete(definition.id);
1209
+ if (deleted.execution.permissionMode === 'fullaccess') {
1210
+ this.auditFullAccessTriggerConfiguration({
1211
+ event: 'fullaccess.trigger.deescalated', definition: deleted, actorId: peerId,
1212
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger deleted',
1213
+ });
1214
+ }
1113
1215
  return `✅ 触发器已删除:**${deleted.name}**`;
1114
1216
  }
1115
1217
  if (sub.startsWith('enable ')) {
1116
1218
  const { definition } = extractAndFindTrigger(sub, 'enable ', 'enable');
1117
- scheduler.setEnabled(definition.id, true);
1219
+ if (definition.execution.permissionMode === 'fullaccess' && (!isDaemonOwner || !isFullAccessEnabled())) {
1220
+ return '❌ 只有 daemon-owner 可以启用 fullaccess Trigger,且 daemon.json.fullaccess.enabled 必须为 true';
1221
+ }
1222
+ if (definition.execution.permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
1223
+ return '❌ 当前认证通道不能批准持久 fullaccess Trigger';
1224
+ }
1225
+ if (definition.execution.permissionMode === 'fullaccess') {
1226
+ const authorizedBy = this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId);
1227
+ if (!authorizedBy)
1228
+ return '❌ 无法确认 fullaccess Trigger 的批准者';
1229
+ scheduler.update(definition.id, { ...definition, authorizedBy });
1230
+ }
1231
+ const enabled = scheduler.setEnabled(definition.id, true);
1232
+ if (enabled.execution.permissionMode === 'fullaccess') {
1233
+ this.auditFullAccessTriggerConfiguration({
1234
+ event: 'fullaccess.trigger.configured', definition: enabled, actorId: peerId,
1235
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger enabled',
1236
+ });
1237
+ }
1118
1238
  return `✅ 触发器已启用:**${definition.name}**`;
1119
1239
  }
1120
1240
  if (sub.startsWith('disable ')) {
@@ -1197,7 +1317,7 @@ export class CommandHandler {
1197
1317
  const result = parseTriggerUpdate(args);
1198
1318
  if (!result.ok)
1199
1319
  return `❌ ${result.error}`;
1200
- const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId);
1320
+ const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1201
1321
  if (!updated.ok)
1202
1322
  return `❌ ${updated.error}`;
1203
1323
  const nextStr = updated.trigger.nextFireAt ? new Date(updated.trigger.nextFireAt).toLocaleString() : '未计算';
@@ -1213,7 +1333,7 @@ export class CommandHandler {
1213
1333
  const result = parseTriggerSet(args);
1214
1334
  if (!result.ok)
1215
1335
  return `❌ ${result.error}`;
1216
- const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin);
1336
+ const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin, isDaemonOwner, canApprovePersistentFullAccess);
1217
1337
  if (!reg.ok)
1218
1338
  return `❌ ${reg.error}`;
1219
1339
  const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
@@ -1232,13 +1352,13 @@ export class CommandHandler {
1232
1352
  /trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
1233
1353
  /trigger delete <名称|ID> — 删除触发器`;
1234
1354
  }
1235
- async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
1355
+ async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1236
1356
  const scheduler = this.getTriggerSchedulerForChannel(channel);
1237
1357
  if (!scheduler)
1238
1358
  return '⚠️ 触发器功能未启用';
1239
- return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId);
1359
+ return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1240
1360
  }
1241
- async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId) {
1361
+ async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1242
1362
  const definition = this.findTriggerDefinition(scheduler, nameOrId, peerId, channel, isAdmin);
1243
1363
  if (!definition) {
1244
1364
  return { ok: false, error: isAdmin ? `未找到触发器:${nameOrId}` : `未找到触发器 "${nameOrId}",或无权限修改` };
@@ -1275,6 +1395,22 @@ export class CommandHandler {
1275
1395
  catch (err) {
1276
1396
  return { ok: false, error: err?.message || String(err) };
1277
1397
  }
1398
+ if (updated.execution.permissionMode === 'fullaccess') {
1399
+ if (!isDaemonOwner) {
1400
+ return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
1401
+ }
1402
+ if (!canApprovePersistentFullAccess) {
1403
+ return { ok: false, error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
1404
+ }
1405
+ updated = {
1406
+ ...updated,
1407
+ authorizedBy: this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId),
1408
+ };
1409
+ }
1410
+ else {
1411
+ const { authorizedBy: _authorizedBy, ...deescalated } = updated;
1412
+ updated = deescalated;
1413
+ }
1278
1414
  const target = this.primaryFeedbackTarget(updated);
1279
1415
  if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
1280
1416
  return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
@@ -1283,14 +1419,35 @@ export class CommandHandler {
1283
1419
  return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
1284
1420
  }
1285
1421
  const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
1286
- const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid);
1287
- if (permissionError)
1288
- return { ok: false, error: permissionError };
1422
+ const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
1423
+ if (permissionError) {
1424
+ return {
1425
+ ok: false,
1426
+ error: permissionError,
1427
+ code: /fullaccess 功能未启用/.test(permissionError) ? 'NOT_SUPPORTED'
1428
+ : /无权限|只有 daemon-owner/.test(permissionError) ? 'FORBIDDEN'
1429
+ : undefined,
1430
+ };
1431
+ }
1289
1432
  const runtimeFieldError = this.validateTriggerRuntimeFields(updated, triggerRole.role, triggerRole.baseagent);
1290
1433
  if (runtimeFieldError)
1291
1434
  return { ok: false, error: runtimeFieldError };
1292
1435
  try {
1293
1436
  const saved = scheduler.update(definition.id, updated);
1437
+ if (saved.execution.permissionMode === 'fullaccess') {
1438
+ this.auditFullAccessTriggerConfiguration({
1439
+ event: 'fullaccess.trigger.configured', definition: saved, actorId: peerId,
1440
+ messageId, isDaemonOwner, reason: definition.execution.permissionMode === 'fullaccess'
1441
+ ? 'fullaccess Trigger updated'
1442
+ : 'Trigger elevated to fullaccess',
1443
+ });
1444
+ }
1445
+ else if (definition.execution.permissionMode === 'fullaccess') {
1446
+ this.auditFullAccessTriggerConfiguration({
1447
+ event: 'fullaccess.trigger.deescalated', definition, actorId: peerId,
1448
+ messageId, isDaemonOwner, reason: 'Trigger permissionMode changed from fullaccess',
1449
+ });
1450
+ }
1294
1451
  return {
1295
1452
  ok: true,
1296
1453
  trigger: this.definitionToTriggerView(saved, scheduler),
@@ -1304,12 +1461,18 @@ export class CommandHandler {
1304
1461
  /** 从已解析的 trigger 参数组装 definition 并注册。文本路径(handleTrigger)与 menu 路径共用。
1305
1462
  * parsed 形状 = parseTriggerSet 的 result.value(ParsedTriggerSet)。
1306
1463
  * 失败 return { ok:false, error };成功 return { ok:true, trigger }。 */
1307
- async registerTriggerFromParsed(parsed, channel, channelId, peerId, messageId, chatType, threadId, isAdmin = false) {
1308
- const built = await this.buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId);
1464
+ async registerTriggerFromParsed(parsed, channel, channelId, peerId, messageId, chatType, threadId, isAdmin = false, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1465
+ const built = await this.buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1309
1466
  if ('error' in built)
1310
1467
  return { ok: false, error: built.error };
1311
1468
  try {
1312
1469
  const created = built.scheduler.create(built.definition, [], { enable: true });
1470
+ if (created.execution.permissionMode === 'fullaccess') {
1471
+ this.auditFullAccessTriggerConfiguration({
1472
+ event: 'fullaccess.trigger.configured', definition: created, actorId: peerId,
1473
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger created',
1474
+ });
1475
+ }
1313
1476
  return { ok: true, trigger: this.definitionToTriggerView(created, built.scheduler) };
1314
1477
  }
1315
1478
  catch (err) {
@@ -1534,6 +1697,7 @@ export class CommandHandler {
1534
1697
  chatType: grant.chatType,
1535
1698
  conversationId,
1536
1699
  processOwners: loadDaemonConfig().owners ?? [],
1700
+ ...trustedExecutionIdentityInput(grant),
1537
1701
  });
1538
1702
  const executeOne = (fieldArgv) => {
1539
1703
  const resolved = resolveConfigCommand(fieldArgv, {
@@ -1606,9 +1770,19 @@ export class CommandHandler {
1606
1770
  const resolved = resolveContactCommand(argv);
1607
1771
  if (!resolved.ok)
1608
1772
  return { ok: false, code: resolved.code, error: resolved.reason };
1609
- if (resolved.command.selfAid !== grant.selfAid) {
1773
+ if (resolved.command.selfAid !== grant.selfAid && !hasTrustedFullAccessDelegation(grant)) {
1610
1774
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent contact book can be targeted' };
1611
1775
  }
1776
+ const selfServiceAdd = resolved.command.kind === 'add';
1777
+ const primaryId = 'primaryId' in resolved.command ? resolved.command.primaryId : undefined;
1778
+ if (selfServiceAdd && !hasTrustedFullAccessDelegation(grant)) {
1779
+ if (grant.channelType !== 'aun' || grant.chatType !== 'private') {
1780
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add requires the current AUN private relation' };
1781
+ }
1782
+ if (primaryId !== conversationId) {
1783
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add may only target the current private peer' };
1784
+ }
1785
+ }
1612
1786
  const subject = buildAuthSubject({
1613
1787
  selfAid: grant.selfAid,
1614
1788
  actorId: grant.actorId,
@@ -1618,17 +1792,25 @@ export class CommandHandler {
1618
1792
  chatType: grant.chatType,
1619
1793
  conversationId,
1620
1794
  processOwners: loadDaemonConfig().owners ?? [],
1795
+ ...trustedExecutionIdentityInput(grant),
1621
1796
  });
1622
1797
  const decision = authorizeOperation({
1623
1798
  source: 'agent-tool',
1624
1799
  subject,
1625
1800
  intent: {
1626
1801
  operation: resolved.command.operationId,
1627
- scope: 'agent',
1802
+ scope: selfServiceAdd ? 'relation' : 'agent',
1628
1803
  source: 'agent-tool',
1629
1804
  args: {
1630
1805
  self: resolved.command.selfAid,
1631
- ...('primaryId' in resolved.command ? { primaryId: resolved.command.primaryId } : {}),
1806
+ ...(primaryId ? { primaryId } : {}),
1807
+ ...(selfServiceAdd
1808
+ ? {
1809
+ targetId: primaryId,
1810
+ peer: primaryId,
1811
+ peerKey: `aun#${primaryId}`,
1812
+ }
1813
+ : {}),
1632
1814
  },
1633
1815
  },
1634
1816
  auditMetadata: {
@@ -1639,6 +1821,47 @@ export class CommandHandler {
1639
1821
  });
1640
1822
  if (!decision.allow)
1641
1823
  return { ok: false, code: decision.code, error: decision.reason };
1824
+ if (selfServiceAdd) {
1825
+ if (!this.contactRequestSubmitter) {
1826
+ return { ok: false, code: 'CONTACT_REQUEST_UNAVAILABLE', error: 'Contact request workflow is not configured' };
1827
+ }
1828
+ try {
1829
+ const submission = await this.contactRequestSubmitter({
1830
+ selfAid: grant.selfAid,
1831
+ applicantAid: primaryId,
1832
+ sourceChannelKey: grant.channel,
1833
+ });
1834
+ if (submission.code === 'blocked') {
1835
+ return {
1836
+ ok: false,
1837
+ code: 'CONTACT_BLOCKED',
1838
+ error: `目标联系人 ${primaryId} 已被拉黑。请先解除拉黑:ec contact unblock ${primaryId} --self ${grant.selfAid}`,
1839
+ };
1840
+ }
1841
+ return {
1842
+ ok: true,
1843
+ result: {
1844
+ ok: true,
1845
+ operation: 'add',
1846
+ selfAid: grant.selfAid,
1847
+ primaryId: primaryId,
1848
+ status: submission.code,
1849
+ changed: submission.code === 'submitted' || submission.code === 'resubmitted',
1850
+ requestId: submission.requestId,
1851
+ expiresAt: submission.expiresAt,
1852
+ contactRevision: submission.contactRevision,
1853
+ reviewCardSent: submission.reviewCardSent === true,
1854
+ },
1855
+ };
1856
+ }
1857
+ catch (error) {
1858
+ return {
1859
+ ok: false,
1860
+ code: error?.code ?? 'CONTACT_REQUEST_FAILED',
1861
+ error: error instanceof Error ? error.message : String(error),
1862
+ };
1863
+ }
1864
+ }
1642
1865
  const result = await executeResolvedContactCommand(resolved.command, grant.actorId);
1643
1866
  return result.ok
1644
1867
  ? { ok: true, result }
@@ -1663,7 +1886,7 @@ export class CommandHandler {
1663
1886
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1664
1887
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1665
1888
  }
1666
- if (parsed.command.aid !== grantSelfAid) {
1889
+ if (parsed.command.aid !== grantSelfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
1667
1890
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1668
1891
  }
1669
1892
  let conversationId;
@@ -1682,6 +1905,7 @@ export class CommandHandler {
1682
1905
  chatType: delegation.grant.chatType,
1683
1906
  conversationId,
1684
1907
  processOwners: loadDaemonConfig().owners ?? [],
1908
+ ...trustedExecutionIdentityInput(delegation.grant),
1685
1909
  });
1686
1910
  const managementRole = isManagementRole(subject.role);
1687
1911
  const decision = authorizeOperation({
@@ -1733,7 +1957,7 @@ export class CommandHandler {
1733
1957
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1734
1958
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1735
1959
  }
1736
- if (parsed.command.from !== grantSelfAid) {
1960
+ if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
1737
1961
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can perform managed group operations' };
1738
1962
  }
1739
1963
  let conversationId;
@@ -1752,12 +1976,16 @@ export class CommandHandler {
1752
1976
  chatType: grant.chatType,
1753
1977
  conversationId,
1754
1978
  processOwners: loadDaemonConfig().owners ?? [],
1979
+ ...trustedExecutionIdentityInput(grant),
1755
1980
  });
1756
- const managementRole = isManagementRole(subject.role);
1757
- if (!managementRole && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
1981
+ const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
1982
+ if (subject.dataScope !== 'daemon'
1983
+ && !managementRole
1984
+ && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
1758
1985
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
1759
1986
  }
1760
- if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner') && subject.role !== 'owner') {
1987
+ if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
1988
+ && subject.dataScope !== 'daemon' && subject.role !== 'owner') {
1761
1989
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${parsed.command.kind} requires Agent owner permission` };
1762
1990
  }
1763
1991
  const decision = authorizeOperation({
@@ -1849,7 +2077,7 @@ export class CommandHandler {
1849
2077
  };
1850
2078
  }
1851
2079
  }
1852
- /** Agent-managed private msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
2080
+ /** Agent-managed msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
1853
2081
  async handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1854
2082
  if (!this.agentDelegationRegistry) {
1855
2083
  return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
@@ -1902,28 +2130,53 @@ export class CommandHandler {
1902
2130
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1903
2131
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1904
2132
  }
1905
- if (grant.channelType !== 'aun' || grant.chatType !== 'private') {
1906
- return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
2133
+ if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
2134
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1907
2135
  }
1908
- let peerAid;
2136
+ const isSelectorHistory = parsed.command.kind === 'history'
2137
+ && !!parsed.command.channelKey
2138
+ && !!parsed.command.peerId;
2139
+ let conversationId;
1909
2140
  try {
1910
- peerAid = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
2141
+ conversationId = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
1911
2142
  }
1912
2143
  catch {
1913
2144
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1914
2145
  }
1915
- if (parsed.command.from !== grantSelfAid || parsed.command.target !== peerAid) {
1916
- return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
2146
+ if (isSelectorHistory) {
2147
+ if (!hasTrustedFullAccessDelegation(grant) && !isTrustedDaemonTriggerSession(session, grantSelfAid)) {
2148
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Cross-channel history selectors require a trusted daemon Trigger session' };
2149
+ }
2150
+ }
2151
+ else {
2152
+ if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
2153
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
2154
+ }
2155
+ if (!hasTrustedFullAccessDelegation(grant) && parsed.command.target !== conversationId) {
2156
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
2157
+ }
2158
+ }
2159
+ const selectorChannel = isSelectorHistory
2160
+ ? tryParseChannelKey(parsed.command.channelKey)
2161
+ : undefined;
2162
+ if (isSelectorHistory && !selectorChannel) {
2163
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'Invalid channel selector' };
1917
2164
  }
2165
+ const targetId = isSelectorHistory ? parsed.command.peerId : parsed.command.target;
2166
+ const targetPeerKey = isSelectorHistory
2167
+ ? formatPeerKey(selectorChannel.type, targetId)
2168
+ : formatPeerKey('aun', targetId);
1918
2169
  const subject = buildAuthSubject({
1919
2170
  selfAid: grant.selfAid,
1920
2171
  actorId: grant.actorId,
1921
2172
  channel: grant.channel,
1922
2173
  channelType: grant.channelType,
1923
- channelId: peerAid,
2174
+ channelId: conversationId,
1924
2175
  chatType: grant.chatType,
1925
- conversationId: peerAid,
2176
+ conversationId,
2177
+ ...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
1926
2178
  processOwners: loadDaemonConfig().owners ?? [],
2179
+ ...trustedExecutionIdentityInput(grant),
1927
2180
  });
1928
2181
  const decision = authorizeOperation({
1929
2182
  source: 'agent-tool',
@@ -1934,9 +2187,15 @@ export class CommandHandler {
1934
2187
  source: 'agent-tool',
1935
2188
  args: {
1936
2189
  self: parsed.command.from,
1937
- targetId: parsed.command.target,
1938
- peer: parsed.command.target,
1939
- peerKey: formatPeerKey('aun', parsed.command.target),
2190
+ targetId,
2191
+ peer: targetId,
2192
+ peerKey: targetPeerKey,
2193
+ ...(isSelectorHistory ? {
2194
+ channelKey: parsed.command.channelKey,
2195
+ peerId: parsed.command.peerId,
2196
+ selectorPeerKey: targetPeerKey,
2197
+ triggerSessionId: session.id,
2198
+ } : {}),
1940
2199
  ...(parsed.command.session ? { session: parsed.command.session } : {}),
1941
2200
  ...(parsed.command.limit !== undefined ? { limit: parsed.command.limit } : {}),
1942
2201
  ...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
@@ -1949,6 +2208,9 @@ export class CommandHandler {
1949
2208
  argvHash: hashArgv(parsed.command.canonicalArgv),
1950
2209
  taskId: grant.taskId,
1951
2210
  messageId: grant.messageId,
2211
+ sessionId,
2212
+ agentAid: grant.selfAid,
2213
+ toolName: 'ec.msg',
1952
2214
  },
1953
2215
  });
1954
2216
  if (!decision.allow)
@@ -1984,7 +2246,7 @@ export class CommandHandler {
1984
2246
  if (sessionSelfAid && sessionSelfAid !== actorAid) {
1985
2247
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1986
2248
  }
1987
- if (grant.channelType !== 'aun' || grant.chatType !== 'group') {
2249
+ if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'group')) {
1988
2250
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs operations require the current AUN group relation' };
1989
2251
  }
1990
2252
  let currentGroup;
@@ -1994,10 +2256,13 @@ export class CommandHandler {
1994
2256
  catch {
1995
2257
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1996
2258
  }
1997
- if ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup) {
2259
+ if (!hasTrustedFullAccessDelegation(grant)
2260
+ && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup)) {
1998
2261
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
1999
2262
  }
2000
- parsed.command.actorAid = actorAid;
2263
+ parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
2264
+ ? (parsed.command.actorAid ?? actorAid)
2265
+ : actorAid;
2001
2266
  const subject = buildAuthSubject({
2002
2267
  selfAid: grant.selfAid,
2003
2268
  actorId: grant.actorId,
@@ -2007,6 +2272,7 @@ export class CommandHandler {
2007
2272
  chatType: grant.chatType,
2008
2273
  conversationId: currentGroup,
2009
2274
  processOwners: loadDaemonConfig().owners ?? [],
2275
+ ...trustedExecutionIdentityInput(grant),
2010
2276
  });
2011
2277
  const intentArgs = {
2012
2278
  self: actorAid,
@@ -2139,6 +2405,7 @@ export class CommandHandler {
2139
2405
  chatType: grant.chatType,
2140
2406
  conversationId,
2141
2407
  processOwners: loadDaemonConfig().owners ?? [],
2408
+ ...trustedExecutionIdentityInput(grant),
2142
2409
  });
2143
2410
  const ctlIntent = this.resolveCtlIntent(cmd, grantSelfAid, grant.peerKey);
2144
2411
  if (!ctlIntent)