evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -1,5 +1,8 @@
1
1
  import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
2
+ import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
3
+ import { closeDiagnosticSession, diagnosticTokenHash, isDiagnosticSessionActive } from './diag-service.js';
2
4
  import { parseMenuClientArgs } from '../../cli/menu-client.js';
5
+ import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
3
6
  import { renderCommandCardAsText } from '../interaction-router.js';
4
7
  import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
5
8
  import { resolvePaths, getPackageRoot } from '../../paths.js';
@@ -8,6 +11,7 @@ import { logger } from '../../utils/logger.js';
8
11
  import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
9
12
  import { isManagementRole } from '../../config/builtin-roles.js';
10
13
  import { formatPeerKey } from '../relation/peer-identity.js';
14
+ import { sameGroupAid } from '../../aun/group/index.js';
11
15
  import crypto from 'crypto';
12
16
  import path from 'path';
13
17
  import fs from 'fs';
@@ -24,7 +28,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
24
28
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
25
29
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
26
30
  import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
27
- import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
31
+ import { executeManagedGroupOperation, isManagedGroupRelationRead, isManagedGroupRelationWrite, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
28
32
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
29
33
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
30
34
  import { validateModelSelectionForRole } from '../model/model-permission.js';
@@ -99,6 +103,14 @@ export class CommandHandler {
99
103
  sessionManager;
100
104
  messageCache;
101
105
  eventBus;
106
+ agentStorageClientResolver;
107
+ groupInviteDecisionResolver;
108
+ setAgentStorageClientResolver(resolver) {
109
+ this.agentStorageClientResolver = resolver;
110
+ }
111
+ setGroupInviteDecisionResolver(resolver) {
112
+ this.groupInviteDecisionResolver = resolver;
113
+ }
102
114
  adapters = new Map();
103
115
  policies = new Map();
104
116
  channelObjects = new Map(); // name → actual channel instance (for /check)
@@ -155,7 +167,34 @@ export class CommandHandler {
155
167
  throw new Error('Menu request file must be a regular file of at most 128 KiB');
156
168
  return fs.readFileSync(target, 'utf8');
157
169
  });
158
- return { ok: true, response: await send(options) };
170
+ const binding = session.metadata?.diagnostic;
171
+ if (binding || session.metadata?.diagnosticStatus === 'cancelled') {
172
+ if (!isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session))) {
173
+ return { ok: false, code: 'DIAG_CLOSED', error: '诊断授权已结束,禁止旧会话继续访问' };
174
+ }
175
+ if (binding && (options.targetAid !== binding.controlAid
176
+ || diagnosticTokenHash(options.remoteAccessToken ?? options.ecwebToken ?? '') !== binding.tokenHash)) {
177
+ return { ok: false, code: 'DIAG_SCOPE_MISMATCH', error: '诊断请求必须使用本会话绑定的主机和 Token' };
178
+ }
179
+ }
180
+ const guardedOptions = binding
181
+ ? {
182
+ ...options,
183
+ beforeSend: () => {
184
+ if (!isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session))) {
185
+ const error = new Error('诊断授权已结束,禁止旧会话继续访问');
186
+ error.code = 'DIAG_CLOSED';
187
+ throw error;
188
+ }
189
+ },
190
+ }
191
+ : options;
192
+ const response = await send(guardedOptions);
193
+ if (binding && response?.error?.code === 'REMOTE_ACCESS_TOKEN_INVALID') {
194
+ closeDiagnosticSession(session, this.sessionManager.getChatDir(session));
195
+ this.messageQueue?.clearBySession(session.id);
196
+ }
197
+ return { ok: true, response };
159
198
  }
160
199
  catch (error) {
161
200
  return { ok: false, code: error.code ?? 'MENU_REQUEST_FAILED', error: error.message };
@@ -208,19 +247,11 @@ export class CommandHandler {
208
247
  throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
209
248
  }
210
249
  getAvailableBaseagentsForOwner(evolName) {
211
- const prefix = `${evolName}::`;
212
- return [...this.agentMap.keys()]
213
- .filter(key => key.startsWith(prefix))
214
- .map(key => key.slice(prefix.length));
250
+ return loadedBaseagentIds(this.agentMap.keys(), evolName);
215
251
  }
216
- /** Return the runner-owned temporary root for a managed session, if any. */
252
+ /** Return the stable temporary root for a managed session, if available. */
217
253
  getManagedSessionTempDir(session) {
218
- try {
219
- return this.getAgent(session.channel, session.baseagent).getManagedTempDir?.(session.id);
220
- }
221
- catch {
222
- return undefined;
223
- }
254
+ return ensureSessionRuntimeDir(session.id);
224
255
  }
225
256
  formatBaseagentUnavailable(error) {
226
257
  const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
@@ -236,21 +267,10 @@ export class CommandHandler {
236
267
  getAvailableBaseagents(channel) {
237
268
  const owner = this.agentRegistry?.resolveByChannel(channel);
238
269
  if (!owner) {
239
- const result = new Set();
240
- for (const key of this.agentMap.keys()) {
241
- const idx = key.indexOf('::');
242
- result.add(idx >= 0 ? key.slice(idx + 2) : key);
243
- }
244
- return [...result];
270
+ return loadedBaseagentIds(this.agentMap.keys());
245
271
  }
246
272
  const evolName = owner.name || '<unknown>';
247
- const prefix = `${evolName}::`;
248
- const result = [];
249
- for (const key of this.agentMap.keys()) {
250
- if (key.startsWith(prefix))
251
- result.push(key.slice(prefix.length));
252
- }
253
- return result;
273
+ return loadedBaseagentIds(this.agentMap.keys(), evolName);
254
274
  }
255
275
  /** Extract the baseagent component from `primaryRunnerKey` (e.g. `aid::claude` → `claude`). */
256
276
  parseDefaultBaseagent() {
@@ -441,6 +461,8 @@ export class CommandHandler {
441
461
  primaryFeedbackTarget(definition) {
442
462
  if (definition.feedback.strategy === 'target')
443
463
  return definition.feedback.target;
464
+ if (definition.feedback.strategy === 'event_session')
465
+ return undefined;
444
466
  if (!definition.origin)
445
467
  return undefined;
446
468
  return {
@@ -625,10 +647,16 @@ export class CommandHandler {
625
647
  if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
626
648
  return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
627
649
  }
650
+ if (!isAdmin && parsed.feedbackStrategy === 'event_session') {
651
+ return { error: '无权限:只有 owner/admin 可以使用事件来源会话' };
652
+ }
628
653
  const source = this.triggerSourceFromSchedule(parsed.scheduleType, parsed.scheduleValue);
629
654
  if (parsed.timezone && source.type === 'cron') {
630
655
  source.timezone = parsed.timezone;
631
656
  }
657
+ if (parsed.eventFilter !== undefined && source.type === 'event') {
658
+ source.filter = parsed.eventFilter;
659
+ }
632
660
  const script = parsed.scriptPath ? {
633
661
  path: parsed.scriptPath,
634
662
  runtime: parsed.scriptRuntime,
@@ -688,6 +716,7 @@ export class CommandHandler {
688
716
  baseagent: executionBaseagent,
689
717
  } : {}),
690
718
  model: parsed.model,
719
+ timeoutMs: parsed.executionTimeoutMs,
691
720
  effort: parsed.effort,
692
721
  ...(permissionMode !== undefined ? { permissionMode } : {}),
693
722
  onError: 'retry',
@@ -847,23 +876,36 @@ export class CommandHandler {
847
876
  * 通用降级应答入口:按 (sessionId, fallbackCommand) 查找 pending interaction 并路由。
848
877
  * 返回 { matched: true } 表示已处理,调用方直接返回 result。
849
878
  */
850
- async handleInteractionFallback(command, args, sessionId, userId) {
879
+ async handleInteractionFallback(command, args, sessionId, userId, channelKey) {
851
880
  if (!this.interactionRouter)
852
881
  return { matched: false };
853
- const pendingId = this.interactionRouter.findPendingByCommand(sessionId, command);
882
+ const candidates = this.interactionRouter.getPending(sessionId, command);
883
+ const explicitId = args.split(/\s+/, 1)[0];
884
+ if (explicitId.startsWith('aq-') && !candidates.includes(explicitId)) {
885
+ return { matched: true, result: '⚠️ 该问题不存在、已回答或已过期,请使用当前问题卡片。' };
886
+ }
887
+ const pendingId = candidates.includes(explicitId)
888
+ ? explicitId : this.interactionRouter.findPendingByCommand(sessionId, command);
854
889
  if (!pendingId)
855
890
  return { matched: false };
891
+ if (explicitId !== pendingId && candidates.length > 1) {
892
+ return { matched: true, result: `有多个待回答问题,请使用 /ask <问题ID> <回答>:\n${candidates.join('\n')}` };
893
+ }
894
+ const answer = explicitId === pendingId ? args.slice(explicitId.length).trim() : args.trim();
895
+ if (!answer)
896
+ return { matched: true, result: `请使用 /ask ${pendingId} <回答>` };
856
897
  const initiatorId = this.interactionRouter.getInitiator(pendingId);
857
898
  if (initiatorId && initiatorId !== userId) {
858
899
  return { matched: true, result: '⚠️ 仅卡片发起者可应答' };
859
900
  }
860
- const handled = this.interactionRouter.handle({
901
+ const handled = await this.interactionRouter.handleAsync({
861
902
  type: 'interaction.response',
862
903
  id: pendingId,
863
- action: args,
904
+ action: answer,
864
905
  operatorId: userId,
906
+ ...(channelKey ? { channelKey } : {}),
865
907
  });
866
- return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 无法验证应答者身份' };
908
+ return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 回答未被接受,请检查选项或问题是否仍有效。' };
867
909
  }
868
910
  setProcessor(processor) {
869
911
  this.processor = processor;
@@ -1003,6 +1045,7 @@ export class CommandHandler {
1003
1045
  const item = {
1004
1046
  value: s.threadId,
1005
1047
  label: displayName,
1048
+ lable: s.lable || null,
1006
1049
  };
1007
1050
  if (s.agentSessionId) {
1008
1051
  item.agentSessionId = s.agentSessionId;
@@ -1031,7 +1074,7 @@ export class CommandHandler {
1031
1074
  ...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
1032
1075
  });
1033
1076
  }
1034
- async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
1077
+ async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
1035
1078
  validateMenuValueBatchArgs(args);
1036
1079
  return await execMenuValueBatch({
1037
1080
  subject,
@@ -1042,12 +1085,12 @@ export class CommandHandler {
1042
1085
  items: args?.items,
1043
1086
  schemaVersion: args?.schemaVersion,
1044
1087
  consistency: args?.consistency,
1045
- execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu'),
1088
+ execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu', resolvedChannelId === channelId ? transportReplyContext : undefined),
1046
1089
  });
1047
1090
  }
1048
1091
  /** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
1049
- async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
1050
- return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source);
1092
+ async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
1093
+ return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source, transportReplyContext);
1051
1094
  }
1052
1095
  // ── Menu Protocol exec ────────────────────────────────────────────────
1053
1096
  async loadMenuContext(channel, channelId) {
@@ -1059,8 +1102,8 @@ export class CommandHandler {
1059
1102
  return s ? null : { error: '当前无活跃会话', code: 'NO_ACTIVE_SESSION' };
1060
1103
  }
1061
1104
  /** menu.query — 查询当前值。 */
1062
- async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu') {
1063
- return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
1105
+ async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
1106
+ return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source, transportReplyContext);
1064
1107
  }
1065
1108
  /** menu.update — 写入新值。 */
1066
1109
  async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
@@ -1513,6 +1556,9 @@ export class CommandHandler {
1513
1556
  updated = deescalated;
1514
1557
  }
1515
1558
  const target = this.primaryFeedbackTarget(updated);
1559
+ if (!isAdmin && updated.feedback.strategy === 'event_session') {
1560
+ return { ok: false, error: '无权限:只有 owner/admin 可以使用事件来源会话' };
1561
+ }
1516
1562
  if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
1517
1563
  return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
1518
1564
  }
@@ -2181,7 +2227,8 @@ export class CommandHandler {
2181
2227
  const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
2182
2228
  if (subject.dataScope !== 'daemon'
2183
2229
  && !managementRole
2184
- && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
2230
+ && (grant.channelType !== 'aun'
2231
+ || !(isManagedGroupRelationRead(parsed.command) || isManagedGroupRelationWrite(parsed.command)))) {
2185
2232
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
2186
2233
  }
2187
2234
  if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
@@ -2213,6 +2260,21 @@ export class CommandHandler {
2213
2260
  });
2214
2261
  if (!decision.allow)
2215
2262
  return { ok: false, code: decision.code, error: decision.reason };
2263
+ if (parsed.command.kind === 'accept-invite' || parsed.command.kind === 'reject-invite') {
2264
+ if (subject.role !== 'owner' || grant.channelType !== 'aun' || grant.chatType !== 'private'
2265
+ || parsed.command.from !== grantSelfAid || !this.groupInviteDecisionResolver) {
2266
+ return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'Targeted invitation decisions require the current Agent Owner' };
2267
+ }
2268
+ try {
2269
+ const result = await this.groupInviteDecisionResolver(grantSelfAid, grant.actorId, parsed.command.inviteId, parsed.command.kind === 'accept-invite' ? 'accept' : 'reject');
2270
+ return result.accepted
2271
+ ? { ok: true, result: { ok: true, status: result.status, group_id: result.groupId, message: result.text } }
2272
+ : { ok: false, code: 'GROUP_INVITE_DECISION_REJECTED', error: result.text };
2273
+ }
2274
+ catch (error) {
2275
+ return { ok: false, code: 'GROUP_INVITE_DECISION_FAILED', error: error instanceof Error ? error.message : String(error) };
2276
+ }
2277
+ }
2216
2278
  let rulesContent;
2217
2279
  if (parsed.command.kind === 'rules.set') {
2218
2280
  const filePath = String(parsed.command.filePath || '');
@@ -2267,7 +2329,10 @@ export class CommandHandler {
2267
2329
  }
2268
2330
  }
2269
2331
  try {
2270
- return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent) };
2332
+ return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent, {
2333
+ defaultThread: grant.chatType === 'group' && conversationId === parsed.command.groupId
2334
+ ? session.threadId || 'main' : 'all',
2335
+ }) };
2271
2336
  }
2272
2337
  catch (error) {
2273
2338
  return {
@@ -2464,7 +2529,7 @@ export class CommandHandler {
2464
2529
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
2465
2530
  }
2466
2531
  if (!hasTrustedFullAccessDelegation(grant)
2467
- && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup)) {
2532
+ && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || !sameGroupAid(parsed.command.groupId, currentGroup))) {
2468
2533
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
2469
2534
  }
2470
2535
  parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
@@ -0,0 +1,273 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { isValidAid } from '../../aun/aid/validation.js';
5
+ import { isExplicitGroupId } from '../../aun/group/index.js';
6
+ import { isStaticAgentOwner } from '../../config/peer-role-resolver.js';
7
+ import { agentDir, agentRelationSharedDir } from '../../paths.js';
8
+ import { formatPeerKey } from '../relation/peer-identity.js';
9
+ export class DataRootError extends Error {
10
+ code;
11
+ data;
12
+ constructor(code, message, data) {
13
+ super(message);
14
+ this.name = 'DataRootError';
15
+ this.code = code;
16
+ this.data = data;
17
+ }
18
+ }
19
+ const DEFAULT_ROOT_TTL_MS = 10 * 60 * 1000;
20
+ const TOKEN_PREFIX = 'dr1';
21
+ function base64Url(value) {
22
+ return Buffer.from(value).toString('base64url');
23
+ }
24
+ function isInside(root, candidate) {
25
+ const relative = path.relative(root, candidate);
26
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
27
+ }
28
+ function authenticatedPrincipal(subject) {
29
+ const principalId = String(subject.principalId ?? '').trim();
30
+ if (!isValidAid(principalId) || isExplicitGroupId(principalId)) {
31
+ throw new DataRootError('ROOT_FORBIDDEN', 'A verified private AUN principal is required');
32
+ }
33
+ return principalId;
34
+ }
35
+ export class DataRootResolver {
36
+ secret;
37
+ ttlMs;
38
+ now;
39
+ isOwner;
40
+ resolveAgentPath;
41
+ resolveRelationSharedPath;
42
+ constructor(options = {}) {
43
+ this.secret = options.secret ?? crypto.randomBytes(32);
44
+ if (this.secret.length < 32)
45
+ throw new Error('Data root signing secret must be at least 32 bytes');
46
+ this.ttlMs = options.ttlMs ?? DEFAULT_ROOT_TTL_MS;
47
+ this.now = options.now ?? (() => Date.now());
48
+ this.isOwner = options.isOwner ?? isStaticAgentOwner;
49
+ this.resolveAgentPath = options.resolveAgentPath ?? agentDir;
50
+ this.resolveRelationSharedPath = options.resolveRelationSharedPath ?? agentRelationSharedDir;
51
+ }
52
+ issueAgentRoot(subject) {
53
+ const targetAid = this.targetAid(subject);
54
+ const principalId = authenticatedPrincipal(subject);
55
+ if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
56
+ throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
57
+ }
58
+ const rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
59
+ return this.issueDescriptor({ scope: 'agent', targetAid, principalId }, rootPath);
60
+ }
61
+ issueRelationRoot(subject, requestedPeerAid) {
62
+ const targetAid = this.targetAid(subject);
63
+ this.assertPrivateAun(subject);
64
+ const principalId = authenticatedPrincipal(subject);
65
+ const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
66
+ const explicitPeerAid = typeof requestedPeerAid === 'string' ? requestedPeerAid.trim() : '';
67
+ if (requestedPeerAid !== undefined && !explicitPeerAid) {
68
+ throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must be a non-empty AUN AID');
69
+ }
70
+ if (explicitPeerAid && !owner) {
71
+ throw new DataRootError('ROOT_FORBIDDEN', 'Only the Agent owner may select another relation');
72
+ }
73
+ const peerAid = explicitPeerAid || principalId;
74
+ if (!isValidAid(peerAid) || isExplicitGroupId(peerAid)) {
75
+ throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must identify an AUN private-chat peer');
76
+ }
77
+ const peerKey = formatPeerKey('aun', peerAid);
78
+ const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
79
+ const rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, peerKey), agentRoot);
80
+ return this.issueDescriptor({ scope: 'relation', targetAid, principalId, peerAid, peerKey }, rootPath);
81
+ }
82
+ resolveRoot(rootId, subject) {
83
+ const claims = this.verifyToken(rootId);
84
+ const targetAid = this.targetAid(subject);
85
+ const principalId = authenticatedPrincipal(subject);
86
+ if (claims.targetAid !== targetAid) {
87
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root does not belong to the receiving Agent');
88
+ }
89
+ if (claims.principalId !== principalId) {
90
+ throw new DataRootError('ROOT_FORBIDDEN', 'Root was issued to another authenticated principal');
91
+ }
92
+ let rootPath;
93
+ if (claims.scope === 'agent') {
94
+ if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
95
+ throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
96
+ }
97
+ rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
98
+ }
99
+ else {
100
+ this.assertPrivateAun(subject);
101
+ if (!claims.peerAid || !claims.peerKey || claims.peerKey !== formatPeerKey('aun', claims.peerAid)) {
102
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Relation root claims are invalid');
103
+ }
104
+ const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
105
+ if (!owner && principalId !== claims.peerAid) {
106
+ throw new DataRootError('ROOT_PEER_MISMATCH', 'Only the relation peer may use this root');
107
+ }
108
+ const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
109
+ rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, claims.peerKey), agentRoot);
110
+ }
111
+ const revision = this.rootRevision(rootPath);
112
+ if (revision !== claims.revision) {
113
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root identity changed; request a new root');
114
+ }
115
+ return {
116
+ scope: claims.scope,
117
+ targetAid,
118
+ ...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
119
+ ...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
120
+ rootPath,
121
+ revision,
122
+ expiresAt: claims.expiresAt,
123
+ };
124
+ }
125
+ resolvePath(rootId, subject, input, expectType) {
126
+ const root = this.resolveRoot(rootId, subject);
127
+ const raw = String(input ?? '').trim();
128
+ if (!raw)
129
+ throw new DataRootError('INVALID_ROOT_PATH', 'A root-relative path is required');
130
+ if (raw.includes('\0'))
131
+ throw new DataRootError('INVALID_ROOT_PATH', 'NUL is not allowed in a path');
132
+ if (path.isAbsolute(raw) || path.win32.isAbsolute(raw) || path.posix.isAbsolute(raw)) {
133
+ throw new DataRootError('INVALID_ROOT_PATH', 'Data root paths must be relative');
134
+ }
135
+ if (raw.split(/[\\/]+/).includes('..')) {
136
+ throw new DataRootError('INVALID_ROOT_PATH', 'Parent traversal is not allowed');
137
+ }
138
+ const lexicalPath = path.resolve(root.rootPath, raw);
139
+ if (!isInside(root.rootPath, lexicalPath)) {
140
+ throw new DataRootError('INVALID_ROOT_PATH', 'Path escapes the data root');
141
+ }
142
+ let realPath;
143
+ let stat;
144
+ try {
145
+ realPath = fs.realpathSync(lexicalPath);
146
+ stat = fs.statSync(realPath);
147
+ }
148
+ catch {
149
+ throw new DataRootError('NOT_FOUND', 'File or directory does not exist');
150
+ }
151
+ if (!isInside(root.rootPath, realPath)) {
152
+ throw new DataRootError('INVALID_ROOT_PATH', 'Symbolic link escapes the data root');
153
+ }
154
+ if (expectType === 'file' && !stat.isFile()) {
155
+ throw new DataRootError('NOT_SUPPORTED', 'Path is not a regular file');
156
+ }
157
+ if (expectType === 'directory' && !stat.isDirectory()) {
158
+ throw new DataRootError('NOT_A_DIRECTORY', 'Path is not a directory');
159
+ }
160
+ const relativePath = path.relative(root.rootPath, realPath).split(path.sep).join('/') || '.';
161
+ return { ...root, realPath, relativePath, stat };
162
+ }
163
+ issueDescriptor(base, rootPath) {
164
+ const issuedAt = this.now();
165
+ const claims = {
166
+ v: 1,
167
+ ...base,
168
+ revision: this.rootRevision(rootPath),
169
+ issuedAt,
170
+ expiresAt: issuedAt + this.ttlMs,
171
+ nonce: crypto.randomBytes(12).toString('base64url'),
172
+ };
173
+ return {
174
+ rootId: this.sign(claims),
175
+ scope: claims.scope,
176
+ targetAid: claims.targetAid,
177
+ ...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
178
+ ...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
179
+ virtualPath: claims.scope === 'agent'
180
+ ? '$AGENTS_DIR/<targetAid>'
181
+ : '$AGENTS_DIR/<targetAid>/relations/<peerKey>/shared',
182
+ revision: `sha256:${claims.revision}`,
183
+ expiresAt: claims.expiresAt,
184
+ readOnly: true,
185
+ capabilities: { list: true, query: true, fetch: true },
186
+ };
187
+ }
188
+ sign(claims) {
189
+ const encoded = base64Url(JSON.stringify(claims));
190
+ const signature = crypto.createHmac('sha256', this.secret).update(encoded).digest('base64url');
191
+ return `${TOKEN_PREFIX}.${encoded}.${signature}`;
192
+ }
193
+ verifyToken(value) {
194
+ if (typeof value !== 'string' || !value.trim()) {
195
+ throw new DataRootError('MISSING_ROOT_ID', 'A data rootId is required');
196
+ }
197
+ const parts = value.split('.');
198
+ if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) {
199
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
200
+ }
201
+ const expected = crypto.createHmac('sha256', this.secret).update(parts[1]).digest();
202
+ let actual;
203
+ try {
204
+ actual = Buffer.from(parts[2], 'base64url');
205
+ }
206
+ catch {
207
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
208
+ }
209
+ if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
210
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId signature');
211
+ }
212
+ let claims;
213
+ try {
214
+ claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
215
+ }
216
+ catch {
217
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId payload');
218
+ }
219
+ if (claims.v !== 1
220
+ || (claims.scope !== 'agent' && claims.scope !== 'relation')
221
+ || typeof claims.targetAid !== 'string'
222
+ || typeof claims.principalId !== 'string'
223
+ || typeof claims.revision !== 'string'
224
+ || !Number.isFinite(claims.expiresAt)) {
225
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId claims');
226
+ }
227
+ if (claims.expiresAt <= this.now()) {
228
+ throw new DataRootError('ROOT_EXPIRED', 'Data rootId expired; query the root again');
229
+ }
230
+ return claims;
231
+ }
232
+ canonicalRoot(candidate, requiredParent) {
233
+ let realPath;
234
+ let stat;
235
+ try {
236
+ realPath = fs.realpathSync(candidate);
237
+ stat = fs.statSync(realPath);
238
+ }
239
+ catch {
240
+ throw new DataRootError('ROOT_NOT_FOUND', 'Data root does not exist');
241
+ }
242
+ if (!stat.isDirectory())
243
+ throw new DataRootError('ROOT_NOT_FOUND', 'Data root is not a directory');
244
+ if (requiredParent && !isInside(requiredParent, realPath)) {
245
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Data root escapes the Agent directory');
246
+ }
247
+ return realPath;
248
+ }
249
+ rootRevision(rootPath) {
250
+ const stat = fs.statSync(rootPath);
251
+ return crypto.createHash('sha256')
252
+ .update(JSON.stringify({ rootPath, dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }))
253
+ .digest('hex');
254
+ }
255
+ targetAid(subject) {
256
+ const targetAid = String(subject.selfAid ?? '').trim();
257
+ if (!isValidAid(targetAid))
258
+ throw new DataRootError('ROOT_FORBIDDEN', 'Receiving Agent AID is unavailable');
259
+ return targetAid;
260
+ }
261
+ assertPrivateAun(subject) {
262
+ if (subject.channelType !== 'aun' || subject.chatType !== 'private') {
263
+ throw new DataRootError('NOT_SUPPORTED', 'Relation data roots currently support AUN private chats only');
264
+ }
265
+ }
266
+ }
267
+ export const dataRootResolver = new DataRootResolver();
268
+ export function dataRootFailure(error) {
269
+ if (error instanceof DataRootError) {
270
+ return { error: error.message, code: error.code, ...(error.data ? { data: error.data } : {}) };
271
+ }
272
+ return { error: 'Internal data root error', code: 'INTERNAL_ERROR' };
273
+ }