evolcore 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +3 -3
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +123 -42
- package/dist/agents/codex-app-server-client.js +33 -9
- package/dist/agents/codex-runner.js +58 -8
- package/dist/agents/ecagent-runner.js +17 -2
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +131 -128
- package/dist/cli/agent-command.js +16 -9
- package/dist/cli/agent.js +82 -19
- package/dist/cli/daemon-commands.js +21 -2
- package/dist/cli/index.js +76 -61
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/gateway-config.js +26 -10
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/operation-authorizer.js +32 -147
- package/dist/core/auth/operation-catalog.js +80 -0
- package/dist/core/bootstrap-messages.js +50 -0
- package/dist/core/bootstrap-service.js +85 -10
- package/dist/core/channel-loader.js +23 -6
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/menu-handler.js +67 -76
- package/dist/core/command/slash-handler.js +4 -4
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +8 -3
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/message-bridge.js +1 -1
- package/dist/core/message/message-log.js +22 -0
- package/dist/core/message/message-queue.js +19 -4
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +231 -93
- package/dist/core/protected-paths.js +10 -7
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/index.js +262 -84
- package/dist/ipc.js +29 -11
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/rules/01-overview.md +9 -0
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/templates/roles/admin.json +5 -0
- package/kits/templates/roles/member.json +17 -0
- package/kits/templates/roles/visitor.json +8 -0
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +2 -1
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
|
@@ -9,6 +9,36 @@ import { normalizeAgentLifecycle, withLifecycleForWrite } from '../config/lifecy
|
|
|
9
9
|
import { renderTemplate } from '../eck/manifest-engine.js';
|
|
10
10
|
import { activeBaseagent } from './model/config-scope.js';
|
|
11
11
|
import { buildEnvelope } from './message/message-utils.js';
|
|
12
|
+
import { BOOTSTRAP_MESSAGE_TTL_MS, bootstrapInitialMessageOperationId } from './bootstrap-messages.js';
|
|
13
|
+
/** Coordinate the write-ahead completion welcome with the lifecycle commit. */
|
|
14
|
+
export async function completeBootstrapWithWelcome(service, aid, welcome) {
|
|
15
|
+
const lifecycle = service.lifecycleOf(aid);
|
|
16
|
+
if (lifecycle === 'bootstrapping') {
|
|
17
|
+
if (!welcome) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
code: 'WELCOME_CHANNEL_UNAVAILABLE',
|
|
21
|
+
error: `No AUN channel available to persist the post-bootstrap welcome for ${aid}`,
|
|
22
|
+
reloaded: false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!await welcome.preparePostBootstrapWelcome()) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
code: 'WELCOME_PREPARE_FAILED',
|
|
29
|
+
error: `Failed to persist the post-bootstrap welcome for ${aid}`,
|
|
30
|
+
reloaded: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const result = service.completeBootstrap(aid);
|
|
35
|
+
if (!result.ok)
|
|
36
|
+
return { ...result, reloaded: false };
|
|
37
|
+
if (!welcome)
|
|
38
|
+
return { ...result, reloaded: true, welcomeQueued: false };
|
|
39
|
+
const welcomeQueued = await welcome.reconcilePostBootstrapWelcome();
|
|
40
|
+
return { ...result, reloaded: true, welcomeQueued };
|
|
41
|
+
}
|
|
12
42
|
export class BootstrapService {
|
|
13
43
|
agentRegistry;
|
|
14
44
|
eventBus;
|
|
@@ -29,23 +59,25 @@ export class BootstrapService {
|
|
|
29
59
|
return false;
|
|
30
60
|
this.inFlight.add(key);
|
|
31
61
|
let lifecycleStarted = false;
|
|
32
|
-
const loadedConfig =
|
|
62
|
+
const loadedConfig = loadAgent(aid) || agent?.config;
|
|
33
63
|
if (!loadedConfig) {
|
|
34
64
|
this.inFlight.delete(key);
|
|
35
65
|
return false;
|
|
36
66
|
}
|
|
37
67
|
const config = normalizeAgentLifecycle(loadedConfig);
|
|
38
|
-
if (config.lifecycle
|
|
68
|
+
if (config.lifecycle === 'active') {
|
|
39
69
|
this.inFlight.delete(key);
|
|
40
70
|
return false;
|
|
41
71
|
}
|
|
72
|
+
const starting = config.lifecycle === 'created';
|
|
42
73
|
const channelType = ctx.channelType || this.channelTypeFromKey(ctx.channelKey);
|
|
43
|
-
const
|
|
74
|
+
const configuredRecipient = this.resolveConfiguredRecipient(config, ctx.channelKey, channelType);
|
|
75
|
+
const recipientId = ctx.recipientId || configuredRecipient;
|
|
44
76
|
if (!recipientId) {
|
|
45
77
|
this.inFlight.delete(key);
|
|
46
78
|
return false;
|
|
47
79
|
}
|
|
48
|
-
if (ctx.recipientId &&
|
|
80
|
+
if (ctx.recipientId && configuredRecipient !== ctx.recipientId) {
|
|
49
81
|
this.inFlight.delete(key);
|
|
50
82
|
return false;
|
|
51
83
|
}
|
|
@@ -57,9 +89,11 @@ export class BootstrapService {
|
|
|
57
89
|
try {
|
|
58
90
|
const agentName = this.resolveAgentDisplayName(aid);
|
|
59
91
|
const baseagent = this.resolveBaseagent(agent, aid);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
if (starting) {
|
|
93
|
+
await this.publishAgentMdIfSupported(ctx.adapter, aid, agentName);
|
|
94
|
+
this.setLifecycle(agent, aid, 'bootstrapping');
|
|
95
|
+
lifecycleStarted = true;
|
|
96
|
+
}
|
|
63
97
|
const text = this.renderWelcome({
|
|
64
98
|
agentAid: aid,
|
|
65
99
|
agentName,
|
|
@@ -72,9 +106,23 @@ export class BootstrapService {
|
|
|
72
106
|
channel: ctx.adapter.channelKey || ctx.adapter.channelName,
|
|
73
107
|
channelId,
|
|
74
108
|
agentName: aid,
|
|
109
|
+
replyContext: {
|
|
110
|
+
metadata: {
|
|
111
|
+
source: 'daemon',
|
|
112
|
+
persistRequired: true,
|
|
113
|
+
operationId: bootstrapInitialMessageOperationId(aid),
|
|
114
|
+
outboxTtl: BOOTSTRAP_MESSAGE_TTL_MS,
|
|
115
|
+
criticalDelivery: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
75
118
|
}), { kind: 'result.text', text, isFinal: true });
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
if (starting) {
|
|
120
|
+
this.eventBus.publish({ type: 'agent:bootstrap-started', aid, channel: channelType || ctx.channelKey, timestamp: Date.now() });
|
|
121
|
+
logger.info(`[Bootstrap] Started for ${aid} via ${ctx.channelKey} (${ctx.source})`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
logger.info(`[Bootstrap] Reconciled initial message for ${aid} via ${ctx.channelKey} (${ctx.source})`);
|
|
125
|
+
}
|
|
78
126
|
return true;
|
|
79
127
|
}
|
|
80
128
|
catch (e) {
|
|
@@ -93,6 +141,33 @@ export class BootstrapService {
|
|
|
93
141
|
this.inFlight.delete(key);
|
|
94
142
|
}
|
|
95
143
|
}
|
|
144
|
+
completeBootstrap(aid) {
|
|
145
|
+
const agent = this.agentRegistry.get(aid);
|
|
146
|
+
const loadedConfig = loadAgent(aid) || agent?.config;
|
|
147
|
+
if (!loadedConfig)
|
|
148
|
+
return { ok: false, error: `Agent "${aid}" not found` };
|
|
149
|
+
const lifecycle = normalizeAgentLifecycle(loadedConfig).lifecycle;
|
|
150
|
+
if (lifecycle === 'active')
|
|
151
|
+
return { ok: true, aid, transitioned: false };
|
|
152
|
+
if (lifecycle !== 'bootstrapping') {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
code: 'INVALID_LIFECYCLE',
|
|
156
|
+
error: `Agent "${aid}" is ${lifecycle}; only a bootstrapping agent can become ready`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
this.setLifecycle(agent, aid, 'active');
|
|
160
|
+
this.eventBus.publish({ type: 'agent:bootstrap-complete', aid, timestamp: Date.now() });
|
|
161
|
+
logger.info(`[Bootstrap] Completed for ${aid} (previous lifecycle=${lifecycle})`);
|
|
162
|
+
return { ok: true, aid, transitioned: true };
|
|
163
|
+
}
|
|
164
|
+
lifecycleOf(aid) {
|
|
165
|
+
const agent = this.agentRegistry.get(aid);
|
|
166
|
+
const loadedConfig = loadAgent(aid) || agent?.config;
|
|
167
|
+
return loadedConfig
|
|
168
|
+
? normalizeAgentLifecycle(loadedConfig).lifecycle
|
|
169
|
+
: null;
|
|
170
|
+
}
|
|
96
171
|
setLifecycle(agent, aid, lifecycle) {
|
|
97
172
|
if (agent?.setLifecycle) {
|
|
98
173
|
agent.setLifecycle(lifecycle);
|
|
@@ -100,7 +175,7 @@ export class BootstrapService {
|
|
|
100
175
|
}
|
|
101
176
|
const cfg = loadAgent(aid);
|
|
102
177
|
if (!cfg)
|
|
103
|
-
|
|
178
|
+
throw new Error(`Agent "${aid}" disappeared while updating lifecycle`);
|
|
104
179
|
saveAgent(withLifecycleForWrite(cfg, lifecycle));
|
|
105
180
|
}
|
|
106
181
|
resolveConfiguredRecipient(config, _channelKey, _channelType) {
|
|
@@ -177,10 +177,14 @@ export function isValidChannelName(name) {
|
|
|
177
177
|
return typeof name === 'string' && name.length > 0 && !name.includes(SEP);
|
|
178
178
|
}
|
|
179
179
|
export function buildReloadHooks(deps) {
|
|
180
|
-
const { channelLoader, channelInstances, registerChannelInstance, unregisterChannelInstance, messageQueue, handoffRuntime, onChannelStarted, onChannelConnected } = deps;
|
|
180
|
+
const { channelLoader, channelInstances, registerChannelInstance, unregisterChannelInstance, messageQueue, handoffRuntime, onChannelStarted, onChannelConnected, onAgentReloaded, stageAgentRunners, } = deps;
|
|
181
181
|
const drainDelayMs = deps.drainDelayMs ?? 500;
|
|
182
182
|
const drainTimeoutMs = deps.drainTimeoutMs ?? 30000;
|
|
183
183
|
return {
|
|
184
|
+
stageAgentRunners,
|
|
185
|
+
async afterReload(agent) {
|
|
186
|
+
await onAgentReloaded?.(agent);
|
|
187
|
+
},
|
|
184
188
|
async prepareHandoffReload(aid) {
|
|
185
189
|
if (!handoffRuntime)
|
|
186
190
|
return;
|
|
@@ -284,11 +288,24 @@ export function buildReloadHooks(deps) {
|
|
|
284
288
|
return;
|
|
285
289
|
}
|
|
286
290
|
registerChannelInstance(newInst);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
try {
|
|
292
|
+
onChannelStarted?.(newInst);
|
|
293
|
+
await newInst.connect();
|
|
294
|
+
channelInstances.push(newInst);
|
|
295
|
+
await onChannelConnected?.(newInst);
|
|
296
|
+
logger.info(`[Reload] Started channel: ${channelName}`);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
try {
|
|
300
|
+
await newInst.disconnect();
|
|
301
|
+
}
|
|
302
|
+
catch { }
|
|
303
|
+
unregisterChannelInstance?.(channelName);
|
|
304
|
+
const index = channelInstances.indexOf(newInst);
|
|
305
|
+
if (index >= 0)
|
|
306
|
+
channelInstances.splice(index, 1);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
292
309
|
},
|
|
293
310
|
};
|
|
294
311
|
}
|
|
@@ -4,7 +4,7 @@ import { resolvePaths, resolveRoot } from '../../paths.js';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { ConfigTarget, ensureFile as cfgEnsure, read as cfgRead, write as cfgWrite, } from '../../config/config-manager.js';
|
|
6
6
|
import { CreateStatusWriter, readCreateStatus } from '../message/create-status.js';
|
|
7
|
-
import { deriveAgentProjectPath } from '../../utils/project-path.js';
|
|
7
|
+
import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../../utils/project-path.js';
|
|
8
8
|
import { uploadAvatar } from '../../utils/avatar-upload.js';
|
|
9
9
|
import { agentmdGet, agentmdPut, updateAgentMdFrontmatterName } from '../../aun/aid/agentmd.js';
|
|
10
10
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
@@ -367,7 +367,7 @@ export async function execAgentAction(action, args, peerId, eventBus) {
|
|
|
367
367
|
if (!isValidAid(owner))
|
|
368
368
|
return { error: `无效 owner AID: ${owner}`, code: 'INVALID_ARGS' };
|
|
369
369
|
if (!a.project || typeof a.project !== 'string') {
|
|
370
|
-
return { error: 'project
|
|
370
|
+
return { error: 'project 缺失(调用方应先解析默认项目路径)', code: 'INVALID_ARGS' };
|
|
371
371
|
}
|
|
372
372
|
const model = typeof a.model === 'string' && a.model.trim() ? a.model.trim() : undefined;
|
|
373
373
|
const rawEffort = typeof a.effort === 'string' && a.effort.trim() ? a.effort.trim() : undefined;
|
|
@@ -401,9 +401,10 @@ export async function execAgentAction(action, args, peerId, eventBus) {
|
|
|
401
401
|
if (action === 'enable' || action === 'disable') {
|
|
402
402
|
if (!a.aid)
|
|
403
403
|
return { error: '缺少 aid', code: 'INVALID_ARGS' };
|
|
404
|
-
const
|
|
404
|
+
const options = { force: a.force === true };
|
|
405
|
+
const res = action === 'enable' ? await agentEnable(a.aid, options) : await agentDisable(a.aid, options);
|
|
405
406
|
if (!('ok' in res) || res.ok !== true)
|
|
406
|
-
return { error: res.error, code: classifyError(res.error) };
|
|
407
|
+
return { error: res.error, code: res.code || classifyError(res.error) };
|
|
407
408
|
eventBus?.publish({
|
|
408
409
|
type: action === 'enable' ? 'agent:enabled' : 'agent:disabled',
|
|
409
410
|
aid: res.aid,
|
|
@@ -415,9 +416,9 @@ export async function execAgentAction(action, args, peerId, eventBus) {
|
|
|
415
416
|
if (action === 'reload') {
|
|
416
417
|
if (!a.aid)
|
|
417
418
|
return { error: '缺少 aid', code: 'INVALID_ARGS' };
|
|
418
|
-
const res = await agentReload(a.aid);
|
|
419
|
+
const res = await agentReload(a.aid, { force: a.force === true });
|
|
419
420
|
if (!('ok' in res) || res.ok !== true)
|
|
420
|
-
return { error: res.error, code: classifyError(res.error) };
|
|
421
|
+
return { error: res.error, code: res.code || classifyError(res.error) };
|
|
421
422
|
eventBus?.publish({ type: 'agent:reloaded', aid: a.aid, timestamp: Date.now() });
|
|
422
423
|
return { data: { aid: a.aid, reloaded: true } };
|
|
423
424
|
}
|
|
@@ -564,14 +565,16 @@ export async function execAgentUpdate(args) {
|
|
|
564
565
|
}
|
|
565
566
|
return { data: { ...data, saved: true, ...(hasConfigPatch ? { requiresReload: true } : {}) } };
|
|
566
567
|
}
|
|
567
|
-
/**
|
|
568
|
+
/**
|
|
569
|
+
* project 解析与 ec agent create 保持一致:
|
|
570
|
+
* 显式值 > 默认项目根目录 + AID 前缀。
|
|
571
|
+
* 默认项目根目录由 defaults.projects.rootPath/defaultPath 或运行时根目录推导。
|
|
572
|
+
*/
|
|
568
573
|
export function resolveProjectPath(explicit, aid, defaults) {
|
|
569
574
|
if (explicit && explicit.trim())
|
|
570
575
|
return explicit;
|
|
571
|
-
const root = defaults
|
|
572
|
-
|
|
573
|
-
return deriveAgentProjectPath(root, aid);
|
|
574
|
-
return defaults?.projects?.defaultPath;
|
|
576
|
+
const root = agentProjectRootFromDefaults(defaults, resolveRoot());
|
|
577
|
+
return deriveAgentProjectPath(root, aid);
|
|
575
578
|
}
|
|
576
579
|
/** name=agent 的 menu.query:查单个 agent 详情,附构建进度(D3)。 */
|
|
577
580
|
export async function execAgentQuery(args) {
|
|
@@ -873,20 +873,36 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
873
873
|
});
|
|
874
874
|
const modelScope = args?.scope === 'agent' ? 'agent' : 'relation';
|
|
875
875
|
if (verb === 'query') {
|
|
876
|
+
if (cmdBase === '/pwd')
|
|
877
|
+
return intent('project.current', 'relation');
|
|
878
|
+
if (cmdBase === '/session' || cmdBase === '/s')
|
|
879
|
+
return intent('session.current', 'relation');
|
|
880
|
+
if (cmdBase === '/topic')
|
|
881
|
+
return intent('session.topic.current', 'relation');
|
|
882
|
+
if (cmdBase === '/baseagent')
|
|
883
|
+
return intent('agent.baseagent.current', fromControlChannel ? 'agent' : 'relation');
|
|
876
884
|
if (cmdBase === '/file')
|
|
877
885
|
return intent('file.fetch', 'filesystem', { filePath: args?.path });
|
|
878
886
|
if (cmdBase === '/model')
|
|
879
887
|
return intent('model.current', modelScope);
|
|
880
888
|
if (cmdBase === '/effort')
|
|
881
|
-
return intent('model.current', modelScope);
|
|
889
|
+
return intent('model.effort.current', modelScope);
|
|
882
890
|
if (cmdBase === '/chatmode')
|
|
883
891
|
return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
884
892
|
if (cmdBase === '/mentionmode')
|
|
885
893
|
return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
894
|
+
if (cmdBase === '/perm')
|
|
895
|
+
return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
896
|
+
if (cmdBase === '/activity')
|
|
897
|
+
return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
898
|
+
if (cmdBase === '/observable')
|
|
899
|
+
return intent('observable.current', 'agent');
|
|
900
|
+
if (cmdBase === '/capability')
|
|
901
|
+
return intent('capability.read', 'agent');
|
|
886
902
|
if (cmdBase === '/gateway')
|
|
887
903
|
return intent('gateway.read', 'process');
|
|
888
904
|
if (cmdBase === '/config')
|
|
889
|
-
return intent('config.read', 'process');
|
|
905
|
+
return intent('config.read', args?.scope === 'agent' ? 'agent' : 'process');
|
|
890
906
|
if (cmdBase === '/system')
|
|
891
907
|
return intent('system.status', 'process');
|
|
892
908
|
if (cmdBase === '/agent') {
|
|
@@ -895,6 +911,32 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
895
911
|
if (cmdBase === '/trigger')
|
|
896
912
|
return intent('trigger.list', 'relation');
|
|
897
913
|
}
|
|
914
|
+
if (verb === 'options') {
|
|
915
|
+
if (cmdBase === '/agent')
|
|
916
|
+
return intent(fromControlChannel ? 'agent.list' : 'agent.show', fromControlChannel ? 'control' : 'agent');
|
|
917
|
+
if (cmdBase === '/capability')
|
|
918
|
+
return intent('capability.read', 'agent');
|
|
919
|
+
if (cmdBase === '/trigger')
|
|
920
|
+
return intent('trigger.list', 'relation');
|
|
921
|
+
if (cmdBase === '/topic')
|
|
922
|
+
return intent('session.topic.list', 'relation');
|
|
923
|
+
if (cmdBase === '/session' || cmdBase === '/s' || cmdBase === '/del')
|
|
924
|
+
return intent('session.list', 'relation');
|
|
925
|
+
if (cmdBase === '/baseagent')
|
|
926
|
+
return intent('agent.baseagent.list', 'agent');
|
|
927
|
+
if (cmdBase === '/model')
|
|
928
|
+
return intent('model.list', modelScope);
|
|
929
|
+
if (cmdBase === '/effort')
|
|
930
|
+
return intent('model.effort.current', modelScope);
|
|
931
|
+
if (cmdBase === '/chatmode')
|
|
932
|
+
return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
933
|
+
if (cmdBase === '/mentionmode')
|
|
934
|
+
return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
935
|
+
if (cmdBase === '/activity')
|
|
936
|
+
return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
937
|
+
if (cmdBase === '/perm')
|
|
938
|
+
return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
|
|
939
|
+
}
|
|
898
940
|
if (verb === 'update') {
|
|
899
941
|
if (cmdBase === '/model')
|
|
900
942
|
return intent('model.use', modelScope, { model: value });
|
|
@@ -1010,6 +1052,9 @@ async function authorizeMenuIntent(params) {
|
|
|
1010
1052
|
peerKey: subject.peerKey,
|
|
1011
1053
|
});
|
|
1012
1054
|
}
|
|
1055
|
+
else if (intent.scope === 'agent' && subject.selfAid && intent.args.self === undefined) {
|
|
1056
|
+
intent.args = { ...intent.args, self: subject.selfAid };
|
|
1057
|
+
}
|
|
1013
1058
|
const source = params.source ?? 'menu';
|
|
1014
1059
|
intent.source = source;
|
|
1015
1060
|
const decision = await authorizeOperation({ source, intent, subject });
|
|
@@ -1428,13 +1473,25 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1428
1473
|
throw { code: result.code, message: result.error, data: result.data };
|
|
1429
1474
|
return result.data;
|
|
1430
1475
|
}
|
|
1476
|
+
const optionsIdentity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1477
|
+
const optionsAuthDenied = await authorizeMenuIntent.call(this, {
|
|
1478
|
+
intent: buildMenuIntent('options', cmdBase0, args, undefined, undefined, fromControlChannel),
|
|
1479
|
+
identity: optionsIdentity,
|
|
1480
|
+
subject,
|
|
1481
|
+
session,
|
|
1482
|
+
explicitChatType,
|
|
1483
|
+
channel,
|
|
1484
|
+
channelId,
|
|
1485
|
+
userId,
|
|
1486
|
+
fromControlChannel,
|
|
1487
|
+
source,
|
|
1488
|
+
});
|
|
1489
|
+
if (optionsAuthDenied)
|
|
1490
|
+
throw { code: optionsAuthDenied.code, message: optionsAuthDenied.error, data: optionsAuthDenied.data };
|
|
1431
1491
|
// ── /agent list(只读) ──
|
|
1432
|
-
// 控制 channel
|
|
1492
|
+
// 控制 channel:返回全量;agent channel:仅返回自身单条。
|
|
1433
1493
|
if (cmd === '/agent') {
|
|
1434
1494
|
if (fromControlChannel) {
|
|
1435
|
-
if (!subject.isDaemonOwner) {
|
|
1436
|
-
throw { code: 'FORBIDDEN', message: '操作需要 owner 权限' };
|
|
1437
|
-
}
|
|
1438
1495
|
const res = await execAgentOptions(args);
|
|
1439
1496
|
if ('error' in res)
|
|
1440
1497
|
throw { code: res.code, message: res.error };
|
|
@@ -1465,24 +1522,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1465
1522
|
}
|
|
1466
1523
|
// ── 关系级 /trigger list(每个 trigger 一个 MenuItem) ──
|
|
1467
1524
|
if (cmd === '/trigger') {
|
|
1468
|
-
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1469
|
-
const authDenied = await authorizeMenuIntent.call(this, {
|
|
1470
|
-
intent: buildMenuIntent('query', cmd, args, undefined, undefined, fromControlChannel),
|
|
1471
|
-
identity,
|
|
1472
|
-
subject,
|
|
1473
|
-
session,
|
|
1474
|
-
explicitChatType,
|
|
1475
|
-
channel,
|
|
1476
|
-
channelId,
|
|
1477
|
-
userId,
|
|
1478
|
-
fromControlChannel,
|
|
1479
|
-
source,
|
|
1480
|
-
});
|
|
1481
|
-
if (authDenied)
|
|
1482
|
-
throw { code: authDenied.code, message: authDenied.error, data: authDenied.data };
|
|
1483
1525
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
1484
1526
|
const scope = args?.options === 'all' ? 'all' : 'enabled';
|
|
1485
|
-
const role =
|
|
1527
|
+
const role = optionsIdentity.role;
|
|
1486
1528
|
const isAdmin = isManagementRole(role);
|
|
1487
1529
|
if (!triggerScheduler)
|
|
1488
1530
|
return [];
|
|
@@ -1501,10 +1543,6 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1501
1543
|
});
|
|
1502
1544
|
}
|
|
1503
1545
|
if (cmd === '/topic') {
|
|
1504
|
-
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1505
|
-
if (!this.canReadTopics(identity.role)) {
|
|
1506
|
-
throw { code: 'FORBIDDEN', message: '无权限查看话题' };
|
|
1507
|
-
}
|
|
1508
1546
|
if (args?.mode === 'fork-turns') {
|
|
1509
1547
|
const source = await resolveTopicForkSource(this.sessionManager, channel, channelId, args?.sourceThreadId);
|
|
1510
1548
|
if (!source)
|
|
@@ -1577,14 +1615,13 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1577
1615
|
return available.map((name) => ({ value: name, label: name, selected: name === currentAgent }));
|
|
1578
1616
|
}
|
|
1579
1617
|
if (cmd === '/model') {
|
|
1580
|
-
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1581
1618
|
const target = resolveMenuModelTarget.call(this, {
|
|
1582
1619
|
args,
|
|
1583
1620
|
session,
|
|
1584
1621
|
channel,
|
|
1585
1622
|
channelId,
|
|
1586
1623
|
userId,
|
|
1587
|
-
role:
|
|
1624
|
+
role: optionsIdentity.role,
|
|
1588
1625
|
explicitChatType,
|
|
1589
1626
|
fromControlChannel,
|
|
1590
1627
|
field: 'model',
|
|
@@ -1593,31 +1630,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1593
1630
|
throw { code: target.code, message: target.error };
|
|
1594
1631
|
const agent = this.getAgent(channel, target.baseagent);
|
|
1595
1632
|
if (hasModelSwitcher(agent) && agent.listModels) {
|
|
1596
|
-
const role = identity.role;
|
|
1597
|
-
const authIntent = {
|
|
1598
|
-
operation: 'model.list',
|
|
1599
|
-
scope: target.scope,
|
|
1600
|
-
source: 'menu',
|
|
1601
|
-
args: {},
|
|
1602
|
-
};
|
|
1603
|
-
const subject = buildMenuAuthSubject(this, {
|
|
1604
|
-
identity,
|
|
1605
|
-
session,
|
|
1606
|
-
explicitChatType,
|
|
1607
|
-
channel,
|
|
1608
|
-
channelId,
|
|
1609
|
-
userId,
|
|
1610
|
-
fromControlChannel,
|
|
1611
|
-
subject: authSubject,
|
|
1612
|
-
});
|
|
1613
|
-
authIntent.args = target.scope === 'relation'
|
|
1614
|
-
? buildRelationIntentArgs({ args, selfAid: target.sel.self, peerKey: target.sel.peerKey })
|
|
1615
|
-
: { ...(args ?? {}), self: target.sel.self };
|
|
1616
|
-
const decision = await authorizeOperation({ source: 'menu', intent: authIntent, subject, audit: false });
|
|
1617
|
-
if (!decision.allow)
|
|
1618
|
-
throw { code: decision.code, message: decision.reason };
|
|
1619
1633
|
const rawModels = await agent.listModels() ?? [];
|
|
1620
|
-
const models = filterModelsForRole(role, target.baseagent, rawModels, agent.resolveModelId?.bind(agent));
|
|
1634
|
+
const models = filterModelsForRole(optionsIdentity.role, target.baseagent, rawModels, agent.resolveModelId?.bind(agent));
|
|
1621
1635
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1622
1636
|
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1623
1637
|
if (models.length > 0)
|
|
@@ -1813,12 +1827,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1813
1827
|
if (authDenied)
|
|
1814
1828
|
return authDenied;
|
|
1815
1829
|
// ── /agent 查询(只读) ──
|
|
1816
|
-
//
|
|
1830
|
+
// 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
|
|
1817
1831
|
if (cmdBase === '/agent') {
|
|
1818
1832
|
if (fromControlChannel) {
|
|
1819
|
-
if (!subject.isDaemonOwner) {
|
|
1820
|
-
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
1821
|
-
}
|
|
1822
1833
|
return await execAgentQuery(args);
|
|
1823
1834
|
}
|
|
1824
1835
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
@@ -1827,29 +1838,17 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1827
1838
|
return await execAgentQuery({ ...(args ?? {}), aid: selfAid });
|
|
1828
1839
|
}
|
|
1829
1840
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
1830
|
-
// 进程级:闸门已要求 fromControlChannel;此处再验 owners 非空。
|
|
1831
1841
|
if (cmdBase === '/gateway') {
|
|
1832
|
-
if (!subject.isDaemonOwner) {
|
|
1833
|
-
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
1834
|
-
}
|
|
1835
1842
|
return gatewayList();
|
|
1836
1843
|
}
|
|
1837
1844
|
// ── /config 查询(只读,查询各层配置) ──
|
|
1838
1845
|
if (cmdBase === '/config') {
|
|
1839
1846
|
const scope = args?.scope || 'process';
|
|
1840
1847
|
if (scope === 'process') {
|
|
1841
|
-
// 进程级配置需要 owner 权限
|
|
1842
|
-
if (!fromControlChannel || !subject.isDaemonOwner) {
|
|
1843
|
-
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
1844
|
-
}
|
|
1845
1848
|
const cfg = loadDaemonConfig();
|
|
1846
1849
|
return { data: { scope: 'process', config: cfg } };
|
|
1847
1850
|
}
|
|
1848
1851
|
if (scope === 'defaults') {
|
|
1849
|
-
// 全局默认配置需要 owner 权限
|
|
1850
|
-
if (!fromControlChannel || !subject.isDaemonOwner) {
|
|
1851
|
-
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
1852
|
-
}
|
|
1853
1852
|
const { read, ConfigTarget } = await import('../../config/config-manager.js');
|
|
1854
1853
|
const cfg = read(ConfigTarget.Defaults, undefined, { cache: true });
|
|
1855
1854
|
return { data: { scope: 'defaults', config: cfg } };
|
|
@@ -1938,9 +1937,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1938
1937
|
return { data };
|
|
1939
1938
|
}
|
|
1940
1939
|
if (cmdBase === '/topic') {
|
|
1941
|
-
if (!this.canReadTopics(identity.role)) {
|
|
1942
|
-
return { error: '无权限查看话题', code: 'FORBIDDEN' };
|
|
1943
|
-
}
|
|
1944
1940
|
const target = (args?.target ?? '').toString().trim();
|
|
1945
1941
|
if (!target)
|
|
1946
1942
|
return { error: '缺少 args.target', code: 'MISSING_VALUE' };
|
|
@@ -2110,8 +2106,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2110
2106
|
};
|
|
2111
2107
|
}
|
|
2112
2108
|
if (cmdBase === '/observable') {
|
|
2113
|
-
if (identity.role !== 'owner')
|
|
2114
|
-
return { error: '观察者模式仅限 owner 查看', code: 'NO_PERMISSION' };
|
|
2115
2109
|
if (!evolagent)
|
|
2116
2110
|
return { error: '找不到通道所属 agent', code: 'MISSING_AID' };
|
|
2117
2111
|
const observable = evolagent?.getObservable() ?? false;
|
|
@@ -2173,9 +2167,6 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2173
2167
|
};
|
|
2174
2168
|
}
|
|
2175
2169
|
if (cmdBase === '/system') {
|
|
2176
|
-
if (!subject.isDaemonOwner) {
|
|
2177
|
-
return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
|
|
2178
|
-
}
|
|
2179
2170
|
const data = {
|
|
2180
2171
|
aid: loadDaemonConfig().aid ?? null,
|
|
2181
2172
|
pid: process.pid,
|
|
@@ -1641,6 +1641,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1641
1641
|
if (!isDaemonOwner && aidArg && aidArg !== selfAid) {
|
|
1642
1642
|
return { kind: 'command.error', text: '❌ 无权限:跨 agent reload 仅限 daemon owner 使用' };
|
|
1643
1643
|
}
|
|
1644
|
+
const targetAid = aidArg ?? selfAid;
|
|
1645
|
+
if (!targetAid) {
|
|
1646
|
+
return { kind: 'command.error', text: '❌ 无法确定目标 agent,请指定 aid:/reload <aid>' };
|
|
1647
|
+
}
|
|
1644
1648
|
const reloadScope = 'agent';
|
|
1645
1649
|
const authDenied = await authorizeIntent({
|
|
1646
1650
|
intent: {
|
|
@@ -1664,10 +1668,6 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1664
1668
|
if (!isDaemonOwner && !isAdmin) {
|
|
1665
1669
|
return { kind: 'command.error', text: '❌ 无权限:/reload 仅限 daemon owner 或 agent owner/admin 使用' };
|
|
1666
1670
|
}
|
|
1667
|
-
const targetAid = aidArg ?? selfAid;
|
|
1668
|
-
if (!targetAid) {
|
|
1669
|
-
return { kind: 'command.error', text: '❌ 无法确定目标 agent,请指定 aid:/reload <aid>' };
|
|
1670
|
-
}
|
|
1671
1671
|
// 繁忙检查(同 menu /agent reload)
|
|
1672
1672
|
const busyInfo = getAgentBusyInfo(this, targetAid);
|
|
1673
1673
|
if (busyInfo && busyInfo.count > 0) {
|