evolcore 0.0.26 → 0.0.27
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 +25 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +63 -26
- package/dist/agents/codex-runner.js +316 -411
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +12 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +522 -233
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +35 -23
- package/dist/cli/diag.js +2 -2
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +9 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +16 -2
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +76 -39
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +348 -77
- package/dist/core/command/menu-protocol.js +40 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +37 -22
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +76 -15
- package/dist/core/message/message-bridge.js +94 -11
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +709 -117
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +61 -29
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +308 -54
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +15 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
|
+
import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
2
3
|
import { parseMenuClientArgs } from '../../cli/menu-client.js';
|
|
4
|
+
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
3
5
|
import { renderCommandCardAsText } from '../interaction-router.js';
|
|
4
6
|
import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
|
|
5
7
|
import { resolvePaths, getPackageRoot } from '../../paths.js';
|
|
@@ -8,6 +10,7 @@ import { logger } from '../../utils/logger.js';
|
|
|
8
10
|
import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
|
|
9
11
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
10
12
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
13
|
+
import { sameGroupAid } from '../../aun/group/index.js';
|
|
11
14
|
import crypto from 'crypto';
|
|
12
15
|
import path from 'path';
|
|
13
16
|
import fs from 'fs';
|
|
@@ -24,7 +27,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
|
|
|
24
27
|
import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
|
|
25
28
|
import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
|
|
26
29
|
import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
|
|
27
|
-
import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
30
|
+
import { executeManagedGroupOperation, isManagedGroupRelationRead, isManagedGroupRelationWrite, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
|
|
28
31
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
29
32
|
import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
|
|
30
33
|
import { validateModelSelectionForRole } from '../model/model-permission.js';
|
|
@@ -99,6 +102,14 @@ export class CommandHandler {
|
|
|
99
102
|
sessionManager;
|
|
100
103
|
messageCache;
|
|
101
104
|
eventBus;
|
|
105
|
+
agentStorageClientResolver;
|
|
106
|
+
groupInviteDecisionResolver;
|
|
107
|
+
setAgentStorageClientResolver(resolver) {
|
|
108
|
+
this.agentStorageClientResolver = resolver;
|
|
109
|
+
}
|
|
110
|
+
setGroupInviteDecisionResolver(resolver) {
|
|
111
|
+
this.groupInviteDecisionResolver = resolver;
|
|
112
|
+
}
|
|
102
113
|
adapters = new Map();
|
|
103
114
|
policies = new Map();
|
|
104
115
|
channelObjects = new Map(); // name → actual channel instance (for /check)
|
|
@@ -208,19 +219,11 @@ export class CommandHandler {
|
|
|
208
219
|
throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
|
|
209
220
|
}
|
|
210
221
|
getAvailableBaseagentsForOwner(evolName) {
|
|
211
|
-
|
|
212
|
-
return [...this.agentMap.keys()]
|
|
213
|
-
.filter(key => key.startsWith(prefix))
|
|
214
|
-
.map(key => key.slice(prefix.length));
|
|
222
|
+
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
215
223
|
}
|
|
216
|
-
/** Return the
|
|
224
|
+
/** Return the stable temporary root for a managed session, if available. */
|
|
217
225
|
getManagedSessionTempDir(session) {
|
|
218
|
-
|
|
219
|
-
return this.getAgent(session.channel, session.baseagent).getManagedTempDir?.(session.id);
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
226
|
+
return ensureSessionRuntimeDir(session.id);
|
|
224
227
|
}
|
|
225
228
|
formatBaseagentUnavailable(error) {
|
|
226
229
|
const available = error.availableBaseagents.length ? error.availableBaseagents.join(', ') : '(none)';
|
|
@@ -236,21 +239,10 @@ export class CommandHandler {
|
|
|
236
239
|
getAvailableBaseagents(channel) {
|
|
237
240
|
const owner = this.agentRegistry?.resolveByChannel(channel);
|
|
238
241
|
if (!owner) {
|
|
239
|
-
|
|
240
|
-
for (const key of this.agentMap.keys()) {
|
|
241
|
-
const idx = key.indexOf('::');
|
|
242
|
-
result.add(idx >= 0 ? key.slice(idx + 2) : key);
|
|
243
|
-
}
|
|
244
|
-
return [...result];
|
|
242
|
+
return loadedBaseagentIds(this.agentMap.keys());
|
|
245
243
|
}
|
|
246
244
|
const evolName = owner.name || '<unknown>';
|
|
247
|
-
|
|
248
|
-
const result = [];
|
|
249
|
-
for (const key of this.agentMap.keys()) {
|
|
250
|
-
if (key.startsWith(prefix))
|
|
251
|
-
result.push(key.slice(prefix.length));
|
|
252
|
-
}
|
|
253
|
-
return result;
|
|
245
|
+
return loadedBaseagentIds(this.agentMap.keys(), evolName);
|
|
254
246
|
}
|
|
255
247
|
/** Extract the baseagent component from `primaryRunnerKey` (e.g. `aid::claude` → `claude`). */
|
|
256
248
|
parseDefaultBaseagent() {
|
|
@@ -441,6 +433,8 @@ export class CommandHandler {
|
|
|
441
433
|
primaryFeedbackTarget(definition) {
|
|
442
434
|
if (definition.feedback.strategy === 'target')
|
|
443
435
|
return definition.feedback.target;
|
|
436
|
+
if (definition.feedback.strategy === 'event_session')
|
|
437
|
+
return undefined;
|
|
444
438
|
if (!definition.origin)
|
|
445
439
|
return undefined;
|
|
446
440
|
return {
|
|
@@ -625,10 +619,16 @@ export class CommandHandler {
|
|
|
625
619
|
if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
|
|
626
620
|
return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
627
621
|
}
|
|
622
|
+
if (!isAdmin && parsed.feedbackStrategy === 'event_session') {
|
|
623
|
+
return { error: '无权限:只有 owner/admin 可以使用事件来源会话' };
|
|
624
|
+
}
|
|
628
625
|
const source = this.triggerSourceFromSchedule(parsed.scheduleType, parsed.scheduleValue);
|
|
629
626
|
if (parsed.timezone && source.type === 'cron') {
|
|
630
627
|
source.timezone = parsed.timezone;
|
|
631
628
|
}
|
|
629
|
+
if (parsed.eventFilter !== undefined && source.type === 'event') {
|
|
630
|
+
source.filter = parsed.eventFilter;
|
|
631
|
+
}
|
|
632
632
|
const script = parsed.scriptPath ? {
|
|
633
633
|
path: parsed.scriptPath,
|
|
634
634
|
runtime: parsed.scriptRuntime,
|
|
@@ -688,6 +688,7 @@ export class CommandHandler {
|
|
|
688
688
|
baseagent: executionBaseagent,
|
|
689
689
|
} : {}),
|
|
690
690
|
model: parsed.model,
|
|
691
|
+
timeoutMs: parsed.executionTimeoutMs,
|
|
691
692
|
effort: parsed.effort,
|
|
692
693
|
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
693
694
|
onError: 'retry',
|
|
@@ -847,23 +848,36 @@ export class CommandHandler {
|
|
|
847
848
|
* 通用降级应答入口:按 (sessionId, fallbackCommand) 查找 pending interaction 并路由。
|
|
848
849
|
* 返回 { matched: true } 表示已处理,调用方直接返回 result。
|
|
849
850
|
*/
|
|
850
|
-
async handleInteractionFallback(command, args, sessionId, userId) {
|
|
851
|
+
async handleInteractionFallback(command, args, sessionId, userId, channelKey) {
|
|
851
852
|
if (!this.interactionRouter)
|
|
852
853
|
return { matched: false };
|
|
853
|
-
const
|
|
854
|
+
const candidates = this.interactionRouter.getPending(sessionId, command);
|
|
855
|
+
const explicitId = args.split(/\s+/, 1)[0];
|
|
856
|
+
if (explicitId.startsWith('aq-') && !candidates.includes(explicitId)) {
|
|
857
|
+
return { matched: true, result: '⚠️ 该问题不存在、已回答或已过期,请使用当前问题卡片。' };
|
|
858
|
+
}
|
|
859
|
+
const pendingId = candidates.includes(explicitId)
|
|
860
|
+
? explicitId : this.interactionRouter.findPendingByCommand(sessionId, command);
|
|
854
861
|
if (!pendingId)
|
|
855
862
|
return { matched: false };
|
|
863
|
+
if (explicitId !== pendingId && candidates.length > 1) {
|
|
864
|
+
return { matched: true, result: `有多个待回答问题,请使用 /ask <问题ID> <回答>:\n${candidates.join('\n')}` };
|
|
865
|
+
}
|
|
866
|
+
const answer = explicitId === pendingId ? args.slice(explicitId.length).trim() : args.trim();
|
|
867
|
+
if (!answer)
|
|
868
|
+
return { matched: true, result: `请使用 /ask ${pendingId} <回答>` };
|
|
856
869
|
const initiatorId = this.interactionRouter.getInitiator(pendingId);
|
|
857
870
|
if (initiatorId && initiatorId !== userId) {
|
|
858
871
|
return { matched: true, result: '⚠️ 仅卡片发起者可应答' };
|
|
859
872
|
}
|
|
860
|
-
const handled = this.interactionRouter.
|
|
873
|
+
const handled = await this.interactionRouter.handleAsync({
|
|
861
874
|
type: 'interaction.response',
|
|
862
875
|
id: pendingId,
|
|
863
|
-
action:
|
|
876
|
+
action: answer,
|
|
864
877
|
operatorId: userId,
|
|
878
|
+
...(channelKey ? { channelKey } : {}),
|
|
865
879
|
});
|
|
866
|
-
return { matched: true, result: handled ? '✓ 已回答' : '⚠️
|
|
880
|
+
return { matched: true, result: handled ? '✓ 已回答' : '⚠️ 回答未被接受,请检查选项或问题是否仍有效。' };
|
|
867
881
|
}
|
|
868
882
|
setProcessor(processor) {
|
|
869
883
|
this.processor = processor;
|
|
@@ -1003,6 +1017,7 @@ export class CommandHandler {
|
|
|
1003
1017
|
const item = {
|
|
1004
1018
|
value: s.threadId,
|
|
1005
1019
|
label: displayName,
|
|
1020
|
+
lable: s.lable || null,
|
|
1006
1021
|
};
|
|
1007
1022
|
if (s.agentSessionId) {
|
|
1008
1023
|
item.agentSessionId = s.agentSessionId;
|
|
@@ -1031,7 +1046,7 @@ export class CommandHandler {
|
|
|
1031
1046
|
...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
|
|
1032
1047
|
});
|
|
1033
1048
|
}
|
|
1034
|
-
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
|
|
1049
|
+
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
|
|
1035
1050
|
validateMenuValueBatchArgs(args);
|
|
1036
1051
|
return await execMenuValueBatch({
|
|
1037
1052
|
subject,
|
|
@@ -1042,12 +1057,12 @@ export class CommandHandler {
|
|
|
1042
1057
|
items: args?.items,
|
|
1043
1058
|
schemaVersion: args?.schemaVersion,
|
|
1044
1059
|
consistency: args?.consistency,
|
|
1045
|
-
execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu'),
|
|
1060
|
+
execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu', resolvedChannelId === channelId ? transportReplyContext : undefined),
|
|
1046
1061
|
});
|
|
1047
1062
|
}
|
|
1048
1063
|
/** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
|
|
1049
|
-
async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
|
|
1050
|
-
return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source);
|
|
1064
|
+
async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
|
|
1065
|
+
return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source, transportReplyContext);
|
|
1051
1066
|
}
|
|
1052
1067
|
// ── Menu Protocol exec ────────────────────────────────────────────────
|
|
1053
1068
|
async loadMenuContext(channel, channelId) {
|
|
@@ -1059,8 +1074,8 @@ export class CommandHandler {
|
|
|
1059
1074
|
return s ? null : { error: '当前无活跃会话', code: 'NO_ACTIVE_SESSION' };
|
|
1060
1075
|
}
|
|
1061
1076
|
/** menu.query — 查询当前值。 */
|
|
1062
|
-
async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu') {
|
|
1063
|
-
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
|
|
1077
|
+
async execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
|
|
1078
|
+
return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source, transportReplyContext);
|
|
1064
1079
|
}
|
|
1065
1080
|
/** menu.update — 写入新值。 */
|
|
1066
1081
|
async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
|
|
@@ -1513,6 +1528,9 @@ export class CommandHandler {
|
|
|
1513
1528
|
updated = deescalated;
|
|
1514
1529
|
}
|
|
1515
1530
|
const target = this.primaryFeedbackTarget(updated);
|
|
1531
|
+
if (!isAdmin && updated.feedback.strategy === 'event_session') {
|
|
1532
|
+
return { ok: false, error: '无权限:只有 owner/admin 可以使用事件来源会话' };
|
|
1533
|
+
}
|
|
1516
1534
|
if (!isAdmin && target && !sameFeedbackTarget(target, definition.origin)) {
|
|
1517
1535
|
return { ok: false, error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
1518
1536
|
}
|
|
@@ -2181,7 +2199,8 @@ export class CommandHandler {
|
|
|
2181
2199
|
const managementRole = isManagementRole(subject.role) || subject.dataScope === 'daemon';
|
|
2182
2200
|
if (subject.dataScope !== 'daemon'
|
|
2183
2201
|
&& !managementRole
|
|
2184
|
-
&& (grant.channelType !== 'aun'
|
|
2202
|
+
&& (grant.channelType !== 'aun'
|
|
2203
|
+
|| !(isManagedGroupRelationRead(parsed.command) || isManagedGroupRelationWrite(parsed.command)))) {
|
|
2185
2204
|
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
|
|
2186
2205
|
}
|
|
2187
2206
|
if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner')
|
|
@@ -2213,6 +2232,21 @@ export class CommandHandler {
|
|
|
2213
2232
|
});
|
|
2214
2233
|
if (!decision.allow)
|
|
2215
2234
|
return { ok: false, code: decision.code, error: decision.reason };
|
|
2235
|
+
if (parsed.command.kind === 'accept-invite' || parsed.command.kind === 'reject-invite') {
|
|
2236
|
+
if (subject.role !== 'owner' || grant.channelType !== 'aun' || grant.chatType !== 'private'
|
|
2237
|
+
|| parsed.command.from !== grantSelfAid || !this.groupInviteDecisionResolver) {
|
|
2238
|
+
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: 'Targeted invitation decisions require the current Agent Owner' };
|
|
2239
|
+
}
|
|
2240
|
+
try {
|
|
2241
|
+
const result = await this.groupInviteDecisionResolver(grantSelfAid, grant.actorId, parsed.command.inviteId, parsed.command.kind === 'accept-invite' ? 'accept' : 'reject');
|
|
2242
|
+
return result.accepted
|
|
2243
|
+
? { ok: true, result: { ok: true, status: result.status, group_id: result.groupId, message: result.text } }
|
|
2244
|
+
: { ok: false, code: 'GROUP_INVITE_DECISION_REJECTED', error: result.text };
|
|
2245
|
+
}
|
|
2246
|
+
catch (error) {
|
|
2247
|
+
return { ok: false, code: 'GROUP_INVITE_DECISION_FAILED', error: error instanceof Error ? error.message : String(error) };
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2216
2250
|
let rulesContent;
|
|
2217
2251
|
if (parsed.command.kind === 'rules.set') {
|
|
2218
2252
|
const filePath = String(parsed.command.filePath || '');
|
|
@@ -2267,7 +2301,10 @@ export class CommandHandler {
|
|
|
2267
2301
|
}
|
|
2268
2302
|
}
|
|
2269
2303
|
try {
|
|
2270
|
-
return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent
|
|
2304
|
+
return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent, {
|
|
2305
|
+
defaultThread: grant.chatType === 'group' && conversationId === parsed.command.groupId
|
|
2306
|
+
? session.threadId || 'main' : 'all',
|
|
2307
|
+
}) };
|
|
2271
2308
|
}
|
|
2272
2309
|
catch (error) {
|
|
2273
2310
|
return {
|
|
@@ -2464,7 +2501,7 @@ export class CommandHandler {
|
|
|
2464
2501
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
|
|
2465
2502
|
}
|
|
2466
2503
|
if (!hasTrustedFullAccessDelegation(grant)
|
|
2467
|
-
&& ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId
|
|
2504
|
+
&& ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || !sameGroupAid(parsed.command.groupId, currentGroup))) {
|
|
2468
2505
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
|
|
2469
2506
|
}
|
|
2470
2507
|
parsed.command.actorAid = hasTrustedFullAccessDelegation(grant)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { isValidAid } from '../../aun/aid/validation.js';
|
|
5
|
+
import { isExplicitGroupId } from '../../aun/group/index.js';
|
|
6
|
+
import { isStaticAgentOwner } from '../../config/peer-role-resolver.js';
|
|
7
|
+
import { agentDir, agentRelationSharedDir } from '../../paths.js';
|
|
8
|
+
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
9
|
+
export class DataRootError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
data;
|
|
12
|
+
constructor(code, message, data) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'DataRootError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.data = data;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_ROOT_TTL_MS = 10 * 60 * 1000;
|
|
20
|
+
const TOKEN_PREFIX = 'dr1';
|
|
21
|
+
function base64Url(value) {
|
|
22
|
+
return Buffer.from(value).toString('base64url');
|
|
23
|
+
}
|
|
24
|
+
function isInside(root, candidate) {
|
|
25
|
+
const relative = path.relative(root, candidate);
|
|
26
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
27
|
+
}
|
|
28
|
+
function authenticatedPrincipal(subject) {
|
|
29
|
+
const principalId = String(subject.principalId ?? '').trim();
|
|
30
|
+
if (!isValidAid(principalId) || isExplicitGroupId(principalId)) {
|
|
31
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'A verified private AUN principal is required');
|
|
32
|
+
}
|
|
33
|
+
return principalId;
|
|
34
|
+
}
|
|
35
|
+
export class DataRootResolver {
|
|
36
|
+
secret;
|
|
37
|
+
ttlMs;
|
|
38
|
+
now;
|
|
39
|
+
isOwner;
|
|
40
|
+
resolveAgentPath;
|
|
41
|
+
resolveRelationSharedPath;
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.secret = options.secret ?? crypto.randomBytes(32);
|
|
44
|
+
if (this.secret.length < 32)
|
|
45
|
+
throw new Error('Data root signing secret must be at least 32 bytes');
|
|
46
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_ROOT_TTL_MS;
|
|
47
|
+
this.now = options.now ?? (() => Date.now());
|
|
48
|
+
this.isOwner = options.isOwner ?? isStaticAgentOwner;
|
|
49
|
+
this.resolveAgentPath = options.resolveAgentPath ?? agentDir;
|
|
50
|
+
this.resolveRelationSharedPath = options.resolveRelationSharedPath ?? agentRelationSharedDir;
|
|
51
|
+
}
|
|
52
|
+
issueAgentRoot(subject) {
|
|
53
|
+
const targetAid = this.targetAid(subject);
|
|
54
|
+
const principalId = authenticatedPrincipal(subject);
|
|
55
|
+
if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
|
|
56
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
|
|
57
|
+
}
|
|
58
|
+
const rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
59
|
+
return this.issueDescriptor({ scope: 'agent', targetAid, principalId }, rootPath);
|
|
60
|
+
}
|
|
61
|
+
issueRelationRoot(subject, requestedPeerAid) {
|
|
62
|
+
const targetAid = this.targetAid(subject);
|
|
63
|
+
this.assertPrivateAun(subject);
|
|
64
|
+
const principalId = authenticatedPrincipal(subject);
|
|
65
|
+
const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
|
|
66
|
+
const explicitPeerAid = typeof requestedPeerAid === 'string' ? requestedPeerAid.trim() : '';
|
|
67
|
+
if (requestedPeerAid !== undefined && !explicitPeerAid) {
|
|
68
|
+
throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must be a non-empty AUN AID');
|
|
69
|
+
}
|
|
70
|
+
if (explicitPeerAid && !owner) {
|
|
71
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Only the Agent owner may select another relation');
|
|
72
|
+
}
|
|
73
|
+
const peerAid = explicitPeerAid || principalId;
|
|
74
|
+
if (!isValidAid(peerAid) || isExplicitGroupId(peerAid)) {
|
|
75
|
+
throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must identify an AUN private-chat peer');
|
|
76
|
+
}
|
|
77
|
+
const peerKey = formatPeerKey('aun', peerAid);
|
|
78
|
+
const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
79
|
+
const rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, peerKey), agentRoot);
|
|
80
|
+
return this.issueDescriptor({ scope: 'relation', targetAid, principalId, peerAid, peerKey }, rootPath);
|
|
81
|
+
}
|
|
82
|
+
resolveRoot(rootId, subject) {
|
|
83
|
+
const claims = this.verifyToken(rootId);
|
|
84
|
+
const targetAid = this.targetAid(subject);
|
|
85
|
+
const principalId = authenticatedPrincipal(subject);
|
|
86
|
+
if (claims.targetAid !== targetAid) {
|
|
87
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root does not belong to the receiving Agent');
|
|
88
|
+
}
|
|
89
|
+
if (claims.principalId !== principalId) {
|
|
90
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Root was issued to another authenticated principal');
|
|
91
|
+
}
|
|
92
|
+
let rootPath;
|
|
93
|
+
if (claims.scope === 'agent') {
|
|
94
|
+
if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
|
|
95
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
|
|
96
|
+
}
|
|
97
|
+
rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.assertPrivateAun(subject);
|
|
101
|
+
if (!claims.peerAid || !claims.peerKey || claims.peerKey !== formatPeerKey('aun', claims.peerAid)) {
|
|
102
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Relation root claims are invalid');
|
|
103
|
+
}
|
|
104
|
+
const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
|
|
105
|
+
if (!owner && principalId !== claims.peerAid) {
|
|
106
|
+
throw new DataRootError('ROOT_PEER_MISMATCH', 'Only the relation peer may use this root');
|
|
107
|
+
}
|
|
108
|
+
const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
|
|
109
|
+
rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, claims.peerKey), agentRoot);
|
|
110
|
+
}
|
|
111
|
+
const revision = this.rootRevision(rootPath);
|
|
112
|
+
if (revision !== claims.revision) {
|
|
113
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root identity changed; request a new root');
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
scope: claims.scope,
|
|
117
|
+
targetAid,
|
|
118
|
+
...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
|
|
119
|
+
...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
|
|
120
|
+
rootPath,
|
|
121
|
+
revision,
|
|
122
|
+
expiresAt: claims.expiresAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
resolvePath(rootId, subject, input, expectType) {
|
|
126
|
+
const root = this.resolveRoot(rootId, subject);
|
|
127
|
+
const raw = String(input ?? '').trim();
|
|
128
|
+
if (!raw)
|
|
129
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'A root-relative path is required');
|
|
130
|
+
if (raw.includes('\0'))
|
|
131
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'NUL is not allowed in a path');
|
|
132
|
+
if (path.isAbsolute(raw) || path.win32.isAbsolute(raw) || path.posix.isAbsolute(raw)) {
|
|
133
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Data root paths must be relative');
|
|
134
|
+
}
|
|
135
|
+
if (raw.split(/[\\/]+/).includes('..')) {
|
|
136
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Parent traversal is not allowed');
|
|
137
|
+
}
|
|
138
|
+
const lexicalPath = path.resolve(root.rootPath, raw);
|
|
139
|
+
if (!isInside(root.rootPath, lexicalPath)) {
|
|
140
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Path escapes the data root');
|
|
141
|
+
}
|
|
142
|
+
let realPath;
|
|
143
|
+
let stat;
|
|
144
|
+
try {
|
|
145
|
+
realPath = fs.realpathSync(lexicalPath);
|
|
146
|
+
stat = fs.statSync(realPath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new DataRootError('NOT_FOUND', 'File or directory does not exist');
|
|
150
|
+
}
|
|
151
|
+
if (!isInside(root.rootPath, realPath)) {
|
|
152
|
+
throw new DataRootError('INVALID_ROOT_PATH', 'Symbolic link escapes the data root');
|
|
153
|
+
}
|
|
154
|
+
if (expectType === 'file' && !stat.isFile()) {
|
|
155
|
+
throw new DataRootError('NOT_SUPPORTED', 'Path is not a regular file');
|
|
156
|
+
}
|
|
157
|
+
if (expectType === 'directory' && !stat.isDirectory()) {
|
|
158
|
+
throw new DataRootError('NOT_A_DIRECTORY', 'Path is not a directory');
|
|
159
|
+
}
|
|
160
|
+
const relativePath = path.relative(root.rootPath, realPath).split(path.sep).join('/') || '.';
|
|
161
|
+
return { ...root, realPath, relativePath, stat };
|
|
162
|
+
}
|
|
163
|
+
issueDescriptor(base, rootPath) {
|
|
164
|
+
const issuedAt = this.now();
|
|
165
|
+
const claims = {
|
|
166
|
+
v: 1,
|
|
167
|
+
...base,
|
|
168
|
+
revision: this.rootRevision(rootPath),
|
|
169
|
+
issuedAt,
|
|
170
|
+
expiresAt: issuedAt + this.ttlMs,
|
|
171
|
+
nonce: crypto.randomBytes(12).toString('base64url'),
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
rootId: this.sign(claims),
|
|
175
|
+
scope: claims.scope,
|
|
176
|
+
targetAid: claims.targetAid,
|
|
177
|
+
...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
|
|
178
|
+
...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
|
|
179
|
+
virtualPath: claims.scope === 'agent'
|
|
180
|
+
? '$AGENTS_DIR/<targetAid>'
|
|
181
|
+
: '$AGENTS_DIR/<targetAid>/relations/<peerKey>/shared',
|
|
182
|
+
revision: `sha256:${claims.revision}`,
|
|
183
|
+
expiresAt: claims.expiresAt,
|
|
184
|
+
readOnly: true,
|
|
185
|
+
capabilities: { list: true, query: true, fetch: true },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
sign(claims) {
|
|
189
|
+
const encoded = base64Url(JSON.stringify(claims));
|
|
190
|
+
const signature = crypto.createHmac('sha256', this.secret).update(encoded).digest('base64url');
|
|
191
|
+
return `${TOKEN_PREFIX}.${encoded}.${signature}`;
|
|
192
|
+
}
|
|
193
|
+
verifyToken(value) {
|
|
194
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
195
|
+
throw new DataRootError('MISSING_ROOT_ID', 'A data rootId is required');
|
|
196
|
+
}
|
|
197
|
+
const parts = value.split('.');
|
|
198
|
+
if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) {
|
|
199
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
|
|
200
|
+
}
|
|
201
|
+
const expected = crypto.createHmac('sha256', this.secret).update(parts[1]).digest();
|
|
202
|
+
let actual;
|
|
203
|
+
try {
|
|
204
|
+
actual = Buffer.from(parts[2], 'base64url');
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
|
|
208
|
+
}
|
|
209
|
+
if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
|
|
210
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId signature');
|
|
211
|
+
}
|
|
212
|
+
let claims;
|
|
213
|
+
try {
|
|
214
|
+
claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId payload');
|
|
218
|
+
}
|
|
219
|
+
if (claims.v !== 1
|
|
220
|
+
|| (claims.scope !== 'agent' && claims.scope !== 'relation')
|
|
221
|
+
|| typeof claims.targetAid !== 'string'
|
|
222
|
+
|| typeof claims.principalId !== 'string'
|
|
223
|
+
|| typeof claims.revision !== 'string'
|
|
224
|
+
|| !Number.isFinite(claims.expiresAt)) {
|
|
225
|
+
throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId claims');
|
|
226
|
+
}
|
|
227
|
+
if (claims.expiresAt <= this.now()) {
|
|
228
|
+
throw new DataRootError('ROOT_EXPIRED', 'Data rootId expired; query the root again');
|
|
229
|
+
}
|
|
230
|
+
return claims;
|
|
231
|
+
}
|
|
232
|
+
canonicalRoot(candidate, requiredParent) {
|
|
233
|
+
let realPath;
|
|
234
|
+
let stat;
|
|
235
|
+
try {
|
|
236
|
+
realPath = fs.realpathSync(candidate);
|
|
237
|
+
stat = fs.statSync(realPath);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new DataRootError('ROOT_NOT_FOUND', 'Data root does not exist');
|
|
241
|
+
}
|
|
242
|
+
if (!stat.isDirectory())
|
|
243
|
+
throw new DataRootError('ROOT_NOT_FOUND', 'Data root is not a directory');
|
|
244
|
+
if (requiredParent && !isInside(requiredParent, realPath)) {
|
|
245
|
+
throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Data root escapes the Agent directory');
|
|
246
|
+
}
|
|
247
|
+
return realPath;
|
|
248
|
+
}
|
|
249
|
+
rootRevision(rootPath) {
|
|
250
|
+
const stat = fs.statSync(rootPath);
|
|
251
|
+
return crypto.createHash('sha256')
|
|
252
|
+
.update(JSON.stringify({ rootPath, dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }))
|
|
253
|
+
.digest('hex');
|
|
254
|
+
}
|
|
255
|
+
targetAid(subject) {
|
|
256
|
+
const targetAid = String(subject.selfAid ?? '').trim();
|
|
257
|
+
if (!isValidAid(targetAid))
|
|
258
|
+
throw new DataRootError('ROOT_FORBIDDEN', 'Receiving Agent AID is unavailable');
|
|
259
|
+
return targetAid;
|
|
260
|
+
}
|
|
261
|
+
assertPrivateAun(subject) {
|
|
262
|
+
if (subject.channelType !== 'aun' || subject.chatType !== 'private') {
|
|
263
|
+
throw new DataRootError('NOT_SUPPORTED', 'Relation data roots currently support AUN private chats only');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export const dataRootResolver = new DataRootResolver();
|
|
268
|
+
export function dataRootFailure(error) {
|
|
269
|
+
if (error instanceof DataRootError) {
|
|
270
|
+
return { error: error.message, code: error.code, ...(error.data ? { data: error.data } : {}) };
|
|
271
|
+
}
|
|
272
|
+
return { error: 'Internal data root error', code: 'INTERNAL_ERROR' };
|
|
273
|
+
}
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import { ConfigTarget, mutateConfig, read as readConfig, resolveEffectiveFieldWithSource, } from '../../config/config-manager.js';
|
|
3
3
|
import { normalizeMentionMode } from '../../config/mention-mode.js';
|
|
4
4
|
import { readRolesConfig } from '../../config/roles.js';
|
|
5
|
-
import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/
|
|
5
|
+
import { groupInfo, groupList, groupDirectoryErrorCode, normalizeGroupAid } from '../../aun/group/index.js';
|
|
6
6
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
7
7
|
const DEFAULT_DIRECTORY = {
|
|
8
8
|
getGroup: groupInfo,
|
|
@@ -185,7 +185,12 @@ function groupIdFromArgs(args, currentGroupId) {
|
|
|
185
185
|
if (!groupId) {
|
|
186
186
|
throw new GroupMenuError('INVALID_ARGUMENT', 'relation settings require args.groupId or args.peerKey');
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
try {
|
|
189
|
+
return normalizeGroupAid(groupId);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw new GroupMenuError('INVALID_ARGUMENT', error.message);
|
|
193
|
+
}
|
|
189
194
|
}
|
|
190
195
|
function settingOptions(key, target) {
|
|
191
196
|
const state = readSettingState(key, target);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { groupInfo, groupMembers } from '../../aun/
|
|
2
|
+
import { groupInfo, groupMembers } from '../../aun/group/index.js';
|
|
3
3
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
4
4
|
import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
5
5
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
@@ -8,6 +8,7 @@ import { readRolesConfig } from '../../config/roles.js';
|
|
|
8
8
|
import { normalizeMenuError } from './menu-protocol.js';
|
|
9
9
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
10
10
|
const DEFAULT_DIRECTORY = { getGroup: groupInfo, getMembers: groupMembers };
|
|
11
|
+
const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
|
|
11
12
|
export function validateMenuValueBatchArgs(args) {
|
|
12
13
|
if (!args || typeof args !== 'object' || Array.isArray(args))
|
|
13
14
|
throw { code: 'INVALID_ARGUMENT', message: 'batch args must be an object' };
|
|
@@ -80,11 +81,11 @@ const ENTRIES = [
|
|
|
80
81
|
{ id: 'activity.agent', type: 'setting', contexts: ['agent'], name: 'activity', label: 'Agent 中间输出', section: 'agent', order: 50, query: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, options: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, update: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.update', scope: 'agent' }, control: 'select' },
|
|
81
82
|
{ id: 'observable.agent', type: 'setting', contexts: ['agent'], name: 'observable', label: '观察模式', section: 'agent', order: 60, query: { cmd: '/observable', operation: 'observable.current', scope: 'agent' }, update: { cmd: '/observable', operation: 'observable.update', scope: 'agent' }, valueSchema: { type: 'boolean' }, control: 'toggle' },
|
|
82
83
|
{ id: 'aid.avatar', type: 'action', contexts: ['agent'], name: 'aid', label: '更换头像', section: 'agent', order: 70, action: { cmd: '/aid', action: 'avatar', operation: 'ec.aid.avatar', scope: 'agent' }, valueSchema: { type: 'object', properties: { data: { type: 'string' }, url: { type: 'string', format: 'uri' } }, oneOf: [{ required: ['data'] }, { required: ['url'] }] }, control: 'action' },
|
|
84
|
+
{ id: 'baseagents.inventory', type: 'list', contexts: ['process'], name: 'baseagents', label: 'Base Agent 清单', section: 'process', order: 9, query: { cmd: '/baseagents', operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
83
85
|
{ id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
84
86
|
{ id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
|
|
85
87
|
{ id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
|
|
86
88
|
{ id: 'config.rawFile', type: 'list', contexts: ['process'], name: 'config', label: '原始配置文件', section: 'process', order: 13, query: { cmd: '/config', args: { view: 'rawFile' }, operation: 'config.read', scope: 'process' }, options: { cmd: '/config', args: { option: 'rawFile' }, operation: 'config.read', scope: 'process', mode: 'dynamic' }, control: 'list' },
|
|
87
|
-
{ id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
|
|
88
89
|
{ id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
|
|
89
90
|
{ id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
|
|
90
91
|
{ id: 'system.paircode', type: 'action', contexts: ['process'], name: 'system', label: '获取配对码', section: 'process', order: 35, action: { cmd: '/system', action: 'paircode', operation: 'system.paircode', scope: 'process' } },
|
|
@@ -304,6 +305,34 @@ export async function resolveExplicitMenuExecutionContext(input) {
|
|
|
304
305
|
};
|
|
305
306
|
}
|
|
306
307
|
function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
|
|
308
|
+
function configuredHostBaseagentEntries() {
|
|
309
|
+
const defaults = readConfig(ConfigTarget.Defaults);
|
|
310
|
+
const configured = defaults?.baseagents;
|
|
311
|
+
if (!configured || typeof configured !== 'object' || Array.isArray(configured))
|
|
312
|
+
return [];
|
|
313
|
+
return HOST_BASEAGENTS
|
|
314
|
+
.filter(baseagent => {
|
|
315
|
+
const block = configured[baseagent];
|
|
316
|
+
return Object.hasOwn(configured, baseagent) && block !== null
|
|
317
|
+
&& typeof block === 'object' && !Array.isArray(block);
|
|
318
|
+
})
|
|
319
|
+
.map((baseagent, index) => ({
|
|
320
|
+
// Value batches only carry entry IDs, so each configured backend gets a
|
|
321
|
+
// binding with an explicit backend instead of an implicit Claude default.
|
|
322
|
+
id: `baseagent.models.${baseagent}`,
|
|
323
|
+
type: 'list',
|
|
324
|
+
contexts: ['process'],
|
|
325
|
+
name: 'baseagent-models',
|
|
326
|
+
label: `${baseagent} 模型目录`,
|
|
327
|
+
section: 'process',
|
|
328
|
+
order: 41 + index,
|
|
329
|
+
query: { cmd: '/baseagent-models', args: { baseagent }, operation: 'baseagent.models.list', scope: 'process' },
|
|
330
|
+
control: 'list',
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
function menuEntryDefinitions(context) {
|
|
334
|
+
return context.kind === 'process' ? [...ENTRIES, ...configuredHostBaseagentEntries()] : ENTRIES;
|
|
335
|
+
}
|
|
307
336
|
function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
|
|
308
337
|
async function operationView(binding, context, options = {}) {
|
|
309
338
|
if (!binding)
|
|
@@ -340,7 +369,8 @@ export async function getMenuCatalog(input) {
|
|
|
340
369
|
const context = await resolveMenuCatalogContext(input);
|
|
341
370
|
const entries = [];
|
|
342
371
|
const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
|
|
343
|
-
|
|
372
|
+
const definitions = menuEntryDefinitions(context);
|
|
373
|
+
for (const entry of definitions.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
|
|
344
374
|
const operations = {};
|
|
345
375
|
for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
|
|
346
376
|
const view = await operationView(binding, context, { paircodeAllowed });
|
|
@@ -378,7 +408,7 @@ export async function execMenuValueBatch(input) {
|
|
|
378
408
|
normalizedItems.push({ itemId, entryId });
|
|
379
409
|
}
|
|
380
410
|
const results = new Array(normalizedItems.length);
|
|
381
|
-
const byId = new Map(
|
|
411
|
+
const byId = new Map(menuEntryDefinitions(context).map(entry => [entry.id, entry]));
|
|
382
412
|
let cursor = 0;
|
|
383
413
|
const resolveItem = async (item) => {
|
|
384
414
|
const { itemId, entryId } = item;
|