evolcore 0.0.24 → 0.0.26
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 +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -79,6 +79,7 @@ const ENTRIES = [
|
|
|
79
79
|
{ id: 'chatmode.agent.nothuman', type: 'setting', contexts: ['agent'], name: 'chatmode', label: 'Agent 非人类对话模式', section: 'agent', order: 43, query: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, options: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, update: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.update', scope: 'agent' }, control: 'select' },
|
|
80
80
|
{ id: 'activity.agent', type: 'setting', contexts: ['agent'], name: 'activity', label: 'Agent 中间输出', section: 'agent', order: 50, query: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, options: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, update: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.update', scope: 'agent' }, control: 'select' },
|
|
81
81
|
{ id: 'observable.agent', type: 'setting', contexts: ['agent'], name: 'observable', label: '观察模式', section: 'agent', order: 60, query: { cmd: '/observable', operation: 'observable.current', scope: 'agent' }, update: { cmd: '/observable', operation: 'observable.update', scope: 'agent' }, valueSchema: { type: 'boolean' }, control: 'toggle' },
|
|
82
|
+
{ id: 'aid.avatar', type: 'action', contexts: ['agent'], name: 'aid', label: '更换头像', section: 'agent', order: 70, action: { cmd: '/aid', action: 'avatar', operation: 'ec.aid.avatar', scope: 'agent' }, valueSchema: { type: 'object', properties: { data: { type: 'string' }, url: { type: 'string', format: 'uri' } }, oneOf: [{ required: ['data'] }, { required: ['url'] }] }, control: 'action' },
|
|
82
83
|
{ id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
83
84
|
{ id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
84
85
|
{ id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
|
|
@@ -86,6 +87,7 @@ const ENTRIES = [
|
|
|
86
87
|
{ id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
|
|
87
88
|
{ id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
|
|
88
89
|
{ id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
|
|
90
|
+
{ id: 'system.paircode', type: 'action', contexts: ['process'], name: 'system', label: '获取配对码', section: 'process', order: 35, action: { cmd: '/system', action: 'paircode', operation: 'system.paircode', scope: 'process' } },
|
|
89
91
|
{ id: 'agent.list', type: 'list', contexts: ['process'], name: 'agent', label: 'Agent 列表', section: 'process', order: 40, options: { cmd: '/agent', args: { options: 'all' }, operation: 'agent.list', scope: 'control', mode: 'dynamic' }, control: 'list' },
|
|
90
92
|
{ id: 'agent.create', type: 'action', contexts: ['process'], name: 'agent', label: '创建 Agent', section: 'process', order: 50, action: { cmd: '/agent', action: 'create', operation: 'agent.create', scope: 'control' } },
|
|
91
93
|
{ id: 'agent.reload', type: 'action', contexts: ['process'], name: 'agent', label: '重载 Agent', section: 'process', order: 60, action: { cmd: '/agent', action: 'reload', operation: 'agent.reload', scope: 'control' } },
|
|
@@ -303,11 +305,18 @@ export async function resolveExplicitMenuExecutionContext(input) {
|
|
|
303
305
|
}
|
|
304
306
|
function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
|
|
305
307
|
function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
|
|
306
|
-
async function operationView(binding, context) {
|
|
308
|
+
async function operationView(binding, context, options = {}) {
|
|
307
309
|
if (!binding)
|
|
308
310
|
return undefined;
|
|
309
311
|
if (binding.supported === false)
|
|
310
312
|
return { supported: false, allowed: false, reason: { code: 'NOT_SUPPORTED', message: 'operation is not supported' } };
|
|
313
|
+
if (binding.operation === 'system.paircode' && options.paircodeAllowed === false) {
|
|
314
|
+
return {
|
|
315
|
+
supported: true,
|
|
316
|
+
allowed: false,
|
|
317
|
+
reason: { code: 'NOT_ALLOWED', message: 'Pairing code issuance requires encrypted DaemonOwner authentication' },
|
|
318
|
+
};
|
|
319
|
+
}
|
|
311
320
|
if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
|
|
312
321
|
return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
|
|
313
322
|
}
|
|
@@ -330,16 +339,17 @@ function wireBinding(binding, context) {
|
|
|
330
339
|
export async function getMenuCatalog(input) {
|
|
331
340
|
const context = await resolveMenuCatalogContext(input);
|
|
332
341
|
const entries = [];
|
|
342
|
+
const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
|
|
333
343
|
for (const entry of ENTRIES.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
|
|
334
344
|
const operations = {};
|
|
335
345
|
for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
|
|
336
|
-
const view = await operationView(binding, context);
|
|
346
|
+
const view = await operationView(binding, context, { paircodeAllowed });
|
|
337
347
|
if (view)
|
|
338
348
|
operations[key] = view;
|
|
339
349
|
}
|
|
340
350
|
entries.push({ id: entry.id, type: entry.type, name: entry.name, label: entry.label, section: entry.section, order: entry.order, target: { scope: context.kind === 'agent' ? 'agent' : context.kind === 'process' ? 'process' : 'relation', ...(context.self ? { self: context.self } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}) }, valueSchema: entry.valueSchema ?? { type: 'object' }, operations, wire: { name: entry.name, operations: { ...(wireBinding(entry.query, context) ? { query: wireBinding(entry.query, context) } : {}), ...(wireBinding(entry.options, context) ? { options: wireBinding(entry.options, context) } : {}), ...(wireBinding(entry.update, context) ? { update: wireBinding(entry.update, context) } : {}), ...(wireBinding(entry.action, context) ? { action: wireBinding(entry.action, context) } : {}) } }, ui: { control: entry.control ?? 'action' } });
|
|
341
351
|
}
|
|
342
|
-
const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, roles: readRolesConfig(context.subject.selfAid) });
|
|
352
|
+
const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, paircodeAllowed, roles: readRolesConfig(context.subject.selfAid) });
|
|
343
353
|
const data = { $schema_version: 1, kind: 'menu.catalog', complete: true, catalogRevision: hash({ entries, context: context.contextKey, policyRevision }), policyRevision, generatedAt: Date.now(), context: { contextKey: context.contextKey, kind: context.kind, channelScope: context.channelScope, transport: context.transport, chatType: context.chatType, ...(context.self ? { self: context.self } : {}), ...(context.actor ? { actor: context.actor } : {}), ...(context.groupId ? { groupId: context.groupId } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}), requesterRole: context.requesterRole, ...(context.groupMembershipRole ? { groupMembershipRole: context.groupMembershipRole } : {}), ...(context.baselineRole !== undefined ? { baselineRole: context.baselineRole } : {}) }, features: { batchValuesV1: true, verifiedExplicitGroupContext: context.kind === 'group_relation' }, entries };
|
|
344
354
|
return data;
|
|
345
355
|
}
|
|
@@ -19,7 +19,7 @@ import { scanInstances } from '../../utils/instance-registry.js';
|
|
|
19
19
|
import { isFullAccessEnabled, 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';
|
|
22
|
-
import { execAgentAction, execAgentQuery, execAgentOptions, resolveProjectPath } from './agent-control.js';
|
|
22
|
+
import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
|
|
23
23
|
import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
|
|
24
24
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
25
25
|
import { buildSessionTurnList } from '../session/session-turns.js';
|
|
@@ -40,7 +40,7 @@ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
|
40
40
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
41
41
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
42
42
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
43
|
-
import { validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
43
|
+
import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
44
44
|
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
45
45
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
46
46
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
@@ -1015,6 +1015,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1015
1015
|
return intent('system.upgrade', 'process', { action });
|
|
1016
1016
|
if (action === 'check')
|
|
1017
1017
|
return intent('system.status', 'process', { action });
|
|
1018
|
+
if (action === 'paircode')
|
|
1019
|
+
return intent('system.paircode', 'process', { action });
|
|
1018
1020
|
}
|
|
1019
1021
|
if (cmdBase === '/agent') {
|
|
1020
1022
|
if (action === 'reload')
|
|
@@ -1028,6 +1030,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1028
1030
|
if (action === 'disable')
|
|
1029
1031
|
return intent('agent.disable', 'control', { action });
|
|
1030
1032
|
}
|
|
1033
|
+
if (cmdBase === '/aid' && action === 'avatar')
|
|
1034
|
+
return intent('ec.aid.avatar', 'agent', { action });
|
|
1031
1035
|
if (cmdBase === '/trigger') {
|
|
1032
1036
|
if (action === 'create' || action === 'set')
|
|
1033
1037
|
return intent('trigger.create', 'relation', { action });
|
|
@@ -1287,7 +1291,11 @@ function menuResultFailure(error) {
|
|
|
1287
1291
|
return { error: normalized.message, code: normalized.code, ...(normalized.data !== undefined ? { data: normalized.data } : {}) };
|
|
1288
1292
|
}
|
|
1289
1293
|
function resolveExternalMenuAgentChannel(owner, payload, fallbackChannel, required) {
|
|
1290
|
-
const requestedAid = typeof payload?.agent === 'string'
|
|
1294
|
+
const requestedAid = typeof payload?.agent === 'string' && payload.agent.trim()
|
|
1295
|
+
? payload.agent.trim()
|
|
1296
|
+
: typeof payload?.args?.aid === 'string'
|
|
1297
|
+
? payload.args.aid.trim()
|
|
1298
|
+
: '';
|
|
1291
1299
|
if (!requestedAid) {
|
|
1292
1300
|
return required
|
|
1293
1301
|
? { error: '缺少顶层 agent 参数', code: 'MISSING_AID' }
|
|
@@ -1761,7 +1769,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1761
1769
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1762
1770
|
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1763
1771
|
if (models.length > 0)
|
|
1764
|
-
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '')
|
|
1772
|
+
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : ''), selected: modelMatches(agent, m, currentModel) }));
|
|
1765
1773
|
}
|
|
1766
1774
|
return null;
|
|
1767
1775
|
}
|
|
@@ -2366,6 +2374,24 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2366
2374
|
}
|
|
2367
2375
|
if (cmdBase === '/system') {
|
|
2368
2376
|
const view = args?.view ?? 'status';
|
|
2377
|
+
if (view === 'version') {
|
|
2378
|
+
const data = {};
|
|
2379
|
+
try {
|
|
2380
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
2381
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2382
|
+
if (pkg?.version)
|
|
2383
|
+
data.version = pkg.version;
|
|
2384
|
+
}
|
|
2385
|
+
catch { }
|
|
2386
|
+
try {
|
|
2387
|
+
const fp = path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json');
|
|
2388
|
+
const fp2 = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
2389
|
+
if (fp2?.version)
|
|
2390
|
+
data.fastaunVersion = fp2.version;
|
|
2391
|
+
}
|
|
2392
|
+
catch { }
|
|
2393
|
+
return { data };
|
|
2394
|
+
}
|
|
2369
2395
|
if (view === 'monitor') {
|
|
2370
2396
|
if (typeof this.daemonMonitorProvider !== 'function') {
|
|
2371
2397
|
return { error: 'Daemon monitor snapshot is unavailable', code: 'DEPENDENCY_UNAVAILABLE' };
|
|
@@ -3061,7 +3087,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3061
3087
|
return menuResultFailure(error);
|
|
3062
3088
|
}
|
|
3063
3089
|
}
|
|
3064
|
-
if (cmdBase === '/agent'
|
|
3090
|
+
if ((cmdBase === '/agent' || cmdBase === '/aid') && !args?.aid) {
|
|
3065
3091
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
3066
3092
|
if (selfAid)
|
|
3067
3093
|
args = { ...(args ?? {}), aid: selfAid };
|
|
@@ -3096,6 +3122,30 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3096
3122
|
return { error: `不支持 gateway action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3097
3123
|
}
|
|
3098
3124
|
const isMenuAdmin = isManagementRole(authIdentity.role);
|
|
3125
|
+
// ── /aid avatar ──
|
|
3126
|
+
// The AID namespace is the only avatar mutation surface.
|
|
3127
|
+
if (cmdBase === '/aid') {
|
|
3128
|
+
if (action !== 'avatar')
|
|
3129
|
+
return { error: `不支持 aid action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3130
|
+
const result = await execAidAvatar(args);
|
|
3131
|
+
if (!('error' in result)) {
|
|
3132
|
+
const data = result.data;
|
|
3133
|
+
if (data?.avatar) {
|
|
3134
|
+
this.eventBus.publish({
|
|
3135
|
+
type: 'agent:avatar_updated',
|
|
3136
|
+
aid: data.aid,
|
|
3137
|
+
avatar: data.avatar,
|
|
3138
|
+
...(data.sha256 !== undefined ? { sha256: data.sha256 } : {}),
|
|
3139
|
+
...(data.mime !== undefined ? { mime: data.mime } : {}),
|
|
3140
|
+
...(data.width !== undefined ? { width: data.width } : {}),
|
|
3141
|
+
...(data.height !== undefined ? { height: data.height } : {}),
|
|
3142
|
+
...(data.aspectRatio !== undefined ? { aspectRatio: data.aspectRatio } : {}),
|
|
3143
|
+
timestamp: Date.now(),
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return result;
|
|
3148
|
+
}
|
|
3099
3149
|
// ── /agent action ──
|
|
3100
3150
|
// 控制 channel:验 evolcore.owners,可执行进程级(create/delete/enable/disable)+ 自管理(update/reload)。
|
|
3101
3151
|
// agent channel:闸门已挡掉进程级 + 跨 agent,仅 update/reload 能到此;reload 允许 owner/admin,update 仅 owner。
|
|
@@ -3855,6 +3905,13 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3855
3905
|
this.eventBus.publish({ type: 'system:restart', channel, channelId });
|
|
3856
3906
|
return { data: { action: 'restart', success: true } };
|
|
3857
3907
|
}
|
|
3908
|
+
if (action === 'paircode') {
|
|
3909
|
+
// Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
|
|
3910
|
+
// the local IPC endpoint. The store reuses an unexpired code and creates
|
|
3911
|
+
// a new one only after the shared TTL has elapsed.
|
|
3912
|
+
const pair = remoteAccessService.getPairingCode(subject.actorId);
|
|
3913
|
+
return { data: { code: pair.code, expiresAt: pair.expiresAt } };
|
|
3914
|
+
}
|
|
3858
3915
|
if (action === 'check') {
|
|
3859
3916
|
const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3860
3917
|
// delegateAsAction 会把 structured 展开到 data 顶层,并保留 data.structured 兼容旧客户端。
|
|
@@ -4157,9 +4214,9 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4157
4214
|
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
4158
4215
|
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
4159
4216
|
const requiresAgentTarget = isRoleRequest || isConnectRequest || [
|
|
4160
|
-
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
|
|
4217
|
+
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group', 'aid',
|
|
4161
4218
|
].includes(name)
|
|
4162
|
-
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
|
|
4219
|
+
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group', '/aid'].includes(compatCmd ?? '')
|
|
4163
4220
|
|| (!isProcessLevel && requestedContextKind !== undefined && requestedContextKind !== 'process');
|
|
4164
4221
|
const target = isProcessLevel
|
|
4165
4222
|
? { channel: SYSTEM_CONTROL_CHANNEL }
|
|
@@ -4169,6 +4226,12 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4169
4226
|
const targetChannel = target.channel;
|
|
4170
4227
|
const targetAid = this.getOwningAgent?.(targetChannel)?.aid
|
|
4171
4228
|
?? this.getDaemonTriggerOwnerAid?.(targetChannel);
|
|
4229
|
+
if (name === 'aid' || compatCmd === '/aid') {
|
|
4230
|
+
const requestedAid = typeof payload?.args?.aid === 'string' ? payload.args.aid.trim().replace(/^@/, '') : '';
|
|
4231
|
+
if (requestedAid && targetAid && requestedAid !== targetAid) {
|
|
4232
|
+
return ecwebErr(id, name, 'INVALID_ARGUMENT', 'args.aid 必须与目标 Agent 一致');
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4172
4235
|
const trustedSubject = buildAuthSubject({
|
|
4173
4236
|
selfAid: targetAid,
|
|
4174
4237
|
actorId: context.actorAid,
|
|
@@ -4185,6 +4248,12 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4185
4248
|
});
|
|
4186
4249
|
const trustedIdentity = trustedSubject.identity;
|
|
4187
4250
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4251
|
+
if (payload?.type === 'menu.action'
|
|
4252
|
+
&& menuCommandToken(cmd) === '/system'
|
|
4253
|
+
&& payload.action === 'paircode'
|
|
4254
|
+
&& (context.authMethod !== 'daemon-owner' || (context.source === 'control' && !context.encrypted))) {
|
|
4255
|
+
return ecwebErr(id, name, 'NOT_ALLOWED', 'Pairing code issuance requires encrypted DaemonOwner authentication');
|
|
4256
|
+
}
|
|
4188
4257
|
try {
|
|
4189
4258
|
if (menuCommandToken(cmd) === '/baseagent-models') {
|
|
4190
4259
|
if (payload.type !== 'menu.query')
|
|
@@ -4203,6 +4272,8 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4203
4272
|
channelScope: 'control',
|
|
4204
4273
|
channelId: targetChannel,
|
|
4205
4274
|
request: payload.args.context,
|
|
4275
|
+
paircodeAllowed: context.authMethod === 'daemon-owner'
|
|
4276
|
+
&& (context.source !== 'control' || context.encrypted),
|
|
4206
4277
|
});
|
|
4207
4278
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
4208
4279
|
}
|
|
@@ -4307,15 +4378,6 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4307
4378
|
const validationError = validateMenuRequest(payload ?? {});
|
|
4308
4379
|
if (validationError)
|
|
4309
4380
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
4310
|
-
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
|
|
4311
|
-
const owners = loadDaemonConfig().owners ?? [];
|
|
4312
|
-
if (isProcessLevel && owners.length === 0) {
|
|
4313
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4314
|
-
code: 'NOT_ALLOWED',
|
|
4315
|
-
message: formatAuthorizationDenial('Configure daemon.json.owners before using process-level operations.', 'DAEMON_OWNER_REQUIRED'),
|
|
4316
|
-
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
4317
|
-
});
|
|
4318
|
-
}
|
|
4319
4381
|
if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
|
|
4320
4382
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4321
4383
|
code: 'PERMISSION_DENIED',
|
|
@@ -4323,29 +4385,22 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4323
4385
|
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4324
4386
|
});
|
|
4325
4387
|
}
|
|
4326
|
-
//
|
|
4327
|
-
//
|
|
4328
|
-
//
|
|
4329
|
-
|
|
4330
|
-
// without requiring owner-bound pairing metadata.
|
|
4388
|
+
// ECWeb authentication grants daemon-owner authority independently of the
|
|
4389
|
+
// configured owners. Prefer an owner for audit/Trigger attribution when
|
|
4390
|
+
// available; otherwise keep local and remote management actors distinct.
|
|
4391
|
+
const owners = loadDaemonConfig().owners ?? [];
|
|
4331
4392
|
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4332
4393
|
? trusted.actorAid
|
|
4333
|
-
: owners[0] ?? (trusted.localDirect ? 'local-direct' :
|
|
4334
|
-
if (!userId) {
|
|
4335
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4336
|
-
code: 'PERMISSION_DENIED',
|
|
4337
|
-
message: 'ECWeb operation requires configured daemon owners',
|
|
4338
|
-
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4339
|
-
});
|
|
4340
|
-
}
|
|
4394
|
+
: owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
|
|
4341
4395
|
return execMenuForSystemControl.call(this, payload, {
|
|
4342
4396
|
source: 'ecweb',
|
|
4343
4397
|
actorAid: userId,
|
|
4398
|
+
authMethod: trusted.localDirect ? 'daemon-owner' : 'remote-access-token',
|
|
4399
|
+
encrypted: trusted.localDirect,
|
|
4344
4400
|
localDirect: trusted.localDirect,
|
|
4345
4401
|
// The ECWeb server/IPC control-token gate authenticates this management
|
|
4346
4402
|
// request. All authenticated ECWeb connections are equally eligible to
|
|
4347
|
-
// approve persistent fullaccess
|
|
4348
|
-
// a current configured owner.
|
|
4403
|
+
// approve persistent fullaccess without owner-bound token metadata.
|
|
4349
4404
|
canApprovePersistentFullAccess: true,
|
|
4350
4405
|
owners,
|
|
4351
4406
|
});
|
|
@@ -4379,6 +4434,8 @@ export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
|
4379
4434
|
return execMenuForSystemControl.call(this, payload, {
|
|
4380
4435
|
source: 'control',
|
|
4381
4436
|
actorAid: managementActor,
|
|
4437
|
+
authMethod: authorization.allowed ? authorization.authMethod : 'daemon-owner',
|
|
4438
|
+
encrypted: auth.encrypted === true,
|
|
4382
4439
|
localDirect: false,
|
|
4383
4440
|
canApprovePersistentFullAccess: false,
|
|
4384
4441
|
owners,
|
|
@@ -120,6 +120,7 @@ export const MENU_NAME_COMMANDS = Object.freeze({
|
|
|
120
120
|
system: '/system',
|
|
121
121
|
cli: '/cli',
|
|
122
122
|
agent: '/agent',
|
|
123
|
+
aid: '/aid',
|
|
123
124
|
trigger: '/trigger',
|
|
124
125
|
file: '/file',
|
|
125
126
|
gateway: '/gateway',
|
|
@@ -566,6 +567,11 @@ function sanitizeLogArgv(value, depth, seen) {
|
|
|
566
567
|
export function sanitizeMenuLogValue(value, key = '', depth = 0, seen = new WeakSet()) {
|
|
567
568
|
if (isSensitiveLogKey(key))
|
|
568
569
|
return '[REDACTED]';
|
|
570
|
+
// Pairing codes are intentionally returned as `code` on the wire for
|
|
571
|
+
// compatibility with /pair and the ECWeb API. Do not persist six-digit
|
|
572
|
+
// credentials in menu-protocol.log.
|
|
573
|
+
if (key === 'code' && typeof value === 'string' && /^\d{6}$/.test(value))
|
|
574
|
+
return '[REDACTED]';
|
|
569
575
|
if (value === null || value === undefined || typeof value === 'number' || typeof value === 'boolean')
|
|
570
576
|
return value;
|
|
571
577
|
if (typeof value === 'bigint')
|
|
@@ -4,6 +4,20 @@ import { agentDataDir } from '../../paths.js';
|
|
|
4
4
|
import { atomicReadJson, atomicWriteJson } from '../../utils/atomic-write.js';
|
|
5
5
|
export const DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS = 30 * 24 * 60 * 60;
|
|
6
6
|
export const MAX_MENU_TOKENS_PER_AGENT = 1_000;
|
|
7
|
+
function validInstallationId(value) {
|
|
8
|
+
return value === undefined || (typeof value === 'string' && value.length <= 256
|
|
9
|
+
&& !/[\u0000-\u001f\u007f]/.test(value));
|
|
10
|
+
}
|
|
11
|
+
function normalizeInstallation(value) {
|
|
12
|
+
if (!value || typeof value !== 'object' || !validInstallationId(value.deviceId)
|
|
13
|
+
|| !validInstallationId(value.slotId))
|
|
14
|
+
return null;
|
|
15
|
+
const deviceId = value.deviceId?.trim() || '';
|
|
16
|
+
const slotId = value.slotId?.trim() || '';
|
|
17
|
+
if (slotId && !deviceId)
|
|
18
|
+
return null;
|
|
19
|
+
return { deviceId, slotId };
|
|
20
|
+
}
|
|
7
21
|
function hashToken(token) {
|
|
8
22
|
return createHash('sha256').update(token).digest('hex');
|
|
9
23
|
}
|
|
@@ -13,6 +27,9 @@ function isTokenRecord(value) {
|
|
|
13
27
|
const record = value;
|
|
14
28
|
return typeof record.peerAid === 'string'
|
|
15
29
|
&& record.peerAid.trim().length > 0
|
|
30
|
+
&& validInstallationId(record.peerDeviceId)
|
|
31
|
+
&& validInstallationId(record.peerSlotId)
|
|
32
|
+
&& (!record.peerSlotId?.trim() || !!record.peerDeviceId?.trim())
|
|
16
33
|
&& typeof record.tokenHash === 'string'
|
|
17
34
|
&& /^[a-f0-9]{64}$/.test(record.tokenHash)
|
|
18
35
|
&& typeof record.requestedAt === 'number'
|
|
@@ -28,6 +45,10 @@ function isTokenRecord(value) {
|
|
|
28
45
|
function isExpired(record, now) {
|
|
29
46
|
return now - record.lastUsedAt >= record.idleTimeout * 1000;
|
|
30
47
|
}
|
|
48
|
+
function installationMatches(record, installation) {
|
|
49
|
+
return (record.peerDeviceId?.trim() || '') === (installation.deviceId?.trim() || '')
|
|
50
|
+
&& (record.peerSlotId?.trim() || '') === (installation.slotId?.trim() || '');
|
|
51
|
+
}
|
|
31
52
|
function hashesEqual(left, right) {
|
|
32
53
|
const leftBytes = Buffer.from(left, 'hex');
|
|
33
54
|
const rightBytes = Buffer.from(right, 'hex');
|
|
@@ -41,10 +62,14 @@ export class MenuTokenStore {
|
|
|
41
62
|
constructor(maxRecords = MAX_MENU_TOKENS_PER_AGENT) {
|
|
42
63
|
this.maxRecords = maxRecords;
|
|
43
64
|
}
|
|
44
|
-
issue(selfAid, peerAid, metadata = {}, now = Date.now()) {
|
|
65
|
+
issue(selfAid, peerAid, metadata = {}, installationOrNow = {}, now = Date.now()) {
|
|
66
|
+
const installation = normalizeInstallation(typeof installationOrNow === 'number' ? {} : installationOrNow);
|
|
67
|
+
if (!installation)
|
|
68
|
+
return null;
|
|
69
|
+
const issuedAt = typeof installationOrNow === 'number' ? installationOrNow : now;
|
|
45
70
|
const file = this.read(selfAid);
|
|
46
71
|
const idleTimeout = DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS;
|
|
47
|
-
const tokens = file.tokens.filter(record => record.peerAid !== peerAid && !isExpired(record,
|
|
72
|
+
const tokens = file.tokens.filter(record => (record.peerAid !== peerAid || !installationMatches(record, installation)) && !isExpired(record, issuedAt));
|
|
48
73
|
if (tokens.length >= this.maxRecords) {
|
|
49
74
|
if (tokens.length !== file.tokens.length)
|
|
50
75
|
this.write(selfAid, tokens);
|
|
@@ -53,9 +78,11 @@ export class MenuTokenStore {
|
|
|
53
78
|
const menuToken = `mt_${randomBytes(32).toString('base64url')}`;
|
|
54
79
|
tokens.push({
|
|
55
80
|
peerAid,
|
|
81
|
+
...(installation.deviceId?.trim() ? { peerDeviceId: installation.deviceId.trim() } : {}),
|
|
82
|
+
...(installation.slotId?.trim() ? { peerSlotId: installation.slotId.trim() } : {}),
|
|
56
83
|
tokenHash: hashToken(menuToken),
|
|
57
|
-
requestedAt:
|
|
58
|
-
lastUsedAt:
|
|
84
|
+
requestedAt: issuedAt,
|
|
85
|
+
lastUsedAt: issuedAt,
|
|
59
86
|
idleTimeout,
|
|
60
87
|
...(metadata.clientName ? { clientName: metadata.clientName } : {}),
|
|
61
88
|
...(metadata.clientVersion ? { clientVersion: metadata.clientVersion } : {}),
|
|
@@ -64,21 +91,27 @@ export class MenuTokenStore {
|
|
|
64
91
|
this.write(selfAid, tokens);
|
|
65
92
|
return { menuToken, idleTimeout };
|
|
66
93
|
}
|
|
67
|
-
use(selfAid, peerAid, menuToken, now = Date.now()) {
|
|
94
|
+
use(selfAid, peerAid, menuToken, installationOrNow = {}, now = Date.now()) {
|
|
95
|
+
const installation = normalizeInstallation(typeof installationOrNow === 'number' ? {} : installationOrNow);
|
|
96
|
+
if (!installation)
|
|
97
|
+
return false;
|
|
98
|
+
const usedAt = typeof installationOrNow === 'number' ? installationOrNow : now;
|
|
68
99
|
const file = this.read(selfAid);
|
|
69
|
-
const activeTokens = file.tokens.filter(record => !isExpired(record,
|
|
100
|
+
const activeTokens = file.tokens.filter(record => !isExpired(record, usedAt));
|
|
70
101
|
const tokenHash = typeof menuToken === 'string' && menuToken.length > 0 && menuToken.length <= 256
|
|
71
102
|
? hashToken(menuToken)
|
|
72
103
|
: undefined;
|
|
73
104
|
const matchingIndex = tokenHash
|
|
74
|
-
? activeTokens.findIndex(record => record.peerAid === peerAid
|
|
105
|
+
? activeTokens.findIndex(record => record.peerAid === peerAid
|
|
106
|
+
&& installationMatches(record, installation)
|
|
107
|
+
&& hashesEqual(record.tokenHash, tokenHash))
|
|
75
108
|
: -1;
|
|
76
109
|
if (matchingIndex < 0) {
|
|
77
110
|
if (activeTokens.length !== file.tokens.length)
|
|
78
111
|
this.write(selfAid, activeTokens);
|
|
79
112
|
return false;
|
|
80
113
|
}
|
|
81
|
-
activeTokens[matchingIndex] = { ...activeTokens[matchingIndex], lastUsedAt:
|
|
114
|
+
activeTokens[matchingIndex] = { ...activeTokens[matchingIndex], lastUsedAt: usedAt };
|
|
82
115
|
this.write(selfAid, activeTokens);
|
|
83
116
|
return true;
|
|
84
117
|
}
|
|
@@ -45,7 +45,8 @@ export function probeSessionActivity(evidence, probes) {
|
|
|
45
45
|
// 支持的命令列表
|
|
46
46
|
const commands = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/stop', '/pause', '/compact', '/renew', '/repair', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair'];
|
|
47
47
|
const deprecatedCommands = ['/clear'];
|
|
48
|
-
|
|
48
|
+
commands.push('/diag');
|
|
49
|
+
const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair', '/diag']);
|
|
49
50
|
/** Parse the execution command without treating it as a quick/result command. */
|
|
50
51
|
export function parseFullAccessCommand(content) {
|
|
51
52
|
const match = content.match(/^\s*(\/fa|\/fullaccess)(?=\s|$)([\s\S]*)$/);
|
|
@@ -64,8 +65,37 @@ const aliases = {
|
|
|
64
65
|
'/rw': '/rewind',
|
|
65
66
|
'/base': '/baseagent',
|
|
66
67
|
};
|
|
68
|
+
const GROUP_READ_ONLY_COMMANDS = new Set(['/mentionmode', '/model', '/effort', '/perm', '/baseagent']);
|
|
69
|
+
const GROUP_TASK_COMMANDS = new Set([
|
|
70
|
+
'/help', '/evolhelp', '/status', '/check', '/ask', '/pause', '/resume', '/stop', '/compact', '/renew',
|
|
71
|
+
]);
|
|
72
|
+
/** Group slash policy applies to every role, including old command-card clicks. */
|
|
73
|
+
export function guardGroupSlashCommand(content, chatType) {
|
|
74
|
+
if (chatType !== 'group' || !content.trim().startsWith('/'))
|
|
75
|
+
return undefined;
|
|
76
|
+
const [inputCommand, ...args] = content.trim().split(/\s+/);
|
|
77
|
+
const command = aliases[inputCommand] ?? inputCommand;
|
|
78
|
+
if (GROUP_READ_ONLY_COMMANDS.has(command)) {
|
|
79
|
+
if (!args.length || (command === '/model' && args.length === 1 && ['current', 'list', 'check'].includes(args[0]))) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
kind: 'command.error',
|
|
84
|
+
text: `❌ 群聊中 ${command} 仅支持查询,请在私聊或管理界面操作`,
|
|
85
|
+
reason: 'GROUP_COMMAND_READ_ONLY',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (GROUP_TASK_COMMANDS.has(command))
|
|
89
|
+
return undefined;
|
|
90
|
+
return {
|
|
91
|
+
kind: 'command.error',
|
|
92
|
+
text: `❌ 群聊中不支持 ${inputCommand},输入 /help 查看可用命令`,
|
|
93
|
+
reason: 'GROUP_COMMAND_UNSUPPORTED',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
67
96
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
68
97
|
const quickCommandPrefixes = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/pause', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair'];
|
|
98
|
+
quickCommandPrefixes.push('/diag');
|
|
69
99
|
/**
|
|
70
100
|
* 计算两个字符串的 Levenshtein 距离(编辑距离)
|
|
71
101
|
*/
|
|
@@ -95,7 +125,7 @@ function levenshteinDistance(str1, str2) {
|
|
|
95
125
|
return matrix[len1][len2];
|
|
96
126
|
}
|
|
97
127
|
export function isQuickCommand(content) {
|
|
98
|
-
return content === '/s' || quickCommandPrefixes.some(cmd => exactBoundaryCommands.has(cmd)
|
|
128
|
+
return content === '/s' || content === '/base' || quickCommandPrefixes.some(cmd => exactBoundaryCommands.has(cmd)
|
|
99
129
|
? content === cmd || content.startsWith(cmd + ' ')
|
|
100
130
|
: content.startsWith(cmd));
|
|
101
131
|
}
|
|
@@ -131,7 +161,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
|
|
|
131
161
|
const userGroupCommands = [
|
|
132
162
|
'/status', '/help', '/evolhelp', '/check', '/chatmode', '/mentionmode',
|
|
133
163
|
'/model', '/effort', '/baseagent', '/perm', '/activity', '/stop', '/pause',
|
|
134
|
-
'/resume', '/trigger', '/file',
|
|
164
|
+
'/resume', '/trigger', '/file', '/ask',
|
|
135
165
|
];
|
|
136
166
|
const userCommands = activeChatType === 'group' && !isAdmin
|
|
137
167
|
? userGroupCommands
|
|
@@ -146,7 +176,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
|
|
|
146
176
|
return {
|
|
147
177
|
kind: 'command.error',
|
|
148
178
|
text: activeChatType === 'group'
|
|
149
|
-
? '❌
|
|
179
|
+
? '❌ 无权限:此命令仅限管理员使用,输入 /help 查看可用命令'
|
|
150
180
|
: '❌ 无权限:此命令仅限管理员使用',
|
|
151
181
|
};
|
|
152
182
|
}
|