evolcore 0.0.10 → 0.0.12

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 (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -15,7 +15,7 @@ import { execFileSync } from 'child_process';
15
15
  import { CronExpressionParser } from 'cron-parser';
16
16
  import { parseDuration } from '../../trigger/parser.js';
17
17
  import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
18
- import { commandExists } from '../../utils/cross-platform.js';
18
+ import { commandExists, spawnDetachedNode } from '../../utils/cross-platform.js';
19
19
  import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
20
20
  import { WEB_PACKAGE_NAME } from '../../product.js';
21
21
  import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
@@ -36,11 +36,11 @@ import { isManagementRole } from '../../config/builtin-roles.js';
36
36
  import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
37
37
  import { logger } from '../../utils/logger.js';
38
38
  import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
39
- import { dispatchToMentionMode } from '../../config/mention-mode.js';
40
- import { menuFailure, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest } from './menu-protocol.js';
39
+ import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
41
40
  import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
42
41
  import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
43
42
  import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
43
+ import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
44
44
  /**
45
45
  * 获取 baseagent CLI 的版本号(claude/gemini/codex)。
46
46
  * 失败返回 null(命令不存在或执行失败)。
@@ -60,6 +60,7 @@ function getBaseagentVersion(cmd) {
60
60
  encoding: 'utf-8',
61
61
  timeout: 3000,
62
62
  stdio: ['ignore', 'pipe', 'pipe'],
63
+ windowsHide: process.platform === 'win32',
63
64
  });
64
65
  const versionOutput = String(output).trim();
65
66
  // claude: "2.1.187 (Claude Code)" → 提取 "2.1.187"
@@ -873,20 +874,36 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
873
874
  });
874
875
  const modelScope = args?.scope === 'agent' ? 'agent' : 'relation';
875
876
  if (verb === 'query') {
877
+ if (cmdBase === '/pwd')
878
+ return intent('project.current', 'relation');
879
+ if (cmdBase === '/session' || cmdBase === '/s')
880
+ return intent('session.current', 'relation');
881
+ if (cmdBase === '/topic')
882
+ return intent('session.topic.current', 'relation');
883
+ if (cmdBase === '/baseagent')
884
+ return intent('agent.baseagent.current', fromControlChannel || args?.scope === 'agent' ? 'agent' : 'relation');
876
885
  if (cmdBase === '/file')
877
886
  return intent('file.fetch', 'filesystem', { filePath: args?.path });
878
887
  if (cmdBase === '/model')
879
888
  return intent('model.current', modelScope);
880
889
  if (cmdBase === '/effort')
881
- return intent('model.current', modelScope);
890
+ return intent('model.effort.current', modelScope);
882
891
  if (cmdBase === '/chatmode')
883
892
  return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
884
893
  if (cmdBase === '/mentionmode')
885
894
  return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
895
+ if (cmdBase === '/perm')
896
+ return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
897
+ if (cmdBase === '/activity')
898
+ return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
899
+ if (cmdBase === '/observable')
900
+ return intent('observable.current', 'agent');
901
+ if (cmdBase === '/capability')
902
+ return intent('capability.read', 'agent');
886
903
  if (cmdBase === '/gateway')
887
904
  return intent('gateway.read', 'process');
888
905
  if (cmdBase === '/config')
889
- return intent('config.read', 'process');
906
+ return intent('config.read', args?.scope === 'agent' ? 'agent' : 'process');
890
907
  if (cmdBase === '/system')
891
908
  return intent('system.status', 'process');
892
909
  if (cmdBase === '/agent') {
@@ -895,7 +912,35 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
895
912
  if (cmdBase === '/trigger')
896
913
  return intent('trigger.list', 'relation');
897
914
  }
915
+ if (verb === 'options') {
916
+ if (cmdBase === '/agent')
917
+ return intent(fromControlChannel ? 'agent.list' : 'agent.show', fromControlChannel ? 'control' : 'agent');
918
+ if (cmdBase === '/capability')
919
+ return intent('capability.read', 'agent');
920
+ if (cmdBase === '/trigger')
921
+ return intent('trigger.list', 'relation');
922
+ if (cmdBase === '/topic')
923
+ return intent('session.topic.list', 'relation');
924
+ if (cmdBase === '/session' || cmdBase === '/s' || cmdBase === '/del')
925
+ return intent('session.list', 'relation');
926
+ if (cmdBase === '/baseagent')
927
+ return intent('agent.baseagent.list', 'agent');
928
+ if (cmdBase === '/model')
929
+ return intent('model.list', modelScope);
930
+ if (cmdBase === '/effort')
931
+ return intent('model.effort.current', modelScope);
932
+ if (cmdBase === '/chatmode')
933
+ return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
934
+ if (cmdBase === '/mentionmode')
935
+ return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
936
+ if (cmdBase === '/activity')
937
+ return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
938
+ if (cmdBase === '/perm')
939
+ return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
940
+ }
898
941
  if (verb === 'update') {
942
+ if (cmdBase === '/baseagent')
943
+ return intent('agent.baseagent.update', 'agent', { baseagent: value });
899
944
  if (cmdBase === '/model')
900
945
  return intent('model.use', modelScope, { model: value });
901
946
  if (cmdBase === '/effort')
@@ -904,6 +949,10 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
904
949
  return intent('chatmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
905
950
  if (cmdBase === '/mentionmode')
906
951
  return intent('mentionmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
952
+ if (cmdBase === '/activity')
953
+ return intent('activity.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
954
+ if (cmdBase === '/observable')
955
+ return intent('observable.update', 'agent', { value });
907
956
  if (cmdBase === '/gateway')
908
957
  return intent('gateway.write', 'process');
909
958
  if (cmdBase === '/config')
@@ -1010,6 +1059,9 @@ async function authorizeMenuIntent(params) {
1010
1059
  peerKey: subject.peerKey,
1011
1060
  });
1012
1061
  }
1062
+ else if (intent.scope === 'agent' && subject.selfAid && intent.args.self === undefined) {
1063
+ intent.args = { ...intent.args, self: subject.selfAid };
1064
+ }
1013
1065
  const source = params.source ?? 'menu';
1014
1066
  intent.source = source;
1015
1067
  const decision = await authorizeOperation({ source, intent, subject });
@@ -1044,6 +1096,9 @@ function buildRoleMenuContext(owner, channel, subject) {
1044
1096
  }
1045
1097
  const aunAvailable = (owningAgent?.channelInstanceNames?.() ?? [])
1046
1098
  .some((channelName) => owner.hasRegisteredChannel?.(channelName, 'aun') === true);
1099
+ const aunChannelName = (owningAgent?.channelInstanceNames?.() ?? [])
1100
+ .find((name) => String(name).split('#', 1)[0].toLowerCase() === 'aun');
1101
+ const contactAdapter = aunChannelName ? owner.getAdapter?.(aunChannelName) : undefined;
1047
1102
  return {
1048
1103
  self,
1049
1104
  actorRole: subject.role,
@@ -1052,6 +1107,26 @@ function buildRoleMenuContext(owner, channel, subject) {
1052
1107
  actorAid: subject.actorId,
1053
1108
  chatType: subject.chatType,
1054
1109
  aunAvailable,
1110
+ ...(contactAdapter?.send ? {
1111
+ notifyContactRequestReviewed: async ({ applicantAid, requestId, expiresAt, status }) => {
1112
+ await contactAdapter.send(buildEnvelope({
1113
+ taskId: `contact-request-${requestId}`,
1114
+ channel: contactAdapter.channelName,
1115
+ channelId: applicantAid,
1116
+ agentName: owningAgent?.name ?? '<unknown>',
1117
+ replyContext: { metadata: evolMenuResponseTransportMetadata() },
1118
+ }), {
1119
+ kind: 'custom',
1120
+ channelType: 'aun',
1121
+ payload: {
1122
+ type: 'contact.request.response',
1123
+ status,
1124
+ request_id: requestId,
1125
+ expires_at: expiresAt,
1126
+ },
1127
+ });
1128
+ },
1129
+ } : {}),
1055
1130
  availableBaseagents: Array.from(new Set([
1056
1131
  ...Object.keys(owningAgent?.config?.baseagents ?? {}),
1057
1132
  ...(owningAgent?.baseagent ? [owningAgent.baseagent] : []),
@@ -1240,6 +1315,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1240
1315
  fromControlChannel: isControlScope,
1241
1316
  }).allow;
1242
1317
  };
1318
+ const canReadModel = canOperation('model.list', 'relation') || canOperation('model.current', 'relation');
1243
1319
  const canUseModel = canOperation('model.use', 'relation');
1244
1320
  const canSetEffort = canOperation('model.effort', 'relation');
1245
1321
  const canSetChatmode = canOperation('chatmode.update', 'relation');
@@ -1248,7 +1324,12 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1248
1324
  const canUseFile = canOperation('file.fetch', 'filesystem') || canOperation('file.list', 'filesystem');
1249
1325
  const canManageProcess = authSubject ? authSubject.isDaemonOwner : isOwner;
1250
1326
  const configurableSettings = [
1251
- ...(canUseModel ? [{ cmd: '/model', label: '切换模型', desc: '切换当前 Agent 使用的模型版本', next: { type: 'select', dynamic: true } }] : []),
1327
+ ...(canReadModel ? [{
1328
+ cmd: '/model',
1329
+ label: canUseModel ? '切换模型' : '查看模型',
1330
+ desc: canUseModel ? '切换当前 Agent 使用的模型版本' : '查看当前 Agent 使用的模型版本',
1331
+ next: canUseModel ? { type: 'select', dynamic: true } : undefined,
1332
+ }] : []),
1252
1333
  ...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', items: [
1253
1334
  { value: 'low', label: 'Low' },
1254
1335
  { value: 'medium', label: 'Medium' },
@@ -1428,13 +1509,25 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1428
1509
  throw { code: result.code, message: result.error, data: result.data };
1429
1510
  return result.data;
1430
1511
  }
1512
+ const optionsIdentity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1513
+ const optionsAuthDenied = await authorizeMenuIntent.call(this, {
1514
+ intent: buildMenuIntent('options', cmdBase0, args, undefined, undefined, fromControlChannel),
1515
+ identity: optionsIdentity,
1516
+ subject,
1517
+ session,
1518
+ explicitChatType,
1519
+ channel,
1520
+ channelId,
1521
+ userId,
1522
+ fromControlChannel,
1523
+ source,
1524
+ });
1525
+ if (optionsAuthDenied)
1526
+ throw { code: optionsAuthDenied.code, message: optionsAuthDenied.error, data: optionsAuthDenied.data };
1431
1527
  // ── /agent list(只读) ──
1432
- // 控制 channel:验 evolcore.owners,返回全量;agent channel:放行但仅返回自身单条。
1528
+ // 控制 channel:返回全量;agent channel:仅返回自身单条。
1433
1529
  if (cmd === '/agent') {
1434
1530
  if (fromControlChannel) {
1435
- if (!subject.isDaemonOwner) {
1436
- throw { code: 'FORBIDDEN', message: '操作需要 owner 权限' };
1437
- }
1438
1531
  const res = await execAgentOptions(args);
1439
1532
  if ('error' in res)
1440
1533
  throw { code: res.code, message: res.error };
@@ -1465,24 +1558,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1465
1558
  }
1466
1559
  // ── 关系级 /trigger list(每个 trigger 一个 MenuItem) ──
1467
1560
  if (cmd === '/trigger') {
1468
- const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1469
- const authDenied = await authorizeMenuIntent.call(this, {
1470
- intent: buildMenuIntent('query', cmd, args, undefined, undefined, fromControlChannel),
1471
- identity,
1472
- subject,
1473
- session,
1474
- explicitChatType,
1475
- channel,
1476
- channelId,
1477
- userId,
1478
- fromControlChannel,
1479
- source,
1480
- });
1481
- if (authDenied)
1482
- throw { code: authDenied.code, message: authDenied.error, data: authDenied.data };
1483
1561
  const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
1484
1562
  const scope = args?.options === 'all' ? 'all' : 'enabled';
1485
- const role = identity.role;
1563
+ const role = optionsIdentity.role;
1486
1564
  const isAdmin = isManagementRole(role);
1487
1565
  if (!triggerScheduler)
1488
1566
  return [];
@@ -1501,10 +1579,6 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1501
1579
  });
1502
1580
  }
1503
1581
  if (cmd === '/topic') {
1504
- const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1505
- if (!this.canReadTopics(identity.role)) {
1506
- throw { code: 'FORBIDDEN', message: '无权限查看话题' };
1507
- }
1508
1582
  if (args?.mode === 'fork-turns') {
1509
1583
  const source = await resolveTopicForkSource(this.sessionManager, channel, channelId, args?.sourceThreadId);
1510
1584
  if (!source)
@@ -1577,14 +1651,13 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1577
1651
  return available.map((name) => ({ value: name, label: name, selected: name === currentAgent }));
1578
1652
  }
1579
1653
  if (cmd === '/model') {
1580
- const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
1581
1654
  const target = resolveMenuModelTarget.call(this, {
1582
1655
  args,
1583
1656
  session,
1584
1657
  channel,
1585
1658
  channelId,
1586
1659
  userId,
1587
- role: identity.role,
1660
+ role: optionsIdentity.role,
1588
1661
  explicitChatType,
1589
1662
  fromControlChannel,
1590
1663
  field: 'model',
@@ -1593,31 +1666,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1593
1666
  throw { code: target.code, message: target.error };
1594
1667
  const agent = this.getAgent(channel, target.baseagent);
1595
1668
  if (hasModelSwitcher(agent) && agent.listModels) {
1596
- const role = identity.role;
1597
- const authIntent = {
1598
- operation: 'model.list',
1599
- scope: target.scope,
1600
- source: 'menu',
1601
- args: {},
1602
- };
1603
- const subject = buildMenuAuthSubject(this, {
1604
- identity,
1605
- session,
1606
- explicitChatType,
1607
- channel,
1608
- channelId,
1609
- userId,
1610
- fromControlChannel,
1611
- subject: authSubject,
1612
- });
1613
- authIntent.args = target.scope === 'relation'
1614
- ? buildRelationIntentArgs({ args, selfAid: target.sel.self, peerKey: target.sel.peerKey })
1615
- : { ...(args ?? {}), self: target.sel.self };
1616
- const decision = await authorizeOperation({ source: 'menu', intent: authIntent, subject, audit: false });
1617
- if (!decision.allow)
1618
- throw { code: decision.code, message: decision.reason };
1619
1669
  const rawModels = await agent.listModels() ?? [];
1620
- const models = filterModelsForRole(role, target.baseagent, rawModels, agent.resolveModelId?.bind(agent));
1670
+ const models = filterModelsForRole(optionsIdentity.role, target.baseagent, rawModels, agent.resolveModelId?.bind(agent));
1621
1671
  const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
1622
1672
  const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
1623
1673
  if (models.length > 0)
@@ -1715,7 +1765,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1715
1765
  });
1716
1766
  if ('error' in target)
1717
1767
  throw { code: target.code, message: target.error };
1718
- const fallback = dispatchToMentionMode(session?.metadata?.dispatchMode) ?? null;
1768
+ const fallback = session?.metadata?.mentionMode ?? null;
1719
1769
  const currentMode = readMenuMentionMode(target, fallback, fallback === null ? null : 'session').value;
1720
1770
  return [
1721
1771
  { value: 'mention-only', label: '@提及时响应', selected: currentMode === 'mention-only' },
@@ -1741,6 +1791,10 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1741
1791
  const cmdBase = cmd.trim().split(' ')[0];
1742
1792
  if (!cmdBase)
1743
1793
  return { error: '缺少命令', code: 'MISSING_CMD' };
1794
+ const isRequesterRoleQuery = cmdBase === '/role' && args?.view === 'requester';
1795
+ if (isRequesterRoleQuery && Object.keys(args).some(key => key !== 'view')) {
1796
+ return { error: 'role requester query only accepts args.view', code: 'INVALID_ARGUMENT' };
1797
+ }
1744
1798
  const gated = gateControlScope.call(this, { cmdBase, args, channel, fromControlChannel });
1745
1799
  if (gated)
1746
1800
  return gated;
@@ -1756,6 +1810,18 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1756
1810
  fromControlChannel,
1757
1811
  subject: authSubject,
1758
1812
  });
1813
+ if (isRequesterRoleQuery) {
1814
+ if (!subject.allowAccess) {
1815
+ return { error: `Role ${subject.role} is not allowed to access this agent`, code: 'ROLE_ACCESS_DENIED' };
1816
+ }
1817
+ return {
1818
+ data: {
1819
+ roleId: subject.role,
1820
+ source: subject.roleSource,
1821
+ allowAccess: subject.allowAccess,
1822
+ },
1823
+ };
1824
+ }
1759
1825
  if (cmdBase === '/role') {
1760
1826
  const authorized = await authorizeRoleMenu(this, {
1761
1827
  kind: 'query', args, identity, subject, session, channel, channelId,
@@ -1813,12 +1879,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1813
1879
  if (authDenied)
1814
1880
  return authDenied;
1815
1881
  // ── /agent 查询(只读) ──
1816
- // 控制 channel:验 owners,按 args.aid 查任意 agent;agent channel:强制查自身。
1882
+ // 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
1817
1883
  if (cmdBase === '/agent') {
1818
1884
  if (fromControlChannel) {
1819
- if (!subject.isDaemonOwner) {
1820
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
1821
- }
1822
1885
  return await execAgentQuery(args);
1823
1886
  }
1824
1887
  const selfAid = this.getOwningAgent?.(channel)?.aid;
@@ -1827,29 +1890,17 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1827
1890
  return await execAgentQuery({ ...(args ?? {}), aid: selfAid });
1828
1891
  }
1829
1892
  // ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
1830
- // 进程级:闸门已要求 fromControlChannel;此处再验 owners 非空。
1831
1893
  if (cmdBase === '/gateway') {
1832
- if (!subject.isDaemonOwner) {
1833
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
1834
- }
1835
1894
  return gatewayList();
1836
1895
  }
1837
1896
  // ── /config 查询(只读,查询各层配置) ──
1838
1897
  if (cmdBase === '/config') {
1839
1898
  const scope = args?.scope || 'process';
1840
1899
  if (scope === 'process') {
1841
- // 进程级配置需要 owner 权限
1842
- if (!fromControlChannel || !subject.isDaemonOwner) {
1843
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
1844
- }
1845
1900
  const cfg = loadDaemonConfig();
1846
1901
  return { data: { scope: 'process', config: cfg } };
1847
1902
  }
1848
1903
  if (scope === 'defaults') {
1849
- // 全局默认配置需要 owner 权限
1850
- if (!fromControlChannel || !subject.isDaemonOwner) {
1851
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
1852
- }
1853
1904
  const { read, ConfigTarget } = await import('../../config/config-manager.js');
1854
1905
  const cfg = read(ConfigTarget.Defaults, undefined, { cache: true });
1855
1906
  return { data: { scope: 'defaults', config: cfg } };
@@ -1938,9 +1989,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1938
1989
  return { data };
1939
1990
  }
1940
1991
  if (cmdBase === '/topic') {
1941
- if (!this.canReadTopics(identity.role)) {
1942
- return { error: '无权限查看话题', code: 'FORBIDDEN' };
1943
- }
1944
1992
  const target = (args?.target ?? '').toString().trim();
1945
1993
  if (!target)
1946
1994
  return { error: '缺少 args.target', code: 'MISSING_VALUE' };
@@ -2095,8 +2143,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2095
2143
  });
2096
2144
  if ('error' in target)
2097
2145
  return target;
2098
- // session metadata 是 AUN 协议词汇,翻译成 mentionMode 词汇;evolagent.config 已是 mentionMode 字段
2099
- const fallback = dispatchToMentionMode(session?.metadata?.dispatchMode) ?? null;
2146
+ const fallback = session?.metadata?.mentionMode ?? null;
2100
2147
  const current = readMenuMentionMode(target, fallback, fallback === null ? null : 'session');
2101
2148
  return {
2102
2149
  data: {
@@ -2110,8 +2157,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2110
2157
  };
2111
2158
  }
2112
2159
  if (cmdBase === '/observable') {
2113
- if (identity.role !== 'owner')
2114
- return { error: '观察者模式仅限 owner 查看', code: 'NO_PERMISSION' };
2115
2160
  if (!evolagent)
2116
2161
  return { error: '找不到通道所属 agent', code: 'MISSING_AID' };
2117
2162
  const observable = evolagent?.getObservable() ?? false;
@@ -2173,9 +2218,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2173
2218
  };
2174
2219
  }
2175
2220
  if (cmdBase === '/system') {
2176
- if (!subject.isDaemonOwner) {
2177
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
2178
- }
2179
2221
  const data = {
2180
2222
  aid: loadDaemonConfig().aid ?? null,
2181
2223
  pid: process.pid,
@@ -2614,8 +2656,6 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
2614
2656
  const newMode = modeMap[arg];
2615
2657
  if (!newMode)
2616
2658
  return { error: `无效模式: ${arg},可选: all / text / none`, code: 'INVALID_VALUE' };
2617
- if (identity.role !== 'owner')
2618
- return { error: '中间输出模式切换仅限 owner', code: 'NO_PERMISSION' };
2619
2659
  const target = resolveMenuActivityTarget.call(this, {
2620
2660
  args,
2621
2661
  session,
@@ -3278,12 +3318,9 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3278
3318
  const controlDir = daemonControlDir();
3279
3319
  fs.mkdirSync(controlDir, { recursive: true });
3280
3320
  fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
3281
- const { spawn } = await import('child_process');
3282
- spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
3283
- detached: true,
3284
- stdio: 'ignore',
3285
- env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
3286
- }).unref();
3321
+ spawnDetachedNode([path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
3322
+ EVOLCORE_HOME: resolvePaths().root,
3323
+ });
3287
3324
  }
3288
3325
  else {
3289
3326
  logger.info('[System] Suppressed real menu restart in test runtime');
@@ -3319,7 +3356,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3319
3356
  data: {
3320
3357
  devMode,
3321
3358
  evolcore: { local: localEvolcore, remote: evolcoreRemote, hasUpdate: cmp(localEvolcore, evolcoreRemote) },
3322
- fastaun: { local: localFastaun, remote: fastaunRemote, hasUpdate: cmp(localFastaun, fastaunRemote) },
3359
+ fastaun: { local: localFastaun, remote: fastaunRemote, hasUpdate: cmp(localFastaun, fastaunRemote), managedBy: 'evolcore' },
3323
3360
  // ecweb 本地版本由 ECWeb 进程自身注入(data.ecwebVersion),此处仅给 remote
3324
3361
  ecweb: { remote: ecwebRemote },
3325
3362
  },
@@ -3500,16 +3537,6 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3500
3537
  }
3501
3538
  return { error: `不支持 action: ${cmdBase}`, code: 'NOT_SUPPORTED' };
3502
3539
  }
3503
- const SYSTEM_CONTROL_NAME_MAP = {
3504
- pwd: '/pwd', session: '/session', baseagent: '/baseagent', model: '/model',
3505
- topic: '/topic',
3506
- effort: '/effort', chatmode: '/chatmode', mentionmode: '/mentionmode',
3507
- permission: '/perm', activity: '/activity', system: '/system',
3508
- observable: '/observable',
3509
- agent: '/agent', trigger: '/trigger', file: '/file', gateway: '/gateway',
3510
- config: '/config', capability: '/capability', role: '/role',
3511
- group: '/group', connect: '/connect',
3512
- };
3513
3540
  function menuCommandToken(cmd) {
3514
3541
  if (typeof cmd !== 'string')
3515
3542
  return undefined;
@@ -3520,6 +3547,13 @@ function isProcessLevelMenu(name, cmd) {
3520
3547
  return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
3521
3548
  || cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config';
3522
3549
  }
3550
+ function isProcessContextMenu(payload) {
3551
+ const isCatalog = payload?.type === 'menu.list' && payload?.args?.view === 'catalog';
3552
+ const isValueBatch = payload?.type === 'menu.query'
3553
+ && payload?.name === 'menu'
3554
+ && payload?.args?.view === 'values';
3555
+ return (isCatalog || isValueBatch) && payload?.args?.context?.kind === 'process';
3556
+ }
3523
3557
  async function execMenuForSystemControl(payload, context) {
3524
3558
  const id = payload?.id ?? '';
3525
3559
  const name = payload?.name;
@@ -3540,13 +3574,15 @@ async function execMenuForSystemControl(payload, context) {
3540
3574
  : 'cli 不在控制 channel 范围';
3541
3575
  return { type: 'menu.response', id, name, error: { code: 'NOT_SUPPORTED', message } };
3542
3576
  }
3543
- const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
3577
+ const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
3578
+ const requestedContextKind = payload?.args?.context?.kind;
3544
3579
  const isRoleRequest = name === 'role' || compatCmd === '/role';
3545
3580
  const isConnectRequest = name === 'connect' || compatCmd === '/connect';
3546
3581
  const requiresAgentTarget = isRoleRequest || isConnectRequest || [
3547
3582
  'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
3548
3583
  ].includes(name)
3549
- || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '');
3584
+ || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
3585
+ || (!isProcessLevel && requestedContextKind !== undefined && requestedContextKind !== 'process');
3550
3586
  const target = isProcessLevel
3551
3587
  ? { channel: SYSTEM_CONTROL_CHANNEL }
3552
3588
  : resolveExternalMenuAgentChannel(this, payload, SYSTEM_CONTROL_CHANNEL, requiresAgentTarget);
@@ -3567,21 +3603,71 @@ async function execMenuForSystemControl(payload, context) {
3567
3603
  fromControlChannel: true,
3568
3604
  });
3569
3605
  const trustedIdentity = trustedSubject.identity;
3570
- const cmd = name ? (SYSTEM_CONTROL_NAME_MAP[name] ?? payload.cmd) : payload.cmd;
3606
+ const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
3571
3607
  try {
3572
3608
  switch (payload?.type) {
3573
3609
  case 'menu.list':
3610
+ if (payload?.args?.view === 'catalog') {
3611
+ validateMenuCatalogArgs(payload.args);
3612
+ const data = await getMenuCatalog({
3613
+ subject: trustedSubject,
3614
+ channelType: 'aun',
3615
+ channelScope: 'control',
3616
+ channelId: targetChannel,
3617
+ request: payload.args.context,
3618
+ });
3619
+ return menuSuccess({ id, ...(name ? { name } : {}) }, data);
3620
+ }
3621
+ if (payload?.args?.view !== undefined)
3622
+ return ecwebErr(id, name, 'NOT_SUPPORTED', `Unsupported menu.list view: ${String(payload.args.view)}`);
3574
3623
  return menuSuccess({ id }, this.getMenuItems(trustedIdentity.role, 'private', 'control', trustedSubject));
3575
3624
  case 'menu.query': {
3625
+ if (name === 'menu' && payload?.args?.view === 'values') {
3626
+ validateMenuValueBatchArgs(payload.args);
3627
+ const data = await execMenuValueBatch({
3628
+ subject: trustedSubject,
3629
+ channelType: 'aun',
3630
+ channelScope: 'control',
3631
+ channelId: targetChannel,
3632
+ request: payload.args.context,
3633
+ items: payload.args.items,
3634
+ schemaVersion: payload.args.schemaVersion,
3635
+ consistency: payload.args.consistency,
3636
+ execQuery: async (entryCmd, resolvedChannelId, queryArgs, catalogContext) => this.execMenuQuery(entryCmd, targetChannel, resolvedChannelId, catalogContext.subject.actorId, queryArgs, catalogContext.subject.chatType, true, catalogContext.subject.identity, catalogContext.subject, context.source),
3637
+ });
3638
+ return menuSuccess({ id, ...(name ? { name } : {}) }, data);
3639
+ }
3640
+ if (name === 'menu')
3641
+ return ecwebErr(id, name, 'NOT_SUPPORTED', `Unsupported menu.query view: ${String(payload?.args?.view)}`);
3576
3642
  if (!cmd)
3577
3643
  return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
3578
- const r = await this.execMenuQuery(cmd, targetChannel, targetChannel, context.actorAid, payload.args, undefined, true, trustedIdentity, trustedSubject, context.source);
3644
+ const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
3645
+ const execution = await resolveExplicitMenuExecutionContext({
3646
+ subject: trustedSubject,
3647
+ channelType: 'aun',
3648
+ channelScope: 'control',
3649
+ channelId: targetChannel,
3650
+ args: payload.args,
3651
+ });
3652
+ const r = hasExplicitContext
3653
+ ? await this.execMenuQuery(cmd, targetChannel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, true, execution.subject.identity, execution.subject, context.source)
3654
+ : await this.execMenuQuery(cmd, targetChannel, targetChannel, context.actorAid, payload.args, undefined, true, trustedIdentity, trustedSubject, context.source);
3579
3655
  return ecwebResp(id, name, r);
3580
3656
  }
3581
3657
  case 'menu.options': {
3582
3658
  if (!cmd)
3583
3659
  return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
3584
- const data = await this.getSubMenuItems(cmd, targetChannel, targetChannel, context.actorAid, payload.args, trustedIdentity, undefined, true, trustedSubject, context.source) ?? [];
3660
+ const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
3661
+ const execution = await resolveExplicitMenuExecutionContext({
3662
+ subject: trustedSubject,
3663
+ channelType: 'aun',
3664
+ channelScope: 'control',
3665
+ channelId: targetChannel,
3666
+ args: payload.args,
3667
+ });
3668
+ const data = hasExplicitContext
3669
+ ? await this.getSubMenuItems(cmd, targetChannel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.identity, execution.subject.chatType, true, execution.subject, context.source) ?? []
3670
+ : await this.getSubMenuItems(cmd, targetChannel, targetChannel, context.actorAid, payload.args, trustedIdentity, undefined, true, trustedSubject, context.source) ?? [];
3585
3671
  return menuSuccess({ id, ...(name ? { name } : {}) }, data);
3586
3672
  }
3587
3673
  case 'menu.update': {
@@ -3589,7 +3675,14 @@ async function execMenuForSystemControl(payload, context) {
3589
3675
  return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
3590
3676
  if (!payload.value)
3591
3677
  return ecwebErr(id, name, 'MISSING_VALUE', '缺少 value');
3592
- const r = await this.execMenuUpdate(cmd, payload.value, targetChannel, targetChannel, context.actorAid, trustedIdentity, true, payload.args, trustedSubject, context.source);
3678
+ const execution = await resolveExplicitMenuExecutionContext({
3679
+ subject: trustedSubject,
3680
+ channelType: 'aun',
3681
+ channelScope: 'control',
3682
+ channelId: targetChannel,
3683
+ args: payload.args,
3684
+ });
3685
+ const r = await this.execMenuUpdate(cmd, payload.value, targetChannel, execution.channelId, execution.subject.actorId, execution.subject.identity, true, execution.args, execution.subject, context.source);
3593
3686
  return ecwebResp(id, name, r);
3594
3687
  }
3595
3688
  case 'menu.action': {
@@ -3597,7 +3690,17 @@ async function execMenuForSystemControl(payload, context) {
3597
3690
  return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
3598
3691
  if (!payload.action)
3599
3692
  return ecwebErr(id, name, 'MISSING_VALUE', '缺少 action');
3600
- const r = await this.execMenuAction(cmd, payload.action, payload.args, targetChannel, targetChannel, context.actorAid, trustedIdentity, undefined, id, true, trustedSubject, context.source);
3693
+ const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
3694
+ const execution = await resolveExplicitMenuExecutionContext({
3695
+ subject: trustedSubject,
3696
+ channelType: 'aun',
3697
+ channelScope: 'control',
3698
+ channelId: targetChannel,
3699
+ args: payload.args,
3700
+ });
3701
+ const r = hasExplicitContext
3702
+ ? await this.execMenuAction(cmd, payload.action, execution.args, targetChannel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.chatType, id, true, execution.subject, context.source)
3703
+ : await this.execMenuAction(cmd, payload.action, payload.args, targetChannel, targetChannel, context.actorAid, trustedIdentity, undefined, id, true, trustedSubject, context.source);
3601
3704
  return ecwebResp(id, name, r);
3602
3705
  }
3603
3706
  default:
@@ -3615,7 +3718,7 @@ export async function execMenuForEcweb(payload, trusted) {
3615
3718
  const validationError = validateMenuRequest(payload ?? {});
3616
3719
  if (validationError)
3617
3720
  return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
3618
- const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
3721
+ const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
3619
3722
  const owners = loadDaemonConfig().owners ?? [];
3620
3723
  if (isProcessLevel && owners.length === 0) {
3621
3724
  return { type: 'menu.response', id, name, error: { code: 'FORBIDDEN', message: '请在 daemon.json 配置 owners 后使用进程级操作' } };