evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
2
2
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
3
|
-
import { modelMatches } from '../model/model-catalog.js';
|
|
3
|
+
import { modelMatches, getCatalog, resolveCatalogModel, assertCatalogSelectable } from '../model/model-catalog.js';
|
|
4
4
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
5
5
|
import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
6
|
-
import { getCodexEfforts } from '../../agents/codex-runner.js';
|
|
7
6
|
import { execCodexCliSync, resolveCodexCliPath } from '../../utils/codex-cli.js';
|
|
8
7
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
8
|
+
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
9
9
|
import { buildEnvelope, isDeliveryTarget, isDeliveryTargetForChannel, replyContextFromSession } from '../message/message-utils.js';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import fs from 'fs';
|
|
@@ -39,6 +39,9 @@ import { logger } from '../../utils/logger.js';
|
|
|
39
39
|
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
|
+
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
43
|
+
import { validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
44
|
+
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
42
45
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
43
46
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
44
47
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
@@ -76,7 +79,7 @@ function getBaseagentVersion(cmd) {
|
|
|
76
79
|
return null;
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
|
-
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
82
|
+
const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
80
83
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
81
84
|
/** menu file: fetch 文件大小上限(与 /file 一致) */
|
|
82
85
|
const FILE_FETCH_MAX_SIZE = 10 * 1024 * 1024;
|
|
@@ -393,14 +396,8 @@ function tokenizeArgv(line) {
|
|
|
393
396
|
}
|
|
394
397
|
return out;
|
|
395
398
|
}
|
|
396
|
-
function getAvailableEfforts(agent, model) {
|
|
397
|
-
|
|
398
|
-
return allEfforts;
|
|
399
|
-
}
|
|
400
|
-
if (agent.name === 'codex') {
|
|
401
|
-
return getCodexEfforts(model);
|
|
402
|
-
}
|
|
403
|
-
return [];
|
|
399
|
+
async function getAvailableEfforts(agent, model) {
|
|
400
|
+
return (await agent.getAvailableEfforts?.(model) ?? []);
|
|
404
401
|
}
|
|
405
402
|
function modelDisplayLabel(agent, model) {
|
|
406
403
|
const full = agent.resolveModelId?.(model);
|
|
@@ -942,6 +939,10 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
942
939
|
return intent('trigger.list', 'relation');
|
|
943
940
|
}
|
|
944
941
|
if (verb === 'options') {
|
|
942
|
+
if (cmdBase === '/system')
|
|
943
|
+
return intent('system.status', 'process');
|
|
944
|
+
if (cmdBase === '/config')
|
|
945
|
+
return intent('config.read', args?.scope === 'agent' ? 'agent' : 'process');
|
|
945
946
|
if (cmdBase === '/agent')
|
|
946
947
|
return intent(fromControlChannel ? 'agent.list' : 'agent.show', fromControlChannel ? 'control' : 'agent');
|
|
947
948
|
if (cmdBase === '/capability')
|
|
@@ -1283,8 +1284,14 @@ function resolveExternalMenuAgentChannel(owner, payload, fallbackChannel, requir
|
|
|
1283
1284
|
: { channel: fallbackChannel };
|
|
1284
1285
|
}
|
|
1285
1286
|
const agent = owner.agentRegistry?.get?.(requestedAid) ?? null;
|
|
1286
|
-
if (!agent)
|
|
1287
|
+
if (!agent) {
|
|
1288
|
+
// The daemon trigger owner is a virtual control identity and therefore is
|
|
1289
|
+
// intentionally absent from the normal Agent registry.
|
|
1290
|
+
const daemonAid = owner.getDaemonTriggerOwnerAid?.();
|
|
1291
|
+
if (daemonAid && requestedAid === daemonAid)
|
|
1292
|
+
return { channel: 'daemon' };
|
|
1287
1293
|
return { error: `未找到 Agent: ${requestedAid}`, code: 'NOT_FOUND' };
|
|
1294
|
+
}
|
|
1288
1295
|
const channel = agent.channelInstanceNames?.()[0];
|
|
1289
1296
|
if (!channel)
|
|
1290
1297
|
return { error: `Agent 无可用 channel: ${requestedAid}`, code: 'NOT_FOUND' };
|
|
@@ -1470,6 +1477,9 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1470
1477
|
...(isControlScope && canManageProcess && canOperation('system.restart', 'process') ? [
|
|
1471
1478
|
{ cmd: '/restart', label: '重启服务', desc: '重启整个 EvolCore 服务进程' },
|
|
1472
1479
|
] : []),
|
|
1480
|
+
...(isControlScope && canManageProcess && canOperation('system.status', 'process') ? [
|
|
1481
|
+
{ cmd: '/system', label: 'Daemon 诊断', desc: '查询 daemon 状态、监控快照和日志' },
|
|
1482
|
+
] : []),
|
|
1473
1483
|
...(canUseFile ? [
|
|
1474
1484
|
{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
1475
1485
|
] : []),
|
|
@@ -1566,6 +1576,18 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1566
1576
|
});
|
|
1567
1577
|
if (optionsAuthDenied)
|
|
1568
1578
|
throw { code: optionsAuthDenied.code, message: optionsAuthDenied.error, data: optionsAuthDenied.data };
|
|
1579
|
+
if (cmdBase0 === '/system') {
|
|
1580
|
+
if (args?.option !== 'logs')
|
|
1581
|
+
throw { code: 'NOT_SUPPORTED', message: `Unsupported system option: ${String(args?.option ?? '')}` };
|
|
1582
|
+
const options = listDiagnosticLogOptions();
|
|
1583
|
+
return [
|
|
1584
|
+
...options.sources.map(source => ({ value: source.id, label: source.label, desc: `${source.fileCount} file(s), ${source.size} bytes` })),
|
|
1585
|
+
...options.levels.map(level => ({ value: `level:${level}`, label: level, desc: 'minimum log level' })),
|
|
1586
|
+
];
|
|
1587
|
+
}
|
|
1588
|
+
if (cmdBase0 === '/config' && args?.option === 'rawFile') {
|
|
1589
|
+
return listRawConfigResources();
|
|
1590
|
+
}
|
|
1569
1591
|
// ── /agent list(只读) ──
|
|
1570
1592
|
// 控制 channel:返回全量;agent channel:仅返回自身单条。
|
|
1571
1593
|
if (cmd === '/agent') {
|
|
@@ -1606,7 +1628,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1606
1628
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
1607
1629
|
const scope = args?.options === 'all' ? 'all' : 'enabled';
|
|
1608
1630
|
const role = optionsIdentity.role;
|
|
1609
|
-
const isAdmin = isManagementRole(role);
|
|
1631
|
+
const isAdmin = isManagementRole(role) || subject.isDaemonOwner;
|
|
1610
1632
|
if (!triggerScheduler)
|
|
1611
1633
|
return [];
|
|
1612
1634
|
const list = triggerScheduler.list({ all: scope === 'all' });
|
|
@@ -1710,13 +1732,15 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1710
1732
|
if ('error' in target)
|
|
1711
1733
|
throw { code: target.code, message: target.error };
|
|
1712
1734
|
const agent = this.getAgent(channel, target.baseagent);
|
|
1713
|
-
if (hasModelSwitcher(agent)
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1735
|
+
if (hasModelSwitcher(agent)) {
|
|
1736
|
+
const catalog = await getCatalog(target.sel.self, target.baseagent);
|
|
1737
|
+
assertCatalogSelectable(catalog);
|
|
1738
|
+
const rawModels = catalog.models.map(m => m.id);
|
|
1739
|
+
const models = filterModelsForRole(optionsIdentity.role, target.baseagent, rawModels, model => resolveCatalogModel(catalog, model), target.sel.self);
|
|
1716
1740
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1717
1741
|
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1718
1742
|
if (models.length > 0)
|
|
1719
|
-
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m), selected: modelMatches(agent, m, currentModel) }));
|
|
1743
|
+
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '') + (catalog.freshness === 'stale' ? ' [stale]' : ''), selected: modelMatches(agent, m, currentModel) }));
|
|
1720
1744
|
}
|
|
1721
1745
|
return null;
|
|
1722
1746
|
}
|
|
@@ -1773,7 +1797,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1773
1797
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'currentModel');
|
|
1774
1798
|
const modelTarget = { ...target, field: 'model', fieldPath: `baseagents.${target.baseagent}.model` };
|
|
1775
1799
|
const currentModel = requestedModel || (hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent).value || agent.getModel()) : agent.name);
|
|
1776
|
-
const efforts = getAvailableEfforts(agent, currentModel);
|
|
1800
|
+
const efforts = await getAvailableEfforts(agent, currentModel);
|
|
1777
1801
|
const currentEffort = menuStringArg(args, 'effort') ?? menuStringArg(args, 'current') ?? readMenuModel(target, agent).value ?? 'auto';
|
|
1778
1802
|
const allItems = [...efforts, 'auto'];
|
|
1779
1803
|
return allItems.map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e, selected: e === currentEffort }));
|
|
@@ -1949,9 +1973,25 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1949
1973
|
}
|
|
1950
1974
|
// ── /config 查询(只读,查询各层配置) ──
|
|
1951
1975
|
if (cmdBase === '/config') {
|
|
1976
|
+
if (args?.view === 'rawFile') {
|
|
1977
|
+
return { data: readRawConfigResource(args, subject.actorId ?? '') };
|
|
1978
|
+
}
|
|
1952
1979
|
const scope = args?.scope || 'process';
|
|
1953
1980
|
if (scope === 'process') {
|
|
1954
1981
|
const cfg = loadDaemonConfig();
|
|
1982
|
+
if (args?.fields !== undefined) {
|
|
1983
|
+
if (!Array.isArray(args.fields) || args.fields.length === 0
|
|
1984
|
+
|| args.fields.some((field) => typeof field !== 'string' || !field.trim())) {
|
|
1985
|
+
return { error: 'fields must be a non-empty string array', code: 'INVALID_ARGUMENT' };
|
|
1986
|
+
}
|
|
1987
|
+
const selected = {};
|
|
1988
|
+
for (const rawField of args.fields) {
|
|
1989
|
+
const field = rawField.trim();
|
|
1990
|
+
if (Object.prototype.hasOwnProperty.call(cfg, field))
|
|
1991
|
+
selected[field] = cfg[field];
|
|
1992
|
+
}
|
|
1993
|
+
return { data: { scope: 'process', fields: args.fields, config: selected } };
|
|
1994
|
+
}
|
|
1955
1995
|
return { data: { scope: 'process', config: cfg } };
|
|
1956
1996
|
}
|
|
1957
1997
|
if (scope === 'defaults') {
|
|
@@ -2283,6 +2323,23 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2283
2323
|
};
|
|
2284
2324
|
}
|
|
2285
2325
|
if (cmdBase === '/system') {
|
|
2326
|
+
const view = args?.view ?? 'status';
|
|
2327
|
+
if (view === 'monitor') {
|
|
2328
|
+
if (typeof this.daemonMonitorProvider !== 'function') {
|
|
2329
|
+
return { error: 'Daemon monitor snapshot is unavailable', code: 'DEPENDENCY_UNAVAILABLE' };
|
|
2330
|
+
}
|
|
2331
|
+
return { data: await this.daemonMonitorProvider() };
|
|
2332
|
+
}
|
|
2333
|
+
if (view === 'logs') {
|
|
2334
|
+
try {
|
|
2335
|
+
return { data: queryDiagnosticLogs(args ?? {}, subject.actorId ?? '') };
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
return { error: error?.message ?? String(error), code: error?.code ?? 'INTERNAL_ERROR' };
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
if (view !== 'status')
|
|
2342
|
+
return { error: `Unsupported system view: ${String(view)}`, code: 'NOT_SUPPORTED' };
|
|
2286
2343
|
const data = {
|
|
2287
2344
|
aid: loadDaemonConfig().aid ?? null,
|
|
2288
2345
|
pid: process.pid,
|
|
@@ -2459,7 +2516,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2459
2516
|
}
|
|
2460
2517
|
if (!patch?.nameOrId)
|
|
2461
2518
|
return { error: '缺少 nameOrId', code: 'INVALID_ARGS' };
|
|
2462
|
-
const isAdmin = isManagementRole(identity.role);
|
|
2519
|
+
const isAdmin = isManagementRole(identity.role) || subject.isDaemonOwner;
|
|
2463
2520
|
if (!isAdmin && !userId)
|
|
2464
2521
|
return { error: '无法确认身份,请确保渠道提供发送者 ID', code: 'FORBIDDEN' };
|
|
2465
2522
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
@@ -2553,13 +2610,16 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2553
2610
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2554
2611
|
let targetModel = arg;
|
|
2555
2612
|
if (hasModelSwitcher(agent)) {
|
|
2556
|
-
const
|
|
2613
|
+
const catalog = await getCatalog(target.sel.self, target.baseagent);
|
|
2614
|
+
assertCatalogSelectable(catalog);
|
|
2615
|
+
const models = catalog.models.map(m => m.id);
|
|
2557
2616
|
const decision = validateModelSelectionForRole({
|
|
2558
2617
|
role: identity.role,
|
|
2559
2618
|
baseagent: target.baseagent,
|
|
2560
2619
|
requestedModel: arg,
|
|
2561
2620
|
models,
|
|
2562
|
-
|
|
2621
|
+
selfAid: target.sel.self,
|
|
2622
|
+
resolveModelId: model => resolveCatalogModel(catalog, model),
|
|
2563
2623
|
});
|
|
2564
2624
|
if (!decision.ok)
|
|
2565
2625
|
return { error: decision.message || `invalid model: ${arg}`, code: decision.code || 'INVALID_VALUE' };
|
|
@@ -2609,7 +2669,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2609
2669
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2610
2670
|
const modelTarget = { ...target, field: 'model', fieldPath: `baseagents.${target.baseagent}.model` };
|
|
2611
2671
|
const currentModel = hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent).value || agent.getModel()) : agent.name;
|
|
2612
|
-
const validEfforts = getAvailableEfforts(agent, currentModel);
|
|
2672
|
+
const validEfforts = await getAvailableEfforts(agent, currentModel);
|
|
2613
2673
|
const allValid = [...validEfforts, 'auto'];
|
|
2614
2674
|
if (!allValid.includes(arg)) {
|
|
2615
2675
|
return { error: `无效推理强度: ${arg},可选: ${allValid.join(' / ')}`, code: 'INVALID_VALUE' };
|
|
@@ -2994,7 +3054,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2994
3054
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
2995
3055
|
if (cmdBase === '/trigger') {
|
|
2996
3056
|
const role = identity.role;
|
|
2997
|
-
const isAdmin = isManagementRole(role);
|
|
3057
|
+
const isAdmin = isManagementRole(role) || subject.isDaemonOwner;
|
|
2998
3058
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
2999
3059
|
if (!triggerScheduler)
|
|
3000
3060
|
return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
|
|
@@ -3563,6 +3623,14 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3563
3623
|
: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
3564
3624
|
};
|
|
3565
3625
|
}
|
|
3626
|
+
const migrationRequirement = inspectDataMigrationRequirement(resolvePaths().root);
|
|
3627
|
+
if (migrationRequirement.required) {
|
|
3628
|
+
return {
|
|
3629
|
+
error: `待处理的用户数据迁移(${migrationRequirement.operationCount} 项)阻止重启。请先运行 ec data migrate --dry-run,然后运行 ec data migrate --apply。`,
|
|
3630
|
+
code: 'DATA_MIGRATION_REQUIRED',
|
|
3631
|
+
data: { reasonCode: 'DATA_MIGRATION_REQUIRED', operationCount: migrationRequirement.operationCount },
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
3566
3634
|
if (!suppressRealRestart) {
|
|
3567
3635
|
const restartInfo = {
|
|
3568
3636
|
channel,
|
|
@@ -3810,6 +3878,48 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3810
3878
|
}
|
|
3811
3879
|
return { error: `不支持 action: ${cmdBase}`, code: 'NOT_SUPPORTED' };
|
|
3812
3880
|
}
|
|
3881
|
+
export function resolveControlMenuAuthorization(input) {
|
|
3882
|
+
const headers = parseMenuClientProtectedHeaders(input.auth.protectedHeaders);
|
|
3883
|
+
const wireAuthMethod = headers.auth_method ?? 'daemon-owner';
|
|
3884
|
+
const authMethod = wireAuthMethod === 'ecweb-token' ? 'remote-access-token' : wireAuthMethod;
|
|
3885
|
+
const remoteToken = headers.remote_access_token ?? headers.ecweb_token;
|
|
3886
|
+
if (authMethod !== 'daemon-owner' && authMethod !== 'remote-access-token') {
|
|
3887
|
+
return { allowed: false, code: 'PERMISSION_DENIED', message: 'Unsupported Control Menu authorization method' };
|
|
3888
|
+
}
|
|
3889
|
+
if (authMethod === 'daemon-owner') {
|
|
3890
|
+
if (remoteToken) {
|
|
3891
|
+
return { allowed: false, code: 'PERMISSION_DENIED', message: 'remote_access_token requires auth_method=remote-access-token' };
|
|
3892
|
+
}
|
|
3893
|
+
if (!isProcessLevelOwner(input.peerId, input.owners)) {
|
|
3894
|
+
return {
|
|
3895
|
+
allowed: false,
|
|
3896
|
+
code: 'NOT_ALLOWED',
|
|
3897
|
+
message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
3898
|
+
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
return { allowed: true, actorAid: input.peerId, authMethod };
|
|
3902
|
+
}
|
|
3903
|
+
if (!input.auth.encrypted || !remoteToken) {
|
|
3904
|
+
return { allowed: false, code: 'REMOTE_ACCESS_TOKEN_INVALID', message: 'Remote access token authorization requires an encrypted protected header' };
|
|
3905
|
+
}
|
|
3906
|
+
const checked = (input.validateToken ?? validateAndRenewRemoteAccessToken)(remoteToken);
|
|
3907
|
+
if (!checked.valid) {
|
|
3908
|
+
return { allowed: false, code: 'REMOTE_ACCESS_TOKEN_INVALID', message: 'Remote access token is invalid or expired' };
|
|
3909
|
+
}
|
|
3910
|
+
const actorAid = checked.authorizedBy && input.owners.includes(checked.authorizedBy)
|
|
3911
|
+
? checked.authorizedBy
|
|
3912
|
+
: input.owners[0];
|
|
3913
|
+
if (!actorAid) {
|
|
3914
|
+
return {
|
|
3915
|
+
allowed: false,
|
|
3916
|
+
code: 'NOT_ALLOWED',
|
|
3917
|
+
message: formatAuthorizationDenial('Configure daemon.json.owners before using Control Menu operations.', 'DAEMON_OWNER_REQUIRED'),
|
|
3918
|
+
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
3919
|
+
};
|
|
3920
|
+
}
|
|
3921
|
+
return { allowed: true, actorAid, authMethod };
|
|
3922
|
+
}
|
|
3813
3923
|
function menuCommandToken(cmd) {
|
|
3814
3924
|
if (typeof cmd !== 'string')
|
|
3815
3925
|
return undefined;
|
|
@@ -3862,7 +3972,8 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3862
3972
|
if ('error' in target)
|
|
3863
3973
|
return ecwebErr(id, name, target.code, target.error);
|
|
3864
3974
|
const targetChannel = target.channel;
|
|
3865
|
-
const targetAid = this.getOwningAgent?.(targetChannel)?.aid
|
|
3975
|
+
const targetAid = this.getOwningAgent?.(targetChannel)?.aid
|
|
3976
|
+
?? this.getDaemonTriggerOwnerAid?.(targetChannel);
|
|
3866
3977
|
const trustedSubject = buildAuthSubject({
|
|
3867
3978
|
selfAid: targetAid,
|
|
3868
3979
|
actorId: context.actorAid,
|
|
@@ -4035,10 +4146,10 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4035
4146
|
owners,
|
|
4036
4147
|
});
|
|
4037
4148
|
}
|
|
4038
|
-
/**
|
|
4039
|
-
*
|
|
4040
|
-
*
|
|
4041
|
-
export async function execMenuForControl(payload, peerId) {
|
|
4149
|
+
/** Control AID entry: daemon-owner and valid ECWeb token modes share the same
|
|
4150
|
+
* process authorization path. The authenticated envelope peerId remains the
|
|
4151
|
+
* requester; token mode maps to a configured owner as its management actor. */
|
|
4152
|
+
export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
4042
4153
|
const id = payload?.id ?? '';
|
|
4043
4154
|
const name = payload?.name;
|
|
4044
4155
|
const validationError = validateMenuRequest(payload ?? {});
|
|
@@ -4048,16 +4159,22 @@ export async function execMenuForControl(payload, peerId) {
|
|
|
4048
4159
|
const compatCmd = menuCommandToken(payload?.cmd);
|
|
4049
4160
|
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
4050
4161
|
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
4051
|
-
|
|
4162
|
+
const authorization = resolveControlMenuAuthorization({ peerId, owners, auth });
|
|
4163
|
+
const hasExplicitAuthMethod = auth.protectedHeaders?.auth_method !== undefined;
|
|
4164
|
+
if (!authorization.allowed && (hasExplicitAuthMethod || (!isRoleRequest && !isConnectRequest))) {
|
|
4052
4165
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4053
|
-
code:
|
|
4054
|
-
message:
|
|
4055
|
-
data:
|
|
4166
|
+
code: authorization.code,
|
|
4167
|
+
message: authorization.message,
|
|
4168
|
+
...(authorization.data !== undefined ? { data: authorization.data } : {}),
|
|
4056
4169
|
});
|
|
4057
4170
|
}
|
|
4171
|
+
// Existing role/connect self-inspection remains available without process
|
|
4172
|
+
// authority. Token-authorized and daemon-owner requests use one canonical
|
|
4173
|
+
// management actor for the common process authorization path.
|
|
4174
|
+
const managementActor = authorization.allowed ? authorization.actorAid : peerId;
|
|
4058
4175
|
return execMenuForSystemControl.call(this, payload, {
|
|
4059
4176
|
source: 'control',
|
|
4060
|
-
actorAid:
|
|
4177
|
+
actorAid: managementActor,
|
|
4061
4178
|
localDirect: false,
|
|
4062
4179
|
canApprovePersistentFullAccess: false,
|
|
4063
4180
|
owners,
|
|
@@ -12,6 +12,93 @@ export const MENU_REQUEST_TYPES = new Set([
|
|
|
12
12
|
'menu.update',
|
|
13
13
|
'menu.action',
|
|
14
14
|
]);
|
|
15
|
+
export function buildMenuClientProtectedHeaders(input) {
|
|
16
|
+
const clientName = input.clientName ?? 'evol';
|
|
17
|
+
if (clientName === 'ec-menu-client') {
|
|
18
|
+
const clientVersion = input.clientVersion?.trim();
|
|
19
|
+
if (!parseStableSemver(clientVersion)) {
|
|
20
|
+
throw Object.assign(new Error('ec-menu-client requires a stable X.Y.Z client version'), { code: 'INVALID_CLIENT' });
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
client_name: clientName,
|
|
24
|
+
client_version: clientVersion,
|
|
25
|
+
...(input.remoteAccessToken || input.ecwebToken ? { auth_method: 'remote-access-token', remote_access_token: input.remoteAccessToken ?? input.ecwebToken } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...(input.evolVersion ? { evol_version: input.evolVersion } : {}),
|
|
30
|
+
...(input.remoteAccessToken || input.ecwebToken ? { auth_method: 'remote-access-token', remote_access_token: input.remoteAccessToken ?? input.ecwebToken } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function parseMenuClientProtectedHeaders(value) {
|
|
34
|
+
if (!value)
|
|
35
|
+
return {};
|
|
36
|
+
const normalized = {};
|
|
37
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
38
|
+
if (typeof raw !== 'string')
|
|
39
|
+
continue;
|
|
40
|
+
normalized[key.trim().toLowerCase()] = raw;
|
|
41
|
+
}
|
|
42
|
+
const result = {};
|
|
43
|
+
if (normalized.client_name === 'evol' || normalized.client_name === 'ec-menu-client')
|
|
44
|
+
result.client_name = normalized.client_name;
|
|
45
|
+
else if (normalized.client_name !== undefined)
|
|
46
|
+
result.client_name = normalized.client_name;
|
|
47
|
+
if (normalized.client_version !== undefined)
|
|
48
|
+
result.client_version = normalized.client_version;
|
|
49
|
+
if (normalized.evol_version !== undefined)
|
|
50
|
+
result.evol_version = normalized.evol_version;
|
|
51
|
+
if (normalized.auth_method === 'daemon-owner' || normalized.auth_method === 'remote-access-token' || normalized.auth_method === 'ecweb-token')
|
|
52
|
+
result.auth_method = normalized.auth_method;
|
|
53
|
+
else if (normalized.auth_method !== undefined)
|
|
54
|
+
result.auth_method = normalized.auth_method;
|
|
55
|
+
if (normalized.remote_access_token !== undefined)
|
|
56
|
+
result.remote_access_token = normalized.remote_access_token;
|
|
57
|
+
if (normalized.ecweb_token !== undefined)
|
|
58
|
+
result.ecweb_token = normalized.ecweb_token;
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/** Validate client identity separately from authorization. These fields never grant access. */
|
|
62
|
+
export function evaluateMenuClientCompatibility(input) {
|
|
63
|
+
const headers = parseMenuClientProtectedHeaders(input.protectedHeaders);
|
|
64
|
+
const clientName = headers.client_name ?? 'evol';
|
|
65
|
+
if (clientName === 'ec-menu-client') {
|
|
66
|
+
if (!input.encrypted || !parseStableSemver(headers.client_version)) {
|
|
67
|
+
return {
|
|
68
|
+
code: 'INVALID_CLIENT',
|
|
69
|
+
message: 'ec-menu-client requires encrypted protected headers and a stable X.Y.Z client_version',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (clientName !== 'evol') {
|
|
75
|
+
return { code: 'INVALID_CLIENT', message: `Unsupported Menu client: ${String(clientName)}` };
|
|
76
|
+
}
|
|
77
|
+
return evaluateEvolMenuVersionGate(input);
|
|
78
|
+
}
|
|
79
|
+
export function parseMenuResponse(value) {
|
|
80
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
81
|
+
throw Object.assign(new Error('Menu response must be an object'), { code: 'INVALID_RESPONSE' });
|
|
82
|
+
}
|
|
83
|
+
const response = value;
|
|
84
|
+
if (response.type !== 'menu.response' || typeof response.id !== 'string' || !response.id.trim()) {
|
|
85
|
+
throw Object.assign(new Error('Invalid Menu response type or id'), { code: 'INVALID_RESPONSE' });
|
|
86
|
+
}
|
|
87
|
+
const hasData = Object.prototype.hasOwnProperty.call(response, 'data');
|
|
88
|
+
const hasError = Object.prototype.hasOwnProperty.call(response, 'error');
|
|
89
|
+
if (hasData === hasError) {
|
|
90
|
+
throw Object.assign(new Error('Menu response must contain exactly one of data or error'), { code: 'INVALID_RESPONSE' });
|
|
91
|
+
}
|
|
92
|
+
if (hasError) {
|
|
93
|
+
const error = response.error;
|
|
94
|
+
if (!error || typeof error !== 'object' || Array.isArray(error)
|
|
95
|
+
|| typeof error.code !== 'string'
|
|
96
|
+
|| typeof error.message !== 'string') {
|
|
97
|
+
throw Object.assign(new Error('Invalid Menu response error'), { code: 'INVALID_RESPONSE' });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
15
102
|
export const MENU_NAME_COMMANDS = Object.freeze({
|
|
16
103
|
pwd: '/pwd',
|
|
17
104
|
session: '/session',
|
|
@@ -259,6 +346,25 @@ export function menuFailure(header, error) {
|
|
|
259
346
|
},
|
|
260
347
|
};
|
|
261
348
|
}
|
|
349
|
+
export function enforceMenuResponseSize(response, maxBytes = 128 * 1024) {
|
|
350
|
+
let bytes;
|
|
351
|
+
try {
|
|
352
|
+
bytes = Buffer.byteLength(JSON.stringify(response), 'utf8');
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return menuFailure({ id: response.id, ...(response.name ? { name: response.name } : {}) }, {
|
|
356
|
+
code: 'INTERNAL_ERROR',
|
|
357
|
+
message: 'Menu response is not serializable',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (bytes <= maxBytes)
|
|
361
|
+
return response;
|
|
362
|
+
return menuFailure({ id: response.id, ...(response.name ? { name: response.name } : {}) }, {
|
|
363
|
+
code: 'RESPONSE_TOO_LARGE',
|
|
364
|
+
message: `Menu response exceeds the ${maxBytes}-byte limit`,
|
|
365
|
+
data: { bytes, maxBytes },
|
|
366
|
+
});
|
|
367
|
+
}
|
|
262
368
|
const INVALID_ARGUMENT_CODES = new Set([
|
|
263
369
|
'ARGUMENT_MISMATCH', 'FILE_TOO_LARGE', 'INVALID_ARGS', 'INVALID_DELEGATION',
|
|
264
370
|
'INVALID_FIELD', 'INVALID_REVISION', 'INVALID_SCOPE', 'INVALID_TYPE', 'INVALID_VALUE', 'MISSING_AID',
|
|
@@ -272,7 +378,9 @@ const STABLE_CODES = new Set([
|
|
|
272
378
|
'TEMPORARILY_UNAVAILABLE', 'INTERNAL_ERROR', 'MISSING_SCOPE', 'UPGRADE_REQUIRED',
|
|
273
379
|
'MENU_TOKEN_REQUIRED', 'MENU_TOKEN_REJECTED', 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
274
380
|
'INVALID_CONTEXT', 'SCHEMA_VERSION_UNSUPPORTED', 'DEPENDENCY_UNAVAILABLE',
|
|
275
|
-
'RATE_LIMITED', 'CATALOG_STALE',
|
|
381
|
+
'RATE_LIMITED', 'CATALOG_STALE', 'DATA_MIGRATION_REQUIRED',
|
|
382
|
+
'INVALID_CLIENT', 'REMOTE_ACCESS_TOKEN_INVALID', 'ECWEB_TOKEN_INVALID', 'CLIENT_BUSY', 'INVALID_RESPONSE', 'TIMEOUT',
|
|
383
|
+
'RESPONSE_TOO_LARGE',
|
|
276
384
|
]);
|
|
277
385
|
export function normalizeMenuError(error) {
|
|
278
386
|
const source = error && typeof error === 'object' ? error : {};
|
|
@@ -493,6 +601,9 @@ function menuFlowBaseRecord(request, context) {
|
|
|
493
601
|
requestId: menuRequestId(request),
|
|
494
602
|
messageId: context.messageId,
|
|
495
603
|
selfAid: context.selfAid,
|
|
604
|
+
requesterAid: context.requesterAid,
|
|
605
|
+
authMethod: context.authMethod,
|
|
606
|
+
clientName: context.clientName,
|
|
496
607
|
};
|
|
497
608
|
}
|
|
498
609
|
function menuRequestBusinessFields(request) {
|
|
@@ -531,11 +642,19 @@ export function logMenuRequestCompleted(request, response, context, options) {
|
|
|
531
642
|
const processingMs = response && typeof response.processing_ms === 'number'
|
|
532
643
|
? response.processing_ms
|
|
533
644
|
: undefined;
|
|
645
|
+
let responseBytes;
|
|
646
|
+
if (response) {
|
|
647
|
+
try {
|
|
648
|
+
responseBytes = Buffer.byteLength(JSON.stringify(response), 'utf8');
|
|
649
|
+
}
|
|
650
|
+
catch { }
|
|
651
|
+
}
|
|
534
652
|
writeMenuFlowRecord({
|
|
535
653
|
...menuFlowBaseRecord(request, context),
|
|
536
654
|
event: options.delivery === 'failed' ? 'menu.request.failed' : 'menu.request.completed',
|
|
537
655
|
delivery: options.delivery,
|
|
538
656
|
processingMs,
|
|
657
|
+
responseBytes,
|
|
539
658
|
reason: options.reason,
|
|
540
659
|
result: response && 'data' in response ? sanitizeMenuLogValue(response.data) : undefined,
|
|
541
660
|
warning: response && 'warning' in response ? sanitizeMenuLogValue(response.warning) : undefined,
|
|
@@ -43,9 +43,9 @@ export function probeSessionActivity(evidence, probes) {
|
|
|
43
43
|
return evaluateSessionActivity(collected);
|
|
44
44
|
}
|
|
45
45
|
// 支持的命令列表
|
|
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'];
|
|
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
|
-
const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop']);
|
|
48
|
+
const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair']);
|
|
49
49
|
/** Parse the execution command without treating it as a quick/result command. */
|
|
50
50
|
export function parseFullAccessCommand(content) {
|
|
51
51
|
const match = content.match(/^\s*(\/fa|\/fullaccess)(?=\s|$)([\s\S]*)$/);
|
|
@@ -65,7 +65,7 @@ const aliases = {
|
|
|
65
65
|
'/base': '/baseagent',
|
|
66
66
|
};
|
|
67
67
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
68
|
-
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'];
|
|
68
|
+
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'];
|
|
69
69
|
/**
|
|
70
70
|
* 计算两个字符串的 Levenshtein 距离(编辑距离)
|
|
71
71
|
*/
|