evolcore 0.0.32 → 0.0.34
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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- 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 +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- 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 +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -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/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -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 +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- 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 +929 -110
- 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 +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- 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 +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -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 +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -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 +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- 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 +67 -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/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
|
|
2
2
|
import { loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
3
|
+
import { grantSessionPath, listRuntimePathGrants, revokeSessionPath } from '../permission/session-path-grants.js';
|
|
3
4
|
import { closeDiagnosticSession, diagnosticTokenHash, isDiagnosticSessionActive } from './diag-service.js';
|
|
4
5
|
import { parseMenuClientArgs } from '../../cli/menu-client.js';
|
|
5
6
|
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
@@ -20,6 +21,7 @@ import { agentmdGet } from '../../aun/aid/agentmd.js';
|
|
|
20
21
|
import { parseDuration, parseTriggerSet, parseTriggerUpdate } from '../../trigger/parser.js';
|
|
21
22
|
import { applyTriggerPatch, replaceEditableTriggerDefinition } from '../../trigger/patch.js';
|
|
22
23
|
import { definitionRevision } from '../../trigger/validation.js';
|
|
24
|
+
import { isDaemonEventSessionTrigger, matchTriggerEvent } from '../../trigger/event-source.js';
|
|
23
25
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
24
26
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
25
27
|
import { buildTranscriptTurns } from '../session/session-transcript.js';
|
|
@@ -45,6 +47,7 @@ import { handleSlashCommand } from './slash-handler.js';
|
|
|
45
47
|
import { resolveChatMode } from '../message/peer-mode.js';
|
|
46
48
|
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
49
|
import { getMenuCatalog, execMenuValueBatch, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
50
|
+
import { resolveCurrentSessionLoopTarget, sessionLoopError } from '../session/session-idle-monitor.js';
|
|
48
51
|
export { isProcessLevelOwner } from './menu-handler.js';
|
|
49
52
|
const CLI_EXEC_TIMEOUT_MS = 15_000;
|
|
50
53
|
const CLI_EXEC_MAX_OUTPUT = 128 * 1024;
|
|
@@ -130,6 +133,8 @@ export class CommandHandler {
|
|
|
130
133
|
primaryRunnerKey;
|
|
131
134
|
agentRegistry;
|
|
132
135
|
memoryConsolidationLauncher;
|
|
136
|
+
/** /skill refresh 的 daemon 侧入口:重建指定 agent 的技能索引 */
|
|
137
|
+
skillIndexRefresher;
|
|
133
138
|
agentApplicationService;
|
|
134
139
|
triggerSchedulerResolver;
|
|
135
140
|
triggerSchedulerFallback;
|
|
@@ -145,9 +150,10 @@ export class CommandHandler {
|
|
|
145
150
|
diagService;
|
|
146
151
|
setDiagService(service) { this.diagService = service; }
|
|
147
152
|
async handleMenuClientOperation(argv, sessionId, delegationToken, delegationCommandHash, send) {
|
|
148
|
-
const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash)
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash)
|
|
154
|
+
?? { ok: false, code: 'DELEGATION_REQUIRED', reason: 'Task delegation registry is not configured', diagnosticCause: 'missing_token' };
|
|
155
|
+
if (!validation.ok)
|
|
156
|
+
return { ok: false, code: validation.code ?? 'DELEGATION_REQUIRED', error: validation.reason ?? 'Task delegation is required' };
|
|
151
157
|
const grant = validation.grant;
|
|
152
158
|
const session = await this.sessionManager.getSessionById(sessionId);
|
|
153
159
|
if (!session || session.selfAID !== grant.selfAid || argv[0] !== 'request' || argv[1] !== grant.selfAid)
|
|
@@ -368,6 +374,45 @@ export class CommandHandler {
|
|
|
368
374
|
getTriggerSchedulerForAgent(agentAid) {
|
|
369
375
|
return this.triggerSchedulerResolver?.(agentAid) ?? (this.triggerSchedulerFallbackAid === agentAid ? this.triggerSchedulerFallback : undefined);
|
|
370
376
|
}
|
|
377
|
+
assertSessionLoopTrigger(session) {
|
|
378
|
+
const daemonAid = this.getDaemonTriggerOwnerAid();
|
|
379
|
+
const schedulers = new Set([
|
|
380
|
+
this.getTriggerSchedulerForAgent(session.selfAID ?? ''),
|
|
381
|
+
daemonAid ? this.getTriggerSchedulerForAgent(daemonAid) : undefined,
|
|
382
|
+
]);
|
|
383
|
+
const triggers = [...schedulers].flatMap(scheduler => scheduler?.list({ all: true }) ?? [])
|
|
384
|
+
.filter(definition => definition.id === '__session-idle-check'
|
|
385
|
+
|| (definition.source.type === 'event' && definition.source.eventPattern === 'session:idle'));
|
|
386
|
+
if (triggers.length === 0) {
|
|
387
|
+
throw sessionLoopError('LOOP_TRIGGER_NOT_FOUND', '未找到 session:idle Trigger,无法开启 loop');
|
|
388
|
+
}
|
|
389
|
+
const enabled = triggers.filter(definition => definition.enabled);
|
|
390
|
+
if (enabled.length === 0) {
|
|
391
|
+
throw sessionLoopError('LOOP_TRIGGER_DISABLED', 'session:idle Trigger 未开启,无法开启 loop');
|
|
392
|
+
}
|
|
393
|
+
const event = {
|
|
394
|
+
type: 'session:idle',
|
|
395
|
+
sessionId: session.id,
|
|
396
|
+
agentAid: session.selfAID,
|
|
397
|
+
channel: session.channelType || session.channel,
|
|
398
|
+
channelName: session.channel,
|
|
399
|
+
channelId: session.channelId,
|
|
400
|
+
chatType: session.chatType,
|
|
401
|
+
...(session.threadId ? { threadId: session.threadId } : {}),
|
|
402
|
+
peerId: session.metadata?.peerId || session.channelId,
|
|
403
|
+
};
|
|
404
|
+
const valid = enabled.some(definition => definition.source.type === 'event'
|
|
405
|
+
&& definition.source.eventPattern === 'session:idle'
|
|
406
|
+
&& definition.execution.type === 'target_session'
|
|
407
|
+
&& !!definition.execution.prompt?.trim()
|
|
408
|
+
&& definition.feedback.strategy === 'event_session'
|
|
409
|
+
&& (definition.agentAid === session.selfAID || isDaemonEventSessionTrigger(definition))
|
|
410
|
+
&& matchTriggerEvent(definition.source, event).matched);
|
|
411
|
+
if (!valid) {
|
|
412
|
+
throw sessionLoopError('LOOP_TRIGGER_INVALID', 'session:idle Trigger 配置不符合要求:事件过滤条件须允许接收当前会话的闲置事件,'
|
|
413
|
+
+ '并使用 target_session 执行、event_session 动态路由,且提示词非空');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
371
416
|
triggerSourceFromSchedule(scheduleType, scheduleValue) {
|
|
372
417
|
if (scheduleType === 'once')
|
|
373
418
|
return { type: 'once' };
|
|
@@ -459,6 +504,7 @@ export class CommandHandler {
|
|
|
459
504
|
createdAt: definition.createdAt,
|
|
460
505
|
updatedAt: definition.updatedAt,
|
|
461
506
|
status: definition.enabled ? 'active' : 'disabled',
|
|
507
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
462
508
|
limits: definition.limits,
|
|
463
509
|
limitState: schedulerDetails?.limitState,
|
|
464
510
|
subscription: schedulerDetails?.subscription,
|
|
@@ -541,17 +587,21 @@ export class CommandHandler {
|
|
|
541
587
|
reason: input.reason,
|
|
542
588
|
});
|
|
543
589
|
}
|
|
544
|
-
triggerRoleContext(selfAid, channel, peerId, chatType) {
|
|
590
|
+
triggerRoleContext(selfAid, channel, peerId, chatType, conversationId) {
|
|
545
591
|
const owning = this.getOwningAgent(channel);
|
|
546
592
|
if (!owning || owning.aid !== selfAid)
|
|
547
593
|
return {};
|
|
594
|
+
const resolvedChatType = chatType === 'group' ? 'group' : 'private';
|
|
595
|
+
const resolvedConversationId = resolvedChatType === 'group'
|
|
596
|
+
? conversationId || peerId
|
|
597
|
+
: peerId;
|
|
548
598
|
const role = resolvePeerRoleDetail({
|
|
549
599
|
selfAid,
|
|
550
600
|
channelKey: channel,
|
|
551
601
|
channelType: this.resolveChannelType(channel),
|
|
552
|
-
chatType:
|
|
602
|
+
chatType: resolvedChatType,
|
|
553
603
|
actorId: peerId,
|
|
554
|
-
conversationId:
|
|
604
|
+
conversationId: resolvedConversationId,
|
|
555
605
|
}).effectiveRole;
|
|
556
606
|
return { role: role || 'none', baseagent: owning.baseagent };
|
|
557
607
|
}
|
|
@@ -596,12 +646,18 @@ export class CommandHandler {
|
|
|
596
646
|
const now = Date.now();
|
|
597
647
|
const id = `trig_${now}_${crypto.randomBytes(4).toString('hex')}`;
|
|
598
648
|
const name = parsed.name ?? `trigger-${now.toString(36)}`;
|
|
649
|
+
const originChatType = chatType === 'group' ? 'group' : 'private';
|
|
650
|
+
const originIsGroup = originChatType === 'group';
|
|
651
|
+
const originConversationId = originIsGroup ? channelId : peerId;
|
|
599
652
|
const feedbackChannel = parsed.targetChannel ?? channel;
|
|
653
|
+
const hasExplicitTarget = parsed.targetChannel !== undefined
|
|
654
|
+
|| parsed.targetChannelId !== undefined
|
|
655
|
+
|| parsed.targetChatType !== undefined;
|
|
600
656
|
if (parsed.targetChannel && !this.adapters.has(parsed.targetChannel)) {
|
|
601
657
|
return { error: `目标渠道不存在或未启用:${parsed.targetChannel}` };
|
|
602
658
|
}
|
|
603
659
|
const targetChannelType = this.resolveChannelType(feedbackChannel);
|
|
604
|
-
const feedbackChannelId = parsed.targetChannelId ??
|
|
660
|
+
const feedbackChannelId = parsed.targetChannelId ?? originConversationId;
|
|
605
661
|
const schedulerAid = parsed.agentId
|
|
606
662
|
?? this.getOwningAgent(feedbackChannel)?.aid
|
|
607
663
|
?? tryParseChannelKey(feedbackChannel)?.selfAID
|
|
@@ -613,19 +669,12 @@ export class CommandHandler {
|
|
|
613
669
|
if (targetChannelType === 'aun' && parsed.targetChannelId && !parsed.targetChatType) {
|
|
614
670
|
return { error: 'AUN 目标必须显式指定 --target-chat-type private 或 group' };
|
|
615
671
|
}
|
|
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
672
|
const target = {
|
|
624
673
|
channelKey: feedbackChannel,
|
|
625
674
|
channelId: feedbackChannelId,
|
|
626
675
|
session: parsed.targetSession,
|
|
627
676
|
threadId: parsed.targetSession === 'thread' ? parsed.targetThreadId : undefined,
|
|
628
|
-
chatType:
|
|
677
|
+
chatType: parsed.targetChatType ?? (hasExplicitTarget ? undefined : originChatType),
|
|
629
678
|
};
|
|
630
679
|
if (target.session === 'thread') {
|
|
631
680
|
const adapter = this.adapters.get(feedbackChannel);
|
|
@@ -634,19 +683,20 @@ export class CommandHandler {
|
|
|
634
683
|
if (!target.threadId)
|
|
635
684
|
return { error: '--target-session thread 需要 --target-thread-id' };
|
|
636
685
|
}
|
|
637
|
-
const originIsGroup = chatType === 'group';
|
|
638
686
|
const activeSession = originIsGroup
|
|
639
687
|
? undefined
|
|
640
688
|
: this.sessionManager.getActiveSessionSync(channel, peerId, this.resolveChannelType(channel), schedulerAid);
|
|
641
689
|
const origin = {
|
|
642
690
|
channelKey: channel,
|
|
643
691
|
channelType: this.resolveChannelType(channel),
|
|
644
|
-
channelId:
|
|
645
|
-
session:
|
|
646
|
-
threadId
|
|
692
|
+
channelId: originConversationId,
|
|
693
|
+
session: threadId ? 'thread' : 'main',
|
|
694
|
+
threadId,
|
|
647
695
|
peerId,
|
|
648
696
|
sessionKey: activeSession?.sessionKey,
|
|
649
|
-
delivery:
|
|
697
|
+
delivery: originIsGroup
|
|
698
|
+
? { chatType: 'group', groupId: originConversationId }
|
|
699
|
+
: { chatType: 'private' },
|
|
650
700
|
};
|
|
651
701
|
if (!isAdmin && parsed.feedbackStrategy === 'target' && !sameFeedbackTarget(target, origin)) {
|
|
652
702
|
return { error: '无权限:非 admin 只能把 trigger 投递到来源会话' };
|
|
@@ -668,7 +718,7 @@ export class CommandHandler {
|
|
|
668
718
|
timeoutMs: parsed.scriptTimeoutMs ?? 30_000,
|
|
669
719
|
} : undefined;
|
|
670
720
|
const permissionMode = this.triggerPermissionFromParsed(parsed);
|
|
671
|
-
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId,
|
|
721
|
+
const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, originChatType, originConversationId);
|
|
672
722
|
const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
673
723
|
if (permissionError)
|
|
674
724
|
return { error: permissionError };
|
|
@@ -707,6 +757,7 @@ export class CommandHandler {
|
|
|
707
757
|
agentAid: schedulerAid,
|
|
708
758
|
enabled: true,
|
|
709
759
|
name,
|
|
760
|
+
...(parsed.description ? { description: parsed.description } : {}),
|
|
710
761
|
createdAt: now,
|
|
711
762
|
updatedAt: now,
|
|
712
763
|
...(authorizedBy ? { authorizedBy } : {}),
|
|
@@ -933,10 +984,21 @@ export class CommandHandler {
|
|
|
933
984
|
setStatsCollector(collector) {
|
|
934
985
|
this.statsCollector = collector;
|
|
935
986
|
}
|
|
936
|
-
/**
|
|
987
|
+
/**
|
|
988
|
+
* 注入记忆整理入口(daemon 的统一整理队列),供 /memory consolidate 使用。
|
|
989
|
+
* 已入队时返回带 queued 的占位结果;返回 null 表示拉起失败。
|
|
990
|
+
*/
|
|
937
991
|
setMemoryConsolidationLauncher(launcher) {
|
|
938
992
|
this.memoryConsolidationLauncher = launcher;
|
|
939
993
|
}
|
|
994
|
+
/** 注入 /skill refresh 的索引重建入口(daemon 启动时调用)。 */
|
|
995
|
+
setSkillIndexRefresher(refresher) {
|
|
996
|
+
this.skillIndexRefresher = refresher;
|
|
997
|
+
}
|
|
998
|
+
/** 供 slash-handler 读取。 */
|
|
999
|
+
getSkillIndexRefresher() {
|
|
1000
|
+
return this.skillIndexRefresher;
|
|
1001
|
+
}
|
|
940
1002
|
registerAdapter(adapter) {
|
|
941
1003
|
this.adapters.set(adapter.channelName, adapter);
|
|
942
1004
|
}
|
|
@@ -1076,7 +1138,7 @@ export class CommandHandler {
|
|
|
1076
1138
|
getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
|
|
1077
1139
|
return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
|
|
1078
1140
|
}
|
|
1079
|
-
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed) {
|
|
1141
|
+
async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed, transportReplyContext) {
|
|
1080
1142
|
return await getMenuCatalog({
|
|
1081
1143
|
subject,
|
|
1082
1144
|
channelType: this.resolveChannelType(channel),
|
|
@@ -1084,6 +1146,19 @@ export class CommandHandler {
|
|
|
1084
1146
|
channelId,
|
|
1085
1147
|
request,
|
|
1086
1148
|
...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
|
|
1149
|
+
resolveLoopTarget: async (context) => {
|
|
1150
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, {
|
|
1151
|
+
channel,
|
|
1152
|
+
channelId: context.channelId,
|
|
1153
|
+
selfAid: context.self ?? '',
|
|
1154
|
+
...(transportReplyContext?.threadId && context.channelId === channelId
|
|
1155
|
+
? { threadId: transportReplyContext.threadId }
|
|
1156
|
+
: {}),
|
|
1157
|
+
});
|
|
1158
|
+
if (target.peerKey !== context.peerKey)
|
|
1159
|
+
throw { code: 'INVALID_CONTEXT', message: '当前会话关系不匹配' };
|
|
1160
|
+
return { level: target.level, sessionId: target.session.id };
|
|
1161
|
+
},
|
|
1087
1162
|
});
|
|
1088
1163
|
}
|
|
1089
1164
|
async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent', transportReplyContext) {
|
|
@@ -1577,7 +1652,11 @@ export class CommandHandler {
|
|
|
1577
1652
|
if (!isAdmin && updated.execution.type === 'script') {
|
|
1578
1653
|
return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
|
|
1579
1654
|
}
|
|
1580
|
-
const
|
|
1655
|
+
const originChatType = definition.origin?.delivery?.chatType;
|
|
1656
|
+
const originConversationId = originChatType === 'group'
|
|
1657
|
+
? definition.origin?.channelId
|
|
1658
|
+
: definition.origin?.peerId;
|
|
1659
|
+
const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId, originChatType, originConversationId);
|
|
1581
1660
|
const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner, canApprovePersistentFullAccess);
|
|
1582
1661
|
if (permissionError) {
|
|
1583
1662
|
return {
|
|
@@ -1786,6 +1865,7 @@ export class CommandHandler {
|
|
|
1786
1865
|
// ── Agent Ctl ──
|
|
1787
1866
|
static CTL_COMMANDS = [
|
|
1788
1867
|
'/status', '/pwd', '/file', '/send', '/rename', '/name', '/queue', '/restart', '/model', '/effort',
|
|
1868
|
+
'/path',
|
|
1789
1869
|
];
|
|
1790
1870
|
/**
|
|
1791
1871
|
* 从 session 恢复 ReplyContext,用于 ctl send 主动发送文本时的路由
|
|
@@ -2426,7 +2506,7 @@ export class CommandHandler {
|
|
|
2426
2506
|
selfAid: grant.selfAid,
|
|
2427
2507
|
peerKey: grant.peerKey,
|
|
2428
2508
|
channel: grant.channel,
|
|
2429
|
-
role: grant.issuedRole,
|
|
2509
|
+
role: grant.issuedRole ?? 'none',
|
|
2430
2510
|
taskId: grant.taskId,
|
|
2431
2511
|
messageId: grant.messageId,
|
|
2432
2512
|
argvHash: hashArgv(argv),
|
|
@@ -2471,7 +2551,7 @@ export class CommandHandler {
|
|
|
2471
2551
|
// stay pinned here, and authorizeOperation below remains the authority
|
|
2472
2552
|
// for every role (member carries currentRelationOnly; visitor is denied).
|
|
2473
2553
|
const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
|
|
2474
|
-
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
|
|
2554
|
+
|| (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole ?? 'none'));
|
|
2475
2555
|
if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
|
|
2476
2556
|
return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
|
|
2477
2557
|
}
|
|
@@ -2494,7 +2574,7 @@ export class CommandHandler {
|
|
|
2494
2574
|
channelId: conversationId,
|
|
2495
2575
|
chatType: grant.chatType,
|
|
2496
2576
|
conversationId,
|
|
2497
|
-
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole) } : {}),
|
|
2577
|
+
...(isSelectorHistory ? { identity: roleToSessionIdentity(grant.issuedRole ?? null) } : {}),
|
|
2498
2578
|
processOwners: loadDaemonConfig().owners ?? [],
|
|
2499
2579
|
...trustedExecutionIdentityInput(grant),
|
|
2500
2580
|
});
|
|
@@ -2947,6 +3027,15 @@ export class CommandHandler {
|
|
|
2947
3027
|
const args = cmd.slice('/queue'.length).trim();
|
|
2948
3028
|
return await this.handleQueueCommand(sessionId, args);
|
|
2949
3029
|
}
|
|
3030
|
+
// 5.2 /path: 运行时路径授权(E3-a)。授权校验见 handlePathCommand。
|
|
3031
|
+
if (cmd === '/path' || cmd.startsWith('/path ')) {
|
|
3032
|
+
const args = cmd.slice('/path'.length).trim();
|
|
3033
|
+
return await this.handlePathCommand(sessionId, args, {
|
|
3034
|
+
selfAid: grantSelfAid,
|
|
3035
|
+
actorId: grant.actorId,
|
|
3036
|
+
role: ctlSubject.role,
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
2950
3039
|
// 6. 调用现有 handle(),不传 sendMessage 回调(结果直接返回)
|
|
2951
3040
|
try {
|
|
2952
3041
|
const result = await this._handleInternal(cmd, session.channel, session.channelId, undefined, // 不发送消息
|
|
@@ -2970,6 +3059,10 @@ export class CommandHandler {
|
|
|
2970
3059
|
source: 'agent-tool',
|
|
2971
3060
|
args: { ...relationArgs, ...extra },
|
|
2972
3061
|
});
|
|
3062
|
+
if (name === 'path') {
|
|
3063
|
+
const action = args[0] === 'grant' ? 'grant' : args[0] === 'revoke' ? 'revoke' : 'list';
|
|
3064
|
+
return intent(`ec.ctl.path.${action}`);
|
|
3065
|
+
}
|
|
2973
3066
|
if (name === 'send')
|
|
2974
3067
|
return intent('ec.ctl.send');
|
|
2975
3068
|
if (name === 'file')
|
|
@@ -3011,6 +3104,83 @@ export class CommandHandler {
|
|
|
3011
3104
|
}
|
|
3012
3105
|
return null;
|
|
3013
3106
|
}
|
|
3107
|
+
// ── Path grant command ──
|
|
3108
|
+
/**
|
|
3109
|
+
* `ec ctl path grant|revoke|list` —— 运行时路径授权(E3-a)。
|
|
3110
|
+
*
|
|
3111
|
+
* 这是提权操作:授予一条路径等于扩大该会话能触及的文件范围。约束有三条,
|
|
3112
|
+
* 缺一条这套机制就能被会话自己解除:
|
|
3113
|
+
* 1. 只有 owner / admin 可调用;
|
|
3114
|
+
* 2. 授予只对**当前会话**生效,不写进任何全局配置;
|
|
3115
|
+
* 3. 会话结束时由 releaseSessionPathGrants(sessionId) 清掉。
|
|
3116
|
+
*
|
|
3117
|
+
* 见 docs/memory/session-profile-design.md §4。
|
|
3118
|
+
*/
|
|
3119
|
+
async handlePathCommand(sessionId, args, actor) {
|
|
3120
|
+
if (actor.role !== 'owner' && actor.role !== 'admin') {
|
|
3121
|
+
return {
|
|
3122
|
+
ok: false,
|
|
3123
|
+
code: 'ROLE_ACCESS_DENIED',
|
|
3124
|
+
error: `路径授权仅 owner / admin 可操作(当前角色:${actor.role ?? 'none'})`,
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
const [action = 'list', ...rest] = args.split(/\s+/).filter(Boolean);
|
|
3128
|
+
const flag = (name) => {
|
|
3129
|
+
const index = rest.indexOf(name);
|
|
3130
|
+
return index >= 0 ? rest[index + 1] : undefined;
|
|
3131
|
+
};
|
|
3132
|
+
if (action === 'list') {
|
|
3133
|
+
const grants = listRuntimePathGrants(sessionId);
|
|
3134
|
+
if (grants.length === 0)
|
|
3135
|
+
return { ok: true, result: '当前会话没有运行时路径授权', data: [] };
|
|
3136
|
+
const data = grants.map(record => ({
|
|
3137
|
+
path: record.path,
|
|
3138
|
+
access: record.access,
|
|
3139
|
+
...(record.identity ? { identity: record.identity } : {}),
|
|
3140
|
+
...(record.grantedBy ? { grantedBy: record.grantedBy } : {}),
|
|
3141
|
+
grantedAt: new Date(record.grantedAt).toISOString(),
|
|
3142
|
+
}));
|
|
3143
|
+
return {
|
|
3144
|
+
ok: true,
|
|
3145
|
+
data,
|
|
3146
|
+
result: data.map(item => `${item.access.padEnd(5)} ${item.path}`).join('\n'),
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
const rawPath = rest[0];
|
|
3150
|
+
if (!rawPath || rawPath.startsWith('--')) {
|
|
3151
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '缺少路径。用法: ec ctl path grant <绝对路径> [--access read|write|none]' };
|
|
3152
|
+
}
|
|
3153
|
+
if (!path.isAbsolute(rawPath)) {
|
|
3154
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '路径必须是绝对路径' };
|
|
3155
|
+
}
|
|
3156
|
+
const resolvedPath = path.resolve(rawPath);
|
|
3157
|
+
if (action === 'revoke') {
|
|
3158
|
+
return revokeSessionPath(sessionId, resolvedPath)
|
|
3159
|
+
? { ok: true, result: `已撤销 ${resolvedPath}` }
|
|
3160
|
+
: { ok: false, code: 'NOT_FOUND', error: `当前会话没有该路径的运行时授权: ${resolvedPath}` };
|
|
3161
|
+
}
|
|
3162
|
+
if (action !== 'grant') {
|
|
3163
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: `未知子命令: ${action}(支持 grant / revoke / list)` };
|
|
3164
|
+
}
|
|
3165
|
+
const accessValue = flag('--access') ?? 'read';
|
|
3166
|
+
if (accessValue !== 'read' && accessValue !== 'write' && accessValue !== 'none') {
|
|
3167
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: '--access 必须是 read / write / none' };
|
|
3168
|
+
}
|
|
3169
|
+
const identity = flag('--identity')?.trim();
|
|
3170
|
+
grantSessionPath({
|
|
3171
|
+
sessionId,
|
|
3172
|
+
path: resolvedPath,
|
|
3173
|
+
access: accessValue,
|
|
3174
|
+
...(identity ? { identity } : {}),
|
|
3175
|
+
...(actor.actorId ? { grantedBy: actor.actorId } : {}),
|
|
3176
|
+
grantedAt: Date.now(),
|
|
3177
|
+
});
|
|
3178
|
+
logger.info(`[PathGrant] granted session=${sessionId} path=${resolvedPath} access=${accessValue} by=${actor.actorId ?? 'unknown'} self=${actor.selfAid}`);
|
|
3179
|
+
return {
|
|
3180
|
+
ok: true,
|
|
3181
|
+
result: `✓ 已授予 ${accessValue} 于 ${resolvedPath}(仅当前会话,会话结束自动撤销)`,
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3014
3184
|
// ── Queue command ──
|
|
3015
3185
|
/** 提取命名参数值(如 --cancel msg123 → "msg123") */
|
|
3016
3186
|
extractArg(args, flag) {
|
|
@@ -30,6 +30,7 @@ const LOG_SOURCES = [
|
|
|
30
30
|
{ id: 'menu-protocol', label: 'Menu Protocol', pattern: /^menu-protocol(?:-.+)?\.log$/ },
|
|
31
31
|
{ id: 'causation', label: 'Causation spans', pattern: /^causation-spans(?:-.+)?\.log$/ },
|
|
32
32
|
{ id: 'command-audit', label: 'Command audit', pattern: /^command-audit(?:-.+)?\.log$/ },
|
|
33
|
+
{ id: 'delegation-audit', label: 'Delegation audit', pattern: /^delegation-audit(?:-.+)?\.log$/ },
|
|
33
34
|
{ id: 'role-mutations', label: 'Role mutations', pattern: /^role-mutations(?:-.+)?\.log$/ },
|
|
34
35
|
];
|
|
35
36
|
const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
@@ -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' }),
|
|
@@ -164,6 +178,9 @@ export async function resolveMenuCatalogContext(input) {
|
|
|
164
178
|
processRole,
|
|
165
179
|
isDaemonOwner: processRole === 'daemon-owner',
|
|
166
180
|
fromControlChannel: subject.fromControlChannel,
|
|
181
|
+
ecwebManagement: subject.ecwebManagement,
|
|
182
|
+
agentFullAccessManagement: subject.agentFullAccessManagement,
|
|
183
|
+
canApprovePersistentFullAccess: subject.canApprovePersistentFullAccess,
|
|
167
184
|
allowAccess: processRole !== 'none' || rebuilt.allowAccess,
|
|
168
185
|
};
|
|
169
186
|
const access = authorizeAccess(bound);
|
|
@@ -272,6 +289,9 @@ export function bindMenuExecutionContextArgs(args, context) {
|
|
|
272
289
|
if (!args && context.kind !== 'group_relation')
|
|
273
290
|
return args;
|
|
274
291
|
const { context: _context, ...bound } = args ?? {};
|
|
292
|
+
const isSessionLoop = bound.resource === 'loop' || bound.view === 'loop';
|
|
293
|
+
if (isSessionLoop)
|
|
294
|
+
return bound;
|
|
275
295
|
if (context.kind === 'private_relation' || context.kind === 'group_relation') {
|
|
276
296
|
if (bound.scope !== 'role')
|
|
277
297
|
bound.scope = 'relation';
|
|
@@ -356,6 +376,12 @@ async function operationView(binding, context, options = {}) {
|
|
|
356
376
|
function wireBinding(binding, context) {
|
|
357
377
|
if (!binding || binding.supported === false)
|
|
358
378
|
return undefined;
|
|
379
|
+
if (binding.operation.startsWith('session.loop.'))
|
|
380
|
+
return {
|
|
381
|
+
args: context.kind === 'group_relation'
|
|
382
|
+
? { ...(binding.args ?? {}), context: { kind: context.kind, ...(context.groupId ? { groupId: context.groupId } : {}) } }
|
|
383
|
+
: binding.args ?? {},
|
|
384
|
+
};
|
|
359
385
|
return {
|
|
360
386
|
args: context.kind === 'group_relation'
|
|
361
387
|
? {
|
|
@@ -379,6 +405,27 @@ export async function getMenuCatalog(input) {
|
|
|
379
405
|
operations[key] = view;
|
|
380
406
|
}
|
|
381
407
|
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' } });
|
|
408
|
+
if (entry.id === 'session.loop') {
|
|
409
|
+
const loopEntry = entries[entries.length - 1];
|
|
410
|
+
let target = { level: 'relation-main' };
|
|
411
|
+
if (input.resolveLoopTarget) {
|
|
412
|
+
try {
|
|
413
|
+
target = await input.resolveLoopTarget(context);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
const reason = normalizeMenuError(error);
|
|
417
|
+
for (const operation of Object.values(operations)) {
|
|
418
|
+
Object.assign(operation, { allowed: false, reason });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
loopEntry.target = target;
|
|
423
|
+
loopEntry.label = target.level === 'topic-session' ? '当前话题闲置触发' : '关系主会话闲置触发';
|
|
424
|
+
loopEntry.ui = {
|
|
425
|
+
control: entry.control,
|
|
426
|
+
idleSeconds: { type: 'integer', minimum: MIN_SESSION_LOOP_SECONDS, maximum: MAX_SESSION_LOOP_SECONDS, default: DEFAULT_SESSION_LOOP_SECONDS, unit: 'seconds' },
|
|
427
|
+
};
|
|
428
|
+
}
|
|
382
429
|
}
|
|
383
430
|
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
431
|
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 +467,10 @@ export async function execMenuValueBatch(input) {
|
|
|
420
467
|
if (allowed?.supported === false || !(allowed?.allowed))
|
|
421
468
|
return { itemId, entryId, ok: false, error: allowed?.reason ?? { code: 'NOT_SUPPORTED', message: 'operation is not supported or denied' } };
|
|
422
469
|
try {
|
|
423
|
-
const
|
|
470
|
+
const queryArgs = entry.id === 'session.loop'
|
|
471
|
+
? { ...(entry.query.args ?? {}) }
|
|
472
|
+
: bindingArgs(entry.query, context);
|
|
473
|
+
const result = await input.execQuery(entry.query.cmd, context.channelId, queryArgs, context);
|
|
424
474
|
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
475
|
}
|
|
426
476
|
catch (error) {
|