evolcore 0.0.2 → 0.0.4

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 (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -25,6 +25,7 @@ import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveC
25
25
  import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
26
26
  import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
27
27
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
28
+ import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
28
29
  import { splitConfigBatchGetArgv } from '../../config/config-batch-get.js';
29
30
  import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
30
31
  import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
@@ -32,9 +33,12 @@ import { resolveRuntimePermissionMode, resolveRuntimeStringField, validateRuntim
32
33
  import { isManagementRole } from '../../config/builtin-roles.js';
33
34
  import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
34
35
  import { logger } from '../../utils/logger.js';
36
+ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
35
37
  import { dispatchToMentionMode } from '../../config/mention-mode.js';
36
38
  import { menuFailure, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest } from './menu-protocol.js';
37
39
  import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
40
+ import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
41
+ import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
38
42
  /**
39
43
  * 获取 baseagent CLI 的版本号(claude/gemini/codex)。
40
44
  * 失败返回 null(命令不存在或执行失败)。
@@ -65,6 +69,43 @@ const FILE_HASH_MAX_SIZE = 2 * 1024 * 1024;
65
69
  const FILE_LIST_DEFAULT_LIMIT = 500;
66
70
  const FILE_LIST_MAX_LIMIT = 1000;
67
71
  const topicForkTargetsInFlight = new Set();
72
+ const GROUP_RULES_RESUME_UNSUPPORTED_NOTE = '群规则加载策略已保存;当前会话使用的 baseagent 不支持在恢复会话中更新群规则上下文。请新建或清空该群会话后生效。';
73
+ function appendGroupRulesCompatibilityNote(execution, command, session) {
74
+ if (!('data' in execution)
75
+ || execution.data?.exitCode !== 0
76
+ || command?.kind !== 'field'
77
+ || (command.subcommand !== 'set' && command.subcommand !== 'unset')
78
+ || command.field !== 'groupRules.mode'
79
+ || !session?.baseagent
80
+ || session.baseagent === 'claude'
81
+ || session.baseagent === 'codex') {
82
+ return execution;
83
+ }
84
+ const note = `${GROUP_RULES_RESUME_UNSUPPORTED_NOTE}(当前:${session.baseagent})`;
85
+ const stdout = String(execution.data.stdout ?? '');
86
+ if (command.format === 'json') {
87
+ try {
88
+ const payload = JSON.parse(stdout);
89
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
90
+ return {
91
+ data: {
92
+ ...execution.data,
93
+ stdout: `${JSON.stringify({ ...payload, note }, null, 2)}\n`,
94
+ },
95
+ };
96
+ }
97
+ }
98
+ catch {
99
+ // Fall through to a text suffix if a future CLI returns non-JSON output.
100
+ }
101
+ }
102
+ return {
103
+ data: {
104
+ ...execution.data,
105
+ stdout: stdout.trimEnd() ? `${stdout.trimEnd()}\n${note}\n` : `${note}\n`,
106
+ },
107
+ };
108
+ }
68
109
  function validateCapabilityScope(args) {
69
110
  const scope = args?.scope ?? 'project';
70
111
  return scope === 'project' ? null : { error: 'MVP 只支持 scope=project', code: 'INVALID_SCOPE' };
@@ -178,9 +219,9 @@ async function resolveTopicForkSource(sessionManager, channel, channelId, source
178
219
  * 沿用 /file 的安全校验链:拒绝 `..` 穿越、realpathSync 后验证落点。
179
220
  * 成功返回 `{ realPath, projectPath, stat }`;失败返回 `{ error, code }`。
180
221
  */
181
- function resolveMenuFilePath(input, session, role, expectType) {
182
- if (!session?.projectPath)
183
- return { error: '当前无活跃会话', code: 'NO_ACTIVE_SESSION' };
222
+ function resolveMenuFilePath(input, projectPath, role, expectType) {
223
+ if (!projectPath)
224
+ return { error: '无法解析当前 agent projectPath', code: 'NO_PROJECT' };
184
225
  const raw = (input ?? '').toString().trim();
185
226
  if (!raw)
186
227
  return { error: '缺少 path 参数', code: 'MISSING_VALUE' };
@@ -189,7 +230,7 @@ function resolveMenuFilePath(input, session, role, expectType) {
189
230
  return { error: '不支持 .. 路径穿越', code: 'NO_PERMISSION' };
190
231
  }
191
232
  // 相对路径基于 projectPath 解析;绝对路径原样
192
- const resolved = path.isAbsolute(raw) ? raw : path.resolve(session.projectPath, raw);
233
+ const resolved = path.isAbsolute(raw) ? raw : path.resolve(projectPath, raw);
193
234
  if (!fs.existsSync(resolved)) {
194
235
  return { error: '文件不存在', code: 'NOT_FOUND' };
195
236
  }
@@ -197,7 +238,7 @@ function resolveMenuFilePath(input, session, role, expectType) {
197
238
  let realProjectPath;
198
239
  try {
199
240
  realPath = fs.realpathSync(resolved);
200
- realProjectPath = fs.realpathSync(session.projectPath);
241
+ realProjectPath = fs.realpathSync(projectPath);
201
242
  }
202
243
  catch {
203
244
  return { error: '文件不存在', code: 'NOT_FOUND' };
@@ -823,6 +864,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
823
864
  });
824
865
  const modelScope = args?.scope === 'agent' ? 'agent' : 'relation';
825
866
  if (verb === 'query') {
867
+ if (cmdBase === '/file')
868
+ return intent('file.fetch', 'filesystem', { filePath: args?.path });
826
869
  if (cmdBase === '/model')
827
870
  return intent('model.current', modelScope);
828
871
  if (cmdBase === '/effort')
@@ -862,9 +905,9 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
862
905
  if (verb === 'action') {
863
906
  if (cmdBase === '/file') {
864
907
  if (action === 'list')
865
- return intent('file.list', 'filesystem');
908
+ return intent('file.list', 'filesystem', { filePath: args?.path });
866
909
  if (action === 'fetch')
867
- return intent('file.fetch', 'filesystem');
910
+ return intent('file.fetch', 'filesystem', { filePath: args?.path });
868
911
  }
869
912
  if (cmdBase === '/gateway')
870
913
  return intent('gateway.write', 'process', { action });
@@ -885,7 +928,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
885
928
  return intent('agent.delete', 'control', { action });
886
929
  }
887
930
  if (cmdBase === '/trigger') {
888
- if (action === 'set')
931
+ if (action === 'create' || action === 'set')
889
932
  return intent('trigger.create', 'relation', { action });
890
933
  if (action === 'show')
891
934
  return intent('trigger.show', 'relation', { action });
@@ -962,13 +1005,14 @@ async function authorizeMenuIntent(params) {
962
1005
  intent.source = source;
963
1006
  const decision = await authorizeOperation({ source, intent, subject });
964
1007
  if (!decision.allow) {
965
- if (intent.operation.startsWith('role.')) {
1008
+ if (intent.operation.startsWith('role.') || intent.operation.startsWith('connect.')) {
1009
+ const namespace = intent.operation.split('.', 1)[0];
966
1010
  return {
967
1011
  error: decision.reason,
968
1012
  code: 'PERMISSION_DENIED',
969
1013
  data: {
970
1014
  $schema_version: 1,
971
- kind: 'role_permission_denied',
1015
+ kind: `${namespace}_permission_denied`,
972
1016
  self: subject.selfAid ?? null,
973
1017
  actorRole: subject.role,
974
1018
  operation: intent.operation,
@@ -1024,7 +1068,7 @@ async function authorizeRoleMenu(owner, params) {
1024
1068
  fromControlChannel: params.fromControlChannel,
1025
1069
  subject: params.subject,
1026
1070
  });
1027
- const operation = roleMenuOperation(params.kind, params.args, params.value);
1071
+ const operation = params.operation ?? roleMenuOperation(params.kind, params.args, params.value);
1028
1072
  const denied = await authorizeMenuIntent.call(owner, {
1029
1073
  intent: { operation, scope: 'agent', source: params.source, args: { ...(params.args ?? {}) } },
1030
1074
  identity: params.identity,
@@ -1040,6 +1084,57 @@ async function authorizeRoleMenu(owner, params) {
1040
1084
  return denied;
1041
1085
  return { context: buildRoleMenuContext(owner, params.channel, subject) };
1042
1086
  }
1087
+ async function executeGroupMenu(owner, params) {
1088
+ let menuIntent;
1089
+ try {
1090
+ menuIntent = groupMenuIntent(params.kind, params.args);
1091
+ }
1092
+ catch (error) {
1093
+ return menuResultFailure(error);
1094
+ }
1095
+ const authorizationArgs = groupMenuAuthorizationArgs(params.args, params.subject.selfAid, menuIntent.scope === 'relation' ? params.subject.peerKey : undefined);
1096
+ const authorizationSubject = params.subject.isDaemonOwner && params.subject.role !== 'owner'
1097
+ ? { ...params.subject, role: 'owner' }
1098
+ : params.subject;
1099
+ const denied = await authorizeMenuIntent.call(owner, {
1100
+ intent: {
1101
+ operation: menuIntent.operation,
1102
+ scope: menuIntent.scope,
1103
+ source: params.source,
1104
+ args: authorizationArgs,
1105
+ },
1106
+ identity: params.identity,
1107
+ subject: authorizationSubject,
1108
+ session: params.session,
1109
+ explicitChatType: params.explicitChatType,
1110
+ channel: params.channel,
1111
+ channelId: params.channelId,
1112
+ userId: params.userId,
1113
+ fromControlChannel: params.fromControlChannel,
1114
+ source: params.source,
1115
+ });
1116
+ if (denied)
1117
+ return { ...denied, code: 'PERMISSION_DENIED' };
1118
+ try {
1119
+ const currentGroupId = params.session?.chatType === 'group'
1120
+ ? String(params.session.metadata?.groupId || params.channelId)
1121
+ : undefined;
1122
+ const roleContext = buildRoleMenuContext(owner, params.channel, params.subject);
1123
+ return {
1124
+ data: await handleGroupMenu({ kind: params.kind, args: params.args, value: params.value }, {
1125
+ self: params.subject.selfAid ?? roleContext.self,
1126
+ role: params.subject.role,
1127
+ isDaemonOwner: params.subject.isDaemonOwner,
1128
+ currentGroupId,
1129
+ baseagent: params.session?.baseagent,
1130
+ roleContext,
1131
+ }),
1132
+ };
1133
+ }
1134
+ catch (error) {
1135
+ return menuResultFailure(error);
1136
+ }
1137
+ }
1043
1138
  function ecwebErr(id, name, code, message) {
1044
1139
  return menuFailure({ id, ...(name ? { name } : {}) }, normalizeMenuError({ code, message }));
1045
1140
  }
@@ -1105,14 +1200,59 @@ export function validateScheduleParams(scheduleType, scheduleValue) {
1105
1200
  }
1106
1201
  /**
1107
1202
  * 返回结构化命令菜单(供 menu.query 使用)
1108
- * owner 看到全部命令,admin 看到管理级命令(不含 owner-only),visitor/member 仅看到用户级命令
1203
+ * owner-only 及尚未迁移的命令保留角色语义;已迁移的命令按 operation capability 展示。
1109
1204
  */
1110
- export function getMenuItems(role, chatType = 'private', scope = 'agent') {
1205
+ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
1111
1206
  const isOwner = role === 'owner';
1112
1207
  const isAdmin = isManagementRole(role);
1113
1208
  const isControlScope = scope === 'control';
1114
1209
  const canReadTopic = role !== 'none';
1115
1210
  const items = [];
1211
+ const normalizedChatType = chatType === 'group' ? 'group' : 'private';
1212
+ const canOperation = (operation, operationScope) => {
1213
+ if (authSubject) {
1214
+ const relationArgs = operationScope === 'relation'
1215
+ ? buildRelationIntentArgs({ selfAid: authSubject.selfAid, peerKey: authSubject.peerKey })
1216
+ : {};
1217
+ return authorizeOperation({
1218
+ source: 'menu',
1219
+ intent: { operation, scope: operationScope, source: 'menu', args: relationArgs },
1220
+ subject: authSubject,
1221
+ audit: false,
1222
+ }).allow;
1223
+ }
1224
+ return evaluateRoleOperationCapability({
1225
+ role,
1226
+ operation,
1227
+ scope: operationScope,
1228
+ chatType: normalizedChatType,
1229
+ fromControlChannel: isControlScope,
1230
+ }).allow;
1231
+ };
1232
+ const canUseModel = canOperation('model.use', 'relation');
1233
+ const canSetEffort = canOperation('model.effort', 'relation');
1234
+ const canSetChatmode = canOperation('chatmode.update', 'relation');
1235
+ const canSetMentionmode = normalizedChatType === 'group' && canOperation('mentionmode.update', 'relation');
1236
+ const canReadPermission = canOperation('permission.current', 'relation');
1237
+ const canUseFile = canOperation('file.fetch', 'filesystem') || canOperation('file.list', 'filesystem');
1238
+ const canManageProcess = authSubject ? authSubject.isDaemonOwner : isOwner;
1239
+ const configurableSettings = [
1240
+ ...(canUseModel ? [{ cmd: '/model', label: '切换模型', desc: '切换当前 Agent 使用的模型版本', next: { type: 'select', dynamic: true } }] : []),
1241
+ ...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', items: [
1242
+ { value: 'low', label: 'Low' },
1243
+ { value: 'medium', label: 'Medium' },
1244
+ { value: 'high', label: 'High' },
1245
+ { value: 'max', label: 'Max' },
1246
+ ] } }] : []),
1247
+ ...(canSetChatmode ? [{ cmd: '/chatmode', label: '切换会话模式', desc: '控制 Agent 主动性(被动响应或主动推进)', next: { type: 'select', items: [
1248
+ { value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
1249
+ { value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
1250
+ ] } }] : []),
1251
+ ...(canSetMentionmode ? [{ cmd: '/mentionmode', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1252
+ { value: 'mention-only', label: '@ 提及', desc: '仅在被 @ 提及时响应' },
1253
+ { value: 'disabled', label: '全部响应', desc: '响应群内所有消息' },
1254
+ ] } }] : []),
1255
+ ];
1116
1256
  if (!isAdmin && chatType === 'group') {
1117
1257
  return [
1118
1258
  ...(canReadTopic ? [{
@@ -1121,11 +1261,20 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent') {
1121
1261
  { cmd: '/topic', label: '话题管理', desc: '查看当前聊天的话题会话', next: { type: 'select', dynamic: true } },
1122
1262
  ]
1123
1263
  }] : []),
1264
+ ...(configurableSettings.length > 0 ? [{
1265
+ group: 'Agent 与模型',
1266
+ commands: configurableSettings,
1267
+ }] : []),
1268
+ ...(canReadPermission ? [{
1269
+ group: '权限管理',
1270
+ commands: [{ cmd: '/perm', label: '查看权限模式', desc: '权限模式由当前角色定义决定' }],
1271
+ }] : []),
1124
1272
  {
1125
1273
  group: '其他',
1126
1274
  commands: [
1127
1275
  { cmd: '/status', label: '显示会话状态' },
1128
1276
  { cmd: '/check', label: '检查 EvolAgent 实例健康' },
1277
+ ...(canUseFile ? [{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' }] : []),
1129
1278
  { cmd: '/help', label: '显示帮助信息' },
1130
1279
  ]
1131
1280
  }
@@ -1146,73 +1295,52 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent') {
1146
1295
  ] : []),
1147
1296
  ]
1148
1297
  });
1149
- if (isAdmin) {
1298
+ if (isAdmin || configurableSettings.length > 0) {
1150
1299
  items.push({
1151
1300
  group: 'Agent 与模型',
1152
1301
  commands: [
1153
- { cmd: '/baseagent', label: '切换 Agent 后端', desc: '切换 Agent 当前 Baseagent', next: { type: 'select', dynamic: true } },
1154
- { cmd: '/model', label: '切换模型', desc: '切换当前 Agent 使用的模型版本', next: { type: 'select', dynamic: true } },
1155
- { cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', items: [
1156
- { value: 'low', label: 'Low' },
1157
- { value: 'medium', label: 'Medium' },
1158
- { value: 'high', label: 'High' },
1159
- { value: 'max', label: 'Max' },
1160
- ] } },
1161
- { cmd: '/chatmode', label: '切换会话模式', desc: '控制 Agent 主动性(被动响应或主动推进)', next: { type: 'select', items: [
1162
- { value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
1163
- { value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
1164
- ] } },
1165
- { cmd: '/mentionmode', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1166
- { value: 'mention-only', label: '@ 提及', desc: '仅在被 @ 提及时响应' },
1167
- { value: 'disabled', label: '全部响应', desc: '响应群内所有消息' },
1168
- ] } },
1169
- { cmd: '/capability', label: '能力开关管理', desc: '查看并管理 Skills / MCP / Plugins' },
1302
+ ...(isAdmin ? [{ cmd: '/baseagent', label: '切换 Agent 后端', desc: '切换 Agent 当前 Baseagent', next: { type: 'select', dynamic: true } }] : []),
1303
+ ...configurableSettings,
1304
+ ...(isAdmin ? [{ cmd: '/capability', label: '能力开关管理', desc: '查看并管理 Skills / MCP / Plugins' }] : []),
1170
1305
  ]
1171
1306
  });
1307
+ }
1308
+ if (canReadPermission) {
1172
1309
  items.push({
1173
1310
  group: '权限管理',
1174
1311
  commands: [
1175
1312
  { cmd: '/perm', label: '查看权限模式', desc: '权限模式由当前角色定义决定' },
1176
1313
  ]
1177
1314
  });
1178
- items.push({
1179
- group: '运维',
1180
- commands: [
1181
- { cmd: '/status', label: '显示会话状态', desc: '查看当前会话、项目、Agent 的详细状态' },
1182
- { cmd: '/stop', label: '中断当前任务', desc: '立即中断正在执行的 Agent 任务' },
1183
- { cmd: '/check', label: '检查 EvolAgent 实例健康', desc: '检查各 EvolAgent 实例、后端与消息通道的健康状态' },
1184
- ...(isOwner && !isControlScope ? [{
1185
- cmd: '/observable',
1186
- label: '观察者模式',
1187
- desc: '向 owners 转发 Agent 入站和出站消息',
1188
- next: { type: 'select', items: [
1189
- { value: 'true', label: '开启' },
1190
- { value: 'false', label: '关闭' },
1191
- ] },
1192
- }] : []),
1193
- { cmd: '/activity', label: '控制中间输出显示', desc: '设置工具调用过程的可见范围', next: { type: 'select', items: [
1315
+ }
1316
+ items.push({
1317
+ group: isAdmin ? '运维' : '其他',
1318
+ commands: [
1319
+ { cmd: '/status', label: '显示会话状态', desc: isAdmin ? '查看当前会话、项目、Agent 的详细状态' : '查看当前会话的基本状态' },
1320
+ ...(isAdmin ? [{ cmd: '/stop', label: '中断当前任务', desc: '立即中断正在执行的 Agent 任务' }] : []),
1321
+ { cmd: '/check', label: '检查 EvolAgent 实例健康', desc: isAdmin ? '检查各 EvolAgent 实例、后端与消息通道的健康状态' : '检查 EvolAgent 实例与消息通道健康状态' },
1322
+ ...(isOwner && !isControlScope ? [{
1323
+ cmd: '/observable',
1324
+ label: '观察者模式',
1325
+ desc: '向 owners 转发 Agent 入站和出站消息',
1326
+ next: { type: 'select', items: [
1327
+ { value: 'true', label: '开启' },
1328
+ { value: 'false', label: '关闭' },
1329
+ ] },
1330
+ }] : []),
1331
+ ...(isAdmin ? [{ cmd: '/activity', label: '控制中间输出显示', desc: '设置工具调用过程的可见范围', next: { type: 'select', items: [
1194
1332
  { value: 'all', label: '全部显示', desc: '所有用户均可见中间输出' },
1195
1333
  { value: 'text', label: '仅文字进展', desc: '显示文字进展,隐藏工具活动' },
1196
1334
  { value: 'none', label: '不显示', desc: '关闭所有中间输出' },
1197
- ] } },
1198
- ...(isControlScope && isOwner ? [
1199
- { cmd: '/restart', label: '重启服务', desc: '重启整个 EvolCore 服务进程' },
1200
- ] : []),
1201
- ...(isAdmin ? [
1202
- { cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
1203
- ] : []),
1204
- ]
1205
- });
1206
- }
1207
- else {
1208
- items.push({
1209
- group: '其他',
1210
- commands: [
1211
- { cmd: '/status', label: '显示会话状态', desc: '查看当前会话的基本状态' },
1212
- { cmd: '/check', label: '检查 EvolAgent 实例健康', desc: '检查 EvolAgent 实例与消息通道健康状态' },
1213
- ]
1214
- });
1215
- }
1335
+ ] } }] : []),
1336
+ ...(isControlScope && canManageProcess && canOperation('system.restart', 'process') ? [
1337
+ { cmd: '/restart', label: '重启服务', desc: '重启整个 EvolCore 服务进程' },
1338
+ ] : []),
1339
+ ...(canUseFile ? [
1340
+ { cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
1341
+ ] : []),
1342
+ ]
1343
+ });
1216
1344
  items.push({
1217
1345
  group: '帮助',
1218
1346
  commands: [
@@ -1248,6 +1376,47 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1248
1376
  throw authorized;
1249
1377
  return await roleMenuOptions(authorized.context, args);
1250
1378
  }
1379
+ if (cmdBase0 === '/connect') {
1380
+ const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1381
+ const authorized = await authorizeRoleMenu(this, {
1382
+ kind: 'options', args, operation: connectMenuOperation('options', args), identity, subject: authSubject, session, channel, channelId,
1383
+ userId, fromControlChannel, source,
1384
+ });
1385
+ if ('error' in authorized)
1386
+ throw authorized;
1387
+ try {
1388
+ const result = await handleConnectMenu({ type: 'menu.options', subtype: 'options', args }, authorized.context);
1389
+ if ('error' in result)
1390
+ throw result.error;
1391
+ const opts = result.data?.options ?? [];
1392
+ return opts.map((opt) => ({
1393
+ value: opt.option,
1394
+ label: opt.label,
1395
+ desc: opt.description,
1396
+ ...(typeof opt.selected === 'boolean' ? { selected: opt.selected } : {}),
1397
+ }));
1398
+ }
1399
+ catch (error) {
1400
+ throw menuResultFailure(error);
1401
+ }
1402
+ }
1403
+ if (cmdBase0 === '/group') {
1404
+ const result = await executeGroupMenu(this, {
1405
+ kind: 'options', args,
1406
+ identity: subject.identity,
1407
+ subject,
1408
+ session,
1409
+ explicitChatType,
1410
+ channel,
1411
+ channelId,
1412
+ userId,
1413
+ fromControlChannel,
1414
+ source,
1415
+ });
1416
+ if ('error' in result)
1417
+ throw { code: result.code, message: result.error, data: result.data };
1418
+ return result.data;
1419
+ }
1251
1420
  // ── /agent list(只读) ──
1252
1421
  // 控制 channel:验 evolcore.owners,返回全量;agent channel:放行但仅返回自身单条。
1253
1422
  if (cmd === '/agent') {
@@ -1590,6 +1759,34 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1590
1759
  return menuResultFailure(error);
1591
1760
  }
1592
1761
  }
1762
+ if (cmdBase === '/connect') {
1763
+ const authorized = await authorizeRoleMenu(this, {
1764
+ kind: 'query', args, operation: connectMenuOperation('query', args), identity, subject, session, channel, channelId,
1765
+ userId, fromControlChannel, source,
1766
+ });
1767
+ if ('error' in authorized)
1768
+ return authorized;
1769
+ try {
1770
+ const result = await handleConnectMenu({ type: 'menu.query', subtype: 'query', args }, authorized.context);
1771
+ if (result.error) {
1772
+ return {
1773
+ error: result.error.message,
1774
+ code: result.error.code,
1775
+ ...(result.error.data === undefined ? {} : { data: result.error.data }),
1776
+ };
1777
+ }
1778
+ return { data: result.data };
1779
+ }
1780
+ catch (error) {
1781
+ return menuResultFailure(error);
1782
+ }
1783
+ }
1784
+ if (cmdBase === '/group') {
1785
+ return await executeGroupMenu(this, {
1786
+ kind: 'query', args, identity, subject, session,
1787
+ explicitChatType, channel, channelId, userId, fromControlChannel, source,
1788
+ });
1789
+ }
1593
1790
  const authDenied = await authorizeMenuIntent.call(this, {
1594
1791
  intent: buildMenuIntent('query', cmdBase, args, undefined, undefined, fromControlChannel),
1595
1792
  identity,
@@ -2013,13 +2210,11 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2013
2210
  return { data };
2014
2211
  }
2015
2212
  // ── name=file:文件元信息(§5.1) ──
2016
- // 权限(§6.3):agent owner/admin 或 aid channel owner。项目外文件仅 owner 可取(§6.2,由 resolveMenuFilePath 校验)。
2213
+ // operation 权限已在入口统一判定;项目外文件仍仅 owner 可取。
2017
2214
  if (cmdBase === '/file') {
2018
2215
  const role = identity.role;
2019
- if (!isManagementRole(role)) {
2020
- return { error: '无权限', code: 'NO_PERMISSION' };
2021
- }
2022
- const resolved = resolveMenuFilePath(args?.path, session, role, 'file');
2216
+ const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
2217
+ const resolved = resolveMenuFilePath(args?.path, projectPath, role, 'file');
2023
2218
  if ('error' in resolved)
2024
2219
  return resolved;
2025
2220
  const { realPath, stat } = resolved;
@@ -2088,6 +2283,34 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
2088
2283
  return menuResultFailure(error);
2089
2284
  }
2090
2285
  }
2286
+ if (cmdBase === '/connect') {
2287
+ const authorized = await authorizeRoleMenu(this, {
2288
+ kind: 'update', args, value: arg, operation: connectMenuOperation('update', args, arg), identity, subject: authSubject, session, channel, channelId,
2289
+ userId, fromControlChannel, source,
2290
+ });
2291
+ if ('error' in authorized)
2292
+ return authorized;
2293
+ try {
2294
+ const result = await handleConnectMenu({ type: 'menu.update', subtype: 'update', args, value: arg }, authorized.context);
2295
+ if (result.error) {
2296
+ return {
2297
+ error: result.error.message,
2298
+ code: result.error.code,
2299
+ ...(result.error.data === undefined ? {} : { data: result.error.data }),
2300
+ };
2301
+ }
2302
+ return { data: result.data };
2303
+ }
2304
+ catch (error) {
2305
+ return menuResultFailure(error);
2306
+ }
2307
+ }
2308
+ if (cmdBase === '/group') {
2309
+ return await executeGroupMenu(this, {
2310
+ kind: 'update', args, value: arg, identity, subject, session,
2311
+ channel, channelId, userId, fromControlChannel, source,
2312
+ });
2313
+ }
2091
2314
  const authDenied = await authorizeMenuIntent.call(this, {
2092
2315
  intent: buildMenuIntent('update', cmdBase, args, undefined, arg, fromControlChannel),
2093
2316
  identity,
@@ -2427,7 +2650,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
2427
2650
  return { error: `不支持 update: ${cmdBase}`, code: 'NOT_SUPPORTED' };
2428
2651
  }
2429
2652
  /** menu.action — 触发动词。 */
2430
- export async function execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu') {
2653
+ export async function execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
2431
2654
  const cmdBase = cmd.trim().split(' ')[0];
2432
2655
  if (!cmdBase)
2433
2656
  return { error: '缺少命令', code: 'MISSING_CMD' };
@@ -2465,6 +2688,28 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2465
2688
  return menuResultFailure(error);
2466
2689
  }
2467
2690
  }
2691
+ if (cmdBase === '/connect') {
2692
+ const authorized = await authorizeRoleMenu(this, {
2693
+ kind: 'action', args, operation: connectMenuOperation('action', args), identity: authIdentity, subject, session: authSession,
2694
+ channel, channelId, userId, fromControlChannel, source,
2695
+ });
2696
+ if ('error' in authorized)
2697
+ return authorized;
2698
+ try {
2699
+ const result = await handleConnectMenu({ type: 'menu.action', subtype: 'action', action, args }, authorized.context);
2700
+ if (result.error) {
2701
+ return {
2702
+ error: result.error.message,
2703
+ code: result.error.code,
2704
+ ...(result.error.data === undefined ? {} : { data: result.error.data }),
2705
+ };
2706
+ }
2707
+ return { data: result.data };
2708
+ }
2709
+ catch (error) {
2710
+ return menuResultFailure(error);
2711
+ }
2712
+ }
2468
2713
  if (cmdBase === '/agent' && !fromControlChannel && !args?.aid) {
2469
2714
  const selfAid = this.getOwningAgent?.(channel)?.aid;
2470
2715
  if (selfAid)
@@ -2499,7 +2744,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2499
2744
  return await gatewaySyncEnv(args);
2500
2745
  return { error: `不支持 gateway action: ${action}`, code: 'NOT_SUPPORTED' };
2501
2746
  }
2502
- const { session } = await this.loadMenuContext(channel, channelId);
2747
+ const { session, evolagent } = await this.loadMenuContext(channel, channelId);
2503
2748
  const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
2504
2749
  const isMenuAdmin = isManagementRole(identity.role);
2505
2750
  // ── /agent action ──
@@ -2618,7 +2863,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2618
2863
  const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
2619
2864
  if (!triggerScheduler)
2620
2865
  return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
2621
- if (action === 'set') {
2866
+ if (action === 'create' || action === 'set') {
2622
2867
  // args 结构化 → 直接组装 ParsedTriggerSet(绕过 parseTriggerSet 文本解析,无注入风险)
2623
2868
  if (!args?.scheduleType || (args.scheduleType !== 'once' && !args?.scheduleValue) || !args?.prompt) {
2624
2869
  return { error: '缺少必填参数:scheduleType / scheduleValue / prompt', code: 'INVALID_ARGS' };
@@ -2652,6 +2897,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2652
2897
  effort: args.effort,
2653
2898
  permissionMode: args.permissionMode,
2654
2899
  triggerThread,
2900
+ baseagent: args.baseagent,
2655
2901
  };
2656
2902
  const r = await this.registerTriggerFromParsed(parsed, channel, channelId, userId ?? '', undefined, this.resolveMenuChatType(channel, channelId, explicitChatType), undefined, isAdmin);
2657
2903
  if (!r.ok)
@@ -3020,18 +3266,26 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3020
3266
  return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
3021
3267
  }
3022
3268
  if (action === 'restart') {
3023
- const restartInfo = { channel, channelId, timestamp: Date.now() };
3024
- const dataDir = resolvePaths().dataDir;
3025
- fs.mkdirSync(dataDir, { recursive: true });
3026
- fs.writeFileSync(path.join(dataDir, 'restart-pending.json'), JSON.stringify(restartInfo));
3027
- const { spawn } = await import('child_process');
3028
- spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
3029
- detached: true,
3030
- stdio: 'ignore',
3031
- env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
3032
- }).unref();
3269
+ const suppressRealRestart = shouldSuppressRealRestart();
3270
+ if (!suppressRealRestart) {
3271
+ const restartInfo = { channel, channelId, timestamp: Date.now() };
3272
+ const dataDir = resolvePaths().dataDir;
3273
+ fs.mkdirSync(dataDir, { recursive: true });
3274
+ fs.writeFileSync(path.join(dataDir, 'restart-pending.json'), JSON.stringify(restartInfo));
3275
+ const { spawn } = await import('child_process');
3276
+ spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
3277
+ detached: true,
3278
+ stdio: 'ignore',
3279
+ env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
3280
+ }).unref();
3281
+ }
3282
+ else {
3283
+ logger.info('[System] Suppressed real menu restart in test runtime');
3284
+ }
3033
3285
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
3034
- setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 1000);
3286
+ if (!suppressRealRestart) {
3287
+ setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 1000);
3288
+ }
3035
3289
  return { data: { action: 'restart', success: true } };
3036
3290
  }
3037
3291
  if (action === 'check') {
@@ -3079,6 +3333,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3079
3333
  if (!argvValidation.ok)
3080
3334
  return { error: argvValidation.reason, code: 'INVALID_ARGUMENT' };
3081
3335
  let argv = argvValidation.argv;
3336
+ const requestedArgv = [...argvValidation.argv];
3082
3337
  argv = normalizeCliArgv(argv);
3083
3338
  const cliAgent = this.getOwningAgent?.(channel);
3084
3339
  const cliSelfAid = cliAgent?.aid;
@@ -3108,6 +3363,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3108
3363
  scope: parsed.intent.scope, dangerous: true, actorId: userId,
3109
3364
  channel, channelId, role: identity.role, decision: 'deny', code: 'NOT_ALLOWED',
3110
3365
  reason: 'Unrecognized CLI command', taskId: requestId, argvHash: hashArgv(intentArgv),
3366
+ name: 'cli', action: 'exec', args: { argv: [...requestedArgv] },
3111
3367
  });
3112
3368
  return { error: 'CLI command is not allowed', code: 'NOT_ALLOWED' };
3113
3369
  }
@@ -3146,14 +3402,21 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3146
3402
  resolvedConfigCommand: parsed.resolvedConfigCommand,
3147
3403
  allowExplicitRelationTarget: true,
3148
3404
  auditAllowed: false,
3149
- auditMetadata: { taskId: requestId, argvHash: hashArgv(authArgv) },
3405
+ auditMetadata: {
3406
+ taskId: requestId,
3407
+ argvHash: hashArgv(authArgv),
3408
+ name: 'cli',
3409
+ action: 'exec',
3410
+ args: { argv: [...requestedArgv] },
3411
+ },
3150
3412
  });
3151
3413
  if (!decision.allow) {
3152
3414
  return { error: decision.reason, code: decision.code };
3153
3415
  }
3154
3416
  authorizedCommands.push({ parsed, decision });
3155
3417
  }
3156
- const execution = await this.execCliPassthrough(argv, subject.role);
3418
+ const rawExecution = await this.execCliPassthrough(argv, subject.role);
3419
+ const execution = appendGroupRulesCompatibilityNote(rawExecution, parsedCommands[0]?.resolvedConfigCommand, session);
3157
3420
  const executionData = 'data' in execution ? execution.data : undefined;
3158
3421
  for (const { parsed, decision } of authorizedCommands) {
3159
3422
  await auditCommandAuthorization({
@@ -3163,6 +3426,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3163
3426
  channel, channelId, role: identity.role, isDaemonOwner,
3164
3427
  fromControlChannel: fromControlChannel ?? false, decision: 'allow',
3165
3428
  matchedRule: decision.command?.matchedRule, taskId: requestId, argvHash: hashArgv(argv),
3429
+ name: 'cli', action: 'exec',
3430
+ args: { argv: [...requestedArgv] },
3166
3431
  durationMs: executionData?.durationMs, exitCode: executionData?.exitCode,
3167
3432
  });
3168
3433
  }
@@ -3173,13 +3438,10 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3173
3438
  // fetch 文件作为独立 result.file 消息异步发回;把请求 id 作为 correlationId 透传,
3174
3439
  // 客户端用它把异步到达的文件消息对回这次 fetch 点击。
3175
3440
  if (cmdBase === '/file') {
3176
- // 权限(§6.3):agent owner/admin aid channel owner。项目外文件仅 owner(§6.2,由 resolveMenuFilePath 校验)。
3177
- if (!isManagementRole(identity.role)) {
3178
- return { error: '无权限', code: 'NO_PERMISSION' };
3179
- }
3441
+ const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
3180
3442
  if (action === 'list') {
3181
3443
  const dirArg = (args?.path ?? '.').toString().trim() || '.';
3182
- const resolved = resolveMenuFilePath(dirArg, session, identity.role, 'directory');
3444
+ const resolved = resolveMenuFilePath(dirArg, projectPath, identity.role, 'directory');
3183
3445
  if ('error' in resolved)
3184
3446
  return resolved;
3185
3447
  const offset = parseFileListOffset(args?.offset);
@@ -3192,7 +3454,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3192
3454
  }
3193
3455
  if (action !== 'fetch')
3194
3456
  return { error: `不支持的 file action: ${action}`, code: 'NOT_SUPPORTED' };
3195
- const resolved = resolveMenuFilePath(args?.path, session, identity.role, 'file');
3457
+ const resolved = resolveMenuFilePath(args?.path, projectPath, identity.role, 'file');
3196
3458
  if ('error' in resolved)
3197
3459
  return resolved;
3198
3460
  const { realPath, stat } = resolved;
@@ -3204,9 +3466,26 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3204
3466
  return { error: '通道不存在', code: 'EXEC_FAILED' };
3205
3467
  if (!adapter.capabilities?.file)
3206
3468
  return { error: '通道不支持文件发送', code: 'NOT_SUPPORTED' };
3207
- try {
3208
- const replyCtx = session ? this.getReplyContext(session) : undefined;
3469
+ const sessionReplyContext = session ? this.getReplyContext(session) : undefined;
3470
+ const replyCtx = transportReplyContext
3471
+ ? {
3472
+ ...(sessionReplyContext ?? {}),
3473
+ ...transportReplyContext,
3474
+ metadata: {
3475
+ ...(sessionReplyContext?.metadata ?? {}),
3476
+ ...(transportReplyContext.metadata ?? {}),
3477
+ },
3478
+ }
3479
+ : sessionReplyContext;
3480
+ const sendFile = async () => {
3209
3481
  await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath, correlationId: requestId });
3482
+ };
3483
+ if (deferSideEffect) {
3484
+ deferSideEffect(sendFile);
3485
+ return { data: { accepted: true } };
3486
+ }
3487
+ try {
3488
+ await sendFile();
3210
3489
  return { data: { accepted: true } };
3211
3490
  }
3212
3491
  catch (e) {
@@ -3223,6 +3502,7 @@ const SYSTEM_CONTROL_NAME_MAP = {
3223
3502
  observable: '/observable',
3224
3503
  agent: '/agent', trigger: '/trigger', file: '/file', gateway: '/gateway',
3225
3504
  config: '/config', capability: '/capability', role: '/role',
3505
+ group: '/group', connect: '/connect',
3226
3506
  };
3227
3507
  function isProcessLevelMenu(name, cmd) {
3228
3508
  return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
@@ -3239,10 +3519,11 @@ async function execMenuForSystemControl(payload, context) {
3239
3519
  }
3240
3520
  const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
3241
3521
  const isRoleRequest = name === 'role' || payload?.cmd === '/role';
3242
- const requiresAgentTarget = isRoleRequest || [
3243
- 'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability',
3522
+ const isConnectRequest = name === 'connect' || payload?.cmd === '/connect';
3523
+ const requiresAgentTarget = isRoleRequest || isConnectRequest || [
3524
+ 'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
3244
3525
  ].includes(name)
3245
- || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability'].includes(payload?.cmd);
3526
+ || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(payload?.cmd);
3246
3527
  const target = isProcessLevel
3247
3528
  ? { channel: SYSTEM_CONTROL_CHANNEL }
3248
3529
  : resolveExternalMenuAgentChannel(this, payload, SYSTEM_CONTROL_CHANNEL, requiresAgentTarget);
@@ -3267,7 +3548,7 @@ async function execMenuForSystemControl(payload, context) {
3267
3548
  try {
3268
3549
  switch (payload?.type) {
3269
3550
  case 'menu.list':
3270
- return menuSuccess({ id }, this.getMenuItems(trustedIdentity.role, 'private', 'control'));
3551
+ return menuSuccess({ id }, this.getMenuItems(trustedIdentity.role, 'private', 'control', trustedSubject));
3271
3552
  case 'menu.query': {
3272
3553
  if (!cmd)
3273
3554
  return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
@@ -3342,7 +3623,8 @@ export async function execMenuForControl(payload, peerId) {
3342
3623
  return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
3343
3624
  const owners = loadDaemonConfig().owners ?? [];
3344
3625
  const isRoleRequest = name === 'role' || payload?.cmd === '/role';
3345
- if (!isRoleRequest && !isProcessLevelOwner(peerId, owners)) {
3626
+ const isConnectRequest = name === 'connect' || payload?.cmd === '/connect';
3627
+ if (!isRoleRequest && !isConnectRequest && !isProcessLevelOwner(peerId, owners)) {
3346
3628
  return menuFailure({ id, ...(name ? { name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: '控制 channel 操作需要 owner 权限' });
3347
3629
  }
3348
3630
  return execMenuForSystemControl.call(this, payload, {