evolcore 0.0.26 → 0.0.27

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 (169) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/claude-runner.js +123 -22
  5. package/dist/agents/codex/app-server-client.js +63 -26
  6. package/dist/agents/codex-runner.js +316 -411
  7. package/dist/agents/ecagent-runner.js +170 -25
  8. package/dist/agents/gemini-runner.js +6 -0
  9. package/dist/agents/runner-types.js +50 -2
  10. package/dist/aun/aid/agentmd.js +73 -31
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +180 -1
  13. package/dist/aun/aid/identity.js +3 -3
  14. package/dist/aun/group/history.js +303 -0
  15. package/dist/aun/group/identity.js +43 -0
  16. package/dist/aun/group/index.js +5 -0
  17. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  18. package/dist/aun/{menu-client.js → menu/client.js} +12 -7
  19. package/dist/aun/menu/index.js +2 -0
  20. package/dist/aun/menu/transport.js +49 -0
  21. package/dist/aun/msg/index.js +2 -1
  22. package/dist/aun/msg/managed-operation.js +84 -18
  23. package/dist/aun/msg/mention-schema.js +51 -0
  24. package/dist/aun/msg/p2p.js +13 -8
  25. package/dist/aun/msg/upload.js +43 -88
  26. package/dist/aun/outbox.js +29 -7
  27. package/dist/aun/rpc/client.js +1 -0
  28. package/dist/aun/storage/client.js +39 -50
  29. package/dist/channels/aun.js +522 -233
  30. package/dist/channels/daemon.js +10 -6
  31. package/dist/cli/agent-command.js +108 -6
  32. package/dist/cli/agent-create.js +515 -2
  33. package/dist/cli/agent.js +109 -8
  34. package/dist/cli/aun-commands.js +191 -42
  35. package/dist/cli/config.js +8 -5
  36. package/dist/cli/contact.js +2 -2
  37. package/dist/cli/ctl-command.js +2 -2
  38. package/dist/cli/daemon-commands.js +35 -23
  39. package/dist/cli/diag.js +2 -2
  40. package/dist/cli/fs-command.js +3 -3
  41. package/dist/cli/handoff-command.js +6 -6
  42. package/dist/cli/index.js +8 -2
  43. package/dist/cli/init.js +34 -1
  44. package/dist/cli/managed-command-guard.js +2 -1
  45. package/dist/cli/menu-client.js +9 -5
  46. package/dist/cli/model.js +3 -3
  47. package/dist/cli/queue-command.js +2 -2
  48. package/dist/cli/response.js +2 -2
  49. package/dist/cli/restart-monitor.js +16 -2
  50. package/dist/cli/task-command.js +59 -0
  51. package/dist/cli/task-context.js +97 -49
  52. package/dist/cli/trigger-command.js +20 -10
  53. package/dist/config/builtin-roles.js +10 -0
  54. package/dist/config/config-field-policy.js +11 -0
  55. package/dist/config/config-manager.js +32 -4
  56. package/dist/config/config-operation-service.js +20 -5
  57. package/dist/config/contact-book-store.js +1 -1
  58. package/dist/config/contact-book.js +1 -1
  59. package/dist/config/contact-operation-service.js +1 -1
  60. package/dist/config/peer-role-resolver.js +1 -1
  61. package/dist/config/resolved-config-op.js +3 -0
  62. package/dist/config-store.js +1 -1
  63. package/dist/core/agent-application-service.js +5 -0
  64. package/dist/core/audit/event-key.js +17 -2
  65. package/dist/core/audit/log-integrity.js +34 -8
  66. package/dist/core/auth/agent-delegation.js +48 -14
  67. package/dist/core/auth/auth-gateway.js +2 -0
  68. package/dist/core/auth/authorization-audit.js +34 -38
  69. package/dist/core/auth/operation-authorizer.js +67 -5
  70. package/dist/core/auth/operation-catalog.js +110 -0
  71. package/dist/core/auth/remote-access-store.js +115 -9
  72. package/dist/core/bootstrap-messages.js +66 -7
  73. package/dist/core/bootstrap-service.js +110 -19
  74. package/dist/core/command/agent-control.js +88 -7
  75. package/dist/core/command/agent-storage-menu.js +443 -0
  76. package/dist/core/command/cli-intent-parser.js +19 -0
  77. package/dist/core/command/command-handler.js +76 -39
  78. package/dist/core/command/data-root-resolver.js +273 -0
  79. package/dist/core/command/group-menu.js +7 -2
  80. package/dist/core/command/menu-catalog.js +34 -4
  81. package/dist/core/command/menu-handler.js +348 -77
  82. package/dist/core/command/menu-protocol.js +40 -6
  83. package/dist/core/command/role-menu.js +2 -2
  84. package/dist/core/command/slash-handler.js +37 -22
  85. package/dist/core/event-catalog.js +4 -0
  86. package/dist/core/evolagent-registry.js +28 -2
  87. package/dist/core/group/invite-service.js +924 -0
  88. package/dist/core/interaction-router.js +22 -8
  89. package/dist/core/message/im-renderer.js +76 -15
  90. package/dist/core/message/message-bridge.js +94 -11
  91. package/dist/core/message/message-log.js +5 -3
  92. package/dist/core/message/message-queue.js +73 -41
  93. package/dist/core/message/response-engine.js +709 -117
  94. package/dist/core/permission/approval-gateway.js +36 -3
  95. package/dist/core/permission/ec-command-parser.js +61 -29
  96. package/dist/core/permission/protected-paths.js +134 -16
  97. package/dist/core/permission/sandbox-runtime.js +72 -3
  98. package/dist/core/permission/tool-error-code.js +15 -0
  99. package/dist/core/permission/tool-policy.js +7 -7
  100. package/dist/core/relation/peer-identity.js +57 -6
  101. package/dist/core/relation/shared-data.js +106 -0
  102. package/dist/core/role/runtime-policy.js +64 -0
  103. package/dist/core/runtime-lock.js +10 -26
  104. package/dist/core/session/runner-task-history.js +170 -0
  105. package/dist/core/session/session-manager.js +34 -6
  106. package/dist/core/session/session-mapper.js +41 -0
  107. package/dist/core/session/session-turn-coordinator.js +37 -7
  108. package/dist/core/session/session-turns.js +48 -7
  109. package/dist/core/task/task-recovery-service.js +176 -0
  110. package/dist/core/task/task-resume-operation.js +32 -0
  111. package/dist/eck/group-rules-sync.js +5 -3
  112. package/dist/eck/kit-renderer.js +1 -0
  113. package/dist/index.js +308 -54
  114. package/dist/ipc.js +141 -15
  115. package/dist/paths.js +17 -4
  116. package/dist/product.js +2 -0
  117. package/dist/trigger/event-source.js +19 -0
  118. package/dist/trigger/feedback.js +233 -40
  119. package/dist/trigger/parser.js +55 -5
  120. package/dist/trigger/patch.js +9 -0
  121. package/dist/trigger/scheduler.js +30 -4
  122. package/dist/trigger/validation.js +20 -3
  123. package/dist/utils/ecweb-utils.js +1 -0
  124. package/dist/utils/error-utils.js +40 -2
  125. package/dist/utils/logger.js +23 -13
  126. package/dist/utils/tool-summary.js +59 -3
  127. package/dist/utils/welcome.js +174 -1
  128. package/ecagent/dist/harness/agent-harness.js +3 -0
  129. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  130. package/kits/docs/INDEX.md +0 -6
  131. package/kits/docs/channels/aun.md +15 -2
  132. package/kits/docs/channels/feishu.md +1 -1
  133. package/kits/docs/context-assembly.md +29 -17
  134. package/kits/docs/evolcore/INDEX.md +8 -3
  135. package/kits/docs/evolcore/agent-create.md +39 -7
  136. package/kits/docs/evolcore/agent.md +33 -1
  137. package/kits/docs/evolcore/aid.md +9 -1
  138. package/kits/docs/evolcore/config.md +40 -1
  139. package/kits/docs/evolcore/group-collaboration.md +1 -1
  140. package/kits/docs/evolcore/group.md +117 -2
  141. package/kits/docs/evolcore/menu-client.md +16 -0
  142. package/kits/docs/evolcore/msg.md +3 -0
  143. package/kits/docs/evolcore/storage.md +3 -0
  144. package/kits/docs/evolcore/task.md +14 -0
  145. package/kits/docs/evolcore/trigger.md +51 -3
  146. package/kits/docs/venues/group.md +5 -0
  147. package/kits/eck_manifest.json +27 -0
  148. package/kits/rules/01-overview.md +11 -7
  149. package/kits/rules/04-relation.md +2 -1
  150. package/kits/rules/05-venue.md +1 -1
  151. package/kits/rules/06-channel.md +6 -4
  152. package/kits/schemas/agent-config.schema.12.json +25 -1
  153. package/kits/schemas/daemon.schema.6.json +2 -2
  154. package/kits/schemas/defaults.schema.5.json +1 -1
  155. package/kits/schemas/relation-config.schema.1.json +1 -1
  156. package/kits/schemas/relation-config.schema.5.json +1 -1
  157. package/kits/schemas/role-config.schema.1.json +14 -0
  158. package/kits/templates/roles/member.json +5 -0
  159. package/kits/templates/roles/visitor.json +5 -0
  160. package/kits/templates/system-fragments/baseagent.md +1 -1
  161. package/kits/templates/system-fragments/bootstrap.md +26 -9
  162. package/kits/templates/system-fragments/channel.md +8 -44
  163. package/kits/templates/system-fragments/commands.md +10 -2
  164. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  165. package/kits/templates/system-fragments/reply.md +56 -0
  166. package/package.json +7 -3
  167. package/dist/aun/group-identity.js +0 -9
  168. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  169. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
2
+ import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
2
3
  import { parseMenuClientArgs } from '../../cli/menu-client.js';
4
+ import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
3
5
  import { renderCommandCardAsText } from '../interaction-router.js';
4
6
  import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
5
7
  import { resolvePaths, getPackageRoot } from '../../paths.js';
@@ -8,6 +10,7 @@ import { logger } from '../../utils/logger.js';
8
10
  import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
9
11
  import { isManagementRole } from '../../config/builtin-roles.js';
10
12
  import { formatPeerKey } from '../relation/peer-identity.js';
13
+ import { sameGroupAid } from '../../aun/group/index.js';
11
14
  import crypto from 'crypto';
12
15
  import path from 'path';
13
16
  import fs from 'fs';
@@ -24,7 +27,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
24
27
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
25
28
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
26
29
  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';
30
+ import { executeManagedGroupOperation, isManagedGroupRelationRead, isManagedGroupRelationWrite, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
28
31
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
29
32
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
30
33
  import { validateModelSelectionForRole } from '../model/model-permission.js';
@@ -99,6 +102,14 @@ export class CommandHandler {
99
102
  sessionManager;
100
103
  messageCache;
101
104
  eventBus;
105
+ agentStorageClientResolver;
106
+ groupInviteDecisionResolver;
107
+ setAgentStorageClientResolver(resolver) {
108
+ this.agentStorageClientResolver = resolver;
109
+ }
110
+ setGroupInviteDecisionResolver(resolver) {
111
+ this.groupInviteDecisionResolver = resolver;
112
+ }
102
113
  adapters = new Map();
103
114
  policies = new Map();
104
115
  channelObjects = new Map(); // name → actual channel instance (for /check)
@@ -208,19 +219,11 @@ export class CommandHandler {
208
219
  throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
209
220
  }
210
221
  getAvailableBaseagentsForOwner(evolName) {
211
- const prefix = `${evolName}::`;
212
- return [...this.agentMap.keys()]
213
- .filter(key => key.startsWith(prefix))
214
- .map(key => key.slice(prefix.length));
222
+ return loadedBaseagentIds(this.agentMap.keys(), evolName);
215
223
  }
216
- /** Return the runner-owned temporary root for a managed session, if any. */
224
+ /** Return the stable temporary root for a managed session, if available. */
217
225
  getManagedSessionTempDir(session) {
218
- try {
219
- return this.getAgent(session.channel, session.baseagent).getManagedTempDir?.(session.id);
220
- }
221
- catch {
222
- return undefined;
223
- }
226
+ return ensureSessionRuntimeDir(session.id);
224
227
  }
225
228
  formatBaseagentUnavailable(error) {
226
229
  const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
@@ -236,21 +239,10 @@ export class CommandHandler {
236
239
  getAvailableBaseagents(channel) {
237
240
  const owner = this.agentRegistry?.resolveByChannel(channel);
238
241
  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];
242
+ return loadedBaseagentIds(this.agentMap.keys());
245
243
  }
246
244
  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;
245
+ return loadedBaseagentIds(this.agentMap.keys(), evolName);
254
246
  }
255
247
  /** Extract the baseagent component from `primaryRunnerKey` (e.g. `aid::claude` → `claude`). */
256
248
  parseDefaultBaseagent() {
@@ -441,6 +433,8 @@ export class CommandHandler {
441
433
  primaryFeedbackTarget(definition) {
442
434
  if (definition.feedback.strategy === 'target')
443
435
  return definition.feedback.target;
436
+ if (definition.feedback.strategy === 'event_session')
437
+ return undefined;
444
438
  if (!definition.origin)
445
439
  return undefined;
446
440
  return {
@@ -625,10 +619,16 @@ export class CommandHandler {
625
619
  if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
626
620
  return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
627
621
  }
622
+ if (!isAdmin && parsed.feedbackStrategy === 'event_session') {
623
+ return { error: '无权限:只有 owner/admin 可以使用事件来源会话' };
624
+ }
628
625
  const source = this.triggerSourceFromSchedule(parsed.scheduleType, parsed.scheduleValue);
629
626
  if (parsed.timezone && source.type === 'cron') {
630
627
  source.timezone = parsed.timezone;
631
628
  }
629
+ if (parsed.eventFilter !== undefined && source.type === 'event') {
630
+ source.filter = parsed.eventFilter;
631
+ }
632
632
  const script = parsed.scriptPath ? {
633
633
  path: parsed.scriptPath,
634
634
  runtime: parsed.scriptRuntime,
@@ -688,6 +688,7 @@ export class CommandHandler {
688
688
  baseagent: executionBaseagent,
689
689
  } : {}),
690
690
  model: parsed.model,
691
+ timeoutMs: parsed.executionTimeoutMs,
691
692
  effort: parsed.effort,
692
693
  ...(permissionMode !== undefined ? { permissionMode } : {}),
693
694
  onError: 'retry',
@@ -847,23 +848,36 @@ export class CommandHandler {
847
848
  * 通用降级应答入口:按 (sessionId, fallbackCommand) 查找 pending interaction 并路由。
848
849
  * 返回 { matched: true } 表示已处理,调用方直接返回 result。
849
850
  */
850
- async handleInteractionFallback(command, args, sessionId, userId) {
851
+ async handleInteractionFallback(command, args, sessionId, userId, channelKey) {
851
852
  if (!this.interactionRouter)
852
853
  return { matched: false };
853
- const pendingId = this.interactionRouter.findPendingByCommand(sessionId, command);
854
+ const candidates = this.interactionRouter.getPending(sessionId, command);
855
+ const explicitId = args.split(/\s+/, 1)[0];
856
+ if (explicitId.startsWith('aq-') && !candidates.includes(explicitId)) {
857
+ return { matched: true, result: '⚠️ 该问题不存在、已回答或已过期,请使用当前问题卡片。' };
858
+ }
859
+ const pendingId = candidates.includes(explicitId)
860
+ ? explicitId : this.interactionRouter.findPendingByCommand(sessionId, command);
854
861
  if (!pendingId)
855
862
  return { matched: false };
863
+ if (explicitId !== pendingId && candidates.length > 1) {
864
+ return { matched: true, result: `有多个待回答问题,请使用 /ask <问题ID> <回答>:\n${candidates.join('\n')}` };
865
+ }
866
+ const answer = explicitId === pendingId ? args.slice(explicitId.length).trim() : args.trim();
867
+ if (!answer)
868
+ return { matched: true, result: `请使用 /ask ${pendingId} <回答>` };
856
869
  const initiatorId = this.interactionRouter.getInitiator(pendingId);
857
870
  if (initiatorId && initiatorId !== userId) {
858
871
  return { matched: true, result: '⚠️ 仅卡片发起者可应答' };
859
872
  }
860
- const handled = this.interactionRouter.handle({
873
+ const handled = await this.interactionRouter.handleAsync({
861
874
  type: 'interaction.response',
862
875
  id: pendingId,
863
- action: args,
876
+ action: answer,
864
877
  operatorId: userId,
878
+ ...(channelKey ? { channelKey } : {}),
865
879
  });
866
- return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 无法验证应答者身份' };
880
+ return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 回答未被接受,请检查选项或问题是否仍有效。' };
867
881
  }
868
882
  setProcessor(processor) {
869
883
  this.processor = processor;
@@ -1003,6 +1017,7 @@ export class CommandHandler {
1003
1017
  const item = {
1004
1018
  value: s.threadId,
1005
1019
  label: displayName,
1020
+ lable: s.lable || null,
1006
1021
  };
1007
1022
  if (s.agentSessionId) {
1008
1023
  item.agentSessionId = s.agentSessionId;
@@ -1031,7 +1046,7 @@ export class CommandHandler {
1031
1046
  ...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
1032
1047
  });
1033
1048
  }
1034
- async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
1049
+ async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
1035
1050
  validateMenuValueBatchArgs(args);
1036
1051
  return await execMenuValueBatch({
1037
1052
  subject,
@@ -1042,12 +1057,12 @@ export class CommandHandler {
1042
1057
  items: args?.items,
1043
1058
  schemaVersion: args?.schemaVersion,
1044
1059
  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'),
1060
+ 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
1061
  });
1047
1062
  }
1048
1063
  /** 动态子菜单:根据 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);
1064
+ async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
1065
+ return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source, transportReplyContext);
1051
1066
  }
1052
1067
  // ── Menu Protocol exec ────────────────────────────────────────────────
1053
1068
  async loadMenuContext(channel, channelId) {
@@ -1059,8 +1074,8 @@ export class CommandHandler {
1059
1074
  return s ? null : { error: '当前无活跃会话', code: 'NO_ACTIVE_SESSION' };
1060
1075
  }
1061
1076
  /** 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);
1077
+ async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
1078
+ return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source, transportReplyContext);
1064
1079
  }
1065
1080
  /** menu.update — 写入新值。 */
1066
1081
  async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
@@ -1513,6 +1528,9 @@ export class CommandHandler {
1513
1528
  updated = deescalated;
1514
1529
  }
1515
1530
  const target = this.primaryFeedbackTarget(updated);
1531
+ if (!isAdmin && updated.feedback.strategy === 'event_session') {
1532
+ return { ok: false, error: '无权限:只有 owner/admin 可以使用事件来源会话' };
1533
+ }
1516
1534
  if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
1517
1535
  return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
1518
1536
  }
@@ -2181,7 +2199,8 @@ export class CommandHandler {
2181
2199
  const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
2182
2200
  if (subject.dataScope !== 'daemon'
2183
2201
  && !managementRole
2184
- && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
2202
+ && (grant.channelType !== 'aun'
2203
+ || !(isManagedGroupRelationRead(parsed.command) || isManagedGroupRelationWrite(parsed.command)))) {
2185
2204
  return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
2186
2205
  }
2187
2206
  if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
@@ -2213,6 +2232,21 @@ export class CommandHandler {
2213
2232
  });
2214
2233
  if (!decision.allow)
2215
2234
  return { ok: false, code: decision.code, error: decision.reason };
2235
+ if (parsed.command.kind === 'accept-invite' || parsed.command.kind === 'reject-invite') {
2236
+ if (subject.role !== 'owner' || grant.channelType !== 'aun' || grant.chatType !== 'private'
2237
+ || parsed.command.from !== grantSelfAid || !this.groupInviteDecisionResolver) {
2238
+ return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'Targeted invitation decisions require the current Agent Owner' };
2239
+ }
2240
+ try {
2241
+ const result = await this.groupInviteDecisionResolver(grantSelfAid, grant.actorId, parsed.command.inviteId, parsed.command.kind === 'accept-invite' ? 'accept' : 'reject');
2242
+ return result.accepted
2243
+ ? { ok: true, result: { ok: true, status: result.status, group_id: result.groupId, message: result.text } }
2244
+ : { ok: false, code: 'GROUP_INVITE_DECISION_REJECTED', error: result.text };
2245
+ }
2246
+ catch (error) {
2247
+ return { ok: false, code: 'GROUP_INVITE_DECISION_FAILED', error: error instanceof Error ? error.message : String(error) };
2248
+ }
2249
+ }
2216
2250
  let rulesContent;
2217
2251
  if (parsed.command.kind === 'rules.set') {
2218
2252
  const filePath = String(parsed.command.filePath || '');
@@ -2267,7 +2301,10 @@ export class CommandHandler {
2267
2301
  }
2268
2302
  }
2269
2303
  try {
2270
- return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent) };
2304
+ return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent, {
2305
+ defaultThread: grant.chatType === 'group' && conversationId === parsed.command.groupId
2306
+ ? session.threadId || 'main' : 'all',
2307
+ }) };
2271
2308
  }
2272
2309
  catch (error) {
2273
2310
  return {
@@ -2464,7 +2501,7 @@ export class CommandHandler {
2464
2501
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
2465
2502
  }
2466
2503
  if (!hasTrustedFullAccessDelegation(grant)
2467
- && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup)) {
2504
+ && ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || !sameGroupAid(parsed.command.groupId, currentGroup))) {
2468
2505
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
2469
2506
  }
2470
2507
  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
+ }
@@ -2,7 +2,7 @@ import crypto from 'crypto';
2
2
  import { ConfigTarget, mutateConfig, read as readConfig, resolveEffectiveFieldWithSource, } from '../../config/config-manager.js';
3
3
  import { normalizeMentionMode } from '../../config/mention-mode.js';
4
4
  import { readRolesConfig } from '../../config/roles.js';
5
- import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/msg/group.js';
5
+ import { groupInfo, groupList, groupDirectoryErrorCode, normalizeGroupAid } from '../../aun/group/index.js';
6
6
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
7
7
  const DEFAULT_DIRECTORY = {
8
8
  getGroup: groupInfo,
@@ -185,7 +185,12 @@ function groupIdFromArgs(args, currentGroupId) {
185
185
  if (!groupId) {
186
186
  throw new GroupMenuError('INVALID_ARGUMENT', 'relation settings require args.groupId or args.peerKey');
187
187
  }
188
- return groupId;
188
+ try {
189
+ return normalizeGroupAid(groupId);
190
+ }
191
+ catch (error) {
192
+ throw new GroupMenuError('INVALID_ARGUMENT', error.message);
193
+ }
189
194
  }
190
195
  function settingOptions(key, target) {
191
196
  const state = readSettingState(key, target);
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { groupInfo, groupMembers } from '../../aun/msg/group.js';
2
+ import { groupInfo, groupMembers } from '../../aun/group/index.js';
3
3
  import { formatPeerKey } from '../relation/peer-identity.js';
4
4
  import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
5
5
  import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
@@ -8,6 +8,7 @@ import { readRolesConfig } from '../../config/roles.js';
8
8
  import { normalizeMenuError } from './menu-protocol.js';
9
9
  import { isValidAid } from '../../aun/aid/validation.js';
10
10
  const DEFAULT_DIRECTORY = { getGroup: groupInfo, getMembers: groupMembers };
11
+ const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
11
12
  export function validateMenuValueBatchArgs(args) {
12
13
  if (!args || typeof args !== 'object' || Array.isArray(args))
13
14
  throw { code: 'INVALID_ARGUMENT', message: 'batch args must be an object' };
@@ -80,11 +81,11 @@ const ENTRIES = [
80
81
  { id: 'activity.agent', type: 'setting', contexts: ['agent'], name: 'activity', label: 'Agent 中间输出', section: 'agent', order: 50, query: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, options: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, update: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.update', scope: 'agent' }, control: 'select' },
81
82
  { id: 'observable.agent', type: 'setting', contexts: ['agent'], name: 'observable', label: '观察模式', section: 'agent', order: 60, query: { cmd: '/observable', operation: 'observable.current', scope: 'agent' }, update: { cmd: '/observable', operation: 'observable.update', scope: 'agent' }, valueSchema: { type: 'boolean' }, control: 'toggle' },
82
83
  { id: 'aid.avatar', type: 'action', contexts: ['agent'], name: 'aid', label: '更换头像', section: 'agent', order: 70, action: { cmd: '/aid', action: 'avatar', operation: 'ec.aid.avatar', scope: 'agent' }, valueSchema: { type: 'object', properties: { data: { type: 'string' }, url: { type: 'string', format: 'uri' } }, oneOf: [{ required: ['data'] }, { required: ['url'] }] }, control: 'action' },
84
+ { id: 'baseagents.inventory', type: 'list', contexts: ['process'], name: 'baseagents', label: 'Base Agent 清单', section: 'process', order: 9, query: { cmd: '/baseagents', operation: 'system.status', scope: 'process' }, control: 'list' },
83
85
  { id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
84
86
  { id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
85
87
  { id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
86
88
  { id: 'config.rawFile', type: 'list', contexts: ['process'], name: 'config', label: '原始配置文件', section: 'process', order: 13, query: { cmd: '/config', args: { view: 'rawFile' }, operation: 'config.read', scope: 'process' }, options: { cmd: '/config', args: { option: 'rawFile' }, operation: 'config.read', scope: 'process', mode: 'dynamic' }, control: 'list' },
87
- { id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
88
89
  { id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
89
90
  { id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
90
91
  { id: 'system.paircode', type: 'action', contexts: ['process'], name: 'system', label: '获取配对码', section: 'process', order: 35, action: { cmd: '/system', action: 'paircode', operation: 'system.paircode', scope: 'process' } },
@@ -304,6 +305,34 @@ export async function resolveExplicitMenuExecutionContext(input) {
304
305
  };
305
306
  }
306
307
  function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
308
+ function configuredHostBaseagentEntries() {
309
+ const defaults = readConfig(ConfigTarget.Defaults);
310
+ const configured = defaults?.baseagents;
311
+ if (!configured || typeof configured !== 'object' || Array.isArray(configured))
312
+ return [];
313
+ return HOST_BASEAGENTS
314
+ .filter(baseagent => {
315
+ const block = configured[baseagent];
316
+ return Object.hasOwn(configured, baseagent) && block !== null
317
+ && typeof block === 'object' && !Array.isArray(block);
318
+ })
319
+ .map((baseagent, index) => ({
320
+ // Value batches only carry entry IDs, so each configured backend gets a
321
+ // binding with an explicit backend instead of an implicit Claude default.
322
+ id: `baseagent.models.${baseagent}`,
323
+ type: 'list',
324
+ contexts: ['process'],
325
+ name: 'baseagent-models',
326
+ label: `${baseagent} 模型目录`,
327
+ section: 'process',
328
+ order: 41 + index,
329
+ query: { cmd: '/baseagent-models', args: { baseagent }, operation: 'baseagent.models.list', scope: 'process' },
330
+ control: 'list',
331
+ }));
332
+ }
333
+ function menuEntryDefinitions(context) {
334
+ return context.kind === 'process' ? [...ENTRIES, ...configuredHostBaseagentEntries()] : ENTRIES;
335
+ }
307
336
  function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
308
337
  async function operationView(binding, context, options = {}) {
309
338
  if (!binding)
@@ -340,7 +369,8 @@ export async function getMenuCatalog(input) {
340
369
  const context = await resolveMenuCatalogContext(input);
341
370
  const entries = [];
342
371
  const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
343
- for (const entry of ENTRIES.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
372
+ const definitions = menuEntryDefinitions(context);
373
+ for (const entry of definitions.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
344
374
  const operations = {};
345
375
  for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
346
376
  const view = await operationView(binding, context, { paircodeAllowed });
@@ -378,7 +408,7 @@ export async function execMenuValueBatch(input) {
378
408
  normalizedItems.push({ itemId, entryId });
379
409
  }
380
410
  const results = new Array(normalizedItems.length);
381
- const byId = new Map(ENTRIES.map(entry => [entry.id, entry]));
411
+ const byId = new Map(menuEntryDefinitions(context).map(entry => [entry.id, entry]));
382
412
  let cursor = 0;
383
413
  const resolveItem = async (item) => {
384
414
  const { itemId, entryId } = item;