evolcore 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +60 -9
  3. package/bin/install-codex-managed-hooks.mjs +61 -0
  4. package/dist/agents/baseagent.js +10 -6
  5. package/dist/agents/claude-runner.js +383 -111
  6. package/dist/agents/codex-app-server-client.js +10 -2
  7. package/dist/agents/codex-runner.js +405 -137
  8. package/dist/agents/ecagent-runner.js +174 -63
  9. package/dist/agents/gemini-runner.js +130 -30
  10. package/dist/agents/request-identity.js +25 -0
  11. package/dist/agents/runner-types.js +19 -0
  12. package/dist/aun/aid/agentmd.js +66 -2
  13. package/dist/aun/aid/identity.js +4 -1
  14. package/dist/aun/aid/index.js +1 -1
  15. package/dist/aun/msg/group.js +86 -9
  16. package/dist/aun/msg/history.js +213 -36
  17. package/dist/aun/msg/managed-operation.js +58 -9
  18. package/dist/aun/msg/p2p.js +26 -11
  19. package/dist/aun/outbox.js +295 -68
  20. package/dist/aun/service-proxy.js +43 -25
  21. package/dist/channels/aun.js +1004 -273
  22. package/dist/channels/daemon.js +6 -1
  23. package/dist/cli/agent-command.js +4 -3
  24. package/dist/cli/agent.js +66 -56
  25. package/dist/cli/aun-commands.js +177 -42
  26. package/dist/cli/command-log.js +10 -11
  27. package/dist/cli/contact.js +1 -0
  28. package/dist/cli/daemon-commands.js +81 -121
  29. package/dist/cli/index.js +1 -0
  30. package/dist/cli/init.js +76 -24
  31. package/dist/cli/restart-monitor.js +3 -3
  32. package/dist/cli/task-context.js +46 -0
  33. package/dist/cli/trigger-command.js +1 -1
  34. package/dist/cli/watch-logs.js +10 -3
  35. package/dist/config/builtin-roles.js +1 -0
  36. package/dist/config/config-field-policy.js +16 -5
  37. package/dist/config/config-manager.js +226 -24
  38. package/dist/config/config-operation-service.js +1 -2
  39. package/dist/config/contact-operation-service.js +32 -1
  40. package/dist/config/contact-request-service.js +44 -0
  41. package/dist/config/daemon-services.js +186 -0
  42. package/dist/config/gateway-config.js +29 -16
  43. package/dist/config/lifecycle.js +16 -5
  44. package/dist/config/role-service.js +54 -3
  45. package/dist/config/schema-migration.js +550 -0
  46. package/dist/config-store.js +161 -12
  47. package/dist/core/agent-application-service.js +279 -0
  48. package/dist/core/audit/log-integrity.js +102 -0
  49. package/dist/core/auth/agent-delegation.js +31 -1
  50. package/dist/core/auth/auth-gateway.js +33 -4
  51. package/dist/core/auth/authorization-audit.js +155 -4
  52. package/dist/core/auth/operation-authorizer.js +41 -1
  53. package/dist/core/auth/operation-catalog.js +9 -1
  54. package/dist/core/bootstrap-messages.js +2 -2
  55. package/dist/core/bootstrap-service.js +27 -38
  56. package/dist/core/causation/aun-association.js +7 -4
  57. package/dist/core/channel-loader.js +0 -2
  58. package/dist/core/command/agent-control.js +56 -16
  59. package/dist/core/command/command-handler.js +290 -44
  60. package/dist/core/command/connect-menu.js +3 -4
  61. package/dist/core/command/group-menu.js +5 -7
  62. package/dist/core/command/menu-handler.js +279 -80
  63. package/dist/core/command/role-menu.js +21 -11
  64. package/dist/core/command/slash-gate.js +85 -18
  65. package/dist/core/command/slash-handler.js +350 -32
  66. package/dist/core/event-catalog.js +5 -0
  67. package/dist/core/evolagent.js +9 -4
  68. package/dist/core/handoff/dispatcher.js +4 -0
  69. package/dist/core/handoff/runtime.js +10 -0
  70. package/dist/core/handoff/store.js +32 -9
  71. package/dist/core/inference/text-inference.js +7 -15
  72. package/dist/core/message/im-renderer.js +83 -84
  73. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  74. package/dist/core/message/message-bridge.js +124 -10
  75. package/dist/core/message/message-log.js +14 -7
  76. package/dist/core/message/message-queue.js +206 -16
  77. package/dist/core/message/message-utils.js +12 -5
  78. package/dist/core/message/response-engine.js +495 -72
  79. package/dist/core/message/send-receipt.js +1 -0
  80. package/dist/core/message/stream-debouncer.js +9 -2
  81. package/dist/core/model/model-catalog.js +23 -15
  82. package/dist/core/model/model-diagnostics.js +28 -10
  83. package/dist/core/permission/approval-gateway.js +180 -6
  84. package/dist/core/permission/ec-command-parser.js +465 -56
  85. package/dist/core/permission/mode.js +18 -3
  86. package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
  87. package/dist/core/permission/readonly-shell-query.js +263 -9
  88. package/dist/core/permission/sandbox-runtime.js +205 -13
  89. package/dist/core/permission/tool-policy.js +673 -62
  90. package/dist/core/relation/peer-identity.js +18 -0
  91. package/dist/core/session/session-fs-store.js +154 -5
  92. package/dist/core/session/session-manager.js +299 -30
  93. package/dist/core/session/session-renew.js +19 -12
  94. package/dist/core/session/session-turn-coordinator.js +11 -4
  95. package/dist/eck/kit-renderer.js +18 -9
  96. package/dist/index.js +283 -65
  97. package/dist/ipc.js +374 -24
  98. package/dist/paths.js +64 -7
  99. package/dist/response-system/context-builder.js +1 -7
  100. package/dist/trigger/anomaly-store.js +1 -0
  101. package/dist/trigger/feedback.js +56 -5
  102. package/dist/trigger/history.js +79 -4
  103. package/dist/trigger/legacy-session-history.js +2 -2
  104. package/dist/trigger/parser.js +3 -2
  105. package/dist/trigger/validation.js +6 -1
  106. package/dist/utils/atomic-write.js +27 -0
  107. package/dist/utils/ecweb-utils.js +16 -2
  108. package/dist/utils/error-utils.js +4 -1
  109. package/dist/utils/logger.js +21 -2
  110. package/dist/utils/process-tree-stats.js +24 -4
  111. package/dist/utils/process-tree-worker.js +31 -0
  112. package/dist/utils/project-path.js +1 -2
  113. package/dist/utils/stats.js +52 -18
  114. package/dist/utils/welcome.js +2 -2
  115. package/kits/docs/INDEX.md +1 -1
  116. package/kits/docs/evolcore/INDEX.md +1 -1
  117. package/kits/docs/evolcore/contact.md +7 -1
  118. package/kits/docs/evolcore/msg.md +16 -0
  119. package/kits/rules/01-overview.md +1 -1
  120. package/kits/rules/03-identity.md +1 -1
  121. package/kits/rules/05-venue.md +1 -1
  122. package/kits/schemas/_meta.json +10 -5
  123. package/kits/schemas/agent-config.schema.10.json +2 -1
  124. package/kits/schemas/agent-config.schema.11.json +421 -0
  125. package/kits/schemas/daemon.schema.5.json +135 -0
  126. package/kits/schemas/daemon.schema.6.json +131 -0
  127. package/kits/schemas/defaults.schema.5.json +119 -0
  128. package/kits/schemas/migrations/README.md +3 -1
  129. package/kits/schemas/relation-config.schema.8.json +13 -0
  130. package/kits/schemas/role-config.schema.1.json +1 -2
  131. package/kits/schemas/single-session.schema.3.json +32 -0
  132. package/kits/templates/roles/admin.json +1 -0
  133. package/kits/templates/roles/member.json +1 -0
  134. package/kits/templates/roles/visitor.json +1 -0
  135. package/package.json +7 -3
  136. package/skills/eclink/SKILL.md +2 -0
@@ -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
  }
@@ -1057,7 +1132,7 @@ export class CommandHandler {
1057
1132
  async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1058
1133
  return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1059
1134
  }
1060
- async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
1135
+ async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1061
1136
  if (content === '/trigger') {
1062
1137
  const visible = scheduler
1063
1138
  .list()
@@ -1110,11 +1185,35 @@ export class CommandHandler {
1110
1185
  const prefix = sub.startsWith('delete ') ? 'delete ' : sub.startsWith('remove ') ? 'remove ' : 'rm ';
1111
1186
  const { definition } = extractAndFindTrigger(sub, prefix, 'delete');
1112
1187
  const deleted = scheduler.delete(definition.id);
1188
+ if (deleted.execution.permissionMode === 'fullaccess') {
1189
+ this.auditFullAccessTriggerConfiguration({
1190
+ event: 'fullaccess.trigger.deescalated', definition: deleted, actorId: peerId,
1191
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger deleted',
1192
+ });
1193
+ }
1113
1194
  return `✅ 触发器已删除:**${deleted.name}**`;
1114
1195
  }
1115
1196
  if (sub.startsWith('enable ')) {
1116
1197
  const { definition } = extractAndFindTrigger(sub, 'enable ', 'enable');
1117
- scheduler.setEnabled(definition.id, true);
1198
+ if (definition.execution.permissionMode === 'fullaccess' && (!isDaemonOwner || !isFullAccessEnabled())) {
1199
+ return '❌ 只有 daemon-owner 可以启用 fullaccess Trigger,且 daemon.json.fullaccess.enabled 必须为 true';
1200
+ }
1201
+ if (definition.execution.permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
1202
+ return '❌ 当前认证通道不能批准持久 fullaccess Trigger';
1203
+ }
1204
+ if (definition.execution.permissionMode === 'fullaccess') {
1205
+ const authorizedBy = this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId);
1206
+ if (!authorizedBy)
1207
+ return '❌ 无法确认 fullaccess Trigger 的批准者';
1208
+ scheduler.update(definition.id, { ...definition, authorizedBy });
1209
+ }
1210
+ const enabled = scheduler.setEnabled(definition.id, true);
1211
+ if (enabled.execution.permissionMode === 'fullaccess') {
1212
+ this.auditFullAccessTriggerConfiguration({
1213
+ event: 'fullaccess.trigger.configured', definition: enabled, actorId: peerId,
1214
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger enabled',
1215
+ });
1216
+ }
1118
1217
  return `✅ 触发器已启用:**${definition.name}**`;
1119
1218
  }
1120
1219
  if (sub.startsWith('disable ')) {
@@ -1197,7 +1296,7 @@ export class CommandHandler {
1197
1296
  const result = parseTriggerUpdate(args);
1198
1297
  if (!result.ok)
1199
1298
  return `❌ ${result.error}`;
1200
- const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId);
1299
+ const updated = await this.updateTriggerFromPatch(scheduler, result.nameOrId, result.value, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1201
1300
  if (!updated.ok)
1202
1301
  return `❌ ${updated.error}`;
1203
1302
  const nextStr = updated.trigger.nextFireAt ? new Date(updated.trigger.nextFireAt).toLocaleString() : '未计算';
@@ -1213,7 +1312,7 @@ export class CommandHandler {
1213
1312
  const result = parseTriggerSet(args);
1214
1313
  if (!result.ok)
1215
1314
  return `❌ ${result.error}`;
1216
- const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin);
1315
+ const reg = await this.registerTriggerFromParsed(result.value, channel, channelId, peerId, messageId, chatType, threadId, isAdmin, isDaemonOwner, canApprovePersistentFullAccess);
1217
1316
  if (!reg.ok)
1218
1317
  return `❌ ${reg.error}`;
1219
1318
  const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
@@ -1232,13 +1331,13 @@ export class CommandHandler {
1232
1331
  /trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
1233
1332
  /trigger delete <名称|ID> — 删除触发器`;
1234
1333
  }
1235
- async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
1334
+ async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1236
1335
  const scheduler = this.getTriggerSchedulerForChannel(channel);
1237
1336
  if (!scheduler)
1238
1337
  return '⚠️ 触发器功能未启用';
1239
- return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId);
1338
+ return await this.handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1240
1339
  }
1241
- async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId) {
1340
+ async updateTriggerFromPatch(scheduler, nameOrId, patch, channel, channelId, peerId, isAdmin, messageId, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1242
1341
  const definition = this.findTriggerDefinition(scheduler, nameOrId, peerId, channel, isAdmin);
1243
1342
  if (!definition) {
1244
1343
  return { ok: false, error: isAdmin ? `未找到触发器:${nameOrId}` : `未找到触发器 "${nameOrId}",或无权限修改` };
@@ -1275,6 +1374,22 @@ export class CommandHandler {
1275
1374
  catch (err) {
1276
1375
  return { ok: false, error: err?.message || String(err) };
1277
1376
  }
1377
+ if (updated.execution.permissionMode === 'fullaccess') {
1378
+ if (!isDaemonOwner) {
1379
+ return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
1380
+ }
1381
+ if (!canApprovePersistentFullAccess) {
1382
+ return { ok: false, error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
1383
+ }
1384
+ updated = {
1385
+ ...updated,
1386
+ authorizedBy: this.canonicalDaemonOwnerId(definition.agentAid, definition.origin?.channelKey || channel, peerId),
1387
+ };
1388
+ }
1389
+ else {
1390
+ const { authorizedBy: _authorizedBy, ...deescalated } = updated;
1391
+ updated = deescalated;
1392
+ }
1278
1393
  const target = this.primaryFeedbackTarget(updated);
1279
1394
  if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
1280
1395
  return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
@@ -1283,14 +1398,35 @@ export class CommandHandler {
1283
1398
  return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
1284
1399
  }
1285
1400
  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 };
1401
+ const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
1402
+ if (permissionError) {
1403
+ return {
1404
+ ok: false,
1405
+ error: permissionError,
1406
+ code: /fullaccess 功能未启用/.test(permissionError) ? 'NOT_SUPPORTED'
1407
+ : /无权限|只有 daemon-owner/.test(permissionError) ? 'FORBIDDEN'
1408
+ : undefined,
1409
+ };
1410
+ }
1289
1411
  const runtimeFieldError = this.validateTriggerRuntimeFields(updated, triggerRole.role, triggerRole.baseagent);
1290
1412
  if (runtimeFieldError)
1291
1413
  return { ok: false, error: runtimeFieldError };
1292
1414
  try {
1293
1415
  const saved = scheduler.update(definition.id, updated);
1416
+ if (saved.execution.permissionMode === 'fullaccess') {
1417
+ this.auditFullAccessTriggerConfiguration({
1418
+ event: 'fullaccess.trigger.configured', definition: saved, actorId: peerId,
1419
+ messageId, isDaemonOwner, reason: definition.execution.permissionMode === 'fullaccess'
1420
+ ? 'fullaccess Trigger updated'
1421
+ : 'Trigger elevated to fullaccess',
1422
+ });
1423
+ }
1424
+ else if (definition.execution.permissionMode === 'fullaccess') {
1425
+ this.auditFullAccessTriggerConfiguration({
1426
+ event: 'fullaccess.trigger.deescalated', definition, actorId: peerId,
1427
+ messageId, isDaemonOwner, reason: 'Trigger permissionMode changed from fullaccess',
1428
+ });
1429
+ }
1294
1430
  return {
1295
1431
  ok: true,
1296
1432
  trigger: this.definitionToTriggerView(saved, scheduler),
@@ -1304,12 +1440,18 @@ export class CommandHandler {
1304
1440
  /** 从已解析的 trigger 参数组装 definition 并注册。文本路径(handleTrigger)与 menu 路径共用。
1305
1441
  * parsed 形状 = parseTriggerSet 的 result.value(ParsedTriggerSet)。
1306
1442
  * 失败 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);
1443
+ async registerTriggerFromParsed(parsed, channel, channelId, peerId, messageId, chatType, threadId, isAdmin = false, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1444
+ const built = await this.buildTriggerDefinitionFromParsed(parsed, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner, canApprovePersistentFullAccess);
1309
1445
  if ('error' in built)
1310
1446
  return { ok: false, error: built.error };
1311
1447
  try {
1312
1448
  const created = built.scheduler.create(built.definition, [], { enable: true });
1449
+ if (created.execution.permissionMode === 'fullaccess') {
1450
+ this.auditFullAccessTriggerConfiguration({
1451
+ event: 'fullaccess.trigger.configured', definition: created, actorId: peerId,
1452
+ messageId, isDaemonOwner, reason: 'fullaccess Trigger created',
1453
+ });
1454
+ }
1313
1455
  return { ok: true, trigger: this.definitionToTriggerView(created, built.scheduler) };
1314
1456
  }
1315
1457
  catch (err) {
@@ -1534,6 +1676,7 @@ export class CommandHandler {
1534
1676
  chatType: grant.chatType,
1535
1677
  conversationId,
1536
1678
  processOwners: loadDaemonConfig().owners ?? [],
1679
+ ...trustedExecutionIdentityInput(grant),
1537
1680
  });
1538
1681
  const executeOne = (fieldArgv) => {
1539
1682
  const resolved = resolveConfigCommand(fieldArgv, {
@@ -1606,9 +1749,19 @@ export class CommandHandler {
1606
1749
  const resolved = resolveContactCommand(argv);
1607
1750
  if (!resolved.ok)
1608
1751
  return { ok: false, code: resolved.code, error: resolved.reason };
1609
- if (resolved.command.selfAid !== grant.selfAid) {
1752
+ if (resolved.command.selfAid !== grant.selfAid && !hasTrustedFullAccessDelegation(grant)) {
1610
1753
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent contact book can be targeted' };
1611
1754
  }
1755
+ const selfServiceAdd = resolved.command.kind === 'add';
1756
+ const primaryId = 'primaryId' in resolved.command ? resolved.command.primaryId : undefined;
1757
+ if (selfServiceAdd && !hasTrustedFullAccessDelegation(grant)) {
1758
+ if (grant.channelType !== 'aun' || grant.chatType !== 'private') {
1759
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add requires the current AUN private relation' };
1760
+ }
1761
+ if (primaryId !== conversationId) {
1762
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'contact add may only target the current private peer' };
1763
+ }
1764
+ }
1612
1765
  const subject = buildAuthSubject({
1613
1766
  selfAid: grant.selfAid,
1614
1767
  actorId: grant.actorId,
@@ -1618,17 +1771,25 @@ export class CommandHandler {
1618
1771
  chatType: grant.chatType,
1619
1772
  conversationId,
1620
1773
  processOwners: loadDaemonConfig().owners ?? [],
1774
+ ...trustedExecutionIdentityInput(grant),
1621
1775
  });
1622
1776
  const decision = authorizeOperation({
1623
1777
  source: 'agent-tool',
1624
1778
  subject,
1625
1779
  intent: {
1626
1780
  operation: resolved.command.operationId,
1627
- scope: 'agent',
1781
+ scope: selfServiceAdd ? 'relation' : 'agent',
1628
1782
  source: 'agent-tool',
1629
1783
  args: {
1630
1784
  self: resolved.command.selfAid,
1631
- ...('primaryId' in resolved.command ? { primaryId: resolved.command.primaryId } : {}),
1785
+ ...(primaryId ? { primaryId } : {}),
1786
+ ...(selfServiceAdd
1787
+ ? {
1788
+ targetId: primaryId,
1789
+ peer: primaryId,
1790
+ peerKey: `aun#${primaryId}`,
1791
+ }
1792
+ : {}),
1632
1793
  },
1633
1794
  },
1634
1795
  auditMetadata: {
@@ -1639,6 +1800,47 @@ export class CommandHandler {
1639
1800
  });
1640
1801
  if (!decision.allow)
1641
1802
  return { ok: false, code: decision.code, error: decision.reason };
1803
+ if (selfServiceAdd) {
1804
+ if (!this.contactRequestSubmitter) {
1805
+ return { ok: false, code: 'CONTACT_REQUEST_UNAVAILABLE', error: 'Contact request workflow is not configured' };
1806
+ }
1807
+ try {
1808
+ const submission = await this.contactRequestSubmitter({
1809
+ selfAid: grant.selfAid,
1810
+ applicantAid: primaryId,
1811
+ sourceChannelKey: grant.channel,
1812
+ });
1813
+ if (submission.code === 'blocked') {
1814
+ return {
1815
+ ok: false,
1816
+ code: 'CONTACT_BLOCKED',
1817
+ error: `目标联系人 ${primaryId} 已被拉黑。请先解除拉黑:ec contact unblock ${primaryId} --self ${grant.selfAid}`,
1818
+ };
1819
+ }
1820
+ return {
1821
+ ok: true,
1822
+ result: {
1823
+ ok: true,
1824
+ operation: 'add',
1825
+ selfAid: grant.selfAid,
1826
+ primaryId: primaryId,
1827
+ status: submission.code,
1828
+ changed: submission.code === 'submitted' || submission.code === 'resubmitted',
1829
+ requestId: submission.requestId,
1830
+ expiresAt: submission.expiresAt,
1831
+ contactRevision: submission.contactRevision,
1832
+ reviewCardSent: submission.reviewCardSent === true,
1833
+ },
1834
+ };
1835
+ }
1836
+ catch (error) {
1837
+ return {
1838
+ ok: false,
1839
+ code: error?.code ?? 'CONTACT_REQUEST_FAILED',
1840
+ error: error instanceof Error ? error.message : String(error),
1841
+ };
1842
+ }
1843
+ }
1642
1844
  const result = await executeResolvedContactCommand(resolved.command, grant.actorId);
1643
1845
  return result.ok
1644
1846
  ? { ok: true, result }
@@ -1663,7 +1865,7 @@ export class CommandHandler {
1663
1865
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1664
1866
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1665
1867
  }
1666
- if (parsed.command.aid !== grantSelfAid) {
1868
+ if (parsed.command.aid !== grantSelfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
1667
1869
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1668
1870
  }
1669
1871
  let conversationId;
@@ -1682,6 +1884,7 @@ export class CommandHandler {
1682
1884
  chatType: delegation.grant.chatType,
1683
1885
  conversationId,
1684
1886
  processOwners: loadDaemonConfig().owners ?? [],
1887
+ ...trustedExecutionIdentityInput(delegation.grant),
1685
1888
  });
1686
1889
  const managementRole = isManagementRole(subject.role);
1687
1890
  const decision = authorizeOperation({
@@ -1733,7 +1936,7 @@ export class CommandHandler {
1733
1936
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1734
1937
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1735
1938
  }
1736
- if (parsed.command.from !== grantSelfAid) {
1939
+ if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
1737
1940
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can perform managed group operations' };
1738
1941
  }
1739
1942
  let conversationId;
@@ -1752,12 +1955,16 @@ export class CommandHandler {
1752
1955
  chatType: grant.chatType,
1753
1956
  conversationId,
1754
1957
  processOwners: loadDaemonConfig().owners ?? [],
1958
+ ...trustedExecutionIdentityInput(grant),
1755
1959
  });
1756
- const managementRole = isManagementRole(subject.role);
1757
- if (!managementRole && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
1960
+ const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
1961
+ if (subject.dataScope !== 'daemon'
1962
+ && !managementRole
1963
+ && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
1758
1964
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
1759
1965
  }
1760
- if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner') && subject.role !== 'owner') {
1966
+ if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
1967
+ && subject.dataScope !== 'daemon' && subject.role !== 'owner') {
1761
1968
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${parsed.command.kind} requires Agent owner permission` };
1762
1969
  }
1763
1970
  const decision = authorizeOperation({
@@ -1849,7 +2056,7 @@ export class CommandHandler {
1849
2056
  };
1850
2057
  }
1851
2058
  }
1852
- /** Agent-managed private msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
2059
+ /** Agent-managed msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
1853
2060
  async handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1854
2061
  if (!this.agentDelegationRegistry) {
1855
2062
  return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
@@ -1902,28 +2109,53 @@ export class CommandHandler {
1902
2109
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1903
2110
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1904
2111
  }
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' };
2112
+ if (parsed.command.from !== grantSelfAid && !hasTrustedFullAccessDelegation(grant)) {
2113
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1907
2114
  }
1908
- let peerAid;
2115
+ const isSelectorHistory = parsed.command.kind === 'history'
2116
+ && !!parsed.command.channelKey
2117
+ && !!parsed.command.peerId;
2118
+ let conversationId;
1909
2119
  try {
1910
- peerAid = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
2120
+ conversationId = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
1911
2121
  }
1912
2122
  catch {
1913
2123
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1914
2124
  }
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' };
2125
+ if (isSelectorHistory) {
2126
+ if (!hasTrustedFullAccessDelegation(grant) && !isTrustedDaemonTriggerSession(session, grantSelfAid)) {
2127
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Cross-channel history selectors require a trusted daemon Trigger session' };
2128
+ }
1917
2129
  }
2130
+ else {
2131
+ if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
2132
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
2133
+ }
2134
+ if (!hasTrustedFullAccessDelegation(grant) && parsed.command.target !== conversationId) {
2135
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
2136
+ }
2137
+ }
2138
+ const selectorChannel = isSelectorHistory
2139
+ ? tryParseChannelKey(parsed.command.channelKey)
2140
+ : undefined;
2141
+ if (isSelectorHistory && !selectorChannel) {
2142
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'Invalid channel selector' };
2143
+ }
2144
+ const targetId = isSelectorHistory ? parsed.command.peerId : parsed.command.target;
2145
+ const targetPeerKey = isSelectorHistory
2146
+ ? formatPeerKey(selectorChannel.type, targetId)
2147
+ : formatPeerKey('aun', targetId);
1918
2148
  const subject = buildAuthSubject({
1919
2149
  selfAid: grant.selfAid,
1920
2150
  actorId: grant.actorId,
1921
2151
  channel: grant.channel,
1922
2152
  channelType: grant.channelType,
1923
- channelId: peerAid,
2153
+ channelId: conversationId,
1924
2154
  chatType: grant.chatType,
1925
- conversationId: peerAid,
2155
+ conversationId,
2156
+ ...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
1926
2157
  processOwners: loadDaemonConfig().owners ?? [],
2158
+ ...trustedExecutionIdentityInput(grant),
1927
2159
  });
1928
2160
  const decision = authorizeOperation({
1929
2161
  source: 'agent-tool',
@@ -1934,9 +2166,15 @@ export class CommandHandler {
1934
2166
  source: 'agent-tool',
1935
2167
  args: {
1936
2168
  self: parsed.command.from,
1937
- targetId: parsed.command.target,
1938
- peer: parsed.command.target,
1939
- peerKey: formatPeerKey('aun', parsed.command.target),
2169
+ targetId,
2170
+ peer: targetId,
2171
+ peerKey: targetPeerKey,
2172
+ ...(isSelectorHistory ? {
2173
+ channelKey: parsed.command.channelKey,
2174
+ peerId: parsed.command.peerId,
2175
+ selectorPeerKey: targetPeerKey,
2176
+ triggerSessionId: session.id,
2177
+ } : {}),
1940
2178
  ...(parsed.command.session ? { session: parsed.command.session } : {}),
1941
2179
  ...(parsed.command.limit !== undefined ? { limit: parsed.command.limit } : {}),
1942
2180
  ...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
@@ -1949,6 +2187,9 @@ export class CommandHandler {
1949
2187
  argvHash: hashArgv(parsed.command.canonicalArgv),
1950
2188
  taskId: grant.taskId,
1951
2189
  messageId: grant.messageId,
2190
+ sessionId,
2191
+ agentAid: grant.selfAid,
2192
+ toolName: 'ec.msg',
1952
2193
  },
1953
2194
  });
1954
2195
  if (!decision.allow)
@@ -1984,7 +2225,7 @@ export class CommandHandler {
1984
2225
  if (sessionSelfAid && sessionSelfAid !== actorAid) {
1985
2226
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1986
2227
  }
1987
- if (grant.channelType !== 'aun' || grant.chatType !== 'group') {
2228
+ if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'group')) {
1988
2229
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs operations require the current AUN group relation' };
1989
2230
  }
1990
2231
  let currentGroup;
@@ -1994,10 +2235,13 @@ export class CommandHandler {
1994
2235
  catch {
1995
2236
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1996
2237
  }
1997
- if ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup) {
2238
+ if (!hasTrustedFullAccessDelegation(grant)
2239
+ && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup)) {
1998
2240
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
1999
2241
  }
2000
- parsed.command.actorAid = actorAid;
2242
+ parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
2243
+ ? (parsed.command.actorAid ?? actorAid)
2244
+ : actorAid;
2001
2245
  const subject = buildAuthSubject({
2002
2246
  selfAid: grant.selfAid,
2003
2247
  actorId: grant.actorId,
@@ -2007,6 +2251,7 @@ export class CommandHandler {
2007
2251
  chatType: grant.chatType,
2008
2252
  conversationId: currentGroup,
2009
2253
  processOwners: loadDaemonConfig().owners ?? [],
2254
+ ...trustedExecutionIdentityInput(grant),
2010
2255
  });
2011
2256
  const intentArgs = {
2012
2257
  self: actorAid,
@@ -2139,6 +2384,7 @@ export class CommandHandler {
2139
2384
  chatType: grant.chatType,
2140
2385
  conversationId,
2141
2386
  processOwners: loadDaemonConfig().owners ?? [],
2387
+ ...trustedExecutionIdentityInput(grant),
2142
2388
  });
2143
2389
  const ctlIntent = this.resolveCtlIntent(cmd, grantSelfAid, grant.peerKey);
2144
2390
  if (!ctlIntent)