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.
- package/CHANGELOG.md +93 -778
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +382 -100
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +285 -94
- package/dist/ipc.js +114 -8
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- 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,
|
|
182
|
-
if (!
|
|
183
|
-
return { error: '
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
|
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
|
-
|
|
1155
|
-
{ cmd: '/
|
|
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
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
//
|
|
2213
|
+
// operation 权限已在入口统一判定;项目外文件仍仅 owner 可取。
|
|
2017
2214
|
if (cmdBase === '/file') {
|
|
2018
2215
|
const role = identity.role;
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
3208
|
-
|
|
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
|
|
3243
|
-
|
|
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
|
-
|
|
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, {
|