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