evolcore 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +63 -26
- package/dist/agents/codex-runner.js +316 -411
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +12 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +522 -233
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +35 -23
- package/dist/cli/diag.js +2 -2
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +9 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +16 -2
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +76 -39
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +348 -77
- package/dist/core/command/menu-protocol.js +40 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +37 -22
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +76 -15
- package/dist/core/message/message-bridge.js +94 -11
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +709 -117
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +61 -29
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +308 -54
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +15 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getBaseagentSnapshot, loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
1
2
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
2
3
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
3
4
|
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable, TRANSPORT_EFFORTS } from '../model/model-catalog.js';
|
|
@@ -13,7 +14,7 @@ import crypto from 'crypto';
|
|
|
13
14
|
import { CronExpressionParser } from 'cron-parser';
|
|
14
15
|
import { parseDuration } from '../../trigger/parser.js';
|
|
15
16
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
16
|
-
import {
|
|
17
|
+
import { resolveCliEntry, launchMonitorAndConfirm } from '../../utils/cross-platform.js';
|
|
17
18
|
import { cachedCliVersion } from '../../utils/cross-platform.js';
|
|
18
19
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
19
20
|
import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
@@ -29,6 +30,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
|
|
|
29
30
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
30
31
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
31
32
|
import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
|
|
33
|
+
import { isUnsupportedBaseagentConfigField } from '../../config/config-field-policy.js';
|
|
32
34
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
33
35
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
34
36
|
import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
@@ -47,6 +49,9 @@ import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
|
47
49
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
48
50
|
import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
49
51
|
import { probeSessionActivity } from './slash-gate.js';
|
|
52
|
+
import { dataRootFailure, dataRootResolver } from './data-root-resolver.js';
|
|
53
|
+
import { agentStorageMenuService, isAgentStorageMenuRequest } from './agent-storage-menu.js';
|
|
54
|
+
import { isArchivedTopicLable, normalizeTopicLable, resolveTopicArchiveFilter, } from '../session/session-mapper.js';
|
|
50
55
|
/**
|
|
51
56
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
52
57
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -180,6 +185,8 @@ function getRenameName(args) {
|
|
|
180
185
|
}
|
|
181
186
|
function buildSessionPayload(session, name) {
|
|
182
187
|
const payload = { id: session.id, name };
|
|
188
|
+
if (session.lable)
|
|
189
|
+
payload.lable = session.lable;
|
|
183
190
|
if (session.agentSessionId)
|
|
184
191
|
payload.agentSessionId = session.agentSessionId;
|
|
185
192
|
return payload;
|
|
@@ -413,7 +420,10 @@ function hostCatalogArgs(args = {}) {
|
|
|
413
420
|
}
|
|
414
421
|
if (args.refresh !== undefined && typeof args.refresh !== 'boolean')
|
|
415
422
|
invalid('refresh must be a boolean');
|
|
416
|
-
|
|
423
|
+
const baseagent = menuStringArg(args, 'baseagent') ?? menuStringArg(args, 'ba');
|
|
424
|
+
if (!baseagent)
|
|
425
|
+
return invalid('baseagent is required (claude / codex / ecagent / gemini)');
|
|
426
|
+
return { baseagent, refresh: args.refresh === true };
|
|
417
427
|
}
|
|
418
428
|
const LEGACY_SESSION_CONFIG_ARG_KEYS = [
|
|
419
429
|
'session',
|
|
@@ -851,7 +861,7 @@ export function isProcessLevelOwner(peerId, owners) {
|
|
|
851
861
|
// ── 控制面双轨鉴权(见 docs/.../2026-06-10-control-channel-auth-design.md)──
|
|
852
862
|
// 白名单而非黑名单:默认安全,新增进程级 action 需显式加入。
|
|
853
863
|
/** /agent 的进程级 action:仅控制 channel 可执行。 */
|
|
854
|
-
export const PROCESS_LEVEL_AGENT_ACTIONS = new Set(['create', 'delete', 'enable', 'disable']);
|
|
864
|
+
export const PROCESS_LEVEL_AGENT_ACTIONS = new Set(['create', 'initialize', 'delete', 'enable', 'disable']);
|
|
855
865
|
/** /agent 的「本 agent 自管理」action:agent channel 仅 owner/admin 可对自身 aid 执行;update 另行收紧为 owner。 */
|
|
856
866
|
export const SELF_MANAGE_AGENT_ACTIONS = new Set(['update', 'reload']);
|
|
857
867
|
/** 判断 (cmdBase, action) 是否为进程级操作(仅控制 channel 可执行)。
|
|
@@ -898,6 +908,10 @@ function gateControlScope(opts) {
|
|
|
898
908
|
return null;
|
|
899
909
|
}
|
|
900
910
|
function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel = false) {
|
|
911
|
+
if (cmdBase === '/agent-data-root' || cmdBase === '/relation-data-root')
|
|
912
|
+
return null;
|
|
913
|
+
if (cmdBase === '/file' && args?.rootId !== undefined)
|
|
914
|
+
return null;
|
|
901
915
|
const payload = { ...(args ?? {}), ...(value !== undefined ? { value } : {}) };
|
|
902
916
|
const intent = (operation, scope = 'relation', extra) => ({
|
|
903
917
|
operation,
|
|
@@ -909,6 +923,9 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
909
923
|
if (verb === 'query') {
|
|
910
924
|
if (cmdBase === '/baseagent-models')
|
|
911
925
|
return intent('baseagent.models.list', 'process');
|
|
926
|
+
if (cmdBase === '/baseagents') {
|
|
927
|
+
return intent(fromControlChannel ? 'system.status' : 'agent.baseagent.list', fromControlChannel ? 'process' : 'agent');
|
|
928
|
+
}
|
|
912
929
|
if (cmdBase === '/pwd')
|
|
913
930
|
return intent('project.current', 'relation');
|
|
914
931
|
if (cmdBase === '/session' || cmdBase === '/s')
|
|
@@ -1019,10 +1036,15 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1019
1036
|
return intent('system.paircode', 'process', { action });
|
|
1020
1037
|
}
|
|
1021
1038
|
if (cmdBase === '/agent') {
|
|
1039
|
+
if (action === 'update' && args?.patch && Object.hasOwn(args.patch, 'avatar')) {
|
|
1040
|
+
return intent('ec.aid.avatar', 'agent', { action });
|
|
1041
|
+
}
|
|
1022
1042
|
if (action === 'reload')
|
|
1023
1043
|
return intent('agent.reload', fromControlChannel ? 'control' : 'agent', { action });
|
|
1024
1044
|
if (action === 'create')
|
|
1025
1045
|
return intent('agent.create', 'control', { action });
|
|
1046
|
+
if (action === 'initialize')
|
|
1047
|
+
return intent('agent.initialize', 'control', { action });
|
|
1026
1048
|
if (action === 'delete')
|
|
1027
1049
|
return intent('agent.delete', 'control', { action });
|
|
1028
1050
|
if (action === 'enable')
|
|
@@ -1063,6 +1085,7 @@ function buildMenuAuthSubject(owner, params) {
|
|
|
1063
1085
|
if (params.subject) {
|
|
1064
1086
|
return {
|
|
1065
1087
|
...params.subject,
|
|
1088
|
+
fromControlChannel: params.fromControlChannel || params.subject.fromControlChannel,
|
|
1066
1089
|
...(params.requestId && !params.subject.requestId ? { requestId: params.requestId } : {}),
|
|
1067
1090
|
...(params.session?.id && !params.subject.sessionId ? { sessionId: params.session.id } : {}),
|
|
1068
1091
|
};
|
|
@@ -1511,9 +1534,27 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1511
1534
|
});
|
|
1512
1535
|
return items;
|
|
1513
1536
|
}
|
|
1537
|
+
/** All Menu reads and writes must resolve the same transport-scoped topic. */
|
|
1538
|
+
async function loadMenuTopicSession(sessionManager, channel, channelId, threadId) {
|
|
1539
|
+
let session;
|
|
1540
|
+
try {
|
|
1541
|
+
if (typeof sessionManager.getThreadSession !== 'function')
|
|
1542
|
+
throw new Error('topic lookup unavailable');
|
|
1543
|
+
session = await sessionManager.getThreadSession(channel, channelId, threadId);
|
|
1544
|
+
}
|
|
1545
|
+
catch (error) {
|
|
1546
|
+
logger.debug(`[MenuHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1547
|
+
throw { code: 'TOPIC_SESSION_LOOKUP_FAILED', message: '无法读取话题会话,拒绝回退到主会话' };
|
|
1548
|
+
}
|
|
1549
|
+
if (!session)
|
|
1550
|
+
throw { code: 'TOPIC_SESSION_NOT_FOUND', message: '话题会话不存在,拒绝回退到主会话' };
|
|
1551
|
+
return session;
|
|
1552
|
+
}
|
|
1514
1553
|
/** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
|
|
1515
|
-
export async function getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
|
|
1516
|
-
const session =
|
|
1554
|
+
export async function getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
|
|
1555
|
+
const session = transportReplyContext?.threadId
|
|
1556
|
+
? await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId)
|
|
1557
|
+
: await this.sessionManager.getActiveSession(channel, channelId);
|
|
1517
1558
|
const subject = buildMenuAuthSubject(this, {
|
|
1518
1559
|
identity: overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session)),
|
|
1519
1560
|
session,
|
|
@@ -1617,19 +1658,23 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1617
1658
|
: await execAgentOptions(args);
|
|
1618
1659
|
if ('error' in res)
|
|
1619
1660
|
throw { code: res.code, message: res.error };
|
|
1620
|
-
return res.data.agents.map(ag => ({
|
|
1661
|
+
return res.data.agents.map(ag => ({
|
|
1662
|
+
value: ag.aid, label: ag.name || ag.aid, desc: ag.status, lifecycle: ag.lifecycle ?? null,
|
|
1663
|
+
}));
|
|
1621
1664
|
}
|
|
1622
1665
|
// agent channel:直接读取自身,避免先枚举全部 Agent 再过滤。
|
|
1623
1666
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1624
1667
|
if (!selfAid)
|
|
1625
1668
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1626
1669
|
const res = this.agentApplicationService
|
|
1627
|
-
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService)
|
|
1628
|
-
: await execAgentQuery({ aid: selfAid });
|
|
1670
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, subject)
|
|
1671
|
+
: await execAgentQuery({ aid: selfAid }, undefined, subject);
|
|
1629
1672
|
if ('error' in res)
|
|
1630
1673
|
throw { code: res.code, message: res.error };
|
|
1631
1674
|
const ag = res.data;
|
|
1632
|
-
return [{
|
|
1675
|
+
return [{
|
|
1676
|
+
value: ag.aid, label: ag.identity?.name || ag.name || ag.aid, desc: ag.status, lifecycle: ag.lifecycle ?? null,
|
|
1677
|
+
}];
|
|
1633
1678
|
}
|
|
1634
1679
|
if (cmd === '/capability') {
|
|
1635
1680
|
const type = args?.type;
|
|
@@ -1685,12 +1730,33 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1685
1730
|
preview: turn.userContent,
|
|
1686
1731
|
turn: turn.index,
|
|
1687
1732
|
assistantMessageId: turn.assistantUuid,
|
|
1733
|
+
assistantMessageIds: turn.assistantUuids,
|
|
1688
1734
|
userMessageId: turn.userUuid,
|
|
1735
|
+
userTextHash: turn.userTextHash,
|
|
1736
|
+
assistantTextHashes: turn.assistantTextHashes,
|
|
1689
1737
|
}));
|
|
1690
1738
|
}
|
|
1691
|
-
const
|
|
1739
|
+
const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
|
|
1740
|
+
let lable;
|
|
1741
|
+
if (hasLable) {
|
|
1742
|
+
try {
|
|
1743
|
+
lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
|
|
1744
|
+
}
|
|
1745
|
+
catch (error) {
|
|
1746
|
+
throw { code: 'INVALID_ARGS', message: error.message };
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
let archiveFilter;
|
|
1750
|
+
try {
|
|
1751
|
+
const archived = args?.archived === undefined && isArchivedTopicLable(lable) ? true : args?.archived;
|
|
1752
|
+
archiveFilter = resolveTopicArchiveFilter(archived);
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
throw { code: 'INVALID_ARGS', message: error.message };
|
|
1756
|
+
}
|
|
1757
|
+
const sessions = await this.sessionManager.listSessions(channel, channelId, { archiveFilter });
|
|
1692
1758
|
return sessions
|
|
1693
|
-
.filter((s) => !!s.threadId)
|
|
1759
|
+
.filter((s) => !!s.threadId && (!hasLable || s.lable === lable))
|
|
1694
1760
|
.map((s) => this.buildTopicMenuItem(s));
|
|
1695
1761
|
}
|
|
1696
1762
|
if (cmd === '/s' || cmd === '/session' || cmd === '/del') {
|
|
@@ -1735,7 +1801,12 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1735
1801
|
const available = requestedAgent?.name
|
|
1736
1802
|
? (this.getAvailableBaseagentsForOwner?.(requestedAgent.name) ?? [])
|
|
1737
1803
|
: this.getAvailableBaseagents(channel);
|
|
1738
|
-
|
|
1804
|
+
const inventory = await getBaseagentSnapshot({
|
|
1805
|
+
config: { active_baseagent: currentAgent }, loaded: available, probe: false,
|
|
1806
|
+
});
|
|
1807
|
+
return inventory.baseagents
|
|
1808
|
+
.filter(item => item.loaded === true)
|
|
1809
|
+
.map(item => ({ value: item.id, label: item.id, selected: item.id === currentAgent }));
|
|
1739
1810
|
}
|
|
1740
1811
|
if (cmd === '/model') {
|
|
1741
1812
|
const target = resolveMenuModelTarget.call(this, {
|
|
@@ -1894,7 +1965,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1894
1965
|
// 所有方法返回 { data } 或 { error, code? }。code 是结构化错误码(NO_ACTIVE_SESSION 等),
|
|
1895
1966
|
// 客户端可据此决定降级策略。message-bridge 把 code 透传到 menu.response。
|
|
1896
1967
|
/** menu.query — 查询当前值。 */
|
|
1897
|
-
export async function execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu') {
|
|
1968
|
+
export async function execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
|
|
1898
1969
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
1899
1970
|
if (!cmdBase)
|
|
1900
1971
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
@@ -1905,7 +1976,17 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1905
1976
|
const gated = gateControlScope.call(this, { cmdBase, args, channel, fromControlChannel });
|
|
1906
1977
|
if (gated)
|
|
1907
1978
|
return gated;
|
|
1908
|
-
const
|
|
1979
|
+
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
1980
|
+
const evolagent = loadedContext.evolagent;
|
|
1981
|
+
let session = loadedContext.session;
|
|
1982
|
+
if (transportReplyContext?.threadId) {
|
|
1983
|
+
try {
|
|
1984
|
+
session = await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId);
|
|
1985
|
+
}
|
|
1986
|
+
catch (error) {
|
|
1987
|
+
return menuResultFailure(error);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1909
1990
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1910
1991
|
const subject = buildMenuAuthSubject(this, {
|
|
1911
1992
|
identity,
|
|
@@ -1917,6 +1998,15 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1917
1998
|
fromControlChannel,
|
|
1918
1999
|
subject: authSubject,
|
|
1919
2000
|
});
|
|
2001
|
+
const storageRequest = { type: 'menu.query', cmd, args };
|
|
2002
|
+
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
2003
|
+
try {
|
|
2004
|
+
return { data: await agentStorageMenuService.execute(storageRequest, subject, this.agentStorageClientResolver) };
|
|
2005
|
+
}
|
|
2006
|
+
catch (error) {
|
|
2007
|
+
return dataRootFailure(error);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1920
2010
|
if (isRequesterRoleQuery) {
|
|
1921
2011
|
if (!subject.allowAccess) {
|
|
1922
2012
|
return { error: `Role ${subject.role} is not allowed to access this agent`, code: 'ROLE_ACCESS_DENIED' };
|
|
@@ -1985,6 +2075,28 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1985
2075
|
});
|
|
1986
2076
|
if (authDenied)
|
|
1987
2077
|
return authDenied;
|
|
2078
|
+
if (cmdBase === '/agent-data-root') {
|
|
2079
|
+
if (args && Object.keys(args).length > 0) {
|
|
2080
|
+
return { error: 'agent-data-root does not accept args', code: 'INVALID_ARGUMENT' };
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
return { data: dataRootResolver.issueAgentRoot(subject) };
|
|
2084
|
+
}
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
return dataRootFailure(error);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (cmdBase === '/relation-data-root') {
|
|
2090
|
+
if (args && Object.keys(args).some(key => key !== 'peerAid')) {
|
|
2091
|
+
return { error: 'relation-data-root only accepts args.peerAid', code: 'INVALID_ARGUMENT' };
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
return { data: dataRootResolver.issueRelationRoot(subject, args?.peerAid) };
|
|
2095
|
+
}
|
|
2096
|
+
catch (error) {
|
|
2097
|
+
return dataRootFailure(error);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
1988
2100
|
if (cmdBase === '/baseagent-models') {
|
|
1989
2101
|
try {
|
|
1990
2102
|
const query = hostCatalogArgs(args);
|
|
@@ -2003,8 +2115,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2003
2115
|
if (fromControlChannel) {
|
|
2004
2116
|
if (args?.aid) {
|
|
2005
2117
|
return this.agentApplicationService
|
|
2006
|
-
? await execAgentQuery(args, this.agentApplicationService)
|
|
2007
|
-
: await execAgentQuery(args);
|
|
2118
|
+
? await execAgentQuery(args, this.agentApplicationService, subject)
|
|
2119
|
+
: await execAgentQuery(args, undefined, subject);
|
|
2008
2120
|
}
|
|
2009
2121
|
return this.agentApplicationService
|
|
2010
2122
|
? await execAgentOptions(args, this.agentApplicationService)
|
|
@@ -2014,8 +2126,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2014
2126
|
if (!selfAid)
|
|
2015
2127
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2016
2128
|
return this.agentApplicationService
|
|
2017
|
-
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService)
|
|
2018
|
-
: await execAgentQuery({ ...(args ?? {}), aid: selfAid });
|
|
2129
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, subject)
|
|
2130
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, subject);
|
|
2019
2131
|
}
|
|
2020
2132
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
2021
2133
|
if (cmdBase === '/gateway') {
|
|
@@ -2095,6 +2207,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2095
2207
|
if (!session) {
|
|
2096
2208
|
return { data: { status: 'no-session' } };
|
|
2097
2209
|
}
|
|
2210
|
+
if (args?.view === 'subtasks') {
|
|
2211
|
+
return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(session.id) } };
|
|
2212
|
+
}
|
|
2098
2213
|
const sessionKey = this.getQueueKey(session, channel, channelId);
|
|
2099
2214
|
const sessionAgent = this.getAgent(channel, session.baseagent);
|
|
2100
2215
|
const isProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
@@ -2117,6 +2232,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2117
2232
|
status: isProcessing ? 'processing' : 'idle',
|
|
2118
2233
|
createdAt: session.createdAt,
|
|
2119
2234
|
updatedAt: session.updatedAt,
|
|
2235
|
+
subtasks: this.sessionManager.getRunnerTaskHistory(session.id).length,
|
|
2236
|
+
subtasksScope: 'retained-history',
|
|
2120
2237
|
};
|
|
2121
2238
|
if (processingDuration !== undefined)
|
|
2122
2239
|
data.processingDuration = processingDuration;
|
|
@@ -2132,6 +2249,21 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2132
2249
|
data.lastError = { type: health.lastErrorType || 'unknown', message: health.lastError.substring(0, 100) };
|
|
2133
2250
|
return { data };
|
|
2134
2251
|
}
|
|
2252
|
+
if (cmdBase === '/baseagents') {
|
|
2253
|
+
if (args && Object.keys(args).length > 0) {
|
|
2254
|
+
return { error: 'baseagents does not accept query arguments', code: 'INVALID_ARGUMENT' };
|
|
2255
|
+
}
|
|
2256
|
+
if (fromControlChannel) {
|
|
2257
|
+
return { data: await getBaseagentSnapshot({ loaded: loadedBaseagentIds(this.agentMap.keys()) }) };
|
|
2258
|
+
}
|
|
2259
|
+
if (!evolagent)
|
|
2260
|
+
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2261
|
+
return { data: await getBaseagentSnapshot({
|
|
2262
|
+
config: evolagent.config ?? {},
|
|
2263
|
+
loaded: this.getAvailableBaseagents(channel),
|
|
2264
|
+
probe: false,
|
|
2265
|
+
}) };
|
|
2266
|
+
}
|
|
2135
2267
|
if (cmdBase === '/topic') {
|
|
2136
2268
|
const target = (args?.target ?? '').toString().trim();
|
|
2137
2269
|
if (!target)
|
|
@@ -2139,6 +2271,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2139
2271
|
const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
|
|
2140
2272
|
if (!topic)
|
|
2141
2273
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
2274
|
+
if (args?.view === 'subtasks') {
|
|
2275
|
+
return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(topic.id) } };
|
|
2276
|
+
}
|
|
2142
2277
|
const sessionKey = this.getQueueKey(topic, channel, channelId);
|
|
2143
2278
|
const sessionAgent = this.getAgent(channel, topic.baseagent);
|
|
2144
2279
|
const isProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
@@ -2157,10 +2292,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2157
2292
|
const data = {
|
|
2158
2293
|
threadId: topic.threadId,
|
|
2159
2294
|
name: topic.name || null,
|
|
2295
|
+
lable: topic.lable || null,
|
|
2160
2296
|
agentSessionId: topic.agentSessionId || null,
|
|
2161
2297
|
status: isProcessing ? 'processing' : 'idle',
|
|
2162
2298
|
createdAt: topic.createdAt,
|
|
2163
2299
|
updatedAt: topic.updatedAt,
|
|
2300
|
+
subtasks: this.sessionManager.getRunnerTaskHistory(topic.id).length,
|
|
2301
|
+
subtasksScope: 'retained-history',
|
|
2164
2302
|
};
|
|
2165
2303
|
if (processingDuration !== undefined)
|
|
2166
2304
|
data.processingDuration = processingDuration;
|
|
@@ -2436,24 +2574,41 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2436
2574
|
if (ecwebPkg?.version)
|
|
2437
2575
|
data.ecwebVersion = ecwebPkg.version;
|
|
2438
2576
|
// 收集主机级可用的所有 baseagent 类型(检测已安装的 CLI + 版本)
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2577
|
+
const inventory = await getBaseagentSnapshot({
|
|
2578
|
+
loaded: loadedBaseagentIds(this.agentMap.keys()),
|
|
2579
|
+
});
|
|
2580
|
+
data.baseagents = inventory.baseagents.map(item => ({
|
|
2581
|
+
...item, name: item.id,
|
|
2582
|
+
version: item.version ?? (item.detected && item.id !== 'ecagent' ? getBaseagentVersion(item.id) : null),
|
|
2583
|
+
}));
|
|
2446
2584
|
return { data };
|
|
2447
2585
|
}
|
|
2448
2586
|
// ── name=file:文件元信息(§5.1) ──
|
|
2449
2587
|
// operation 权限已在入口统一判定;项目外文件仍仅 owner 可取。
|
|
2450
2588
|
if (cmdBase === '/file') {
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2589
|
+
let realPath;
|
|
2590
|
+
let stat;
|
|
2591
|
+
let responsePath = (args?.path ?? '').toString();
|
|
2592
|
+
let dataRoot;
|
|
2593
|
+
if (args?.rootId !== undefined) {
|
|
2594
|
+
try {
|
|
2595
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, args?.path, 'file');
|
|
2596
|
+
({ realPath, stat } = resolved);
|
|
2597
|
+
responsePath = resolved.relativePath;
|
|
2598
|
+
dataRoot = resolved;
|
|
2599
|
+
}
|
|
2600
|
+
catch (error) {
|
|
2601
|
+
return dataRootFailure(error);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
else {
|
|
2605
|
+
const role = identity.role;
|
|
2606
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
2607
|
+
const resolved = resolveMenuFilePath(args?.path, projectPath, role, 'file');
|
|
2608
|
+
if ('error' in resolved)
|
|
2609
|
+
return resolved;
|
|
2610
|
+
({ realPath, stat } = resolved);
|
|
2611
|
+
}
|
|
2457
2612
|
// sha256 仅对 ≤ 2 MB 文件计算,超过返回 null,客户端降级到 size+mtime(§4、§7 决策 2)
|
|
2458
2613
|
let sha256 = null;
|
|
2459
2614
|
if (stat.size <= FILE_HASH_MAX_SIZE) {
|
|
@@ -2466,7 +2621,16 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2466
2621
|
}
|
|
2467
2622
|
return {
|
|
2468
2623
|
data: {
|
|
2469
|
-
path:
|
|
2624
|
+
path: responsePath,
|
|
2625
|
+
...(dataRoot ? {
|
|
2626
|
+
root: {
|
|
2627
|
+
scope: dataRoot.scope,
|
|
2628
|
+
targetAid: dataRoot.targetAid,
|
|
2629
|
+
...(dataRoot.peerAid ? { peerAid: dataRoot.peerAid } : {}),
|
|
2630
|
+
...(dataRoot.peerKey ? { peerKey: dataRoot.peerKey } : {}),
|
|
2631
|
+
expiresAt: dataRoot.expiresAt,
|
|
2632
|
+
},
|
|
2633
|
+
} : {}),
|
|
2470
2634
|
sha256,
|
|
2471
2635
|
size: stat.size,
|
|
2472
2636
|
mtime: stat.mtimeMs,
|
|
@@ -2495,17 +2659,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2495
2659
|
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
2496
2660
|
let session = loadedContext.session;
|
|
2497
2661
|
const evolagent = loadedContext.evolagent;
|
|
2498
|
-
if (transportReplyContext?.threadId
|
|
2662
|
+
if (transportReplyContext?.threadId) {
|
|
2499
2663
|
try {
|
|
2500
|
-
|
|
2501
|
-
if (!topicSession) {
|
|
2502
|
-
return { error: '话题会话不存在,拒绝回退到主会话', code: 'TOPIC_SESSION_NOT_FOUND' };
|
|
2503
|
-
}
|
|
2504
|
-
session = topicSession;
|
|
2664
|
+
session = await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId);
|
|
2505
2665
|
}
|
|
2506
2666
|
catch (error) {
|
|
2507
|
-
|
|
2508
|
-
return { error: '无法读取话题会话,拒绝回退到主会话', code: 'TOPIC_SESSION_LOOKUP_FAILED' };
|
|
2667
|
+
return menuResultFailure(error);
|
|
2509
2668
|
}
|
|
2510
2669
|
}
|
|
2511
2670
|
if (cmdBase === '/baseagent-models')
|
|
@@ -2784,7 +2943,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2784
2943
|
if ('error' in target)
|
|
2785
2944
|
return target;
|
|
2786
2945
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2787
|
-
|
|
2946
|
+
const requestedModel = arg;
|
|
2788
2947
|
if (hasModelSwitcher(agent)) {
|
|
2789
2948
|
const catalog = await getCatalog(target.sel.self, target.baseagent);
|
|
2790
2949
|
assertCatalogSelectable(catalog);
|
|
@@ -2792,20 +2951,22 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2792
2951
|
const decision = validateModelSelectionForRole({
|
|
2793
2952
|
role: identity.role,
|
|
2794
2953
|
baseagent: target.baseagent,
|
|
2795
|
-
requestedModel
|
|
2954
|
+
requestedModel,
|
|
2796
2955
|
models,
|
|
2797
2956
|
selfAid: target.sel.self,
|
|
2798
2957
|
resolveModelId: model => resolveCatalogModel(catalog, model),
|
|
2799
2958
|
});
|
|
2800
2959
|
if (!decision.ok)
|
|
2801
2960
|
return { error: decision.message || `invalid model: ${arg}`, code: decision.code || 'INVALID_VALUE' };
|
|
2802
|
-
|
|
2803
|
-
|
|
2961
|
+
// Validate the resolved ID, but persist/echo the selected value so aliases
|
|
2962
|
+
// such as `sonnet` keep following the catalog instead of pinning a version.
|
|
2963
|
+
const resolvedModel = decision.model || requestedModel;
|
|
2964
|
+
if (models.length && !models.includes(resolvedModel)) {
|
|
2804
2965
|
return { error: `无效模型: ${arg}`, code: 'INVALID_VALUE' };
|
|
2805
2966
|
}
|
|
2806
2967
|
}
|
|
2807
2968
|
try {
|
|
2808
|
-
writeMenuModel(target,
|
|
2969
|
+
writeMenuModel(target, requestedModel);
|
|
2809
2970
|
}
|
|
2810
2971
|
catch (e) {
|
|
2811
2972
|
return { error: e?.message || String(e), code: e?.code || 'CONFIG_WRITE_FAILED', ...(e?.data !== undefined ? { data: e.data } : {}) };
|
|
@@ -2815,12 +2976,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2815
2976
|
sessionId: session?.id,
|
|
2816
2977
|
agentName: evolagent?.name,
|
|
2817
2978
|
baseagent: target.baseagent,
|
|
2818
|
-
model:
|
|
2979
|
+
model: requestedModel,
|
|
2819
2980
|
timestamp: Date.now(),
|
|
2820
2981
|
});
|
|
2821
2982
|
return {
|
|
2822
2983
|
data: {
|
|
2823
|
-
model:
|
|
2984
|
+
model: requestedModel,
|
|
2824
2985
|
baseagent: target.baseagent,
|
|
2825
2986
|
scope: target.scope,
|
|
2826
2987
|
field: target.fieldPath,
|
|
@@ -3044,6 +3205,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3044
3205
|
fromControlChannel,
|
|
3045
3206
|
subject: authSubject,
|
|
3046
3207
|
});
|
|
3208
|
+
const storageRequest = { type: 'menu.action', cmd, action, args };
|
|
3209
|
+
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
3210
|
+
try {
|
|
3211
|
+
return { data: await agentStorageMenuService.execute(storageRequest, subject, this.agentStorageClientResolver) };
|
|
3212
|
+
}
|
|
3213
|
+
catch (error) {
|
|
3214
|
+
return dataRootFailure(error);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3047
3217
|
if (cmdBase === '/system' && fromControlChannel && !subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
3048
3218
|
return {
|
|
3049
3219
|
error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
@@ -3123,7 +3293,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3123
3293
|
}
|
|
3124
3294
|
const isMenuAdmin = isManagementRole(authIdentity.role);
|
|
3125
3295
|
// ── /aid avatar ──
|
|
3126
|
-
//
|
|
3296
|
+
// Canonical avatar action; legacy agent/update patch.avatar reuses the same uploader.
|
|
3127
3297
|
if (cmdBase === '/aid') {
|
|
3128
3298
|
if (action !== 'avatar')
|
|
3129
3299
|
return { error: `不支持 aid action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
@@ -3265,7 +3435,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3265
3435
|
}
|
|
3266
3436
|
}
|
|
3267
3437
|
}
|
|
3268
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService);
|
|
3438
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, subject);
|
|
3269
3439
|
}
|
|
3270
3440
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3271
3441
|
if (cmdBase === '/trigger') {
|
|
@@ -3310,6 +3480,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3310
3480
|
targetThreadId: args.targetThreadId,
|
|
3311
3481
|
agentId: args.agentId,
|
|
3312
3482
|
model: args.model,
|
|
3483
|
+
executionTimeoutMs: args.executionTimeoutMs,
|
|
3313
3484
|
effort: args.effort,
|
|
3314
3485
|
permissionMode: args.permissionMode,
|
|
3315
3486
|
triggerThread,
|
|
@@ -3447,6 +3618,16 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3447
3618
|
return { error: `不支持的 trigger action: ${action}`, code: 'INVALID_ARGS' };
|
|
3448
3619
|
}
|
|
3449
3620
|
if (cmdBase === '/topic') {
|
|
3621
|
+
const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
|
|
3622
|
+
let lable;
|
|
3623
|
+
if (hasLable && (action === 'fork' || action === 'label')) {
|
|
3624
|
+
try {
|
|
3625
|
+
lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
|
|
3626
|
+
}
|
|
3627
|
+
catch (error) {
|
|
3628
|
+
return { error: error.message, code: 'INVALID_ARGS' };
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3450
3631
|
if (action === 'fork') {
|
|
3451
3632
|
if (!this.canReadTopics(identity.role))
|
|
3452
3633
|
return { error: '无权限创建话题分支', code: 'FORBIDDEN' };
|
|
@@ -3523,21 +3704,24 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3523
3704
|
const executeFork = async (current, currentAgent) => {
|
|
3524
3705
|
const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
|
|
3525
3706
|
const turns = buildSessionTurnList(messages);
|
|
3526
|
-
|
|
3707
|
+
// fork-turns resolves bubble aliases to this canonical, complete-turn
|
|
3708
|
+
// anchor. Never fall back to a runner iteration or a guessed index.
|
|
3709
|
+
const forkTurn = turns.find(turn => turn.assistantUuid === sourceAssistantMessageId);
|
|
3527
3710
|
if (!forkTurn)
|
|
3528
3711
|
return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
|
|
3529
|
-
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath,
|
|
3712
|
+
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, forkTurn.assistantUuid, name || undefined, { sessionId: current.id, permissionMode: subject.permissionMode, role: subject.role });
|
|
3530
3713
|
const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
|
|
3531
3714
|
name: name || undefined,
|
|
3715
|
+
...(hasLable ? { lable: lable ?? null } : {}),
|
|
3532
3716
|
creatorPeerId: userId,
|
|
3533
|
-
sourceAssistantMessageId,
|
|
3717
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3534
3718
|
sourceTurn: forkTurn.index,
|
|
3535
3719
|
requestId,
|
|
3536
3720
|
});
|
|
3537
3721
|
await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
|
|
3538
3722
|
evolcoreSessionId: topic.id,
|
|
3539
3723
|
sourceSessionId: current.id,
|
|
3540
|
-
sourceAssistantMessageId,
|
|
3724
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3541
3725
|
sourceTurn: forkTurn.index,
|
|
3542
3726
|
}).catch((error) => {
|
|
3543
3727
|
logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
|
|
@@ -3548,7 +3732,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3548
3732
|
sourceSessionId: current.id,
|
|
3549
3733
|
name: topic.name,
|
|
3550
3734
|
threadId: topic.threadId,
|
|
3551
|
-
sourceAssistantMessageId,
|
|
3735
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3552
3736
|
sourceTurn: forkTurn.index,
|
|
3553
3737
|
});
|
|
3554
3738
|
return {
|
|
@@ -3560,7 +3744,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3560
3744
|
threadId: topic.threadId,
|
|
3561
3745
|
sourceSessionId: current.id,
|
|
3562
3746
|
sourceThreadId: current.threadId || null,
|
|
3563
|
-
sourceAssistantMessageId,
|
|
3747
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3564
3748
|
sourceTurn: forkTurn.index,
|
|
3565
3749
|
},
|
|
3566
3750
|
},
|
|
@@ -3649,7 +3833,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3649
3833
|
topicForkTargetsInFlight.delete(targetKey);
|
|
3650
3834
|
}
|
|
3651
3835
|
}
|
|
3652
|
-
if (action !== 'delete' && action !== 'rename') {
|
|
3836
|
+
if (action !== 'delete' && action !== 'rename' && action !== 'label') {
|
|
3653
3837
|
return { error: `不支持的 topic action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3654
3838
|
}
|
|
3655
3839
|
const target = (args?.target ?? '').toString().trim();
|
|
@@ -3658,6 +3842,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3658
3842
|
const renameName = action === 'rename' ? getRenameName(args) : '';
|
|
3659
3843
|
if (action === 'rename' && !renameName)
|
|
3660
3844
|
return { error: '缺少 args.name', code: 'MISSING_VALUE' };
|
|
3845
|
+
if (action === 'label' && !hasLable)
|
|
3846
|
+
return { error: '缺少 args.lable', code: 'MISSING_VALUE' };
|
|
3661
3847
|
const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
|
|
3662
3848
|
if (!topic)
|
|
3663
3849
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
@@ -3667,7 +3853,18 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3667
3853
|
? 'private'
|
|
3668
3854
|
: this.resolveMenuChatType(channel, channelId, explicitChatType);
|
|
3669
3855
|
if (!this.canDeleteTopic(identity.role, chatType, topic, userId)) {
|
|
3670
|
-
return { error: action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
|
|
3856
|
+
return { error: action === 'label' ? '无权限修改话题分类' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
|
|
3857
|
+
}
|
|
3858
|
+
if (action === 'label') {
|
|
3859
|
+
const success = await this.sessionManager.setTopicLable(topic.id, lable ?? null);
|
|
3860
|
+
if (!success)
|
|
3861
|
+
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
3862
|
+
return {
|
|
3863
|
+
data: {
|
|
3864
|
+
action: 'label', success: true,
|
|
3865
|
+
topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, lable: lable ?? null },
|
|
3866
|
+
},
|
|
3867
|
+
};
|
|
3671
3868
|
}
|
|
3672
3869
|
if (action === 'rename') {
|
|
3673
3870
|
const newName = renameName;
|
|
@@ -3907,9 +4104,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3907
4104
|
}
|
|
3908
4105
|
if (action === 'paircode') {
|
|
3909
4106
|
// Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
|
|
3910
|
-
// the local IPC endpoint.
|
|
3911
|
-
|
|
3912
|
-
const pair = remoteAccessService.getPairingCode(subject.actorId);
|
|
4107
|
+
// the local IPC endpoint. Each successful redemption consumes the code.
|
|
4108
|
+
const pair = remoteAccessService.getPairingCode(subject.actorId, Date.now(), source === 'ecweb' ? 'ecweb-local' : 'menu');
|
|
3913
4109
|
return { data: { code: pair.code, expiresAt: pair.expiresAt } };
|
|
3914
4110
|
}
|
|
3915
4111
|
if (action === 'check') {
|
|
@@ -4043,6 +4239,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4043
4239
|
},
|
|
4044
4240
|
});
|
|
4045
4241
|
if (!decision.allow) {
|
|
4242
|
+
// Keep unsupported runner fields in the aggregate so the CLI can
|
|
4243
|
+
// return a per-field error while still returning supported values.
|
|
4244
|
+
if (isBatchGet
|
|
4245
|
+
&& decision.code === 'UNSUPPORTED_FIELD'
|
|
4246
|
+
&& parsed.resolvedConfigCommand?.kind === 'field'
|
|
4247
|
+
&& parsed.resolvedConfigCommand.subcommand === 'get'
|
|
4248
|
+
&& isUnsupportedBaseagentConfigField(parsed.resolvedConfigCommand.field)) {
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4046
4251
|
return { error: decision.reason, code: decision.code };
|
|
4047
4252
|
}
|
|
4048
4253
|
authorizedCommands.push({ parsed, decision });
|
|
@@ -4074,26 +4279,78 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4074
4279
|
// fetch 文件作为独立 result.file 消息异步发回;把请求 id 作为 correlationId 透传,
|
|
4075
4280
|
// 客户端用它把异步到达的文件消息对回这次 fetch 点击。
|
|
4076
4281
|
if (cmdBase === '/file') {
|
|
4077
|
-
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4078
4282
|
if (action === 'list') {
|
|
4079
4283
|
const dirArg = (args?.path ?? '.').toString().trim() || '.';
|
|
4080
|
-
const resolved = resolveMenuFilePath(dirArg, projectPath, identity.role, 'directory');
|
|
4081
|
-
if ('error' in resolved)
|
|
4082
|
-
return resolved;
|
|
4083
4284
|
const offset = parseFileListOffset(args?.offset);
|
|
4084
4285
|
const limit = parseFileListLimit(args?.limit);
|
|
4085
4286
|
const includeHidden = args?.includeHidden === true;
|
|
4086
|
-
|
|
4287
|
+
let listed;
|
|
4288
|
+
let responsePath = dirArg;
|
|
4289
|
+
let dataRoot;
|
|
4290
|
+
if (args?.rootId !== undefined) {
|
|
4291
|
+
try {
|
|
4292
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, dirArg, 'directory');
|
|
4293
|
+
listed = listDirectory(resolved.realPath, {
|
|
4294
|
+
offset,
|
|
4295
|
+
limit,
|
|
4296
|
+
includeHidden,
|
|
4297
|
+
projectPath: resolved.rootPath,
|
|
4298
|
+
role: undefined,
|
|
4299
|
+
});
|
|
4300
|
+
responsePath = resolved.relativePath;
|
|
4301
|
+
dataRoot = resolved;
|
|
4302
|
+
}
|
|
4303
|
+
catch (error) {
|
|
4304
|
+
return dataRootFailure(error);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
else {
|
|
4308
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4309
|
+
const resolved = resolveMenuFilePath(dirArg, projectPath, identity.role, 'directory');
|
|
4310
|
+
if ('error' in resolved)
|
|
4311
|
+
return resolved;
|
|
4312
|
+
listed = listDirectory(resolved.realPath, { offset, limit, includeHidden, projectPath: resolved.projectPath, role: identity.role });
|
|
4313
|
+
}
|
|
4087
4314
|
if ('error' in listed)
|
|
4088
4315
|
return listed;
|
|
4089
|
-
return {
|
|
4316
|
+
return {
|
|
4317
|
+
data: {
|
|
4318
|
+
path: responsePath,
|
|
4319
|
+
...(dataRoot ? {
|
|
4320
|
+
root: {
|
|
4321
|
+
scope: dataRoot.scope,
|
|
4322
|
+
targetAid: dataRoot.targetAid,
|
|
4323
|
+
...(dataRoot.peerAid ? { peerAid: dataRoot.peerAid } : {}),
|
|
4324
|
+
...(dataRoot.peerKey ? { peerKey: dataRoot.peerKey } : {}),
|
|
4325
|
+
expiresAt: dataRoot.expiresAt,
|
|
4326
|
+
},
|
|
4327
|
+
} : {}),
|
|
4328
|
+
...listed.data,
|
|
4329
|
+
},
|
|
4330
|
+
};
|
|
4090
4331
|
}
|
|
4091
4332
|
if (action !== 'fetch')
|
|
4092
4333
|
return { error: `不支持的 file action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4334
|
+
let realPath;
|
|
4335
|
+
let stat;
|
|
4336
|
+
let dataRoot;
|
|
4337
|
+
if (args?.rootId !== undefined) {
|
|
4338
|
+
try {
|
|
4339
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, args?.path, 'file');
|
|
4340
|
+
({ realPath, stat } = resolved);
|
|
4341
|
+
dataRoot = resolved;
|
|
4342
|
+
}
|
|
4343
|
+
catch (error) {
|
|
4344
|
+
return dataRootFailure(error);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
else {
|
|
4348
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4349
|
+
const resolved = resolveMenuFilePath(args?.path, projectPath, identity.role, 'file');
|
|
4350
|
+
if ('error' in resolved)
|
|
4351
|
+
return resolved;
|
|
4352
|
+
({ realPath, stat } = resolved);
|
|
4353
|
+
}
|
|
4097
4354
|
if (stat.size > FILE_FETCH_MAX_SIZE) {
|
|
4098
4355
|
return { error: `文件过大: ${(stat.size / 1024 / 1024).toFixed(1)} MB (限制 ${FILE_FETCH_MAX_SIZE / 1024 / 1024} MB)`, code: 'FILE_TOO_LARGE' };
|
|
4099
4356
|
}
|
|
@@ -4114,15 +4371,21 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4114
4371
|
}
|
|
4115
4372
|
: sessionReplyContext;
|
|
4116
4373
|
const sendFile = async () => {
|
|
4117
|
-
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath, correlationId: requestId });
|
|
4374
|
+
const receipt = await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath, correlationId: requestId });
|
|
4375
|
+
if (receipt && typeof receipt === 'object' && receipt.status === 'failed') {
|
|
4376
|
+
throw Object.assign(new Error(receipt.error || '文件发送失败'), {
|
|
4377
|
+
code: receipt.code ?? 'EXEC_FAILED',
|
|
4378
|
+
outboxId: receipt.outboxId,
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4118
4381
|
};
|
|
4119
4382
|
if (deferSideEffect) {
|
|
4120
4383
|
deferSideEffect(sendFile);
|
|
4121
|
-
return { data: { accepted: true } };
|
|
4384
|
+
return { data: { accepted: true, ...(dataRoot ? { scope: dataRoot.scope, expiresAt: dataRoot.expiresAt } : {}) } };
|
|
4122
4385
|
}
|
|
4123
4386
|
try {
|
|
4124
4387
|
await sendFile();
|
|
4125
|
-
return { data: { accepted: true } };
|
|
4388
|
+
return { data: { accepted: true, ...(dataRoot ? { scope: dataRoot.scope, expiresAt: dataRoot.expiresAt } : {}) } };
|
|
4126
4389
|
}
|
|
4127
4390
|
catch (e) {
|
|
4128
4391
|
return { error: `文件发送失败: ${e?.message ?? e}`, code: 'EXEC_FAILED' };
|
|
@@ -4179,8 +4442,8 @@ function menuCommandToken(cmd) {
|
|
|
4179
4442
|
}
|
|
4180
4443
|
function isProcessLevelMenu(name, cmd) {
|
|
4181
4444
|
const cmdToken = menuCommandToken(cmd);
|
|
4182
|
-
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config' || name === 'baseagent-models'
|
|
4183
|
-
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config' || cmdToken === '/baseagent-models';
|
|
4445
|
+
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config' || name === 'baseagent-models' || name === 'baseagents'
|
|
4446
|
+
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config' || cmdToken === '/baseagent-models' || cmdToken === '/baseagents';
|
|
4184
4447
|
}
|
|
4185
4448
|
function isProcessContextMenu(payload) {
|
|
4186
4449
|
const isCatalog = payload?.type === 'menu.list' && payload?.args?.view === 'catalog';
|
|
@@ -4200,8 +4463,15 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4200
4463
|
const isFileFetch = (payload?.type === 'menu.query'
|
|
4201
4464
|
|| (payload?.type === 'menu.action' && payload?.action === 'fetch'))
|
|
4202
4465
|
&& (name === 'file' || compatCmd === '/file');
|
|
4203
|
-
|
|
4204
|
-
|
|
4466
|
+
const isDataRootQuery = payload?.type === 'menu.query'
|
|
4467
|
+
&& (name === 'agent-data-root'
|
|
4468
|
+
|| name === 'relation-data-root'
|
|
4469
|
+
|| compatCmd === '/agent-data-root'
|
|
4470
|
+
|| compatCmd === '/relation-data-root');
|
|
4471
|
+
const isRootedFile = (name === 'file' || compatCmd === '/file')
|
|
4472
|
+
&& typeof payload?.args?.rootId === 'string';
|
|
4473
|
+
if (isFileFetch || isDataRootQuery || isRootedFile || isAgentStorageMenuRequest(payload ?? {})) {
|
|
4474
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', 'Host/control 不支持数据根文件访问,请通过目标 Agent AID 请求');
|
|
4205
4475
|
}
|
|
4206
4476
|
if (name === 'cli' || compatCmd === '/cli') {
|
|
4207
4477
|
const message = context.source === 'ecweb'
|
|
@@ -4243,6 +4513,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4243
4513
|
conversationId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
|
|
4244
4514
|
processOwners: context.owners,
|
|
4245
4515
|
fromControlChannel: true,
|
|
4516
|
+
ecwebManagement: context.source === 'ecweb',
|
|
4246
4517
|
canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
|
|
4247
4518
|
trustedProcessRole: context.source === 'ecweb' ? 'daemon-owner' : undefined,
|
|
4248
4519
|
});
|