evolcore 0.0.32 → 0.0.33
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 +24 -0
- package/bin/install-codex-managed-hooks.mjs +124 -37
- package/dist/agents/claude-runner.js +29 -2
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +76 -44
- package/dist/agents/codex-runner.js +23 -19
- package/dist/agents/ecagent-runner.js +5 -13
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +28 -2
- package/dist/channels/daemon.js +257 -2
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +365 -29
- package/dist/cli/contact.js +8 -2
- package/dist/cli/daemon-commands.js +81 -12
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +81 -64
- package/dist/cli/init.js +2 -2
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +8 -1
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +620 -0
- package/dist/cli/trigger-command.js +4 -2
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +2 -3
- package/dist/config/config-manager.js +7 -2
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +15 -0
- package/dist/core/auth/agent-delegation.js +25 -1
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/operation-authorizer.js +24 -1
- package/dist/core/auth/operation-catalog.js +33 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/command-handler.js +60 -24
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +48 -1
- package/dist/core/command/menu-handler.js +92 -16
- package/dist/core/command/menu-protocol.js +24 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +193 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +139 -2
- package/dist/core/memory/consolidation-context.js +49 -16
- package/dist/core/memory/consolidation-ops-records.js +35 -0
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +174 -180
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +149 -6
- package/dist/core/message/message-bridge.js +13 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +349 -67
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +126 -7
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +54 -9
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +4 -1
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +777 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +8 -5
- package/dist/ipc.js +89 -10
- package/dist/paths.js +23 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +7 -1
- package/dist/trigger/parser.js +63 -1
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/error-utils.js +22 -1
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +39 -28
- package/kits/docs/evolcore/agent.md +19 -4
- package/kits/docs/evolcore/ec-capabilities.md +4 -0
- package/kits/docs/evolcore/group-collaboration.md +3 -5
- package/kits/docs/evolcore/group.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +1 -1
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/msg.md +5 -8
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/eck_manifest.json +61 -26
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/07-memory-and-thread.md +202 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +5 -0
- package/kits/templates/memory-consolidation/generalize.md +5 -0
- package/kits/templates/memory-consolidation/profile.md +5 -0
- package/kits/templates/memory-consolidation/promote.md +5 -0
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +66 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/reply.md +3 -2
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +3 -1
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -45,6 +45,7 @@ import { handleSlashCommand } from './slash-handler.js';
|
|
|
45
45
|
import { resolveChatMode } from '../message/peer-mode.js';
|
|
46
46
|
import { execMenuAction as menuExecMenuAction, execMenuForEcweb as menuExecMenuForEcweb, execMenuForControl as menuExecMenuForControl, execMenuQuery as menuExecMenuQuery, execMenuUpdate as menuExecMenuUpdate, getMenuItems as menuGetMenuItems, getSubMenuItems as menuGetSubMenuItems, buildTopicForkSourcePayload, } from './menu-handler.js';
|
|
47
47
|
import { getMenuCatalog, execMenuValueBatch, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
48
|
+
import { resolveCurrentSessionLoopTarget } from '../session/session-idle-monitor.js';
|
|
48
49
|
export { isProcessLevelOwner } from './menu-handler.js';
|
|
49
50
|
const CLI_EXEC_TIMEOUT_MS = 15_000;
|
|
50
51
|
const CLI_EXEC_MAX_OUTPUT = 128 * 1024;
|
|
@@ -130,6 +131,8 @@ export class CommandHandler {
|
|
|
130
131
|
primaryRunnerKey;
|
|
131
132
|
agentRegistry;
|
|
132
133
|
memoryConsolidationLauncher;
|
|
134
|
+
/** /skill refresh 的 daemon 侧入口:重建指定 agent 的技能索引 */
|
|
135
|
+
skillIndexRefresher;
|
|
133
136
|
agentApplicationService;
|
|
134
137
|
triggerSchedulerResolver;
|
|
135
138
|
triggerSchedulerFallback;
|
|
@@ -145,9 +148,10 @@ export class CommandHandler {
|
|
|
145
148
|
diagService;
|
|
146
149
|
setDiagService(service) { this.diagService = service; }
|
|
147
150
|
async handleMenuClientOperation(argv, sessionId, delegationToken, delegationCommandHash, send) {
|
|
148
|
-
const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash)
|
|
152
|
+
?? { ok: false, code: 'DELEGATION_REQUIRED', reason: 'Task delegation registry is not configured', diagnosticCause: 'missing_token' };
|
|
153
|
+
if (!validation.ok)
|
|
154
|
+
return { ok: false, code: validation.code ?? 'DELEGATION_REQUIRED', error: validation.reason ?? 'Task delegation is required' };
|
|
151
155
|
const grant = validation.grant;
|
|
152
156
|
const session = await this.sessionManager.getSessionById(sessionId);
|
|
153
157
|
if (!session || session.selfAID !== grant.selfAid || argv[0] !== 'request' || argv[1] !== grant.selfAid)
|
|
@@ -541,17 +545,21 @@ export class CommandHandler {
|
|
|
541
545
|
reason: input.reason,
|
|
542
546
|
});
|
|
543
547
|
}
|
|
544
|
-
triggerRoleContext(selfAid, channel, peerId, chatType) {
|
|
548
|
+
triggerRoleContext(selfAid, channel, peerId, chatType, conversationId) {
|
|
545
549
|
const owning = this.getOwningAgent(channel);
|
|
546
550
|
if (!owning || owning.aid !== selfAid)
|
|
547
551
|
return {};
|
|
552
|
+
const resolvedChatType = chatType === 'group' ? 'group' : 'private';
|
|
553
|
+
const resolvedConversationId = resolvedChatType === 'group'
|
|
554
|
+
? conversationId || peerId
|
|
555
|
+
: peerId;
|
|
548
556
|
const role = resolvePeerRoleDetail({
|
|
549
557
|
selfAid,
|
|
550
558
|
channelKey: channel,
|
|
551
559
|
channelType: this.resolveChannelType(channel),
|
|
552
|
-
chatType:
|
|
560
|
+
chatType: resolvedChatType,
|
|
553
561
|
actorId: peerId,
|
|
554
|
-
conversationId:
|
|
562
|
+
conversationId: resolvedConversationId,
|
|
555
563
|
}).effectiveRole;
|
|
556
564
|
return { role: role || 'none', baseagent: owning.baseagent };
|
|
557
565
|
}
|
|
@@ -596,12 +604,18 @@ export class CommandHandler {
|
|
|
596
604
|
const now = Date.now();
|
|
597
605
|
const id = `trig_${now}_${crypto.randomBytes(4).toString('hex')}`;
|
|
598
606
|
const name = parsed.name ?? `trigger-${now.toString(36)}`;
|
|
607
|
+
const originChatType = chatType === 'group' ? 'group' : 'private';
|
|
608
|
+
const originIsGroup = originChatType === 'group';
|
|
609
|
+
const originConversationId = originIsGroup ? channelId : peerId;
|
|
599
610
|
const feedbackChannel = parsed.targetChannel ?? channel;
|
|
611
|
+
const hasExplicitTarget = parsed.targetChannel !== undefined
|
|
612
|
+
|| parsed.targetChannelId !== undefined
|
|
613
|
+
|| parsed.targetChatType !== undefined;
|
|
600
614
|
if (parsed.targetChannel && !this.adapters.has(parsed.targetChannel)) {
|
|
601
615
|
return { error: `目标渠道不存在或未启用:${parsed.targetChannel}` };
|
|
602
616
|
}
|
|
603
617
|
const targetChannelType = this.resolveChannelType(feedbackChannel);
|
|
604
|
-
const feedbackChannelId = parsed.targetChannelId ??
|
|
618
|
+
const feedbackChannelId = parsed.targetChannelId ?? originConversationId;
|
|
605
619
|
const schedulerAid = parsed.agentId
|
|
606
620
|
?? this.getOwningAgent(feedbackChannel)?.aid
|
|
607
621
|
?? tryParseChannelKey(feedbackChannel)?.selfAID
|
|
@@ -613,19 +627,12 @@ export class CommandHandler {
|
|
|
613
627
|
if (targetChannelType === 'aun' && parsed.targetChannelId && !parsed.targetChatType) {
|
|
614
628
|
return { error: 'AUN 目标必须显式指定 --target-chat-type private 或 group' };
|
|
615
629
|
}
|
|
616
|
-
const targetDelivery = targetChannelType === 'aun'
|
|
617
|
-
? parsed.targetChatType === 'group'
|
|
618
|
-
? { chatType: 'group', groupId: feedbackChannelId }
|
|
619
|
-
: parsed.targetChatType === 'private'
|
|
620
|
-
? { chatType: 'private' }
|
|
621
|
-
: { chatType: 'private' }
|
|
622
|
-
: undefined;
|
|
623
630
|
const target = {
|
|
624
631
|
channelKey: feedbackChannel,
|
|
625
632
|
channelId: feedbackChannelId,
|
|
626
633
|
session: parsed.targetSession,
|
|
627
634
|
threadId: parsed.targetSession === 'thread' ? parsed.targetThreadId : undefined,
|
|
628
|
-
chatType:
|
|
635
|
+
chatType: parsed.targetChatType ?? (hasExplicitTarget ? undefined : originChatType),
|
|
629
636
|
};
|
|
630
637
|
if (target.session === 'thread') {
|
|
631
638
|
const adapter = this.adapters.get(feedbackChannel);
|
|
@@ -634,19 +641,20 @@ export class CommandHandler {
|
|
|
634
641
|
if (!target.threadId)
|
|
635
642
|
return { error: '--target-session thread 需要 --target-thread-id' };
|
|
636
643
|
}
|
|
637
|
-
const originIsGroup = chatType === 'group';
|
|
638
644
|
const activeSession = originIsGroup
|
|
639
645
|
? undefined
|
|
640
646
|
: this.sessionManager.getActiveSessionSync(channel, peerId, this.resolveChannelType(channel), schedulerAid);
|
|
641
647
|
const origin = {
|
|
642
648
|
channelKey: channel,
|
|
643
649
|
channelType: this.resolveChannelType(channel),
|
|
644
|
-
channelId:
|
|
645
|
-
session:
|
|
646
|
-
threadId
|
|
650
|
+
channelId: originConversationId,
|
|
651
|
+
session: threadId ? 'thread' : 'main',
|
|
652
|
+
threadId,
|
|
647
653
|
peerId,
|
|
648
654
|
sessionKey: activeSession?.sessionKey,
|
|
649
|
-
delivery:
|
|
655
|
+
delivery: originIsGroup
|
|
656
|
+
? { chatType: 'group', groupId: originConversationId }
|
|
657
|
+
: { chatType: 'private' },
|
|
650
658
|
};
|
|
651
659
|
if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
|
|
652
660
|
return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
@@ -668,7 +676,7 @@ export class CommandHandler {
|
|
|
668
676
|
timeoutMs: parsed.scriptTimeoutMs ?? 30_000,
|
|
669
677
|
} : undefined;
|
|
670
678
|
const permissionMode = this.triggerPermissionFromParsed(parsed);
|
|
671
|
-
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId,
|
|
679
|
+
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, originChatType, originConversationId);
|
|
672
680
|
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
673
681
|
if (permissionError)
|
|
674
682
|
return { error: permissionError };
|
|
@@ -933,10 +941,21 @@ export class CommandHandler {
|
|
|
933
941
|
setStatsCollector(collector) {
|
|
934
942
|
this.statsCollector = collector;
|
|
935
943
|
}
|
|
936
|
-
/**
|
|
944
|
+
/**
|
|
945
|
+
* 注入记忆整理入口(daemon 的统一整理队列),供 /memory consolidate 使用。
|
|
946
|
+
* 已入队时返回带 queued 的占位结果;返回 null 表示拉起失败。
|
|
947
|
+
*/
|
|
937
948
|
setMemoryConsolidationLauncher(launcher) {
|
|
938
949
|
this.memoryConsolidationLauncher = launcher;
|
|
939
950
|
}
|
|
951
|
+
/** 注入 /skill refresh 的索引重建入口(daemon 启动时调用)。 */
|
|
952
|
+
setSkillIndexRefresher(refresher) {
|
|
953
|
+
this.skillIndexRefresher = refresher;
|
|
954
|
+
}
|
|
955
|
+
/** 供 slash-handler 读取。 */
|
|
956
|
+
getSkillIndexRefresher() {
|
|
957
|
+
return this.skillIndexRefresher;
|
|
958
|
+
}
|
|
940
959
|
registerAdapter(adapter) {
|
|
941
960
|
this.adapters.set(adapter.channelName, adapter);
|
|
942
961
|
}
|
|
@@ -1076,7 +1095,7 @@ export class CommandHandler {
|
|
|
1076
1095
|
getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
|
|
1077
1096
|
return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
|
|
1078
1097
|
}
|
|
1079
|
-
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed) {
|
|
1098
|
+
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed, transportReplyContext) {
|
|
1080
1099
|
return await getMenuCatalog({
|
|
1081
1100
|
subject,
|
|
1082
1101
|
channelType: this.resolveChannelType(channel),
|
|
@@ -1084,6 +1103,19 @@ export class CommandHandler {
|
|
|
1084
1103
|
channelId,
|
|
1085
1104
|
request,
|
|
1086
1105
|
...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
|
|
1106
|
+
resolveLoopTarget: async (context) => {
|
|
1107
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, {
|
|
1108
|
+
channel,
|
|
1109
|
+
channelId: context.channelId,
|
|
1110
|
+
selfAid: context.self ?? '',
|
|
1111
|
+
...(transportReplyContext?.threadId && context.channelId === channelId
|
|
1112
|
+
? { threadId: transportReplyContext.threadId }
|
|
1113
|
+
: {}),
|
|
1114
|
+
});
|
|
1115
|
+
if (target.peerKey !== context.peerKey)
|
|
1116
|
+
throw { code: 'INVALID_CONTEXT', message: '当前会话关系不匹配' };
|
|
1117
|
+
return { level: target.level, sessionId: target.session.id };
|
|
1118
|
+
},
|
|
1087
1119
|
});
|
|
1088
1120
|
}
|
|
1089
1121
|
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
|
|
@@ -1577,7 +1609,11 @@ export class CommandHandler {
|
|
|
1577
1609
|
if (!isAdmin && updated.execution.type === 'script') {
|
|
1578
1610
|
return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
|
|
1579
1611
|
}
|
|
1580
|
-
const
|
|
1612
|
+
const originChatType = definition.origin?.delivery?.chatType;
|
|
1613
|
+
const originConversationId = originChatType === 'group'
|
|
1614
|
+
? definition.origin?.channelId
|
|
1615
|
+
: definition.origin?.peerId;
|
|
1616
|
+
const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId, originChatType, originConversationId);
|
|
1581
1617
|
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1582
1618
|
if (permissionError) {
|
|
1583
1619
|
return {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 不支持跨关系浏览:relation 永远是发起命令的这段关系。
|
|
9
9
|
*/
|
|
10
10
|
import { CONSOLIDATION_MODES, MODE_SCOPE } from '../memory/consolidation-context.js';
|
|
11
|
+
import { formatQueueDetails } from '../memory/consolidation-queue-format.js';
|
|
11
12
|
import { MEMORY_SCOPES, isMemoryScope, memoryOverview, listMemoryFiles, formatMemoryFiles, readMemoryIndex, showMemoryFile, formatPendingNotes, showPendingNote, reindexScope, dropNote, archiveMemoryFile, restoreMemoryFile, } from '../memory/memory-admin.js';
|
|
12
13
|
const USAGE = [
|
|
13
14
|
'用法:',
|
|
@@ -263,8 +264,11 @@ async function consolidate(ctx, target, rawMode, rawScope, needScope, isPayload)
|
|
|
263
264
|
...(scopeOverride ? { scopeOverride } : {}),
|
|
264
265
|
});
|
|
265
266
|
const label = `${mode}${scopeOverride ? `(${scopeOverride})` : ''}`;
|
|
266
|
-
if (!launched
|
|
267
|
-
return
|
|
267
|
+
if (!launched)
|
|
268
|
+
return error(`拉起整理会话失败(${label}),详见 daemon 日志`);
|
|
269
|
+
if (launched.queued) {
|
|
270
|
+
return result(`⏳ 整理请求已入队(${label}),前一个整理会话结束后自动开始\n` +
|
|
271
|
+
formatQueueDetails(launched.queued).join('\n'));
|
|
268
272
|
}
|
|
269
273
|
return result(`✅ 已拉起整理会话(${label})${launched.executionSessionId ? `\n会话: ${launched.executionSessionId}` : ''}\n完成后会在当前对话反馈结果`);
|
|
270
274
|
}
|
|
@@ -7,6 +7,7 @@ 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
9
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
10
|
+
import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS } from '../session/session-idle-monitor.js';
|
|
10
11
|
const DEFAULT_DIRECTORY = { getGroup: groupInfo, getMembers: groupMembers };
|
|
11
12
|
const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
|
|
12
13
|
export function validateMenuValueBatchArgs(args) {
|
|
@@ -65,6 +66,19 @@ const relation = (id, name, label, order, cmd, operation, extra) => ({
|
|
|
65
66
|
...extra,
|
|
66
67
|
});
|
|
67
68
|
const ENTRIES = [
|
|
69
|
+
{
|
|
70
|
+
id: 'session.loop',
|
|
71
|
+
type: 'setting',
|
|
72
|
+
contexts: ['private_relation', 'group_relation'],
|
|
73
|
+
name: 'session',
|
|
74
|
+
label: '当前会话闲置触发',
|
|
75
|
+
section: 'relation_behavior',
|
|
76
|
+
order: 5,
|
|
77
|
+
query: { cmd: '/session', args: { view: 'loop' }, operation: 'session.loop.current', scope: 'relation' },
|
|
78
|
+
update: { cmd: '/session', args: { resource: 'loop' }, operation: 'session.loop.update', scope: 'relation' },
|
|
79
|
+
valueSchema: { type: 'boolean' },
|
|
80
|
+
control: 'toggle-with-number',
|
|
81
|
+
},
|
|
68
82
|
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' }),
|
|
69
83
|
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' }),
|
|
70
84
|
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' }),
|
|
@@ -272,6 +286,9 @@ export function bindMenuExecutionContextArgs(args, context) {
|
|
|
272
286
|
if (!args && context.kind !== 'group_relation')
|
|
273
287
|
return args;
|
|
274
288
|
const { context: _context, ...bound } = args ?? {};
|
|
289
|
+
const isSessionLoop = bound.resource === 'loop' || bound.view === 'loop';
|
|
290
|
+
if (isSessionLoop)
|
|
291
|
+
return bound;
|
|
275
292
|
if (context.kind === 'private_relation' || context.kind === 'group_relation') {
|
|
276
293
|
if (bound.scope !== 'role')
|
|
277
294
|
bound.scope = 'relation';
|
|
@@ -356,6 +373,12 @@ async function operationView(binding, context, options = {}) {
|
|
|
356
373
|
function wireBinding(binding, context) {
|
|
357
374
|
if (!binding || binding.supported === false)
|
|
358
375
|
return undefined;
|
|
376
|
+
if (binding.operation.startsWith('session.loop.'))
|
|
377
|
+
return {
|
|
378
|
+
args: context.kind === 'group_relation'
|
|
379
|
+
? { ...(binding.args ?? {}), context: { kind: context.kind, ...(context.groupId ? { groupId: context.groupId } : {}) } }
|
|
380
|
+
: binding.args ?? {},
|
|
381
|
+
};
|
|
359
382
|
return {
|
|
360
383
|
args: context.kind === 'group_relation'
|
|
361
384
|
? {
|
|
@@ -379,6 +402,27 @@ export async function getMenuCatalog(input) {
|
|
|
379
402
|
operations[key] = view;
|
|
380
403
|
}
|
|
381
404
|
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' } });
|
|
405
|
+
if (entry.id === 'session.loop') {
|
|
406
|
+
const loopEntry = entries[entries.length - 1];
|
|
407
|
+
let target = { level: 'relation-main' };
|
|
408
|
+
if (input.resolveLoopTarget) {
|
|
409
|
+
try {
|
|
410
|
+
target = await input.resolveLoopTarget(context);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
const reason = normalizeMenuError(error);
|
|
414
|
+
for (const operation of Object.values(operations)) {
|
|
415
|
+
Object.assign(operation, { allowed: false, reason });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
loopEntry.target = target;
|
|
420
|
+
loopEntry.label = target.level === 'topic-session' ? '当前话题闲置触发' : '关系主会话闲置触发';
|
|
421
|
+
loopEntry.ui = {
|
|
422
|
+
control: entry.control,
|
|
423
|
+
idleSeconds: { type: 'integer', minimum: MIN_SESSION_LOOP_SECONDS, maximum: MAX_SESSION_LOOP_SECONDS, default: DEFAULT_SESSION_LOOP_SECONDS, unit: 'seconds' },
|
|
424
|
+
};
|
|
425
|
+
}
|
|
382
426
|
}
|
|
383
427
|
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) });
|
|
384
428
|
const data = { $schema_version: 1, kind: 'menu.catalog', complete: true, catalogRevision: hash({ entries, context: context.contextKey, policyRevision }), policyRevision, generatedAt: Date.now(), context: { contextKey: context.contextKey, kind: context.kind, channelScope: context.channelScope, transport: context.transport, chatType: context.chatType, ...(context.self ? { self: context.self } : {}), ...(context.actor ? { actor: context.actor } : {}), ...(context.groupId ? { groupId: context.groupId } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}), requesterRole: context.requesterRole, ...(context.groupMembershipRole ? { groupMembershipRole: context.groupMembershipRole } : {}), ...(context.baselineRole !== undefined ? { baselineRole: context.baselineRole } : {}) }, features: { batchValuesV1: true, verifiedExplicitGroupContext: context.kind === 'group_relation' }, entries };
|
|
@@ -420,7 +464,10 @@ export async function execMenuValueBatch(input) {
|
|
|
420
464
|
if (allowed?.supported === false || !(allowed?.allowed))
|
|
421
465
|
return { itemId, entryId, ok: false, error: allowed?.reason ?? { code: 'NOT_SUPPORTED', message: 'operation is not supported or denied' } };
|
|
422
466
|
try {
|
|
423
|
-
const
|
|
467
|
+
const queryArgs = entry.id === 'session.loop'
|
|
468
|
+
? { ...(entry.query.args ?? {}) }
|
|
469
|
+
: bindingArgs(entry.query, context);
|
|
470
|
+
const result = await input.execQuery(entry.query.cmd, context.channelId, queryArgs, context);
|
|
424
471
|
return !('error' in result) ? { itemId, entryId, ok: true, data: result.data } : { itemId, entryId, ok: false, error: { code: normalizeMenuError({ code: result.code, message: result.error, data: result.data }).code, message: result.error, ...(result.data !== undefined ? { data: result.data } : {}) } };
|
|
425
472
|
}
|
|
426
473
|
catch (error) {
|
|
@@ -2,7 +2,7 @@ import { memoryHelpEntries } from './memory-slash.js';
|
|
|
2
2
|
import { getBaseagentSnapshot, loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
3
3
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
4
4
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
5
|
-
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable,
|
|
5
|
+
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable, CONFIGURABLE_EFFORTS, configurableEfforts } from '../model/model-catalog.js';
|
|
6
6
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
7
7
|
import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
8
8
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
@@ -45,6 +45,7 @@ import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
|
45
45
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateTaskActivityQueryArgs, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
46
46
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
47
47
|
import { TaskActivityStore } from '../task/task-activity-store.js';
|
|
48
|
+
import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS, refreshSessionLoopTarget, resolveCurrentSessionLoopTarget, sessionLoopPayload, validateIdleSeconds, writeSessionLoopConfig, } from '../session/session-idle-monitor.js';
|
|
48
49
|
import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
49
50
|
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
50
51
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
@@ -63,7 +64,7 @@ import { normalizeTopicLabel, normalizeTopicStatus, resolveTopicArchiveFilter, }
|
|
|
63
64
|
function getBaseagentVersion(cmd) {
|
|
64
65
|
return cachedCliVersion(cmd);
|
|
65
66
|
}
|
|
66
|
-
const allEfforts =
|
|
67
|
+
const allEfforts = CONFIGURABLE_EFFORTS;
|
|
67
68
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
68
69
|
/** menu file: fetch 文件大小上限(与 /file 和 AUN sendFile 一致) */
|
|
69
70
|
const FILE_FETCH_MAX_SIZE = MAX_AUN_ATTACHMENT_SIZE;
|
|
@@ -402,14 +403,14 @@ function tokenizeArgv(line) {
|
|
|
402
403
|
return out;
|
|
403
404
|
}
|
|
404
405
|
async function getAvailableEfforts(agent, model) {
|
|
406
|
+
const baseagent = agent?.baseagent || agent?.name;
|
|
405
407
|
const dynamic = await agent.getAvailableEfforts?.(model);
|
|
406
408
|
if (Array.isArray(dynamic))
|
|
407
|
-
return dynamic;
|
|
409
|
+
return configurableEfforts(baseagent, dynamic);
|
|
408
410
|
// Keep Menu usable with legacy/embedded runner implementations that do not
|
|
409
411
|
// expose the optional catalog method. Full runners still take the dynamic
|
|
410
412
|
// path above, so this fallback cannot broaden their advertised capabilities.
|
|
411
|
-
|
|
412
|
-
return (TRANSPORT_EFFORTS[baseagent] ?? []);
|
|
413
|
+
return configurableEfforts(baseagent);
|
|
413
414
|
}
|
|
414
415
|
function isDaemonControlSubject(subject) {
|
|
415
416
|
return subject.isDaemonOwner === true
|
|
@@ -1456,12 +1457,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1456
1457
|
desc: canUseModel ? '切换当前 Agent 使用的模型版本' : '查看当前 Agent 使用的模型版本',
|
|
1457
1458
|
next: canUseModel ? { type: 'select', dynamic: true } : undefined,
|
|
1458
1459
|
}] : []),
|
|
1459
|
-
...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select',
|
|
1460
|
-
{ value: 'low', label: 'Low' },
|
|
1461
|
-
{ value: 'medium', label: 'Medium' },
|
|
1462
|
-
{ value: 'high', label: 'High' },
|
|
1463
|
-
{ value: 'max', label: 'Max' },
|
|
1464
|
-
] } }] : []),
|
|
1460
|
+
...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', dynamic: true } }] : []),
|
|
1465
1461
|
...(canSetChatmode ? [{ cmd: '/chatmode', label: '切换会话模式', desc: '控制 Agent 主动性(被动响应或主动推进)', next: { type: 'select', items: [
|
|
1466
1462
|
{ value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
|
|
1467
1463
|
{ value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
|
|
@@ -1966,7 +1962,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1966
1962
|
}
|
|
1967
1963
|
catch (error) {
|
|
1968
1964
|
if (isDaemonControlSubject(subject)) {
|
|
1969
|
-
return [
|
|
1965
|
+
return [...configurableEfforts(target.baseagent), 'auto'].map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e }));
|
|
1970
1966
|
}
|
|
1971
1967
|
throw error;
|
|
1972
1968
|
}
|
|
@@ -2066,6 +2062,26 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2066
2062
|
fromControlChannel,
|
|
2067
2063
|
subject: authSubject,
|
|
2068
2064
|
});
|
|
2065
|
+
if (cmdBase === '/session' && args?.view === 'loop') {
|
|
2066
|
+
if (Object.keys(args).some(key => key !== 'view' && key !== 'context')) {
|
|
2067
|
+
return { error: 'session.loop query only accepts args.view', code: 'INVALID_ARGUMENT' };
|
|
2068
|
+
}
|
|
2069
|
+
try {
|
|
2070
|
+
const route = { channel, channelId, selfAid: subject.selfAid ?? '', threadId: transportReplyContext?.threadId };
|
|
2071
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
|
|
2072
|
+
const denied = await authorizeMenuIntent.call(this, {
|
|
2073
|
+
intent: { operation: 'session.loop.current', scope: 'relation', source, args: { sessionId: target.session.id, peer: target.peerKey } },
|
|
2074
|
+
identity, subject: { ...subject, sessionId: target.session.id }, session: target.session, channel, channelId, userId, fromControlChannel, source,
|
|
2075
|
+
});
|
|
2076
|
+
if (denied)
|
|
2077
|
+
return denied;
|
|
2078
|
+
const current = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
2079
|
+
return { data: sessionLoopPayload(current) };
|
|
2080
|
+
}
|
|
2081
|
+
catch (error) {
|
|
2082
|
+
return menuResultFailure(error);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2069
2085
|
const storageRequest = { type: 'menu.query', cmd, args };
|
|
2070
2086
|
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
2071
2087
|
try {
|
|
@@ -2863,6 +2879,21 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2863
2879
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
2864
2880
|
if (!cmdBase)
|
|
2865
2881
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
2882
|
+
const isLoopUpdate = cmdBase === '/session' && args?.resource === 'loop';
|
|
2883
|
+
if (isLoopUpdate) {
|
|
2884
|
+
if (typeof value !== 'boolean' || Object.keys(args ?? {}).some(key => key !== 'resource' && key !== 'idleSeconds' && key !== 'context')) {
|
|
2885
|
+
return { error: 'session.loop requires a boolean value and only accepts resource / idleSeconds', code: 'INVALID_ARGUMENT' };
|
|
2886
|
+
}
|
|
2887
|
+
if (!value && Object.hasOwn(args ?? {}, 'idleSeconds')) {
|
|
2888
|
+
return { error: '关闭时不能提供 idleSeconds', code: 'INVALID_ARGUMENT' };
|
|
2889
|
+
}
|
|
2890
|
+
if (Object.hasOwn(args ?? {}, 'idleSeconds') && validateIdleSeconds(args?.idleSeconds) === undefined) {
|
|
2891
|
+
return { error: `idleSeconds 必须是 ${MIN_SESSION_LOOP_SECONDS}-${MAX_SESSION_LOOP_SECONDS} 的整数`, code: 'INVALID_ARGUMENT' };
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
else if (typeof value !== 'string') {
|
|
2895
|
+
return { error: 'value 必须是字符串', code: 'INVALID_ARGUMENT' };
|
|
2896
|
+
}
|
|
2866
2897
|
if (cmdBase === '/perm') {
|
|
2867
2898
|
return { error: 'permissionMode 只能通过角色策略编辑器修改', code: 'ROLE_POLICY_MANAGED' };
|
|
2868
2899
|
}
|
|
@@ -2888,9 +2919,6 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2888
2919
|
}
|
|
2889
2920
|
if (cmdBase === '/baseagent-models')
|
|
2890
2921
|
return { error: 'Baseagent catalog only supports menu.query', code: 'NOT_SUPPORTED' };
|
|
2891
|
-
const arg = value.trim();
|
|
2892
|
-
if (!arg)
|
|
2893
|
-
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
2894
2922
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
2895
2923
|
const isAdmin = isManagementRole(identity.role);
|
|
2896
2924
|
const subject = buildMenuAuthSubject(this, {
|
|
@@ -2902,6 +2930,29 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2902
2930
|
fromControlChannel,
|
|
2903
2931
|
subject: authSubject,
|
|
2904
2932
|
});
|
|
2933
|
+
if (isLoopUpdate) {
|
|
2934
|
+
try {
|
|
2935
|
+
const route = { channel, channelId, selfAid: subject.selfAid ?? '', threadId: transportReplyContext?.threadId };
|
|
2936
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
|
|
2937
|
+
const denied = await authorizeMenuIntent.call(this, {
|
|
2938
|
+
intent: { operation: 'session.loop.update', scope: 'relation', source, args: { sessionId: target.session.id, peer: target.peerKey, value, ...(value ? { idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS } : {}) } },
|
|
2939
|
+
identity, subject: { ...subject, sessionId: target.session.id }, session: target.session, channel, channelId, userId, fromControlChannel, source,
|
|
2940
|
+
});
|
|
2941
|
+
if (denied)
|
|
2942
|
+
return denied;
|
|
2943
|
+
const current = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
2944
|
+
const config = value === true
|
|
2945
|
+
? { enabled: true, idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS }
|
|
2946
|
+
: { enabled: false };
|
|
2947
|
+
return { data: sessionLoopPayload(writeSessionLoopConfig(this.sessionManager, current, config)) };
|
|
2948
|
+
}
|
|
2949
|
+
catch (error) {
|
|
2950
|
+
return menuResultFailure(error);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
const arg = value.trim();
|
|
2954
|
+
if (!arg)
|
|
2955
|
+
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
2905
2956
|
if (cmdBase === '/role') {
|
|
2906
2957
|
const authorized = await authorizeRoleMenu(this, {
|
|
2907
2958
|
kind: 'update', args, value: arg, identity, subject: authSubject, session, channel, channelId,
|
|
@@ -4123,6 +4174,21 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4123
4174
|
}
|
|
4124
4175
|
catch { }
|
|
4125
4176
|
}
|
|
4177
|
+
// P1a.5.3: 若为话题会话,同步更新话题索引
|
|
4178
|
+
if (topic.threadId) {
|
|
4179
|
+
try {
|
|
4180
|
+
const { ThreadService } = await import('../../core/thread/index.js');
|
|
4181
|
+
const selfAid = topic.selfAID;
|
|
4182
|
+
const peer = topic.channelId;
|
|
4183
|
+
if (selfAid && peer) {
|
|
4184
|
+
const svc = new ThreadService(this.sessionManager);
|
|
4185
|
+
await svc.renameThread({ selfAid, peer, threadId: topic.threadId, name: newName });
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
catch (err) {
|
|
4189
|
+
logger.debug('[MenuHandler] Thread index rename sync failed:', err);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4126
4192
|
this.eventBus.publish({ type: 'session:renamed', sessionId: topic.id, oldName, newName });
|
|
4127
4193
|
return {
|
|
4128
4194
|
data: {
|
|
@@ -4781,6 +4847,14 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4781
4847
|
request: payload.args.context,
|
|
4782
4848
|
paircodeAllowed: context.authMethod === 'daemon-owner'
|
|
4783
4849
|
&& (context.source !== 'control' || context.encrypted),
|
|
4850
|
+
resolveLoopTarget: async (catalogContext) => {
|
|
4851
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, {
|
|
4852
|
+
channel: targetChannel, channelId: catalogContext.channelId, selfAid: catalogContext.self ?? '',
|
|
4853
|
+
});
|
|
4854
|
+
if (target.peerKey !== catalogContext.peerKey)
|
|
4855
|
+
throw { code: 'INVALID_CONTEXT', message: '当前会话关系不匹配' };
|
|
4856
|
+
return { level: target.level, sessionId: target.session.id };
|
|
4857
|
+
},
|
|
4784
4858
|
});
|
|
4785
4859
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
4786
4860
|
}
|
|
@@ -4839,8 +4913,10 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4839
4913
|
case 'menu.update': {
|
|
4840
4914
|
if (!cmd)
|
|
4841
4915
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
4842
|
-
if (
|
|
4916
|
+
if (payload.value === undefined || payload.value === null
|
|
4917
|
+
|| (typeof payload.value === 'string' && payload.value.length === 0)) {
|
|
4843
4918
|
return ecwebErr(id, name, 'MISSING_VALUE', '缺少 value');
|
|
4919
|
+
}
|
|
4844
4920
|
const execution = await resolveExplicitMenuExecutionContext({
|
|
4845
4921
|
subject: trustedSubject,
|
|
4846
4922
|
channelType: 'aun',
|
|
@@ -4,6 +4,7 @@ import { loadDaemonConfig } from '../../config-store.js';
|
|
|
4
4
|
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { compareStableSemver, parseStableSemver } from '../../utils/stable-semver.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { validateIdleSeconds } from '../session/session-idle-monitor.js';
|
|
7
8
|
export const MENU_REQUEST_TYPES = new Set([
|
|
8
9
|
'menu.token.request',
|
|
9
10
|
'menu.list',
|
|
@@ -334,9 +335,31 @@ export function validateMenuRequest(request) {
|
|
|
334
335
|
if (scopeError)
|
|
335
336
|
return scopeError;
|
|
336
337
|
}
|
|
337
|
-
|
|
338
|
+
const isSessionLoopUpdate = type === 'menu.update'
|
|
339
|
+
&& (request.name === 'session' || request.cmd === '/session')
|
|
340
|
+
&& request.args?.resource === 'loop';
|
|
341
|
+
if (isSessionLoopUpdate) {
|
|
342
|
+
if (typeof request.value !== 'boolean')
|
|
343
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.loop value must be a boolean' };
|
|
344
|
+
const args = request.args;
|
|
345
|
+
const unknown = Object.keys(args ?? {}).find(key => !['resource', 'idleSeconds', 'context'].includes(key));
|
|
346
|
+
if (unknown)
|
|
347
|
+
return { code: 'INVALID_ARGUMENT', message: `session.loop update does not accept args.${unknown}` };
|
|
348
|
+
if (Object.hasOwn(args ?? {}, 'idleSeconds') && validateIdleSeconds(args?.idleSeconds) === undefined) {
|
|
349
|
+
return { code: 'INVALID_ARGUMENT', message: 'idleSeconds must be an integer between 30 and 86400' };
|
|
350
|
+
}
|
|
351
|
+
if (request.value === false && Object.hasOwn(args ?? {}, 'idleSeconds')) {
|
|
352
|
+
return { code: 'INVALID_ARGUMENT', message: 'idleSeconds is not allowed when session.loop is disabled' };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (type === 'menu.update' && (typeof request.value !== 'string' || request.value.length === 0)) {
|
|
338
356
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request value is required' };
|
|
339
357
|
}
|
|
358
|
+
if (type === 'menu.query' && (request.name === 'session' || request.cmd === '/session')
|
|
359
|
+
&& request.args?.view === 'loop'
|
|
360
|
+
&& Object.keys(request.args).some(key => key !== 'view' && key !== 'context')) {
|
|
361
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.loop query only accepts args.view' };
|
|
362
|
+
}
|
|
340
363
|
if (type === 'menu.action' && (typeof request.action !== 'string' || request.action.trim().length === 0)) {
|
|
341
364
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request action is required' };
|
|
342
365
|
}
|