evolcore 0.0.12 → 0.0.14

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 (116) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/codex-managed-hook.mjs +4 -1
  3. package/bin/ec-safe-output.js +26 -7
  4. package/bin/install-codex-managed-hooks.mjs +201 -0
  5. package/dist/agents/claude-runner.js +105 -11
  6. package/dist/agents/codex-app-server-client.js +176 -7
  7. package/dist/agents/codex-runner.js +248 -84
  8. package/dist/agents/ecagent-runner.js +21 -2
  9. package/dist/agents/gemini-runner.js +9 -4
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/store.js +13 -7
  12. package/dist/aun/group-fs-download.js +56 -0
  13. package/dist/aun/group-identity.js +1 -2
  14. package/dist/aun/msg/group.js +11 -33
  15. package/dist/aun/msg/index.js +1 -2
  16. package/dist/aun/msg/managed-operation.js +176 -55
  17. package/dist/aun/msg/upload.js +16 -1
  18. package/dist/aun/outbox.js +31 -2
  19. package/dist/aun/rpc/client.js +66 -0
  20. package/dist/aun/rpc/index.js +1 -2
  21. package/dist/aun/storage/{upload.js → client.js} +11 -2
  22. package/dist/aun/storage/index.js +1 -2
  23. package/dist/channels/aun.js +514 -150
  24. package/dist/channels/daemon.js +2 -0
  25. package/dist/cli/agent.js +9 -96
  26. package/dist/cli/aun-commands.js +1 -1
  27. package/dist/cli/cli-argv.js +10 -0
  28. package/dist/cli/config.js +9 -17
  29. package/dist/cli/ctl-command.js +1 -1
  30. package/dist/cli/daemon-commands.js +39 -24
  31. package/dist/cli/fs-command.js +65 -19
  32. package/dist/cli/init.js +77 -8
  33. package/dist/cli/queue-command.js +42 -0
  34. package/dist/cli/restart-monitor.js +3 -14
  35. package/dist/cli/task-context.js +172 -0
  36. package/dist/cli/trigger-command.js +7 -0
  37. package/dist/config/access-policy.js +1 -2
  38. package/dist/config/builtin-roles.js +7 -0
  39. package/dist/config/config-manager.js +27 -3
  40. package/dist/config/contact-book-store.js +7 -2
  41. package/dist/config/peer-role-resolver.js +6 -2
  42. package/dist/config/role-migration-startup.js +53 -0
  43. package/dist/config/role-store.js +1 -1
  44. package/dist/core/auth/agent-delegation.js +1 -1
  45. package/dist/core/auth/auth-gateway.js +30 -1
  46. package/dist/core/auth/authorization-audit.js +47 -0
  47. package/dist/core/auth/operation-authorizer.js +1 -0
  48. package/dist/core/auth/operation-catalog.js +10 -1
  49. package/dist/core/bootstrap-messages.js +9 -2
  50. package/dist/core/bootstrap-service.js +8 -1
  51. package/dist/core/command/command-handler.js +201 -51
  52. package/dist/core/command/menu-catalog.js +4 -0
  53. package/dist/core/command/menu-handler.js +85 -18
  54. package/dist/core/command/menu-protocol.js +23 -2
  55. package/dist/core/command/slash-handler.js +58 -10
  56. package/dist/core/event-catalog.js +8 -0
  57. package/dist/core/handoff/runtime.js +12 -1
  58. package/dist/core/handoff/store.js +5 -1
  59. package/dist/core/interaction-router.js +10 -0
  60. package/dist/core/message/im-renderer.js +34 -1
  61. package/dist/core/message/message-bridge.js +68 -13
  62. package/dist/core/message/message-log.js +2 -0
  63. package/dist/core/message/message-queue.js +267 -39
  64. package/dist/core/message/message-utils.js +81 -4
  65. package/dist/core/message/response-engine.js +242 -48
  66. package/dist/core/message/stream-debouncer.js +10 -2
  67. package/dist/core/permission/approval-gateway.js +1 -0
  68. package/dist/core/permission/ec-command-parser.js +649 -18
  69. package/dist/core/permission/index.js +1 -1
  70. package/dist/core/permission/mode.js +15 -0
  71. package/dist/core/permission/readonly-shell-query.js +148 -8
  72. package/dist/core/permission/sandbox-runtime.js +21 -0
  73. package/dist/core/permission/tool-policy.js +266 -56
  74. package/dist/core/protected-paths.js +107 -2
  75. package/dist/core/runtime-lock.js +101 -0
  76. package/dist/core/session/session-manager.js +21 -2
  77. package/dist/core/session/session-mapper.js +20 -1
  78. package/dist/eck/detect.js +18 -1
  79. package/dist/eck/group-rules-sync.js +15 -44
  80. package/dist/index.js +191 -89
  81. package/dist/ipc.js +16 -3
  82. package/dist/paths.js +3 -0
  83. package/dist/response-system/engines/v1/proactive-flow.js +92 -8
  84. package/dist/response-system/modes/single-session/index.js +3 -0
  85. package/dist/trigger/feedback.js +64 -11
  86. package/dist/trigger/history.js +42 -7
  87. package/dist/trigger/manager.js +21 -1
  88. package/dist/trigger/parser.js +14 -2
  89. package/dist/trigger/scheduler.js +1 -0
  90. package/dist/trigger/validation.js +67 -6
  91. package/dist/utils/ecweb-supervisor.js +332 -0
  92. package/dist/utils/error-utils.js +17 -0
  93. package/dist/utils/logger.js +37 -4
  94. package/kits/docs/evolcore/trigger.md +17 -1
  95. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  96. package/kits/schemas/role-config.schema.1.json +1 -0
  97. package/kits/templates/roles/admin.json +7 -0
  98. package/kits/templates/roles/member.json +8 -1
  99. package/kits/templates/roles/visitor.json +2 -0
  100. package/package.json +1 -1
  101. package/dist/aun/msg/payload-type.js +0 -27
  102. package/dist/aun/rpc/caller.js +0 -42
  103. package/dist/aun/rpc/connection.js +0 -25
  104. package/dist/aun/storage/manage.js +0 -10
  105. package/dist/config/access-policy-domain.js +0 -18
  106. package/dist/config/config-batch-get.js +0 -11
  107. package/dist/config/owner-policy.js +0 -4
  108. package/dist/config/role-config-v4-startup.js +0 -32
  109. package/dist/config/role-config-v5-startup.js +0 -27
  110. package/dist/core/auth/trigger-authorization.js +0 -15
  111. package/dist/core/interaction-registration.js +0 -10
  112. package/dist/core/permission/execution-sandbox.js +0 -16
  113. package/dist/core/relation/peer-key.js +0 -1
  114. package/dist/core/session/session-key.js +0 -24
  115. package/dist/eck/baseagent-caps.js +0 -18
  116. package/dist/eck/rules-loader.js +0 -28
@@ -600,7 +600,7 @@ export class MessageBridge {
600
600
  channelId: route.approverOperatorId,
601
601
  agentName: owningAgent?.name ?? selfAid,
602
602
  chatmode: 'interactive',
603
- replyContext: { metadata: { source: 'handoff', chatmode: 'interactive' } },
603
+ replyContext: { delivery: { chatType: 'private' }, metadata: { source: 'handoff', chatmode: 'interactive' } },
604
604
  });
605
605
  const sent = await sendInteractionPayload(route.adapter, envelope, interaction, renderActionAsText(interaction), envelope.replyContext);
606
606
  if (!sent)
@@ -615,14 +615,14 @@ export class MessageBridge {
615
615
  : code === 'request-stale' ? '申请已更新或已被处理。'
616
616
  : code === 'revision-conflict' ? '联系人列表已变化,请从联系人列表重新审核。'
617
617
  : '当前无审核权限。';
618
- await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: ownerAid, agentName: agentName ?? '<unknown>' }), { kind: 'system.notice', text, subtype: 'contact-review' });
618
+ await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: ownerAid, agentName: agentName ?? '<unknown>', delivery: { chatType: 'private' } }), { kind: 'system.notice', text, subtype: 'contact-review' });
619
619
  }
620
620
  async notifyContactApplicant(adapter, applicantAid, code, requestId, expiresAt, agentName) {
621
621
  await adapter.send(buildEnvelope({
622
622
  channel: adapter.channelName,
623
623
  channelId: applicantAid,
624
624
  agentName: agentName ?? '<unknown>',
625
- replyContext: { metadata: evolMenuResponseTransportMetadata() },
625
+ replyContext: { delivery: { chatType: 'private' }, metadata: evolMenuResponseTransportMetadata() },
626
626
  }), {
627
627
  kind: 'custom',
628
628
  channelType: 'aun',
@@ -687,11 +687,23 @@ export class MessageBridge {
687
687
  onMessage(async (msg) => {
688
688
  try {
689
689
  let content = msg.content.trim();
690
+ // Channels that expose adapter receive time (notably AUN) must keep
691
+ // that timestamp through session resolution and queue processing.
692
+ // The fallback is only for legacy adapters that do not provide it.
693
+ const receivedAt = msg.receivedAt ?? Date.now();
690
694
  const channelKey = adapter?.channelKey || channelName;
691
695
  const owningAgent = this.agentRegistry?.resolveByChannel(channelKey)
692
696
  ?? this.agentRegistry?.resolveByChannel(channelName);
693
697
  const parsedChannelKey = tryParseChannelKey(channelKey);
694
698
  const chatType = msg.chatType || 'private';
699
+ const delivery = chatType === 'group'
700
+ ? { chatType: 'group', groupId: msg.groupId || msg.channelId }
701
+ : { chatType: 'private' };
702
+ msg.groupId = delivery.chatType === 'group' ? delivery.groupId : undefined;
703
+ msg.replyContext = {
704
+ ...(msg.replyContext ?? {}),
705
+ delivery,
706
+ };
695
707
  const actorId = msg.peerId;
696
708
  const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
697
709
  const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
@@ -860,7 +872,7 @@ export class MessageBridge {
860
872
  return;
861
873
  }
862
874
  // 普通消息保留原始日志;控制 payload 使用脱敏元数据日志。
863
- logger.channelIn({ channel: channelName, channelId: msg.channelId, peerId: msg.peerId, peerName: msg.peerName, chatType: msg.chatType, msgId: msg.messageId, threadId: msg.threadId, content, images: msg.images?.length ?? 0, mentions: msg.mentions, replyContext: msg.replyContext });
875
+ logger.channelIn({ channel: channelName, channelId: msg.channelId, selfAid: msg.selfAID, peerId: msg.peerId, peerName: msg.peerName, chatType: msg.chatType, msgId: msg.messageId, correlationId: msg.messageId, threadId: msg.threadId, content, images: msg.images?.length ?? 0, mentions: msg.mentions, replyContext: msg.replyContext });
864
876
  const accessDecision = authorizeAccess(authSubject);
865
877
  if (!accessDecision.allow) {
866
878
  logger.warn(`[MessageBridge] Access denied before command routing: channel=${channelName} channelId=${msg.channelId} actor=${actorId ?? '<none>'} role=${identity.role} reason=${accessDecision.reason}`);
@@ -925,7 +937,9 @@ export class MessageBridge {
925
937
  content: msg.messageLogContent ?? content,
926
938
  replyTo: inboundReplyTo,
927
939
  permMode: null,
928
- timestamp: Date.now(),
940
+ timestamp: receivedAt,
941
+ receivedAt,
942
+ gatewaySeq: msg.gatewaySeq,
929
943
  encrypt: inboundEncrypt,
930
944
  chatmode: inboundChatmode,
931
945
  peerName: msg.peerName,
@@ -940,7 +954,8 @@ export class MessageBridge {
940
954
  }
941
955
  }
942
956
  if (msg.source !== 'handoff' && await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
943
- logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId: `cmd-${msg.messageId || Date.now()}`, payload: { kind: 'command.result', text } });
957
+ const taskId = `cmd-${msg.messageId || Date.now()}`;
958
+ logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId, correlationId: taskId, sessionId: msg.replyContext?.sessionId, agentAid: msg.selfAID, payload: { kind: 'command.result', text } });
944
959
  return sendReply(msg.channelId, text, msg.replyContext);
945
960
  }, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID, authSubject))
946
961
  return;
@@ -1017,9 +1032,12 @@ export class MessageBridge {
1017
1032
  channelType: msg.channelType || effectiveChannelType,
1018
1033
  channelId: msg.channelId, content,
1019
1034
  selfAID: msg.selfAID,
1035
+ groupId: msg.groupId,
1020
1036
  baseagent: session.baseagent,
1021
1037
  chatType,
1022
- images: msg.images, timestamp: Date.now(),
1038
+ images: msg.images, timestamp: receivedAt,
1039
+ receivedAt,
1040
+ gatewaySeq: msg.gatewaySeq,
1023
1041
  peerId: msg.peerId, peerName: msg.peerName,
1024
1042
  peerType: msg.peerType,
1025
1043
  sameDevice: msg.sameDevice,
@@ -1062,6 +1080,8 @@ export class MessageBridge {
1062
1080
  replyTo: inboundReplyTo,
1063
1081
  permMode: session.identity?.role ?? null,
1064
1082
  timestamp: fullMessage.timestamp,
1083
+ receivedAt: msg.receivedAt,
1084
+ gatewaySeq: msg.gatewaySeq,
1065
1085
  encrypt: inboundEncrypt,
1066
1086
  chatmode: inboundChatmode,
1067
1087
  peerName: msg.peerName,
@@ -1200,6 +1220,31 @@ export class MessageBridge {
1200
1220
  if (!hasValidMenuId(parsed))
1201
1221
  return;
1202
1222
  const header = { id: parsed.id, ...(parsed.name?.trim() ? { name: parsed.name } : {}) };
1223
+ // Menu IDs are the protocol's stable correlation key. Bind it, together
1224
+ // with the active EvolCore session, before authorization so every
1225
+ // CommandAudit record and denial can be joined to the same request.
1226
+ let activeSession;
1227
+ if (msg.threadId && typeof this.sessionManager.getThreadSession === 'function') {
1228
+ try {
1229
+ activeSession = await this.sessionManager.getThreadSession(channel, msg.channelId, msg.threadId);
1230
+ }
1231
+ catch {
1232
+ activeSession = undefined;
1233
+ }
1234
+ }
1235
+ if (!activeSession && typeof this.sessionManager.getActiveSession === 'function') {
1236
+ try {
1237
+ activeSession = await this.sessionManager.getActiveSession(channel, msg.channelId);
1238
+ }
1239
+ catch {
1240
+ activeSession = undefined;
1241
+ }
1242
+ }
1243
+ const menuAuthSubject = {
1244
+ ...authSubject,
1245
+ requestId: parsed.id,
1246
+ ...(activeSession?.id ? { sessionId: activeSession.id } : {}),
1247
+ };
1203
1248
  const isTokenRequest = parsed.type === 'menu.token.request';
1204
1249
  if (isTokenRequest) {
1205
1250
  if (msg.chatType !== 'private') {
@@ -1218,7 +1263,7 @@ export class MessageBridge {
1218
1263
  }
1219
1264
  }
1220
1265
  if (!isTokenRequest) {
1221
- const access = authorizeAccess(authSubject);
1266
+ const access = authorizeAccess(menuAuthSubject);
1222
1267
  if (!access.allow) {
1223
1268
  this.logMenuDiagnostic('access-denied', channel, msg, parsed, access.code);
1224
1269
  await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, {
@@ -1233,7 +1278,7 @@ export class MessageBridge {
1233
1278
  let afterResponse;
1234
1279
  const deduped = await this.menuDeduper.execute(dedupKey, menuPayloadFingerprint(parsed.raw), () => isTokenRequest
1235
1280
  ? this.issueMenuTokenResponse(header, channel, msg)
1236
- : this.dispatchMenuRequest(parsed.request, header, channel, msg, authSubject, effect => { afterResponse = effect; }));
1281
+ : this.dispatchMenuRequest(parsed.request, header, channel, msg, menuAuthSubject, effect => { afterResponse = effect; }));
1237
1282
  if ('conflict' in deduped) {
1238
1283
  this.logMenuDiagnostic('request-conflict', channel, msg, parsed, 'CONFLICT');
1239
1284
  await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, {
@@ -1244,6 +1289,8 @@ export class MessageBridge {
1244
1289
  }
1245
1290
  if (deduped.replayed)
1246
1291
  this.logMenuDiagnostic('request-replay', channel, msg, parsed);
1292
+ if (deduped.retry)
1293
+ this.logMenuDiagnostic('request-retry', channel, msg, parsed);
1247
1294
  const response = responseWarning && 'data' in deduped.value
1248
1295
  ? { ...deduped.value, warning: responseWarning }
1249
1296
  : deduped.value;
@@ -1380,6 +1427,10 @@ export class MessageBridge {
1380
1427
  ...evolMenuResponseTransportMetadata(),
1381
1428
  },
1382
1429
  };
1430
+ if (msg.channelType === 'aun' && !replyContext.delivery) {
1431
+ logger.warn(`[MessageBridge] dropping AUN menu response without trusted delivery route: channel=${channel} channelId=${msg.channelId}`);
1432
+ return;
1433
+ }
1383
1434
  const envelope = buildEnvelope({
1384
1435
  taskId: `menu-${randomBytes(4).toString('hex')}`,
1385
1436
  channel,
@@ -1418,9 +1469,11 @@ export class MessageBridge {
1418
1469
  logger.channelIn({
1419
1470
  channel,
1420
1471
  channelId: this.shortHash(msg.channelId),
1472
+ selfAid: msg.selfAID,
1421
1473
  peerId: this.shortHash(msg.peerId),
1422
1474
  chatType: msg.chatType,
1423
1475
  msgId: msg.messageId,
1476
+ correlationId: msg.messageId,
1424
1477
  control: {
1425
1478
  id: parsed.id,
1426
1479
  type: parsed.type,
@@ -1438,6 +1491,8 @@ export class MessageBridge {
1438
1491
  logger.warn(`[MenuControl] category=${category} request=${parsed.id || '<missing>'}`
1439
1492
  + ` type=${parsed.type} name=${parsed.name || '<none>'} action=${parsed.action || '<none>'}`
1440
1493
  + ` transport=${channel} scope=${this.shortHash(scope)} sender=${this.shortHash(msg.peerId)}`
1494
+ + (category === 'request-replay' ? ' replay=true' : '')
1495
+ + (category === 'request-retry' ? ' retry=true' : '')
1441
1496
  + `${code ? ` code=${code}` : ''}${decision.suppressed ? ` suppressed=${decision.suppressed}` : ''}`);
1442
1497
  }
1443
1498
  shortHash(value) {
@@ -1556,17 +1611,17 @@ export class MessageBridge {
1556
1611
  * 撤回消息:先查 debounce 窗口,再查 message queue,最后查正在执行的任务。
1557
1612
  * @returns true 如果找到并取消/中断
1558
1613
  */
1559
- cancel(messageId) {
1614
+ cancel(messageId, selfAID) {
1560
1615
  // 阶段 1: debounce 窗口(尚未入队)
1561
1616
  for (const d of this.debouncers.values()) {
1562
- if (d.cancel(messageId))
1617
+ if (d.cancel(messageId, selfAID))
1563
1618
  return true;
1564
1619
  }
1565
1620
  // 阶段 2: 已入队但未处理(合并后 messageId 可能是逗号分隔的多个 id)
1566
- if (this.messageQueue.cancel(messageId))
1621
+ if (this.messageQueue.cancel(messageId, selfAID))
1567
1622
  return true;
1568
1623
  // 阶段 3: 正在执行的任务 → 触发 interrupt
1569
- return this.messageQueue.cancelActive(messageId);
1624
+ return this.messageQueue.cancelActive(messageId, selfAID);
1570
1625
  }
1571
1626
  /** 清理资源 */
1572
1627
  dispose() {
@@ -289,6 +289,8 @@ export function buildInboundEntry(opts) {
289
289
  const isCommand = isCommandCapable && opts.content.startsWith('/');
290
290
  return {
291
291
  ts,
292
+ ...(opts.receivedAt !== undefined ? { receivedAt: opts.receivedAt } : {}),
293
+ ...(opts.gatewaySeq !== undefined ? { gatewaySeq: opts.gatewaySeq } : {}),
292
294
  time: formatTimestampMs(ts),
293
295
  sessionId: opts.sessionId,
294
296
  threadId: opts.threadId ?? null,