evolcore 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/claude-runner.js +35 -2
- package/dist/agents/codex-app-server-client.js +120 -14
- package/dist/agents/codex-runner.js +273 -35
- package/dist/agents/ecagent-runner.js +36 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/channels/aun.js +288 -108
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +170 -6
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +31 -31
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +4 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +30 -7
- package/dist/core/auth/operation-catalog.js +187 -25
- package/dist/core/bootstrap-messages.js +18 -0
- package/dist/core/bootstrap-service.js +63 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +147 -35
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +41 -21
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +236 -78
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +1 -0
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/tool-policy.js +73 -30
- package/dist/core/protected-paths.js +7 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +384 -98
- package/dist/ipc.js +218 -9
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +22 -0
- package/kits/templates/roles/member.json +27 -0
- package/kits/templates/roles/visitor.json +33 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -15,7 +15,7 @@ import { execFileSync } from 'child_process';
|
|
|
15
15
|
import { CronExpressionParser } from 'cron-parser';
|
|
16
16
|
import { parseDuration } from '../../trigger/parser.js';
|
|
17
17
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
18
|
-
import { commandExists } from '../../utils/cross-platform.js';
|
|
18
|
+
import { commandExists, spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
19
19
|
import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
20
20
|
import { WEB_PACKAGE_NAME } from '../../product.js';
|
|
21
21
|
import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
@@ -36,11 +36,11 @@ import { isManagementRole } from '../../config/builtin-roles.js';
|
|
|
36
36
|
import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
|
|
37
37
|
import { logger } from '../../utils/logger.js';
|
|
38
38
|
import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
39
|
-
import {
|
|
40
|
-
import { menuFailure, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest } from './menu-protocol.js';
|
|
39
|
+
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
41
40
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
42
41
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
43
42
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
43
|
+
import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
44
44
|
/**
|
|
45
45
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
46
46
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -60,6 +60,7 @@ function getBaseagentVersion(cmd) {
|
|
|
60
60
|
encoding: 'utf-8',
|
|
61
61
|
timeout: 3000,
|
|
62
62
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
windowsHide: process.platform === 'win32',
|
|
63
64
|
});
|
|
64
65
|
const versionOutput = String(output).trim();
|
|
65
66
|
// claude: "2.1.187 (Claude Code)" → 提取 "2.1.187"
|
|
@@ -880,7 +881,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
880
881
|
if (cmdBase === '/topic')
|
|
881
882
|
return intent('session.topic.current', 'relation');
|
|
882
883
|
if (cmdBase === '/baseagent')
|
|
883
|
-
return intent('agent.baseagent.current', fromControlChannel ? 'agent' : 'relation');
|
|
884
|
+
return intent('agent.baseagent.current', fromControlChannel || args?.scope === 'agent' ? 'agent' : 'relation');
|
|
884
885
|
if (cmdBase === '/file')
|
|
885
886
|
return intent('file.fetch', 'filesystem', { filePath: args?.path });
|
|
886
887
|
if (cmdBase === '/model')
|
|
@@ -938,6 +939,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
938
939
|
return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
939
940
|
}
|
|
940
941
|
if (verb === 'update') {
|
|
942
|
+
if (cmdBase === '/baseagent')
|
|
943
|
+
return intent('agent.baseagent.update', 'agent', { baseagent: value });
|
|
941
944
|
if (cmdBase === '/model')
|
|
942
945
|
return intent('model.use', modelScope, { model: value });
|
|
943
946
|
if (cmdBase === '/effort')
|
|
@@ -946,6 +949,10 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
946
949
|
return intent('chatmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
|
|
947
950
|
if (cmdBase === '/mentionmode')
|
|
948
951
|
return intent('mentionmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
|
|
952
|
+
if (cmdBase === '/activity')
|
|
953
|
+
return intent('activity.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
|
|
954
|
+
if (cmdBase === '/observable')
|
|
955
|
+
return intent('observable.update', 'agent', { value });
|
|
949
956
|
if (cmdBase === '/gateway')
|
|
950
957
|
return intent('gateway.write', 'process');
|
|
951
958
|
if (cmdBase === '/config')
|
|
@@ -1089,6 +1096,9 @@ function buildRoleMenuContext(owner, channel, subject) {
|
|
|
1089
1096
|
}
|
|
1090
1097
|
const aunAvailable = (owningAgent?.channelInstanceNames?.() ?? [])
|
|
1091
1098
|
.some((channelName) => owner.hasRegisteredChannel?.(channelName, 'aun') === true);
|
|
1099
|
+
const aunChannelName = (owningAgent?.channelInstanceNames?.() ?? [])
|
|
1100
|
+
.find((name) => String(name).split('#', 1)[0].toLowerCase() === 'aun');
|
|
1101
|
+
const contactAdapter = aunChannelName ? owner.getAdapter?.(aunChannelName) : undefined;
|
|
1092
1102
|
return {
|
|
1093
1103
|
self,
|
|
1094
1104
|
actorRole: subject.role,
|
|
@@ -1097,6 +1107,26 @@ function buildRoleMenuContext(owner, channel, subject) {
|
|
|
1097
1107
|
actorAid: subject.actorId,
|
|
1098
1108
|
chatType: subject.chatType,
|
|
1099
1109
|
aunAvailable,
|
|
1110
|
+
...(contactAdapter?.send ? {
|
|
1111
|
+
notifyContactRequestReviewed: async ({ applicantAid, requestId, expiresAt, status }) => {
|
|
1112
|
+
await contactAdapter.send(buildEnvelope({
|
|
1113
|
+
taskId: `contact-request-${requestId}`,
|
|
1114
|
+
channel: contactAdapter.channelName,
|
|
1115
|
+
channelId: applicantAid,
|
|
1116
|
+
agentName: owningAgent?.name ?? '<unknown>',
|
|
1117
|
+
replyContext: { metadata: evolMenuResponseTransportMetadata() },
|
|
1118
|
+
}), {
|
|
1119
|
+
kind: 'custom',
|
|
1120
|
+
channelType: 'aun',
|
|
1121
|
+
payload: {
|
|
1122
|
+
type: 'contact.request.response',
|
|
1123
|
+
status,
|
|
1124
|
+
request_id: requestId,
|
|
1125
|
+
expires_at: expiresAt,
|
|
1126
|
+
},
|
|
1127
|
+
});
|
|
1128
|
+
},
|
|
1129
|
+
} : {}),
|
|
1100
1130
|
availableBaseagents: Array.from(new Set([
|
|
1101
1131
|
...Object.keys(owningAgent?.config?.baseagents ?? {}),
|
|
1102
1132
|
...(owningAgent?.baseagent ? [owningAgent.baseagent] : []),
|
|
@@ -1285,6 +1315,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1285
1315
|
fromControlChannel: isControlScope,
|
|
1286
1316
|
}).allow;
|
|
1287
1317
|
};
|
|
1318
|
+
const canReadModel = canOperation('model.list', 'relation') || canOperation('model.current', 'relation');
|
|
1288
1319
|
const canUseModel = canOperation('model.use', 'relation');
|
|
1289
1320
|
const canSetEffort = canOperation('model.effort', 'relation');
|
|
1290
1321
|
const canSetChatmode = canOperation('chatmode.update', 'relation');
|
|
@@ -1293,7 +1324,12 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1293
1324
|
const canUseFile = canOperation('file.fetch', 'filesystem') || canOperation('file.list', 'filesystem');
|
|
1294
1325
|
const canManageProcess = authSubject ? authSubject.isDaemonOwner : isOwner;
|
|
1295
1326
|
const configurableSettings = [
|
|
1296
|
-
...(
|
|
1327
|
+
...(canReadModel ? [{
|
|
1328
|
+
cmd: '/model',
|
|
1329
|
+
label: canUseModel ? '切换模型' : '查看模型',
|
|
1330
|
+
desc: canUseModel ? '切换当前 Agent 使用的模型版本' : '查看当前 Agent 使用的模型版本',
|
|
1331
|
+
next: canUseModel ? { type: 'select', dynamic: true } : undefined,
|
|
1332
|
+
}] : []),
|
|
1297
1333
|
...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', items: [
|
|
1298
1334
|
{ value: 'low', label: 'Low' },
|
|
1299
1335
|
{ value: 'medium', label: 'Medium' },
|
|
@@ -1729,7 +1765,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1729
1765
|
});
|
|
1730
1766
|
if ('error' in target)
|
|
1731
1767
|
throw { code: target.code, message: target.error };
|
|
1732
|
-
const fallback =
|
|
1768
|
+
const fallback = session?.metadata?.mentionMode ?? null;
|
|
1733
1769
|
const currentMode = readMenuMentionMode(target, fallback, fallback === null ? null : 'session').value;
|
|
1734
1770
|
return [
|
|
1735
1771
|
{ value: 'mention-only', label: '@提及时响应', selected: currentMode === 'mention-only' },
|
|
@@ -1755,6 +1791,10 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1755
1791
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
1756
1792
|
if (!cmdBase)
|
|
1757
1793
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
1794
|
+
const isRequesterRoleQuery = cmdBase === '/role' && args?.view === 'requester';
|
|
1795
|
+
if (isRequesterRoleQuery && Object.keys(args).some(key => key !== 'view')) {
|
|
1796
|
+
return { error: 'role requester query only accepts args.view', code: 'INVALID_ARGUMENT' };
|
|
1797
|
+
}
|
|
1758
1798
|
const gated = gateControlScope.call(this, { cmdBase, args, channel, fromControlChannel });
|
|
1759
1799
|
if (gated)
|
|
1760
1800
|
return gated;
|
|
@@ -1770,6 +1810,18 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1770
1810
|
fromControlChannel,
|
|
1771
1811
|
subject: authSubject,
|
|
1772
1812
|
});
|
|
1813
|
+
if (isRequesterRoleQuery) {
|
|
1814
|
+
if (!subject.allowAccess) {
|
|
1815
|
+
return { error: `Role ${subject.role} is not allowed to access this agent`, code: 'ROLE_ACCESS_DENIED' };
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
data: {
|
|
1819
|
+
roleId: subject.role,
|
|
1820
|
+
source: subject.roleSource,
|
|
1821
|
+
allowAccess: subject.allowAccess,
|
|
1822
|
+
},
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1773
1825
|
if (cmdBase === '/role') {
|
|
1774
1826
|
const authorized = await authorizeRoleMenu(this, {
|
|
1775
1827
|
kind: 'query', args, identity, subject, session, channel, channelId,
|
|
@@ -2091,8 +2143,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2091
2143
|
});
|
|
2092
2144
|
if ('error' in target)
|
|
2093
2145
|
return target;
|
|
2094
|
-
|
|
2095
|
-
const fallback = dispatchToMentionMode(session?.metadata?.dispatchMode) ?? null;
|
|
2146
|
+
const fallback = session?.metadata?.mentionMode ?? null;
|
|
2096
2147
|
const current = readMenuMentionMode(target, fallback, fallback === null ? null : 'session');
|
|
2097
2148
|
return {
|
|
2098
2149
|
data: {
|
|
@@ -2605,8 +2656,6 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2605
2656
|
const newMode = modeMap[arg];
|
|
2606
2657
|
if (!newMode)
|
|
2607
2658
|
return { error: `无效模式: ${arg},可选: all / text / none`, code: 'INVALID_VALUE' };
|
|
2608
|
-
if (identity.role !== 'owner')
|
|
2609
|
-
return { error: '中间输出模式切换仅限 owner', code: 'NO_PERMISSION' };
|
|
2610
2659
|
const target = resolveMenuActivityTarget.call(this, {
|
|
2611
2660
|
args,
|
|
2612
2661
|
session,
|
|
@@ -3269,12 +3318,9 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3269
3318
|
const controlDir = daemonControlDir();
|
|
3270
3319
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
3271
3320
|
fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
stdio: 'ignore',
|
|
3276
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
3277
|
-
}).unref();
|
|
3321
|
+
spawnDetachedNode([path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
|
|
3322
|
+
EVOLCORE_HOME: resolvePaths().root,
|
|
3323
|
+
});
|
|
3278
3324
|
}
|
|
3279
3325
|
else {
|
|
3280
3326
|
logger.info('[System] Suppressed real menu restart in test runtime');
|
|
@@ -3310,7 +3356,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3310
3356
|
data: {
|
|
3311
3357
|
devMode,
|
|
3312
3358
|
evolcore: { local: localEvolcore, remote: evolcoreRemote, hasUpdate: cmp(localEvolcore, evolcoreRemote) },
|
|
3313
|
-
fastaun: { local: localFastaun, remote: fastaunRemote, hasUpdate: cmp(localFastaun, fastaunRemote) },
|
|
3359
|
+
fastaun: { local: localFastaun, remote: fastaunRemote, hasUpdate: cmp(localFastaun, fastaunRemote), managedBy: 'evolcore' },
|
|
3314
3360
|
// ecweb 本地版本由 ECWeb 进程自身注入(data.ecwebVersion),此处仅给 remote
|
|
3315
3361
|
ecweb: { remote: ecwebRemote },
|
|
3316
3362
|
},
|
|
@@ -3491,16 +3537,6 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3491
3537
|
}
|
|
3492
3538
|
return { error: `不支持 action: ${cmdBase}`, code: 'NOT_SUPPORTED' };
|
|
3493
3539
|
}
|
|
3494
|
-
const SYSTEM_CONTROL_NAME_MAP = {
|
|
3495
|
-
pwd: '/pwd', session: '/session', baseagent: '/baseagent', model: '/model',
|
|
3496
|
-
topic: '/topic',
|
|
3497
|
-
effort: '/effort', chatmode: '/chatmode', mentionmode: '/mentionmode',
|
|
3498
|
-
permission: '/perm', activity: '/activity', system: '/system',
|
|
3499
|
-
observable: '/observable',
|
|
3500
|
-
agent: '/agent', trigger: '/trigger', file: '/file', gateway: '/gateway',
|
|
3501
|
-
config: '/config', capability: '/capability', role: '/role',
|
|
3502
|
-
group: '/group', connect: '/connect',
|
|
3503
|
-
};
|
|
3504
3540
|
function menuCommandToken(cmd) {
|
|
3505
3541
|
if (typeof cmd !== 'string')
|
|
3506
3542
|
return undefined;
|
|
@@ -3511,6 +3547,13 @@ function isProcessLevelMenu(name, cmd) {
|
|
|
3511
3547
|
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
|
|
3512
3548
|
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config';
|
|
3513
3549
|
}
|
|
3550
|
+
function isProcessContextMenu(payload) {
|
|
3551
|
+
const isCatalog = payload?.type === 'menu.list' && payload?.args?.view === 'catalog';
|
|
3552
|
+
const isValueBatch = payload?.type === 'menu.query'
|
|
3553
|
+
&& payload?.name === 'menu'
|
|
3554
|
+
&& payload?.args?.view === 'values';
|
|
3555
|
+
return (isCatalog || isValueBatch) && payload?.args?.context?.kind === 'process';
|
|
3556
|
+
}
|
|
3514
3557
|
async function execMenuForSystemControl(payload, context) {
|
|
3515
3558
|
const id = payload?.id ?? '';
|
|
3516
3559
|
const name = payload?.name;
|
|
@@ -3531,13 +3574,15 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3531
3574
|
: 'cli 不在控制 channel 范围';
|
|
3532
3575
|
return { type: 'menu.response', id, name, error: { code: 'NOT_SUPPORTED', message } };
|
|
3533
3576
|
}
|
|
3534
|
-
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
|
|
3577
|
+
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
|
|
3578
|
+
const requestedContextKind = payload?.args?.context?.kind;
|
|
3535
3579
|
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
3536
3580
|
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
3537
3581
|
const requiresAgentTarget = isRoleRequest || isConnectRequest || [
|
|
3538
3582
|
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
|
|
3539
3583
|
].includes(name)
|
|
3540
|
-
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
|
|
3584
|
+
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
|
|
3585
|
+
|| (!isProcessLevel && requestedContextKind !== undefined && requestedContextKind !== 'process');
|
|
3541
3586
|
const target = isProcessLevel
|
|
3542
3587
|
? { channel: SYSTEM_CONTROL_CHANNEL }
|
|
3543
3588
|
: resolveExternalMenuAgentChannel(this, payload, SYSTEM_CONTROL_CHANNEL, requiresAgentTarget);
|
|
@@ -3558,21 +3603,71 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3558
3603
|
fromControlChannel: true,
|
|
3559
3604
|
});
|
|
3560
3605
|
const trustedIdentity = trustedSubject.identity;
|
|
3561
|
-
const cmd = name ? (
|
|
3606
|
+
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
3562
3607
|
try {
|
|
3563
3608
|
switch (payload?.type) {
|
|
3564
3609
|
case 'menu.list':
|
|
3610
|
+
if (payload?.args?.view === 'catalog') {
|
|
3611
|
+
validateMenuCatalogArgs(payload.args);
|
|
3612
|
+
const data = await getMenuCatalog({
|
|
3613
|
+
subject: trustedSubject,
|
|
3614
|
+
channelType: 'aun',
|
|
3615
|
+
channelScope: 'control',
|
|
3616
|
+
channelId: targetChannel,
|
|
3617
|
+
request: payload.args.context,
|
|
3618
|
+
});
|
|
3619
|
+
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
3620
|
+
}
|
|
3621
|
+
if (payload?.args?.view !== undefined)
|
|
3622
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', `Unsupported menu.list view: ${String(payload.args.view)}`);
|
|
3565
3623
|
return menuSuccess({ id }, this.getMenuItems(trustedIdentity.role, 'private', 'control', trustedSubject));
|
|
3566
3624
|
case 'menu.query': {
|
|
3625
|
+
if (name === 'menu' && payload?.args?.view === 'values') {
|
|
3626
|
+
validateMenuValueBatchArgs(payload.args);
|
|
3627
|
+
const data = await execMenuValueBatch({
|
|
3628
|
+
subject: trustedSubject,
|
|
3629
|
+
channelType: 'aun',
|
|
3630
|
+
channelScope: 'control',
|
|
3631
|
+
channelId: targetChannel,
|
|
3632
|
+
request: payload.args.context,
|
|
3633
|
+
items: payload.args.items,
|
|
3634
|
+
schemaVersion: payload.args.schemaVersion,
|
|
3635
|
+
consistency: payload.args.consistency,
|
|
3636
|
+
execQuery: async (entryCmd, resolvedChannelId, queryArgs, catalogContext) => this.execMenuQuery(entryCmd, targetChannel, resolvedChannelId, catalogContext.subject.actorId, queryArgs, catalogContext.subject.chatType, true, catalogContext.subject.identity, catalogContext.subject, context.source),
|
|
3637
|
+
});
|
|
3638
|
+
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
3639
|
+
}
|
|
3640
|
+
if (name === 'menu')
|
|
3641
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', `Unsupported menu.query view: ${String(payload?.args?.view)}`);
|
|
3567
3642
|
if (!cmd)
|
|
3568
3643
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
3569
|
-
const
|
|
3644
|
+
const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
|
|
3645
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
3646
|
+
subject: trustedSubject,
|
|
3647
|
+
channelType: 'aun',
|
|
3648
|
+
channelScope: 'control',
|
|
3649
|
+
channelId: targetChannel,
|
|
3650
|
+
args: payload.args,
|
|
3651
|
+
});
|
|
3652
|
+
const r = hasExplicitContext
|
|
3653
|
+
? await this.execMenuQuery(cmd, targetChannel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, true, execution.subject.identity, execution.subject, context.source)
|
|
3654
|
+
: await this.execMenuQuery(cmd, targetChannel, targetChannel, context.actorAid, payload.args, undefined, true, trustedIdentity, trustedSubject, context.source);
|
|
3570
3655
|
return ecwebResp(id, name, r);
|
|
3571
3656
|
}
|
|
3572
3657
|
case 'menu.options': {
|
|
3573
3658
|
if (!cmd)
|
|
3574
3659
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
3575
|
-
const
|
|
3660
|
+
const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
|
|
3661
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
3662
|
+
subject: trustedSubject,
|
|
3663
|
+
channelType: 'aun',
|
|
3664
|
+
channelScope: 'control',
|
|
3665
|
+
channelId: targetChannel,
|
|
3666
|
+
args: payload.args,
|
|
3667
|
+
});
|
|
3668
|
+
const data = hasExplicitContext
|
|
3669
|
+
? await this.getSubMenuItems(cmd, targetChannel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.identity, execution.subject.chatType, true, execution.subject, context.source) ?? []
|
|
3670
|
+
: await this.getSubMenuItems(cmd, targetChannel, targetChannel, context.actorAid, payload.args, trustedIdentity, undefined, true, trustedSubject, context.source) ?? [];
|
|
3576
3671
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
3577
3672
|
}
|
|
3578
3673
|
case 'menu.update': {
|
|
@@ -3580,7 +3675,14 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3580
3675
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
3581
3676
|
if (!payload.value)
|
|
3582
3677
|
return ecwebErr(id, name, 'MISSING_VALUE', '缺少 value');
|
|
3583
|
-
const
|
|
3678
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
3679
|
+
subject: trustedSubject,
|
|
3680
|
+
channelType: 'aun',
|
|
3681
|
+
channelScope: 'control',
|
|
3682
|
+
channelId: targetChannel,
|
|
3683
|
+
args: payload.args,
|
|
3684
|
+
});
|
|
3685
|
+
const r = await this.execMenuUpdate(cmd, payload.value, targetChannel, execution.channelId, execution.subject.actorId, execution.subject.identity, true, execution.args, execution.subject, context.source);
|
|
3584
3686
|
return ecwebResp(id, name, r);
|
|
3585
3687
|
}
|
|
3586
3688
|
case 'menu.action': {
|
|
@@ -3588,7 +3690,17 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3588
3690
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
3589
3691
|
if (!payload.action)
|
|
3590
3692
|
return ecwebErr(id, name, 'MISSING_VALUE', '缺少 action');
|
|
3591
|
-
const
|
|
3693
|
+
const hasExplicitContext = !!payload?.args && Object.prototype.hasOwnProperty.call(payload.args, 'context');
|
|
3694
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
3695
|
+
subject: trustedSubject,
|
|
3696
|
+
channelType: 'aun',
|
|
3697
|
+
channelScope: 'control',
|
|
3698
|
+
channelId: targetChannel,
|
|
3699
|
+
args: payload.args,
|
|
3700
|
+
});
|
|
3701
|
+
const r = hasExplicitContext
|
|
3702
|
+
? await this.execMenuAction(cmd, payload.action, execution.args, targetChannel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.chatType, id, true, execution.subject, context.source)
|
|
3703
|
+
: await this.execMenuAction(cmd, payload.action, payload.args, targetChannel, targetChannel, context.actorAid, trustedIdentity, undefined, id, true, trustedSubject, context.source);
|
|
3592
3704
|
return ecwebResp(id, name, r);
|
|
3593
3705
|
}
|
|
3594
3706
|
default:
|
|
@@ -3606,7 +3718,7 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
3606
3718
|
const validationError = validateMenuRequest(payload ?? {});
|
|
3607
3719
|
if (validationError)
|
|
3608
3720
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
3609
|
-
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd);
|
|
3721
|
+
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
|
|
3610
3722
|
const owners = loadDaemonConfig().owners ?? [];
|
|
3611
3723
|
if (isProcessLevel && owners.length === 0) {
|
|
3612
3724
|
return { type: 'menu.response', id, name, error: { code: 'FORBIDDEN', message: '请在 daemon.json 配置 owners 后使用进程级操作' } };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
+
import { loadDaemonConfig } from '../../config-store.js';
|
|
3
|
+
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
4
|
+
import { compareStableSemver, parseStableSemver } from '../../utils/stable-semver.js';
|
|
2
5
|
export const MENU_REQUEST_TYPES = new Set([
|
|
3
6
|
'menu.token.request',
|
|
4
7
|
'menu.list',
|
|
@@ -7,6 +10,72 @@ export const MENU_REQUEST_TYPES = new Set([
|
|
|
7
10
|
'menu.update',
|
|
8
11
|
'menu.action',
|
|
9
12
|
]);
|
|
13
|
+
export const MENU_NAME_COMMANDS = Object.freeze({
|
|
14
|
+
pwd: '/pwd',
|
|
15
|
+
session: '/session',
|
|
16
|
+
topic: '/topic',
|
|
17
|
+
baseagent: '/baseagent',
|
|
18
|
+
model: '/model',
|
|
19
|
+
effort: '/effort',
|
|
20
|
+
chatmode: '/chatmode',
|
|
21
|
+
mentionmode: '/mentionmode',
|
|
22
|
+
group: '/group',
|
|
23
|
+
permission: '/perm',
|
|
24
|
+
activity: '/activity',
|
|
25
|
+
dispatch: '/dispatch',
|
|
26
|
+
observable: '/observable',
|
|
27
|
+
system: '/system',
|
|
28
|
+
cli: '/cli',
|
|
29
|
+
agent: '/agent',
|
|
30
|
+
trigger: '/trigger',
|
|
31
|
+
file: '/file',
|
|
32
|
+
gateway: '/gateway',
|
|
33
|
+
config: '/config',
|
|
34
|
+
capability: '/capability',
|
|
35
|
+
role: '/role',
|
|
36
|
+
connect: '/connect',
|
|
37
|
+
});
|
|
38
|
+
export function menuCommandForName(name) {
|
|
39
|
+
if (typeof name !== 'string')
|
|
40
|
+
return undefined;
|
|
41
|
+
return MENU_NAME_COMMANDS[name];
|
|
42
|
+
}
|
|
43
|
+
export function evaluateEvolMenuVersionGate(input) {
|
|
44
|
+
const minimum = loadDaemonConfig().aun?.minEvolVersion;
|
|
45
|
+
if (!minimum)
|
|
46
|
+
return null;
|
|
47
|
+
const minimumVersion = parseStableSemver(minimum);
|
|
48
|
+
if (!minimumVersion) {
|
|
49
|
+
throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
|
|
50
|
+
}
|
|
51
|
+
const received = input.protectedHeaders?.evol_version;
|
|
52
|
+
const receivedVersion = input.encrypted ? parseStableSemver(received) : null;
|
|
53
|
+
if (!receivedVersion || compareStableSemver(receivedVersion, minimumVersion) < 0) {
|
|
54
|
+
return {
|
|
55
|
+
code: 'UPGRADE_REQUIRED',
|
|
56
|
+
message: `Evol ${minimum} or later is required`,
|
|
57
|
+
data: {
|
|
58
|
+
minimum,
|
|
59
|
+
received: receivedVersion ? received : null,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function isEvolMenuVersionGateEnabled() {
|
|
66
|
+
return loadDaemonConfig().aun?.minEvolVersion !== undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Token may be issued while disabled, but only this flag makes it an access requirement. */
|
|
69
|
+
export function isAunMenuTokenRequired() {
|
|
70
|
+
return loadDaemonConfig().aun?.menuTokenRequired === true;
|
|
71
|
+
}
|
|
72
|
+
export function evolMenuResponseTransportMetadata() {
|
|
73
|
+
return {
|
|
74
|
+
// Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
|
|
75
|
+
encrypted: undefined,
|
|
76
|
+
protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
10
79
|
const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
|
|
11
80
|
'model',
|
|
12
81
|
'effort',
|
|
@@ -155,6 +224,8 @@ const STABLE_CODES = new Set([
|
|
|
155
224
|
'METHOD_NOT_FOUND', 'NOT_FOUND', 'CONFLICT', 'EXPIRED', 'EXECUTION_TIMEOUT',
|
|
156
225
|
'TEMPORARILY_UNAVAILABLE', 'INTERNAL_ERROR', 'MISSING_SCOPE', 'UPGRADE_REQUIRED',
|
|
157
226
|
'MENU_TOKEN_REQUIRED', 'MENU_TOKEN_REJECTED', 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
227
|
+
'INVALID_CONTEXT', 'SCHEMA_VERSION_UNSUPPORTED', 'DEPENDENCY_UNAVAILABLE',
|
|
228
|
+
'RATE_LIMITED', 'CATALOG_STALE',
|
|
158
229
|
]);
|
|
159
230
|
export function normalizeMenuError(error) {
|
|
160
231
|
const source = error && typeof error === 'object' ? error : {};
|
|
@@ -114,7 +114,9 @@ export async function guardIdleCommand(opts) {
|
|
|
114
114
|
catch {
|
|
115
115
|
// Runner mismatch should not block recovery commands such as /baseagent.
|
|
116
116
|
}
|
|
117
|
-
const isBusy = hasActiveStream
|
|
117
|
+
const isBusy = hasActiveStream
|
|
118
|
+
|| opts.messageQueue?.isProcessing(threadSession.id)
|
|
119
|
+
|| (opts.messageQueue?.getQueueLength(threadSession.id) ?? 0) > 0;
|
|
118
120
|
if (isBusy) {
|
|
119
121
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
120
122
|
}
|
|
@@ -122,7 +124,8 @@ export async function guardIdleCommand(opts) {
|
|
|
122
124
|
}
|
|
123
125
|
else if (opts.activeSession) {
|
|
124
126
|
const isBusy = (opts.activeAgent?.hasActiveStream(opts.activeSession.id) ?? false) ||
|
|
125
|
-
opts.messageQueue?.isProcessing(opts.activeSession.id)
|
|
127
|
+
opts.messageQueue?.isProcessing(opts.activeSession.id) ||
|
|
128
|
+
(opts.messageQueue?.getQueueLength(opts.activeSession.id) ?? 0) > 0;
|
|
126
129
|
if (isBusy) {
|
|
127
130
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
128
131
|
}
|
|
@@ -21,9 +21,9 @@ import { filterModelsForRole, validateModelSelectionForRole } from '../model/mod
|
|
|
21
21
|
import { displaySessionTitle, isSyntheticCliPrompt } from '../session/session-title.js';
|
|
22
22
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
23
23
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
24
|
-
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
25
24
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
26
25
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
26
|
+
import { spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
27
27
|
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, } from './slash-gate.js';
|
|
28
28
|
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
29
29
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
@@ -259,7 +259,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
259
259
|
cwd: projectPath,
|
|
260
260
|
encoding: 'utf8',
|
|
261
261
|
timeout: 1000,
|
|
262
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
262
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
263
|
+
windowsHide: process.platform === 'win32',
|
|
263
264
|
}).trim();
|
|
264
265
|
if (isInsideWorkTree !== 'true')
|
|
265
266
|
return null;
|
|
@@ -268,14 +269,16 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
268
269
|
cwd: projectPath,
|
|
269
270
|
encoding: 'utf8',
|
|
270
271
|
timeout: 1000,
|
|
271
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
272
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
273
|
+
windowsHide: process.platform === 'win32',
|
|
272
274
|
}).trim();
|
|
273
275
|
if (!branch) {
|
|
274
276
|
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
275
277
|
cwd: projectPath,
|
|
276
278
|
encoding: 'utf8',
|
|
277
279
|
timeout: 1000,
|
|
278
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
280
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
281
|
+
windowsHide: process.platform === 'win32',
|
|
279
282
|
}).trim();
|
|
280
283
|
}
|
|
281
284
|
if (!branch)
|
|
@@ -285,7 +288,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
285
288
|
cwd: projectPath,
|
|
286
289
|
encoding: 'utf8',
|
|
287
290
|
timeout: 1000,
|
|
288
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
291
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
292
|
+
windowsHide: process.platform === 'win32',
|
|
289
293
|
});
|
|
290
294
|
// 解析文件状态统计
|
|
291
295
|
const stats = { modified: 0, added: 0, deleted: 0, untracked: 0 };
|
|
@@ -328,7 +332,13 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
328
332
|
}
|
|
329
333
|
// 获取 ahead/behind 信息
|
|
330
334
|
try {
|
|
331
|
-
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
335
|
+
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
336
|
+
cwd: projectPath,
|
|
337
|
+
timeout: 1000,
|
|
338
|
+
encoding: 'utf8',
|
|
339
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
340
|
+
windowsHide: process.platform === 'win32',
|
|
341
|
+
});
|
|
332
342
|
const revParts = revOutput.trim().split(/\s+/);
|
|
333
343
|
if (revParts.length === 2) {
|
|
334
344
|
const behind = parseInt(revParts[0], 10) || 0;
|
|
@@ -1924,8 +1934,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1924
1934
|
if ('error' in mentionTarget) {
|
|
1925
1935
|
return { kind: 'command.error', text: `❌ ${mentionTarget.error}` };
|
|
1926
1936
|
}
|
|
1927
|
-
|
|
1928
|
-
const mentionFallback = dispatchToMentionMode(mentionSession.metadata?.dispatchMode) ?? null;
|
|
1937
|
+
const mentionFallback = mentionSession.metadata?.mentionMode ?? null;
|
|
1929
1938
|
const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
|
|
1930
1939
|
if (!arg) {
|
|
1931
1940
|
const displayMode = currentMode ?? '未设置(跟随群设置)';
|
|
@@ -2121,20 +2130,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2121
2130
|
selfAID,
|
|
2122
2131
|
role: sessionRole,
|
|
2123
2132
|
});
|
|
2124
|
-
|
|
2125
|
-
const mentionModeFallback = dispatchToMentionMode(session.metadata?.dispatchMode) ?? null;
|
|
2133
|
+
const mentionModeFallback = session.metadata?.mentionMode ?? null;
|
|
2126
2134
|
const mentionMode = 'error' in mentionModeTarget
|
|
2127
2135
|
? (mentionModeFallback ?? '未设置(跟随群设置)')
|
|
2128
2136
|
: (readSlashMentionMode(mentionModeTarget, mentionModeFallback) ?? '未设置(跟随群设置)');
|
|
2129
2137
|
const chatModeLine = `会话模式: ${chatMode}`;
|
|
2130
|
-
const
|
|
2138
|
+
const mentionModeLine = session.chatType === 'group' ? `@ 处理模式: ${mentionMode}` : null;
|
|
2131
2139
|
if (isAdmin) {
|
|
2132
2140
|
const gitInfo = await getGitWorkingDirInfo(session.projectPath);
|
|
2133
2141
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${this.resolveChannelType(channel)} / 项目: ${projectName} / 会话: ${displaySessionTitle(session.name, '(未命名)')}`, `会话ID: ${session.id}`, `项目路径: ${session.projectPath}`);
|
|
2134
2142
|
if (gitInfo) {
|
|
2135
2143
|
lines.push(`Git: ${gitInfo}`);
|
|
2136
2144
|
}
|
|
2137
|
-
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2145
|
+
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
|
|
2138
2146
|
if (health.consecutiveErrors > 0) {
|
|
2139
2147
|
lines.push(`异常计数: ${health.consecutiveErrors}`);
|
|
2140
2148
|
}
|
|
@@ -2142,7 +2150,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2142
2150
|
}
|
|
2143
2151
|
else {
|
|
2144
2152
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${channel} / 项目: ${projectName} / ${session.baseagent}会话`);
|
|
2145
|
-
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2153
|
+
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
|
|
2146
2154
|
}
|
|
2147
2155
|
if (health.lastError) {
|
|
2148
2156
|
lines.push('');
|
|
@@ -2171,7 +2179,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2171
2179
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
2172
2180
|
groupId: previousMetadata.groupId || channelId,
|
|
2173
2181
|
...(previousMetadata.groupName ? { groupName: previousMetadata.groupName } : {}),
|
|
2174
|
-
...(previousMetadata.
|
|
2182
|
+
...(previousMetadata.mentionMode ? { mentionMode: previousMetadata.mentionMode } : {}),
|
|
2175
2183
|
}
|
|
2176
2184
|
: {
|
|
2177
2185
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
@@ -2180,6 +2188,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2180
2188
|
...(previousMetadata.peerType ? { peerType: previousMetadata.peerType } : {}),
|
|
2181
2189
|
};
|
|
2182
2190
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent, newSessionIdentityMetadata);
|
|
2191
|
+
const previousSession = session || activeSession;
|
|
2192
|
+
const handoffSelfAid = previousSession?.selfAID
|
|
2193
|
+
|| selfAID
|
|
2194
|
+
|| this.getOwningAgent(channel)?.aid
|
|
2195
|
+
|| this.resolveSelfAID(channel);
|
|
2196
|
+
let handoffDiscardWarning;
|
|
2197
|
+
if (previousSession?.id && handoffSelfAid && this.handoffRuntime) {
|
|
2198
|
+
try {
|
|
2199
|
+
await this.handoffRuntime.discardSession(handoffSelfAid, previousSession.id);
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
handoffDiscardWarning = '⚠️ 原会话 Handoff 清理失败,请检查 daemon 日志';
|
|
2203
|
+
logger.error(`[Handoff] failed to discard handoffs after /new: session=${previousSession.id}`, error);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2183
2206
|
const previousAgent = getActiveAgentIfAvailable();
|
|
2184
2207
|
if (session && previousAgent) {
|
|
2185
2208
|
// Reset agent backend state so the new
|
|
@@ -2214,7 +2237,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2214
2237
|
?? newRunner.getEffort?.()
|
|
2215
2238
|
?? newAgent?.effort;
|
|
2216
2239
|
const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
|
|
2217
|
-
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s
|
|
2240
|
+
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s 查看${handoffDiscardWarning ? `\n${handoffDiscardWarning}` : ''}` };
|
|
2218
2241
|
}
|
|
2219
2242
|
// /check 命令:检查 EvolAgent 实例健康(visitor/member 可用,详情仅 admin)
|
|
2220
2243
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
@@ -2485,12 +2508,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2485
2508
|
const controlDir = daemonControlDir();
|
|
2486
2509
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
2487
2510
|
fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
stdio: 'ignore',
|
|
2492
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
2493
|
-
}).unref();
|
|
2511
|
+
spawnDetachedNode([path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
|
|
2512
|
+
EVOLCORE_HOME: resolvePaths().root,
|
|
2513
|
+
});
|
|
2494
2514
|
}
|
|
2495
2515
|
else {
|
|
2496
2516
|
logger.info('[System] Suppressed real restart in test runtime');
|