evolcore 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +7 -5
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +249 -24
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-manager.js +25 -3
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +7 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from 'crypto';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { agentDataDir } from '../../paths.js';
|
|
4
|
+
import { atomicReadJson, atomicWriteJson } from '../../utils/atomic-write.js';
|
|
5
|
+
export const DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS = 30 * 24 * 60 * 60;
|
|
6
|
+
export const MAX_MENU_TOKENS_PER_AGENT = 1_000;
|
|
7
|
+
function hashToken(token) {
|
|
8
|
+
return createHash('sha256').update(token).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
function isTokenRecord(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
return false;
|
|
13
|
+
const record = value;
|
|
14
|
+
return typeof record.peerAid === 'string'
|
|
15
|
+
&& record.peerAid.trim().length > 0
|
|
16
|
+
&& typeof record.tokenHash === 'string'
|
|
17
|
+
&& /^[a-f0-9]{64}$/.test(record.tokenHash)
|
|
18
|
+
&& typeof record.requestedAt === 'number'
|
|
19
|
+
&& Number.isFinite(record.requestedAt)
|
|
20
|
+
&& record.requestedAt >= 0
|
|
21
|
+
&& typeof record.lastUsedAt === 'number'
|
|
22
|
+
&& Number.isFinite(record.lastUsedAt)
|
|
23
|
+
&& record.lastUsedAt >= 0
|
|
24
|
+
&& typeof record.idleTimeout === 'number'
|
|
25
|
+
&& Number.isInteger(record.idleTimeout)
|
|
26
|
+
&& record.idleTimeout > 0;
|
|
27
|
+
}
|
|
28
|
+
function isExpired(record, now) {
|
|
29
|
+
return now - record.lastUsedAt >= record.idleTimeout * 1000;
|
|
30
|
+
}
|
|
31
|
+
function hashesEqual(left, right) {
|
|
32
|
+
const leftBytes = Buffer.from(left, 'hex');
|
|
33
|
+
const rightBytes = Buffer.from(right, 'hex');
|
|
34
|
+
return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes);
|
|
35
|
+
}
|
|
36
|
+
export function menuTokenStorePath(selfAid) {
|
|
37
|
+
return path.join(agentDataDir(selfAid), 'menu-tokens.json');
|
|
38
|
+
}
|
|
39
|
+
export class MenuTokenStore {
|
|
40
|
+
maxRecords;
|
|
41
|
+
constructor(maxRecords = MAX_MENU_TOKENS_PER_AGENT) {
|
|
42
|
+
this.maxRecords = maxRecords;
|
|
43
|
+
}
|
|
44
|
+
issue(selfAid, peerAid, metadata = {}, now = Date.now()) {
|
|
45
|
+
const file = this.read(selfAid);
|
|
46
|
+
const idleTimeout = DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS;
|
|
47
|
+
const tokens = file.tokens.filter(record => record.peerAid !== peerAid && !isExpired(record, now));
|
|
48
|
+
if (tokens.length >= this.maxRecords) {
|
|
49
|
+
if (tokens.length !== file.tokens.length)
|
|
50
|
+
this.write(selfAid, tokens);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const menuToken = `mt_${randomBytes(32).toString('base64url')}`;
|
|
54
|
+
tokens.push({
|
|
55
|
+
peerAid,
|
|
56
|
+
tokenHash: hashToken(menuToken),
|
|
57
|
+
requestedAt: now,
|
|
58
|
+
lastUsedAt: now,
|
|
59
|
+
idleTimeout,
|
|
60
|
+
...(metadata.clientName ? { clientName: metadata.clientName } : {}),
|
|
61
|
+
...(metadata.clientVersion ? { clientVersion: metadata.clientVersion } : {}),
|
|
62
|
+
...(metadata.evolVersion ? { evolVersion: metadata.evolVersion } : {}),
|
|
63
|
+
});
|
|
64
|
+
this.write(selfAid, tokens);
|
|
65
|
+
return { menuToken, idleTimeout };
|
|
66
|
+
}
|
|
67
|
+
use(selfAid, peerAid, menuToken, now = Date.now()) {
|
|
68
|
+
const file = this.read(selfAid);
|
|
69
|
+
const activeTokens = file.tokens.filter(record => !isExpired(record, now));
|
|
70
|
+
const tokenHash = typeof menuToken === 'string' && menuToken.length > 0 && menuToken.length <= 256
|
|
71
|
+
? hashToken(menuToken)
|
|
72
|
+
: undefined;
|
|
73
|
+
const matchingIndex = tokenHash
|
|
74
|
+
? activeTokens.findIndex(record => record.peerAid === peerAid && hashesEqual(record.tokenHash, tokenHash))
|
|
75
|
+
: -1;
|
|
76
|
+
if (matchingIndex < 0) {
|
|
77
|
+
if (activeTokens.length !== file.tokens.length)
|
|
78
|
+
this.write(selfAid, activeTokens);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
activeTokens[matchingIndex] = { ...activeTokens[matchingIndex], lastUsedAt: now };
|
|
82
|
+
this.write(selfAid, activeTokens);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
read(selfAid) {
|
|
86
|
+
try {
|
|
87
|
+
const value = atomicReadJson(menuTokenStorePath(selfAid));
|
|
88
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
89
|
+
return { schemaVersion: 1, tokens: [] };
|
|
90
|
+
const file = value;
|
|
91
|
+
if (file.schemaVersion !== 1 || !Array.isArray(file.tokens))
|
|
92
|
+
return { schemaVersion: 1, tokens: [] };
|
|
93
|
+
return { schemaVersion: 1, tokens: file.tokens.filter(isTokenRecord) };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { schemaVersion: 1, tokens: [] };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
write(selfAid, tokens) {
|
|
100
|
+
atomicWriteJson(menuTokenStorePath(selfAid), { schemaVersion: 1, tokens });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -71,7 +71,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
|
|
|
71
71
|
const userGroupCommands = [
|
|
72
72
|
'/status', '/help', '/evolhelp', '/check', '/chatmode', '/mentionmode',
|
|
73
73
|
'/model', '/effort', '/baseagent', '/perm', '/activity', '/stop',
|
|
74
|
-
'/resume', '/trigger',
|
|
74
|
+
'/resume', '/trigger', '/file',
|
|
75
75
|
];
|
|
76
76
|
const userCommands = activeChatType === 'group' && !isAdmin
|
|
77
77
|
? userGroupCommands
|
|
@@ -432,9 +432,23 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
432
432
|
...params,
|
|
433
433
|
subject: authSubject,
|
|
434
434
|
});
|
|
435
|
-
|
|
435
|
+
const canAccessSlashOperation = (operation, scope) => {
|
|
436
|
+
const args = scope === 'relation'
|
|
437
|
+
? buildSlashRelationIntentArgs({ selfAid: authSubject.selfAid, peerKey: authSubject.peerKey })
|
|
438
|
+
: {};
|
|
439
|
+
return authorizeOperation({
|
|
440
|
+
source: 'slash',
|
|
441
|
+
intent: { operation, scope, source: 'slash', args },
|
|
442
|
+
subject: authSubject,
|
|
443
|
+
audit: false,
|
|
444
|
+
}).allow;
|
|
445
|
+
};
|
|
446
|
+
// roleGuard 仅对进程级命令(/restart /upgrade /reload)放行 daemon owner 绕过,
|
|
436
447
|
// 其余命令严格按 agent-channel 的 isAdmin 判定,不越权。
|
|
437
|
-
const isProcessLevelSlash = normalizedContent === '/restart'
|
|
448
|
+
const isProcessLevelSlash = normalizedContent === '/restart'
|
|
449
|
+
|| normalizedContent === '/upgrade'
|
|
450
|
+
|| normalizedContent === '/reload'
|
|
451
|
+
|| normalizedContent.startsWith('/reload ');
|
|
438
452
|
const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
|
|
439
453
|
if (roleGuard)
|
|
440
454
|
return roleGuard;
|
|
@@ -459,18 +473,50 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
459
473
|
return undefined;
|
|
460
474
|
// /help 命令不需要会话
|
|
461
475
|
if (normalizedContent === '/help') {
|
|
476
|
+
const canReadModel = canAccessSlashOperation('model.current', 'relation')
|
|
477
|
+
|| canAccessSlashOperation('model.list', 'relation');
|
|
478
|
+
const canUseModel = canAccessSlashOperation('model.use', 'relation');
|
|
479
|
+
const canSetEffort = canAccessSlashOperation('model.effort', 'relation');
|
|
480
|
+
const canReadPermission = canAccessSlashOperation('permission.current', 'relation');
|
|
481
|
+
const canAnswerPermission = canAccessSlashOperation('permission.answer', 'relation');
|
|
482
|
+
const canReadChatmode = canAccessSlashOperation('chatmode.current', 'relation');
|
|
483
|
+
const canSetChatmode = canAccessSlashOperation('chatmode.update', 'relation');
|
|
484
|
+
const canReadMentionmode = activeChatType === 'group'
|
|
485
|
+
&& canAccessSlashOperation('mentionmode.current', 'relation');
|
|
486
|
+
const canSetMentionmode = activeChatType === 'group'
|
|
487
|
+
&& canAccessSlashOperation('mentionmode.update', 'relation');
|
|
488
|
+
const canUseFile = canAccessSlashOperation('file.fetch', 'filesystem');
|
|
462
489
|
const appendProcessCommands = (lines) => {
|
|
463
490
|
if (!isDaemonOwner)
|
|
464
491
|
return;
|
|
465
|
-
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /reload [aid] - 热重载 Agent 配置');
|
|
492
|
+
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置');
|
|
466
493
|
};
|
|
467
494
|
if (!isAdmin && activeChatType === 'group') {
|
|
468
495
|
const lines = [
|
|
469
496
|
'可用命令:',
|
|
497
|
+
...((canReadModel || canUseModel || canSetEffort) ? [
|
|
498
|
+
'',
|
|
499
|
+
'🤖 Agent 与模型:',
|
|
500
|
+
...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
|
|
501
|
+
...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
|
|
502
|
+
] : []),
|
|
503
|
+
...((canReadChatmode || canSetChatmode || canReadMentionmode || canSetMentionmode) ? [
|
|
504
|
+
'',
|
|
505
|
+
'💬 聊天设置:',
|
|
506
|
+
...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`] : []),
|
|
507
|
+
...(canReadMentionmode || canSetMentionmode ? [` /mentionmode${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '或切换' : ''}群聊 @ 处理模式`] : []),
|
|
508
|
+
] : []),
|
|
509
|
+
...(canReadPermission || canAnswerPermission ? [
|
|
510
|
+
'',
|
|
511
|
+
'🔐 权限管理:',
|
|
512
|
+
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
513
|
+
...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
|
|
514
|
+
] : []),
|
|
470
515
|
'',
|
|
471
516
|
'其他:',
|
|
472
517
|
' /status - 显示会话状态',
|
|
473
518
|
' /check - 检查 EvolAgent 实例健康',
|
|
519
|
+
...(canUseFile ? [' /file <path> - 发送项目内文件'] : []),
|
|
474
520
|
' /help - 显示此帮助信息',
|
|
475
521
|
];
|
|
476
522
|
appendProcessCommands(lines);
|
|
@@ -487,6 +533,28 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
487
533
|
' /del <名称> - 删除指定会话(仅解绑,不删除文件)',
|
|
488
534
|
' /status - 显示会话状态',
|
|
489
535
|
' /check - 检查 EvolAgent 实例健康',
|
|
536
|
+
...((canReadModel || canUseModel || canSetEffort) ? [
|
|
537
|
+
'',
|
|
538
|
+
'🤖 Agent 与模型:',
|
|
539
|
+
...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
|
|
540
|
+
...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
|
|
541
|
+
] : []),
|
|
542
|
+
...((canReadChatmode || canSetChatmode) ? [
|
|
543
|
+
'',
|
|
544
|
+
'💬 聊天设置:',
|
|
545
|
+
` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`,
|
|
546
|
+
] : []),
|
|
547
|
+
...(canReadPermission || canAnswerPermission ? [
|
|
548
|
+
'',
|
|
549
|
+
'🔐 权限管理:',
|
|
550
|
+
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
551
|
+
...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
|
|
552
|
+
] : []),
|
|
553
|
+
...(canUseFile ? [
|
|
554
|
+
'',
|
|
555
|
+
'🧰 工具:',
|
|
556
|
+
' /file <path> - 发送项目内文件',
|
|
557
|
+
] : []),
|
|
490
558
|
'',
|
|
491
559
|
'❓ 帮助:',
|
|
492
560
|
' /help - 显示此帮助信息',
|
|
@@ -512,18 +580,18 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
512
580
|
'',
|
|
513
581
|
'🤖 Agent 与模型:',
|
|
514
582
|
' /baseagent [name] - 查看或切换 Agent 后端(别名: /base)',
|
|
515
|
-
|
|
516
|
-
' /effort [level] - 查看或切换推理强度',
|
|
583
|
+
...(canReadModel || canUseModel ? [` /model${canUseModel ? ' [model]' : ''} - 查看${canUseModel ? '或切换' : ''}模型`] : []),
|
|
584
|
+
...(canSetEffort ? [' /effort [level] - 查看或切换推理强度'] : []),
|
|
517
585
|
'',
|
|
518
586
|
'💬 聊天设置:',
|
|
519
587
|
' /activity [all|text|none] - 查看/控制中间输出显示模式',
|
|
520
|
-
|
|
521
|
-
|
|
588
|
+
...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '/切换' : ''}会话模式(被动响应或主动推进)`] : []),
|
|
589
|
+
...(canReadMentionmode || canSetMentionmode ? [` /mentionmode${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '/切换' : ''}群聊 @ 处理模式`] : []),
|
|
522
590
|
'',
|
|
523
591
|
'🔐 权限管理:',
|
|
524
|
-
' /perm - 查看当前权限模式',
|
|
525
|
-
...(
|
|
526
|
-
' /perm allow|always|deny - 审批权限请求',
|
|
592
|
+
...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
|
|
593
|
+
...(canReadPermission ? [' 权限模式请通过角色策略编辑器修改'] : []),
|
|
594
|
+
...(canAnswerPermission ? [' /perm allow|always|deny - 审批权限请求'] : []),
|
|
527
595
|
'',
|
|
528
596
|
'🛠️ 运维:',
|
|
529
597
|
' /status - 显示会话状态',
|
|
@@ -539,7 +607,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
539
607
|
...(!isDaemonOwner && isAdmin ? [
|
|
540
608
|
' /reload - 热重载当前 Agent 配置',
|
|
541
609
|
] : []),
|
|
542
|
-
...(
|
|
610
|
+
...(canUseFile ? [
|
|
543
611
|
'',
|
|
544
612
|
'🧰 工具:',
|
|
545
613
|
` /file ${isOwner ? '[channel] ' : ''}<path> - 发送项目内文件`,
|
|
@@ -553,6 +621,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
553
621
|
// /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
|
|
554
622
|
if (normalizedContent === '/evolhelp') {
|
|
555
623
|
const cmds = [];
|
|
624
|
+
const canReadModel = canAccessSlashOperation('model.current', 'relation')
|
|
625
|
+
|| canAccessSlashOperation('model.list', 'relation');
|
|
626
|
+
const canUseModel = canAccessSlashOperation('model.use', 'relation');
|
|
627
|
+
const canSetEffort = canAccessSlashOperation('model.effort', 'relation');
|
|
628
|
+
const canReadPermission = canAccessSlashOperation('permission.current', 'relation');
|
|
629
|
+
const canAnswerPermission = canAccessSlashOperation('permission.answer', 'relation');
|
|
630
|
+
const canReadChatmode = canAccessSlashOperation('chatmode.current', 'relation');
|
|
631
|
+
const canSetChatmode = canAccessSlashOperation('chatmode.update', 'relation');
|
|
632
|
+
const canReadMentionmode = activeChatType === 'group'
|
|
633
|
+
&& canAccessSlashOperation('mentionmode.current', 'relation');
|
|
634
|
+
const canSetMentionmode = activeChatType === 'group'
|
|
635
|
+
&& canAccessSlashOperation('mentionmode.update', 'relation');
|
|
636
|
+
const canUseFile = canAccessSlashOperation('file.fetch', 'filesystem');
|
|
556
637
|
// 项目
|
|
557
638
|
cmds.push({ command: '/pwd', description: '显示当前项目路径', category: '项目', roles: ['admin', 'owner'] });
|
|
558
639
|
// 会话管理
|
|
@@ -566,16 +647,17 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
566
647
|
cmds.push({ command: '/compact', description: '压缩会话上下文(减少 token 用量)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
567
648
|
}
|
|
568
649
|
// Agent 与模型
|
|
569
|
-
if (isAdmin)
|
|
650
|
+
if (isAdmin)
|
|
570
651
|
cmds.push({ command: '/baseagent', aliases: ['/base'], args: '[name]', description: '查看或切换 Agent 后端', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
571
|
-
|
|
572
|
-
cmds.push({ command: '/
|
|
573
|
-
|
|
652
|
+
if (canReadModel || canUseModel)
|
|
653
|
+
cmds.push({ command: '/model', ...(canUseModel ? { args: '[model]' } : {}), description: `查看${canUseModel ? '或切换' : ''}模型`, category: 'Agent 与模型', roles: [identity.role] });
|
|
654
|
+
if (canSetEffort)
|
|
655
|
+
cmds.push({ command: '/effort', args: '[level]', description: '查看或切换推理强度', category: 'Agent 与模型', roles: [identity.role] });
|
|
574
656
|
// 权限管理
|
|
575
|
-
if (
|
|
576
|
-
cmds.push({ command: '/perm', description: '查看当前角色决定的权限模式', category: '权限管理', roles: [
|
|
577
|
-
|
|
578
|
-
|
|
657
|
+
if (canReadPermission)
|
|
658
|
+
cmds.push({ command: '/perm', description: '查看当前角色决定的权限模式', category: '权限管理', roles: [identity.role] });
|
|
659
|
+
if (canAnswerPermission)
|
|
660
|
+
cmds.push({ command: '/perm', args: 'allow|always|deny', description: '审批权限请求', category: '权限管理', roles: [identity.role] });
|
|
579
661
|
// 运维
|
|
580
662
|
cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
|
|
581
663
|
cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
|
|
@@ -588,19 +670,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
588
670
|
}
|
|
589
671
|
if (isDaemonOwner) {
|
|
590
672
|
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['daemon-owner'] });
|
|
673
|
+
cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
|
|
591
674
|
cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
|
|
592
675
|
}
|
|
593
676
|
if (!isDaemonOwner && isAdmin) {
|
|
594
677
|
cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
|
|
595
678
|
}
|
|
596
|
-
if (
|
|
597
|
-
cmds.push({ command: '/file', args: isOwner ? '[channel] <path>' : '<path>', description: '发送项目内文件', category: '工具', roles: [
|
|
598
|
-
}
|
|
679
|
+
if (canUseFile)
|
|
680
|
+
cmds.push({ command: '/file', args: isOwner ? '[channel] <path>' : '<path>', description: '发送项目内文件', category: '工具', roles: [identity.role] });
|
|
599
681
|
// 聊天设置
|
|
600
|
-
if (
|
|
601
|
-
cmds.push({ command: '/chatmode', args: '[interactive|proactive]', description: '
|
|
602
|
-
|
|
603
|
-
|
|
682
|
+
if (canReadChatmode || canSetChatmode)
|
|
683
|
+
cmds.push({ command: '/chatmode', ...(canSetChatmode ? { args: '[interactive|proactive]' } : {}), description: `查看${canSetChatmode ? '/切换' : ''}会话模式(被动响应或主动推进)`, category: '聊天设置', roles: [identity.role] });
|
|
684
|
+
if (canReadMentionmode || canSetMentionmode)
|
|
685
|
+
cmds.push({ command: '/mentionmode', ...(canSetMentionmode ? { args: '[mention-only|disabled]' } : {}), description: `查看${canSetMentionmode ? '/切换' : ''}群聊 @ 处理模式`, category: '聊天设置', roles: [identity.role] });
|
|
604
686
|
// 交互
|
|
605
687
|
cmds.push({ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
|
|
606
688
|
// 帮助
|
|
@@ -2083,7 +2165,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2083
2165
|
await sendMessage(channelId, `⏳ 正在创建新会话${sessionName ? `: ${sessionName}` : ''}...`, this.getReplyContext(session));
|
|
2084
2166
|
}
|
|
2085
2167
|
const newSessionBaseagent = this.agentRegistry?.resolveByChannel(channel)?.baseagent || this.parseDefaultBaseagent();
|
|
2086
|
-
const
|
|
2168
|
+
const previousMetadata = session?.metadata || activeSession?.metadata || {};
|
|
2169
|
+
const newSessionIdentityMetadata = activeChatType === 'group'
|
|
2170
|
+
? {
|
|
2171
|
+
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
2172
|
+
groupId: previousMetadata.groupId || channelId,
|
|
2173
|
+
...(previousMetadata.groupName ? { groupName: previousMetadata.groupName } : {}),
|
|
2174
|
+
...(previousMetadata.dispatchMode ? { dispatchMode: previousMetadata.dispatchMode } : {}),
|
|
2175
|
+
}
|
|
2176
|
+
: {
|
|
2177
|
+
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
2178
|
+
...((userId || previousMetadata.peerId) ? { peerId: userId || previousMetadata.peerId } : {}),
|
|
2179
|
+
...(previousMetadata.peerName ? { peerName: previousMetadata.peerName } : {}),
|
|
2180
|
+
...(previousMetadata.peerType ? { peerType: previousMetadata.peerType } : {}),
|
|
2181
|
+
};
|
|
2182
|
+
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent, newSessionIdentityMetadata);
|
|
2087
2183
|
const previousAgent = getActiveAgentIfAvailable();
|
|
2088
2184
|
if (session && previousAgent) {
|
|
2089
2185
|
// Reset agent backend state so the new
|
|
@@ -2460,8 +2556,24 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2460
2556
|
}
|
|
2461
2557
|
// /upgrade 命令:检查版本更新,提示用户手动重启
|
|
2462
2558
|
if (normalizedContent === '/upgrade') {
|
|
2463
|
-
|
|
2464
|
-
|
|
2559
|
+
const upgradeAuthDenied = await authorizeIntent({
|
|
2560
|
+
intent: {
|
|
2561
|
+
operation: 'system.upgrade',
|
|
2562
|
+
scope: 'process',
|
|
2563
|
+
source: 'slash',
|
|
2564
|
+
args: {},
|
|
2565
|
+
dangerous: true,
|
|
2566
|
+
},
|
|
2567
|
+
identity: isDaemonOwner ? { ...identity, role: 'owner' } : identity,
|
|
2568
|
+
session: activeSession,
|
|
2569
|
+
channel,
|
|
2570
|
+
channelId,
|
|
2571
|
+
userId,
|
|
2572
|
+
selfAid: this.agentRegistry?.resolveByChannel(channel)?.aid,
|
|
2573
|
+
isDaemonOwner,
|
|
2574
|
+
});
|
|
2575
|
+
if (upgradeAuthDenied)
|
|
2576
|
+
return upgradeAuthDenied;
|
|
2465
2577
|
if (isLinkedInstall()) {
|
|
2466
2578
|
return { kind: 'command.result', text: '⏭ 开发模式,跳过升级检查' };
|
|
2467
2579
|
}
|
|
@@ -2493,11 +2605,26 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2493
2605
|
}
|
|
2494
2606
|
// /file 命令:发送项目内文件,支持 /file path 和 /file channel path
|
|
2495
2607
|
if (normalizedContent.startsWith('/file')) {
|
|
2496
|
-
if (!isAdmin)
|
|
2497
|
-
return { kind: 'command.error', text: '❌ 无权限:此命令仅限管理员使用' };
|
|
2498
2608
|
// 飞书会将 .md 等后缀自动转为 Markdown 链接: foo.md → [foo.md](http://foo.md/)
|
|
2499
2609
|
// 还原: 将 [text](url) 替换为 text
|
|
2500
2610
|
const rawArg = normalizedContent.slice(5).trim().replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
2611
|
+
const fileAuthDenied = await authorizeIntent({
|
|
2612
|
+
intent: {
|
|
2613
|
+
operation: 'file.fetch',
|
|
2614
|
+
scope: 'filesystem',
|
|
2615
|
+
source: 'slash',
|
|
2616
|
+
args: rawArg ? { filePath: rawArg } : {},
|
|
2617
|
+
},
|
|
2618
|
+
identity,
|
|
2619
|
+
session: activeSession,
|
|
2620
|
+
channel,
|
|
2621
|
+
channelId,
|
|
2622
|
+
userId,
|
|
2623
|
+
selfAid: this.agentRegistry?.resolveByChannel(channel)?.aid,
|
|
2624
|
+
isDaemonOwner,
|
|
2625
|
+
});
|
|
2626
|
+
if (fileAuthDenied)
|
|
2627
|
+
return fileAuthDenied;
|
|
2501
2628
|
if (!rawArg) {
|
|
2502
2629
|
const usage = isOwner
|
|
2503
2630
|
? '用法: /file <相对路径> 或 /file <渠道> <相对路径>\n示例: /file src/index.ts\n示例: /file feishu report.md'
|
|
@@ -214,6 +214,34 @@ function readFileOrNull(file) {
|
|
|
214
214
|
return null;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* 带错误捕获的 reader。默认 reader 把所有读盘异常吞成 null,调用方无从区分
|
|
219
|
+
* ENOENT(文件确实没有)与 EACCES/EISDIR(部署或权限事故)——对随包发布的
|
|
220
|
+
* 文件(schema、角色模板)来说这层信息正是定位问题的关键。
|
|
221
|
+
*
|
|
222
|
+
* 用法:建一个实例,把 `read` 交给 fileCache,loader 里拿到 null 时用
|
|
223
|
+
* `lastError()` 拼文案、把 `lastErrorCause()` 挂到新 Error 的 `cause` 上——
|
|
224
|
+
* 后者保住原始异常对象,`code`/`path`/`errno` 这些结构化字段不至于只剩字符串。
|
|
225
|
+
* 注意 loader 只在未命中时执行,与 read 同步成对调用,因此闭包里的 last
|
|
226
|
+
* 不会串到别的文件上。
|
|
227
|
+
*/
|
|
228
|
+
export function createErrorCapturingReader() {
|
|
229
|
+
let last = null;
|
|
230
|
+
return {
|
|
231
|
+
read: (file) => {
|
|
232
|
+
try {
|
|
233
|
+
last = null;
|
|
234
|
+
return fs.readFileSync(file, 'utf-8');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
last = error;
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
lastError: () => (last instanceof Error ? last.message : last === null ? 'unreadable' : String(last)),
|
|
242
|
+
lastErrorCause: () => last,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
217
245
|
/** 缓存值内存占用近似:用读入的原始字符串长度(字节级近似),null 记 0。 */
|
|
218
246
|
function approxBytes(raw) {
|
|
219
247
|
return raw === null ? 0 : raw.length;
|
|
@@ -89,9 +89,37 @@ const CATALOG = [
|
|
|
89
89
|
{ path: 'name', type: 'string', optional: true },
|
|
90
90
|
{ path: 'chatType', type: 'string', optional: true },
|
|
91
91
|
{ path: 'threadId', type: 'string', optional: true },
|
|
92
|
+
{ path: 'cause', type: 'string', optional: true },
|
|
93
|
+
{ path: 'sourceSessionId', type: 'string', optional: true },
|
|
94
|
+
{ path: 'renewSource', type: 'string', optional: true },
|
|
92
95
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
93
96
|
],
|
|
94
97
|
},
|
|
98
|
+
{
|
|
99
|
+
type: 'session:renew-evaluated',
|
|
100
|
+
namespace: 'session',
|
|
101
|
+
name: 'renew-evaluated',
|
|
102
|
+
description: 'Session Renew 判定完成',
|
|
103
|
+
fields: [
|
|
104
|
+
{ path: 'candidateSessionId', type: 'string' },
|
|
105
|
+
{ path: 'selectedSessionId', type: 'string' },
|
|
106
|
+
{ path: 'renewed', type: 'boolean' },
|
|
107
|
+
{ path: 'decision', type: 'string' },
|
|
108
|
+
{ path: 'source', type: 'string' },
|
|
109
|
+
{ path: 'channel', type: 'string' },
|
|
110
|
+
{ path: 'channelName', type: 'string', optional: true },
|
|
111
|
+
{ path: 'channelId', type: 'string' },
|
|
112
|
+
{ path: 'selfAid', type: 'string', optional: true },
|
|
113
|
+
{ path: 'idleHours', type: 'number' },
|
|
114
|
+
{ path: 'responseMode', type: 'string', optional: true },
|
|
115
|
+
{ path: 'baseagent', type: 'string', optional: true },
|
|
116
|
+
{ path: 'model', type: 'string', optional: true },
|
|
117
|
+
{ path: 'effort', type: 'string', optional: true },
|
|
118
|
+
{ path: 'fallbackReason', type: 'string', optional: true },
|
|
119
|
+
{ path: 'durationMs', type: 'number' },
|
|
120
|
+
{ path: 'timestamp', type: 'number' },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
95
123
|
{
|
|
96
124
|
type: 'session:switched',
|
|
97
125
|
namespace: 'session',
|
|
@@ -407,6 +435,7 @@ const CATALOG = [
|
|
|
407
435
|
{ path: 'description', type: 'string', optional: true },
|
|
408
436
|
{ path: 'subagentType', type: 'string', optional: true },
|
|
409
437
|
{ path: 'status', type: 'string' },
|
|
438
|
+
{ path: 'terminalReason', type: 'string', optional: true },
|
|
410
439
|
{ path: 'outputFile', type: 'string', optional: true },
|
|
411
440
|
{ path: 'summary', type: 'string', optional: true },
|
|
412
441
|
{ path: 'usage', type: 'object', optional: true },
|
|
@@ -2,7 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { EvolAgent } from './evolagent.js';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
|
-
import {
|
|
5
|
+
import { agentPersonalDir } from '../paths.js';
|
|
6
|
+
import { invalidateAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
6
7
|
import { loadAllAgents, ensureAgentDirSkeleton, loadAgent, validateAgentConfig, } from '../config-store.js';
|
|
7
8
|
import { resolveEffective } from '../config/config-manager.js';
|
|
8
9
|
// ── Channel Fingerprint ───────────────────────────────────────────────────
|
|
@@ -210,9 +211,8 @@ export class EvolAgentRegistry {
|
|
|
210
211
|
return [...this.agents.values()].map(a => this.toInfo(a));
|
|
211
212
|
}
|
|
212
213
|
invalidateAgentDisplayCache(aid) {
|
|
213
|
-
|
|
214
|
+
invalidateAgentDisplayName(aid);
|
|
214
215
|
this.personalNameCache.delete(aid);
|
|
215
|
-
this.displayNamePending.delete(aid);
|
|
216
216
|
}
|
|
217
217
|
runnableAgents() {
|
|
218
218
|
return [...this.agents.values()].filter(a => a.status === 'stopped');
|
|
@@ -442,43 +442,8 @@ export class EvolAgentRegistry {
|
|
|
442
442
|
}
|
|
443
443
|
return null;
|
|
444
444
|
}
|
|
445
|
-
displayNameCache = new Map();
|
|
446
|
-
displayNamePending = new Set();
|
|
447
445
|
resolveDisplayName(aid) {
|
|
448
|
-
|
|
449
|
-
if (cached)
|
|
450
|
-
return cached;
|
|
451
|
-
try {
|
|
452
|
-
const mdPath = agentMdPath(aid);
|
|
453
|
-
if (fs.existsSync(mdPath)) {
|
|
454
|
-
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
455
|
-
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
456
|
-
if (fm) {
|
|
457
|
-
const nm = fm[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
458
|
-
if (nm?.[1]) {
|
|
459
|
-
this.displayNameCache.set(aid, nm[1]);
|
|
460
|
-
return nm[1];
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
catch { }
|
|
466
|
-
if (!this.displayNamePending.has(aid)) {
|
|
467
|
-
this.displayNamePending.add(aid);
|
|
468
|
-
import('../aun/aid/index.js').then(({ agentmdGet }) => {
|
|
469
|
-
agentmdGet(aid).then(content => {
|
|
470
|
-
if (typeof content === 'string') {
|
|
471
|
-
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
472
|
-
if (fm) {
|
|
473
|
-
const nm = fm[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
474
|
-
if (nm?.[1])
|
|
475
|
-
this.displayNameCache.set(aid, nm[1]);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}).catch(() => { }).finally(() => this.displayNamePending.delete(aid));
|
|
479
|
-
}).catch(() => { this.displayNamePending.delete(aid); });
|
|
480
|
-
}
|
|
481
|
-
return undefined;
|
|
446
|
+
return resolveAgentDisplayName(aid);
|
|
482
447
|
}
|
|
483
448
|
personalNameCache = new Map();
|
|
484
449
|
resolvePersonalName(aid) {
|