evolcore 0.0.39 → 0.0.40
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 +49 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +51 -27
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +1 -3
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +23 -4
- package/dist/cli/aid-bind-command.js +4 -1
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/index.js +25 -13
- package/dist/cli/memory-command.js +33 -14
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/task-context.js +1 -0
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +72 -4
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +384 -178
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +29 -12
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
|
@@ -130,6 +130,18 @@ export async function cmdRestartMonitor() {
|
|
|
130
130
|
const hasCorrelatedRequest = requestId !== 'unknown';
|
|
131
131
|
const upgradeJobId = hasCorrelatedRequest ? readRestartRequest(pendingFile, requestId)?.upgradeJobId : undefined;
|
|
132
132
|
const upgradeJob = typeof upgradeJobId === 'string' ? readUpgradeJob(upgradeJobId) : null;
|
|
133
|
+
let configuredAutoUpgrade = true;
|
|
134
|
+
if (!upgradeJob && !process.argv.includes('--apply-package-upgrades') && !process.argv.includes('--skip-package-upgrades')) {
|
|
135
|
+
try {
|
|
136
|
+
configuredAutoUpgrade = loadDaemonConfig().upgrade?.autoOnStart !== false;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
configuredAutoUpgrade = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const applyPackageUpgrades = !!upgradeJob
|
|
143
|
+
|| process.argv.includes('--apply-package-upgrades')
|
|
144
|
+
|| (!process.argv.includes('--skip-package-upgrades') && configuredAutoUpgrade);
|
|
133
145
|
if (upgradeJobId && (!upgradeJob || upgradeJob.restartRequestId !== requestId || upgradeJob.released || upgradeJob.deadline <= Date.now())) {
|
|
134
146
|
if (upgradeJob)
|
|
135
147
|
updateUpgradeJob(upgradeJob.jobId, { status: 'timeout', success: false, released: true, code: 'UPGRADE_TIMEOUT', reason: 'Update expired before restart preflight' }, '更新已超时,未执行重启');
|
|
@@ -371,16 +383,24 @@ export async function cmdRestartMonitor() {
|
|
|
371
383
|
const stoppedCodex = stopCodexAppServerOrphans();
|
|
372
384
|
if (stoppedCodex)
|
|
373
385
|
log(`Cleaned ${stoppedCodex} EvolCore codex app-server orphan(s)`);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
386
|
+
let upgradeBackup = null;
|
|
387
|
+
let upgrade;
|
|
388
|
+
if (applyPackageUpgrades) {
|
|
389
|
+
// Apply only the preflight cache. There is no registry VERSION query after
|
|
390
|
+
// shutdown (npm still downloads the exact cached package when needed).
|
|
391
|
+
log('Applying cached version information...');
|
|
392
|
+
// 升级会原地替换整包,先留一个可回退的产物副本。备份放 EVOLCORE_HOME 而非包目录:
|
|
393
|
+
// Linux/macOS 的全局目录常常不可写,而 HOME 一定可写。
|
|
394
|
+
upgradeBackup = await backupDistForUpgrade(p, log);
|
|
395
|
+
[upgrade] = await applyCachedPackageUpgrades(undefined, {
|
|
396
|
+
log,
|
|
397
|
+
...(upgradeJob ? { target: upgradeJob.target, distTag: upgradeJob.distTag, npmConfig: upgradeJob.npmConfig } : {}),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
log('Skipping cached package upgrade because upgrade.autoOnStart=false');
|
|
402
|
+
upgrade = { status: 'skipped', error: 'Automatic package upgrades disabled by autoOnStart' };
|
|
403
|
+
}
|
|
384
404
|
let upgradeRolledBack;
|
|
385
405
|
switch (upgrade.status) {
|
|
386
406
|
case 'upgraded':
|
|
@@ -405,9 +425,11 @@ export async function cmdRestartMonitor() {
|
|
|
405
425
|
log(`Already up to date (${upgrade.from})`);
|
|
406
426
|
break;
|
|
407
427
|
case 'skipped':
|
|
408
|
-
log(upgrade.error
|
|
409
|
-
? 'Skipped upgrade (
|
|
410
|
-
:
|
|
428
|
+
log(upgrade.error === 'Automatic package upgrades disabled by autoOnStart'
|
|
429
|
+
? 'Skipped package upgrade (autoOnStart=false)'
|
|
430
|
+
: upgrade.error
|
|
431
|
+
? 'Skipped upgrade (network unavailable)'
|
|
432
|
+
: 'Skipped upgrade check (dev mode)');
|
|
411
433
|
break;
|
|
412
434
|
case 'failed':
|
|
413
435
|
log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}): ${upgrade.error}`);
|
package/dist/cli/task-context.js
CHANGED
|
@@ -47,6 +47,7 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
47
47
|
executionSource: value.executionSource === 'fullaccess-command'
|
|
48
48
|
|| value.executionSource === 'trigger'
|
|
49
49
|
|| value.executionSource === 'agent-policy'
|
|
50
|
+
|| value.executionSource === 'session-policy'
|
|
50
51
|
? value.executionSource : undefined,
|
|
51
52
|
daemonRuntimeEpoch: optionalString(value.daemonRuntimeEpoch),
|
|
52
53
|
threadId: optionalString(value.threadId),
|
|
@@ -227,6 +227,7 @@ export function resolveConfigFieldRule(fieldPath, scope) {
|
|
|
227
227
|
}
|
|
228
228
|
// 数值参数:整理节奏与容量上限,均 owner-only
|
|
229
229
|
if (field === 'memory.idleHours'
|
|
230
|
+
|| field === 'memory.maxUnconsolidatedHours'
|
|
230
231
|
|| field === 'memory.checkIntervalHours'
|
|
231
232
|
|| field === 'memory.backlogHours'
|
|
232
233
|
|| field === 'memory.indexMaxBytes'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const ECWEB_SERVICE_PROTOCOL = Object.freeze({
|
|
2
|
+
name: 'evolcore.ecweb',
|
|
3
|
+
major: 1,
|
|
4
|
+
minor: 1,
|
|
5
|
+
capabilities: Object.freeze([
|
|
6
|
+
'daemon.status',
|
|
7
|
+
'agent.directory',
|
|
8
|
+
'aun.directory',
|
|
9
|
+
'menu.execute',
|
|
10
|
+
'provider.management',
|
|
11
|
+
'basic-settings',
|
|
12
|
+
'model.catalog',
|
|
13
|
+
'baseagent.snapshot',
|
|
14
|
+
'monitor.snapshot',
|
|
15
|
+
'monitor.subscription',
|
|
16
|
+
'remote-auth',
|
|
17
|
+
'trigger.history',
|
|
18
|
+
]),
|
|
19
|
+
});
|
|
20
|
+
export function validateEcwebClientProtocolRequirements(value) {
|
|
21
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
22
|
+
return 'ECWeb client protocol requirements are missing or malformed';
|
|
23
|
+
}
|
|
24
|
+
const requirements = value;
|
|
25
|
+
if (requirements.name !== ECWEB_SERVICE_PROTOCOL.name) {
|
|
26
|
+
return `Unsupported ECWeb protocol name: ${String(requirements.name ?? 'missing')}`;
|
|
27
|
+
}
|
|
28
|
+
if (requirements.major !== ECWEB_SERVICE_PROTOCOL.major) {
|
|
29
|
+
return `Unsupported ECWeb protocol major version: ${String(requirements.major ?? 'missing')}`;
|
|
30
|
+
}
|
|
31
|
+
if (!Number.isInteger(requirements.minMinor) || requirements.minMinor < 0
|
|
32
|
+
|| requirements.minMinor > ECWEB_SERVICE_PROTOCOL.minor) {
|
|
33
|
+
return `ECWeb requires protocol minor ${String(requirements.minMinor ?? 'missing')}, host supports ${ECWEB_SERVICE_PROTOCOL.minor}`;
|
|
34
|
+
}
|
|
35
|
+
if (!Array.isArray(requirements.requiredCapabilities)
|
|
36
|
+
|| requirements.requiredCapabilities.some(capability => typeof capability !== 'string' || !capability.trim())) {
|
|
37
|
+
return 'ECWeb required capabilities are malformed';
|
|
38
|
+
}
|
|
39
|
+
const supported = new Set(ECWEB_SERVICE_PROTOCOL.capabilities);
|
|
40
|
+
const missing = requirements.requiredCapabilities.filter(capability => !supported.has(capability));
|
|
41
|
+
if (missing.length > 0)
|
|
42
|
+
return `ECWeb requires unsupported capabilities: ${missing.join(', ')}`;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -226,6 +226,11 @@ export function createAgentInitializationRuntime({ agentRegistry, agentMap, chan
|
|
|
226
226
|
bindOwner: async (aid, ownerAid) => {
|
|
227
227
|
await ownerResolver.bind(aid, ownerAid);
|
|
228
228
|
},
|
|
229
|
+
ownerBindActive: (aid, ownerAid) => {
|
|
230
|
+
const task = ownerResolver.snapshot(aid).task;
|
|
231
|
+
return task?.operation === 'bind' && task.owner_aid === ownerAid
|
|
232
|
+
&& (task.status === 'pending' || task.status === 'completed');
|
|
233
|
+
},
|
|
229
234
|
checkOwner: async (aid, expectedOwnerAid) => {
|
|
230
235
|
const view = await ownerResolver.refresh(aid, { maxAgeMs: OWNERSHIP_AUTHORIZATION_MAX_AGE_MS });
|
|
231
236
|
if (view.consensusStatus === 'consensus')
|
|
@@ -257,6 +262,7 @@ export function createAgentInitializationRuntime({ agentRegistry, agentMap, chan
|
|
|
257
262
|
const agent = agentRegistry.get(aid);
|
|
258
263
|
return !!agent && agent.config.enabled !== false && agentMap.has(`${aid}::${agent.baseagent}`) && isConnected(aid);
|
|
259
264
|
},
|
|
265
|
+
authorize: actor => actor === 'local-control' || ownerResolver.daemonOwners().includes(actor),
|
|
260
266
|
});
|
|
261
267
|
}
|
|
262
268
|
export function recoverIncompleteAgentInitializations(service, agentsDir = resolvePaths().agentsDir) {
|
|
@@ -440,7 +440,7 @@ export async function authorizeDelegatedAunMsgSend(registry, input) {
|
|
|
440
440
|
catch {
|
|
441
441
|
return { ok: false, code: 'INVALID_DELEGATION', reason: 'Delegation relation key is invalid' };
|
|
442
442
|
}
|
|
443
|
-
await refreshOwnershipForAuthorization(grant.selfAid);
|
|
443
|
+
await refreshOwnershipForAuthorization(grant.selfAid, { allowTransient: true });
|
|
444
444
|
const builtSubject = buildAuthSubject({
|
|
445
445
|
selfAid: grant.selfAid,
|
|
446
446
|
actorId: grant.actorId,
|
|
@@ -503,10 +503,13 @@ function isValidFullAccessExecutionIdentity(identity) {
|
|
|
503
503
|
|| identity.processRole !== 'fullaccess-run'
|
|
504
504
|
|| identity.dataScope !== 'daemon')
|
|
505
505
|
return false;
|
|
506
|
-
if (identity.source === 'fullaccess-command' || identity.source === 'agent-policy') {
|
|
506
|
+
if (identity.source === 'fullaccess-command' || identity.source === 'agent-policy' || identity.source === 'session-policy') {
|
|
507
507
|
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
508
|
-
&& (identity.source
|
|
509
|
-
|| (typeof identity.agentAid === 'string'
|
|
508
|
+
&& (identity.source === 'fullaccess-command'
|
|
509
|
+
|| (typeof identity.agentAid === 'string'
|
|
510
|
+
&& identity.agentAid.length > 0
|
|
511
|
+
&& (identity.source !== 'session-policy'
|
|
512
|
+
|| (typeof identity.sessionId === 'string' && identity.sessionId.length > 0))));
|
|
510
513
|
}
|
|
511
514
|
return identity.source === 'trigger'
|
|
512
515
|
&& typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
@@ -107,7 +107,8 @@ export async function authorizeFreshOperation(params) {
|
|
|
107
107
|
const configCommand = params.resolvedConfigCommand;
|
|
108
108
|
const changesOwner = configCommand?.kind === 'field' && configCommand.subcommand !== 'get'
|
|
109
109
|
&& ['owner', 'owners'].includes(configCommand.field.split('.')[0]);
|
|
110
|
-
const force = changesOwner ||
|
|
110
|
+
const force = changesOwner || ['ec.aid.bind', 'ec.aid.bind.status', 'ec.aid.unbind', 'ec.aid.transfer']
|
|
111
|
+
.includes(params.intent.operation) || params.intent.operation === 'config.restore';
|
|
111
112
|
const operation = getOperationMeta(params.intent.operation);
|
|
112
113
|
const sensitive = force || params.intent.dangerous || operation?.dangerous || operation?.category !== 'read'
|
|
113
114
|
|| configCommand?.dangerous || subject.processRole === 'fullaccess-run';
|
|
@@ -116,6 +117,7 @@ export async function authorizeFreshOperation(params) {
|
|
|
116
117
|
...(sensitive ? {} : { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS }),
|
|
117
118
|
includeDaemon: subject.isDaemonOwner || subject.processRole === 'fullaccess-run'
|
|
118
119
|
|| params.intent.scope === 'process' || params.intent.scope === 'control' || force,
|
|
120
|
+
allowTransient: !force,
|
|
119
121
|
});
|
|
120
122
|
const current = buildAuthSubject({
|
|
121
123
|
selfAid: subject.selfAid, agentName: subject.agentName, actorId: subject.actorId,
|
|
@@ -25,11 +25,15 @@ export function auditCommandAuthorization(event) {
|
|
|
25
25
|
}
|
|
26
26
|
export function auditFullAccessEvent(input) {
|
|
27
27
|
const configurationEvent = input.event === 'fullaccess.trigger.configured'
|
|
28
|
-
|| input.event === 'fullaccess.trigger.deescalated'
|
|
28
|
+
|| input.event === 'fullaccess.trigger.deescalated'
|
|
29
|
+
|| input.event === 'fullaccess.session-policy.configured'
|
|
30
|
+
|| input.event === 'fullaccess.session-policy.deescalated';
|
|
29
31
|
const executionEvent = input.event === 'fullaccess.execution.started'
|
|
30
32
|
|| input.event === 'fullaccess.execution.ended'
|
|
31
33
|
|| input.event === 'fullaccess.agent-policy.execution.started'
|
|
32
34
|
|| input.event === 'fullaccess.agent-policy.execution.ended'
|
|
35
|
+
|| input.event === 'fullaccess.session-policy.execution.started'
|
|
36
|
+
|| input.event === 'fullaccess.session-policy.execution.ended'
|
|
33
37
|
|| input.event === 'fullaccess.trigger.execution.started'
|
|
34
38
|
|| input.event === 'fullaccess.trigger.execution.ended';
|
|
35
39
|
// The scheduler triggers a run, but the executing subject is the task-bound
|
|
@@ -127,7 +127,11 @@ export function buildDebugTaskContext(input) {
|
|
|
127
127
|
chatType,
|
|
128
128
|
selfAid: session.selfAID,
|
|
129
129
|
peerId,
|
|
130
|
-
|
|
130
|
+
// 系统会话的目标关系在 metadata.peerKey(与 response-engine 的任务上下文同源),
|
|
131
|
+
// 不能从后台伪对端 peerId 推。
|
|
132
|
+
peerKey: meta.systemSession && typeof meta.peerKey === 'string' && meta.peerKey
|
|
133
|
+
? meta.peerKey
|
|
134
|
+
: (channel && peerKeyId ? formatPeerKey(channel, peerKeyId) : undefined),
|
|
131
135
|
peerName: meta.peerName,
|
|
132
136
|
peerType: meta.peerType,
|
|
133
137
|
threadId,
|
|
@@ -323,6 +323,22 @@ const OPERATIONS = [
|
|
|
323
323
|
description: '更新当前主会话或话题的 idle 触发设置',
|
|
324
324
|
sources: ['slash', 'menu', 'ecweb', 'control'],
|
|
325
325
|
},
|
|
326
|
+
{
|
|
327
|
+
id: 'session.fullaccess.current',
|
|
328
|
+
category: 'read',
|
|
329
|
+
dangerous: true,
|
|
330
|
+
defaultScopes: ['relation'],
|
|
331
|
+
description: '查看当前 EC 会话的 fullaccess 状态',
|
|
332
|
+
sources: ['slash', 'menu'],
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: 'session.fullaccess.update',
|
|
336
|
+
category: 'dangerous',
|
|
337
|
+
dangerous: true,
|
|
338
|
+
defaultScopes: ['relation'],
|
|
339
|
+
description: '更新当前 EC 会话的 fullaccess 状态',
|
|
340
|
+
sources: ['slash', 'menu'],
|
|
341
|
+
},
|
|
326
342
|
{
|
|
327
343
|
id: 'session.topic.current',
|
|
328
344
|
category: 'read',
|
|
@@ -1380,6 +1380,7 @@ export class CommandHandler {
|
|
|
1380
1380
|
? {
|
|
1381
1381
|
kind: 'system.error',
|
|
1382
1382
|
text: result.text,
|
|
1383
|
+
...(result.raw !== undefined ? { raw: result.raw } : {}),
|
|
1383
1384
|
subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.error`,
|
|
1384
1385
|
recoverable: true,
|
|
1385
1386
|
}
|
|
@@ -1394,7 +1395,12 @@ export class CommandHandler {
|
|
|
1394
1395
|
catch (error) {
|
|
1395
1396
|
if (error instanceof BaseagentRunnerUnavailableError) {
|
|
1396
1397
|
logger.error(`[CommandHandler] baseagent mismatch blocked: channel=${channel} requested=${error.baseagent} owner=${error.evolagentName} available=${error.availableBaseagents.join(',') || '<none>'}`);
|
|
1397
|
-
return {
|
|
1398
|
+
return {
|
|
1399
|
+
kind: 'command.error',
|
|
1400
|
+
text: this.formatBaseagentUnavailable(error),
|
|
1401
|
+
raw: error.message,
|
|
1402
|
+
reason: error.code,
|
|
1403
|
+
};
|
|
1398
1404
|
}
|
|
1399
1405
|
throw error;
|
|
1400
1406
|
}
|
|
@@ -6,6 +6,7 @@ import { authorizationDenialData, formatAuthorizationDenial } from '../auth/auth
|
|
|
6
6
|
import { ConfigTarget, read as readConfig } from '../../config/config-manager.js';
|
|
7
7
|
import { readRolesConfig } from '../../config/roles.js';
|
|
8
8
|
import { normalizeMenuError } from './menu-protocol.js';
|
|
9
|
+
import { isAgentFullAccessOwner } from '../role/runtime-policy.js';
|
|
9
10
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
10
11
|
import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS } from '../session/session-idle-monitor.js';
|
|
11
12
|
const DEFAULT_DIRECTORY = { getGroup: groupInfo, getMembers: groupMembers };
|
|
@@ -79,6 +80,19 @@ const ENTRIES = [
|
|
|
79
80
|
valueSchema: { type: 'boolean' },
|
|
80
81
|
control: 'toggle-with-number',
|
|
81
82
|
},
|
|
83
|
+
{
|
|
84
|
+
id: 'session.fullaccess',
|
|
85
|
+
type: 'setting',
|
|
86
|
+
contexts: ['private_relation'],
|
|
87
|
+
name: 'session',
|
|
88
|
+
label: '当前 EC 会话 fullaccess',
|
|
89
|
+
section: 'relation_behavior',
|
|
90
|
+
order: 6,
|
|
91
|
+
query: { cmd: '/session', args: { view: 'fullaccess' }, operation: 'session.fullaccess.current', scope: 'relation' },
|
|
92
|
+
update: { cmd: '/session', args: { resource: 'fullaccess' }, operation: 'session.fullaccess.update', scope: 'relation' },
|
|
93
|
+
valueSchema: { type: 'boolean' },
|
|
94
|
+
control: 'toggle',
|
|
95
|
+
},
|
|
82
96
|
relation('model.relation', 'model', '模型', 10, '/model', 'model.current', { options: { cmd: '/model', operation: 'model.list', scope: 'relation', mode: 'dynamic' }, update: { cmd: '/model', operation: 'model.use', scope: 'relation' }, control: 'select' }),
|
|
83
97
|
relation('effort.relation', 'effort', '推理强度', 20, '/effort', 'model.effort.current', { options: { cmd: '/effort', operation: 'model.effort.current', scope: 'relation', mode: 'dynamic', dependsOn: ['model.relation'] }, update: { cmd: '/effort', operation: 'model.effort', scope: 'relation' }, control: 'select' }),
|
|
84
98
|
relation('chatmode.relation', 'chatmode', '对话模式', 30, '/chatmode', 'chatmode.current', { options: { cmd: '/chatmode', operation: 'chatmode.current', scope: 'relation', mode: 'static', items: [{ value: 'interactive', label: '交互模式' }, { value: 'proactive', label: '主动模式' }] }, update: { cmd: '/chatmode', operation: 'chatmode.update', scope: 'relation' }, control: 'select' }),
|
|
@@ -290,8 +304,8 @@ export function bindMenuExecutionContextArgs(args, context) {
|
|
|
290
304
|
if (!args && context.kind !== 'group_relation')
|
|
291
305
|
return args;
|
|
292
306
|
const { context: _context, ...bound } = args ?? {};
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
307
|
+
const isSessionScoped = ['loop', 'fullaccess'].includes(String(bound.resource ?? bound.view ?? ''));
|
|
308
|
+
if (isSessionScoped)
|
|
295
309
|
return bound;
|
|
296
310
|
if (context.kind === 'private_relation' || context.kind === 'group_relation') {
|
|
297
311
|
if (bound.scope !== 'role')
|
|
@@ -368,6 +382,17 @@ async function operationView(binding, context, options = {}) {
|
|
|
368
382
|
reason: { code: 'NOT_ALLOWED', message: 'Pairing code issuance requires encrypted DaemonOwner authentication' },
|
|
369
383
|
};
|
|
370
384
|
}
|
|
385
|
+
if (binding.operation.startsWith('session.fullaccess.')) {
|
|
386
|
+
const allowed = context.kind === 'private_relation'
|
|
387
|
+
&& context.subject.chatType === 'private'
|
|
388
|
+
&& context.subject.isDaemonOwner === true
|
|
389
|
+
&& isAgentFullAccessOwner(context.subject.selfAid ?? '', context.subject.principalId);
|
|
390
|
+
return {
|
|
391
|
+
supported: true,
|
|
392
|
+
allowed,
|
|
393
|
+
...(allowed ? {} : { reason: { code: 'FORBIDDEN', message: 'session fullaccess requires the Agent Owner and daemon Owner identity' } }),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
371
396
|
if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
|
|
372
397
|
return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
|
|
373
398
|
}
|
|
@@ -377,7 +402,7 @@ async function operationView(binding, context, options = {}) {
|
|
|
377
402
|
function wireBinding(binding, context) {
|
|
378
403
|
if (!binding || binding.supported === false)
|
|
379
404
|
return undefined;
|
|
380
|
-
if (binding.operation.startsWith('session.loop.'))
|
|
405
|
+
if (binding.operation.startsWith('session.loop.') || binding.operation.startsWith('session.fullaccess.'))
|
|
381
406
|
return {
|
|
382
407
|
args: context.kind === 'group_relation'
|
|
383
408
|
? { ...(binding.args ?? {}), context: { kind: context.kind, ...(context.groupId ? { groupId: context.groupId } : {}) } }
|
|
@@ -406,10 +431,10 @@ export async function getMenuCatalog(input) {
|
|
|
406
431
|
operations[key] = view;
|
|
407
432
|
}
|
|
408
433
|
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' } });
|
|
409
|
-
if (entry.id === 'session.loop') {
|
|
434
|
+
if (entry.id === 'session.loop' || entry.id === 'session.fullaccess') {
|
|
410
435
|
const loopEntry = entries[entries.length - 1];
|
|
411
|
-
let target = { level: 'relation-main' };
|
|
412
|
-
if (input.resolveLoopTarget) {
|
|
436
|
+
let target = { level: 'relation-main', scope: 'agent' };
|
|
437
|
+
if ((entry.id === 'session.loop' || entry.id === 'session.fullaccess') && input.resolveLoopTarget) {
|
|
413
438
|
try {
|
|
414
439
|
target = await input.resolveLoopTarget(context);
|
|
415
440
|
}
|
|
@@ -420,12 +445,23 @@ export async function getMenuCatalog(input) {
|
|
|
420
445
|
}
|
|
421
446
|
}
|
|
422
447
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
448
|
+
if (entry.id === 'session.loop') {
|
|
449
|
+
loopEntry.target = target;
|
|
450
|
+
loopEntry.label = target.level === 'topic-session' ? '当前话题闲置触发' : '关系主会话闲置触发';
|
|
451
|
+
loopEntry.ui = {
|
|
452
|
+
control: entry.control,
|
|
453
|
+
idleSeconds: { type: 'integer', minimum: MIN_SESSION_LOOP_SECONDS, maximum: MAX_SESSION_LOOP_SECONDS, default: DEFAULT_SESSION_LOOP_SECONDS, unit: 'seconds' },
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
loopEntry.target = {
|
|
458
|
+
level: target.level,
|
|
459
|
+
sessionId: target.sessionId,
|
|
460
|
+
scope: target.level === 'topic-session' ? 'session' : 'agent',
|
|
461
|
+
};
|
|
462
|
+
loopEntry.label = '当前 EC 会话 fullaccess';
|
|
463
|
+
loopEntry.ui = { control: entry.control };
|
|
464
|
+
}
|
|
429
465
|
}
|
|
430
466
|
}
|
|
431
467
|
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) });
|
|
@@ -42,7 +42,7 @@ import { isUnsupportedBaseagentConfigField } from '../../config/config-field-pol
|
|
|
42
42
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
43
43
|
import { chatmodeFieldForPeer, isPrivateChatmodeRoleLocked, resolveChatModeForField } from '../message/peer-mode.js';
|
|
44
44
|
import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
45
|
-
import { resolveRuntimePermissionMode, resolveRuntimeStringField, validateRuntimeStringFieldOverride, } from '../role/runtime-policy.js';
|
|
45
|
+
import { isAgentFullAccessOwner, resolveRuntimePermissionMode, resolveRuntimeStringField, sessionFullAccessStatus, validateRuntimeStringFieldOverride, } from '../role/runtime-policy.js';
|
|
46
46
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
47
47
|
import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
|
|
48
48
|
import { logger } from '../../utils/logger.js';
|
|
@@ -2149,6 +2149,21 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2149
2149
|
return menuResultFailure(error);
|
|
2150
2150
|
}
|
|
2151
2151
|
}
|
|
2152
|
+
if (cmdBase === '/session' && args?.view === 'fullaccess') {
|
|
2153
|
+
if (Object.keys(args).some(key => key !== 'view' && key !== 'context')) {
|
|
2154
|
+
return { error: 'session.fullaccess query only accepts args.view', code: 'INVALID_ARGUMENT' };
|
|
2155
|
+
}
|
|
2156
|
+
if (!session || !subject.selfAid)
|
|
2157
|
+
return { error: '当前没有可绑定的 EC 会话', code: 'NO_ACTIVE_SESSION' };
|
|
2158
|
+
if (session.chatType !== 'private')
|
|
2159
|
+
return { error: '/fa 仅支持私聊中的当前 EC 会话', code: 'NOT_SUPPORTED' };
|
|
2160
|
+
const principalId = subject.principalId;
|
|
2161
|
+
if (!principalId || !subject.isDaemonOwner || !isAgentFullAccessOwner(subject.selfAid, principalId)) {
|
|
2162
|
+
return { error: '/fa 需要同一身份同时具备 Agent Owner 和 daemon Owner 权限', code: 'FORBIDDEN' };
|
|
2163
|
+
}
|
|
2164
|
+
const status = sessionFullAccessStatus(session, principalId);
|
|
2165
|
+
return { data: { ...status, sessionId: session.id, scope: isTopicSession(session) ? 'session' : 'agent' } };
|
|
2166
|
+
}
|
|
2152
2167
|
const storageRequest = { type: 'menu.query', cmd, args };
|
|
2153
2168
|
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
2154
2169
|
try {
|
|
@@ -2948,6 +2963,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2948
2963
|
if (!cmdBase)
|
|
2949
2964
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
2950
2965
|
const isLoopUpdate = cmdBase === '/session' && args?.resource === 'loop';
|
|
2966
|
+
const isFullAccessUpdate = cmdBase === '/session' && args?.resource === 'fullaccess';
|
|
2951
2967
|
if (isLoopUpdate) {
|
|
2952
2968
|
if (typeof value !== 'boolean' || Object.keys(args ?? {}).some(key => key !== 'resource' && key !== 'idleSeconds' && key !== 'context')) {
|
|
2953
2969
|
return { error: 'session.loop requires a boolean value and only accepts resource / idleSeconds', code: 'INVALID_ARGUMENT' };
|
|
@@ -2959,9 +2975,13 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2959
2975
|
return { error: `idleSeconds 必须是 ${MIN_SESSION_LOOP_SECONDS}-${MAX_SESSION_LOOP_SECONDS} 的整数`, code: 'INVALID_ARGUMENT' };
|
|
2960
2976
|
}
|
|
2961
2977
|
}
|
|
2962
|
-
else if (typeof value !== 'string') {
|
|
2978
|
+
else if (!isFullAccessUpdate && typeof value !== 'string') {
|
|
2963
2979
|
return { error: 'value 必须是字符串', code: 'INVALID_ARGUMENT' };
|
|
2964
2980
|
}
|
|
2981
|
+
if (isFullAccessUpdate && (typeof value !== 'boolean'
|
|
2982
|
+
|| Object.keys(args ?? {}).some(key => key !== 'resource' && key !== 'context'))) {
|
|
2983
|
+
return { error: 'session.fullaccess requires a boolean value and only accepts resource', code: 'INVALID_ARGUMENT' };
|
|
2984
|
+
}
|
|
2965
2985
|
if (cmdBase === '/perm') {
|
|
2966
2986
|
return { error: 'permissionMode 只能通过角色策略编辑器修改', code: 'ROLE_POLICY_MANAGED' };
|
|
2967
2987
|
}
|
|
@@ -3020,6 +3040,53 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3020
3040
|
return menuResultFailure(error);
|
|
3021
3041
|
}
|
|
3022
3042
|
}
|
|
3043
|
+
if (isFullAccessUpdate) {
|
|
3044
|
+
if (!session || !subject.selfAid)
|
|
3045
|
+
return { error: '当前没有可绑定的 EC 会话', code: 'NO_ACTIVE_SESSION' };
|
|
3046
|
+
const route = { channel, channelId, selfAid: subject.selfAid, threadId: transportReplyContext?.threadId };
|
|
3047
|
+
let target;
|
|
3048
|
+
try {
|
|
3049
|
+
target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
|
|
3050
|
+
if (target.session.id !== session.id)
|
|
3051
|
+
return { error: '当前会话已发生变化,请重新查询', code: 'CONFLICT' };
|
|
3052
|
+
target = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
3053
|
+
session = target.session;
|
|
3054
|
+
}
|
|
3055
|
+
catch (error) {
|
|
3056
|
+
return menuResultFailure(error);
|
|
3057
|
+
}
|
|
3058
|
+
const principalId = subject.principalId;
|
|
3059
|
+
if (session.chatType !== 'private' || !principalId || !subject.isDaemonOwner
|
|
3060
|
+
|| !isAgentFullAccessOwner(subject.selfAid, principalId)) {
|
|
3061
|
+
return { error: '/fa 需要私聊中同一身份同时具备 Agent Owner 和 daemon Owner 权限', code: 'FORBIDDEN' };
|
|
3062
|
+
}
|
|
3063
|
+
try {
|
|
3064
|
+
if (isTopicSession(session)) {
|
|
3065
|
+
this.sessionManager.updateSessionFullAccess(session.id, { enabled: value, authorizedBy: principalId });
|
|
3066
|
+
}
|
|
3067
|
+
else {
|
|
3068
|
+
const config = cfgRead(ConfigTarget.Agent, { self: subject.selfAid }, { cache: false });
|
|
3069
|
+
if (!config)
|
|
3070
|
+
return { error: '找不到当前 Agent 配置', code: 'NOT_FOUND' };
|
|
3071
|
+
config.fullaccess = { enabled: value };
|
|
3072
|
+
cfgWrite(ConfigTarget.Agent, config, { self: subject.selfAid });
|
|
3073
|
+
}
|
|
3074
|
+
auditFullAccessEvent({
|
|
3075
|
+
event: value ? 'fullaccess.session-policy.configured' : 'fullaccess.session-policy.deescalated',
|
|
3076
|
+
source: 'session-policy',
|
|
3077
|
+
actorId: principalId,
|
|
3078
|
+
processRole: 'daemon-owner',
|
|
3079
|
+
agentAid: subject.selfAid,
|
|
3080
|
+
sessionId: session.id,
|
|
3081
|
+
authorizedBy: principalId,
|
|
3082
|
+
reason: isTopicSession(session) ? 'topic session fullaccess updated via Menu' : 'Agent main-session fullaccess updated via Menu',
|
|
3083
|
+
});
|
|
3084
|
+
return { data: { enabled: value, sessionId: session.id, scope: isTopicSession(session) ? 'session' : 'agent' } };
|
|
3085
|
+
}
|
|
3086
|
+
catch (error) {
|
|
3087
|
+
return menuResultFailure(error);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3023
3090
|
const arg = value.trim();
|
|
3024
3091
|
if (!arg)
|
|
3025
3092
|
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
@@ -4325,6 +4392,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4325
4392
|
sessionId: sessionKey,
|
|
4326
4393
|
reason: 'stop',
|
|
4327
4394
|
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
4395
|
+
agentAid: session.selfAID,
|
|
4328
4396
|
});
|
|
4329
4397
|
await this.processor.interruptSession(sessionKey, 'stop');
|
|
4330
4398
|
return { data: { action: 'stop', success: true } };
|
|
@@ -5156,7 +5224,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
5156
5224
|
}
|
|
5157
5225
|
/** ECWeb 专用入口:只信任 IPC 信封中的服务端认证上下文,不信任 Menu payload 自报身份。 */
|
|
5158
5226
|
export async function execMenuForEcweb(payload, trusted) {
|
|
5159
|
-
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS });
|
|
5227
|
+
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS, allowTransient: true });
|
|
5160
5228
|
const id = payload?.id ?? '';
|
|
5161
5229
|
const name = payload?.name;
|
|
5162
5230
|
const validationError = validateMenuRequest(payload ?? {});
|
|
@@ -5200,7 +5268,7 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
5200
5268
|
* process authorization path. The authenticated envelope peerId remains the
|
|
5201
5269
|
* requester; token mode maps to a configured owner as its management actor. */
|
|
5202
5270
|
export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
5203
|
-
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS });
|
|
5271
|
+
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS, allowTransient: true });
|
|
5204
5272
|
const id = payload?.id ?? '';
|
|
5205
5273
|
const name = payload?.name;
|
|
5206
5274
|
const validationError = validateMenuRequest(payload ?? {});
|
|
@@ -367,14 +367,33 @@ export function validateMenuRequest(request) {
|
|
|
367
367
|
return { code: 'INVALID_ARGUMENT', message: 'idleSeconds is not allowed when session.loop is disabled' };
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
-
else if (type === 'menu.update'
|
|
370
|
+
else if (type === 'menu.update'
|
|
371
|
+
&& !((request.name === 'session' || request.cmd === '/session')
|
|
372
|
+
&& request.args?.resource === 'fullaccess')
|
|
373
|
+
&& (typeof request.value !== 'string' || request.value.length === 0)) {
|
|
371
374
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request value is required' };
|
|
372
375
|
}
|
|
376
|
+
const isSessionFullAccessUpdate = type === 'menu.update'
|
|
377
|
+
&& (request.name === 'session' || request.cmd === '/session')
|
|
378
|
+
&& request.args?.resource === 'fullaccess';
|
|
379
|
+
if (isSessionFullAccessUpdate) {
|
|
380
|
+
if (typeof request.value !== 'boolean')
|
|
381
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.fullaccess value must be a boolean' };
|
|
382
|
+
const args = request.args;
|
|
383
|
+
const unknown = Object.keys(args ?? {}).find(key => !['resource', 'context'].includes(key));
|
|
384
|
+
if (unknown)
|
|
385
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.fullaccess update does not accept args.' + unknown };
|
|
386
|
+
}
|
|
373
387
|
if (type === 'menu.query' && (request.name === 'session' || request.cmd === '/session')
|
|
374
388
|
&& request.args?.view === 'loop'
|
|
375
389
|
&& Object.keys(request.args).some(key => key !== 'view' && key !== 'context')) {
|
|
376
390
|
return { code: 'INVALID_ARGUMENT', message: 'session.loop query only accepts args.view' };
|
|
377
391
|
}
|
|
392
|
+
if (type === 'menu.query' && (request.name === 'session' || request.cmd === '/session')
|
|
393
|
+
&& request.args?.view === 'fullaccess'
|
|
394
|
+
&& Object.keys(request.args).some(key => key !== 'view' && key !== 'context')) {
|
|
395
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.fullaccess query only accepts args.view' };
|
|
396
|
+
}
|
|
378
397
|
if (type === 'menu.action' && (typeof request.action !== 'string' || request.action.trim().length === 0)) {
|
|
379
398
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request action is required' };
|
|
380
399
|
}
|
|
@@ -43,7 +43,7 @@ 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', '/mention', '/loop', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory', '/skill', '/thread'];
|
|
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', '/mention', '/loop', '/fa', '/fullaccess', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory', '/skill', '/thread'];
|
|
47
47
|
const deprecatedCommands = ['/clear'];
|
|
48
48
|
commands.push('/diag');
|
|
49
49
|
const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair', '/diag']);
|
|
@@ -55,7 +55,14 @@ export function parseFullAccessCommand(content) {
|
|
|
55
55
|
return {
|
|
56
56
|
matched: true,
|
|
57
57
|
command: match[1],
|
|
58
|
-
|
|
58
|
+
action: match[2].trim() === ''
|
|
59
|
+
? 'status'
|
|
60
|
+
: match[2].trim() === 'on'
|
|
61
|
+
? 'on'
|
|
62
|
+
: match[2].trim() === 'off'
|
|
63
|
+
? 'off'
|
|
64
|
+
: 'invalid',
|
|
65
|
+
raw: match[2].trim(),
|
|
59
66
|
};
|
|
60
67
|
}
|
|
61
68
|
// 命令别名映射
|
|
@@ -78,6 +85,13 @@ export function guardGroupSlashCommand(content, chatType) {
|
|
|
78
85
|
return undefined;
|
|
79
86
|
const [inputCommand, ...args] = content.trim().split(/\s+/);
|
|
80
87
|
const command = aliases[inputCommand] ?? inputCommand;
|
|
88
|
+
if (command === '/fa' || command === '/fullaccess') {
|
|
89
|
+
return {
|
|
90
|
+
kind: 'command.error',
|
|
91
|
+
text: '❌ 群聊中不支持 /fa,请在私聊会话操作',
|
|
92
|
+
reason: 'GROUP_COMMAND_UNSUPPORTED',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
81
95
|
if (GROUP_READ_ONLY_COMMANDS.has(command)) {
|
|
82
96
|
if (!args.length || (command === '/model' && args.length === 1 && ['current', 'list', 'check'].includes(args[0]))) {
|
|
83
97
|
return undefined;
|
|
@@ -97,7 +111,7 @@ export function guardGroupSlashCommand(content, chatType) {
|
|
|
97
111
|
};
|
|
98
112
|
}
|
|
99
113
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
100
|
-
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', '/mention', '/loop', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory', '/skill', '/thread'];
|
|
114
|
+
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', '/mention', '/loop', '/fa', '/fullaccess', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory', '/skill', '/thread'];
|
|
101
115
|
quickCommandPrefixes.push('/diag');
|
|
102
116
|
/**
|
|
103
117
|
* 计算两个字符串的 Levenshtein 距离(编辑距离)
|