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.
Files changed (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. 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
- if (!validation?.ok)
150
- return { ok: false, code: validation?.code ?? 'DELEGATION_REQUIRED', error: validation?.reason ?? 'Task delegation is required' };
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: chatType === 'group' ? 'group' : 'private',
602
+ chatType: resolvedChatType,
553
603
  actorId: peerId,
554
- conversationId: peerId,
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 ?? peerId;
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: targetDelivery?.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: peerId,
645
- session: !originIsGroup && threadId ? 'thread' : 'main',
646
- threadId: !originIsGroup ? threadId : undefined,
692
+ channelId: originConversationId,
693
+ session: threadId ? 'thread' : 'main',
694
+ threadId,
647
695
  peerId,
648
696
  sessionKey: activeSession?.sessionKey,
649
- delivery: { chatType: 'private' },
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, chatType);
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
- /** 注入记忆整理入口(daemon 的统一整理队列),供 /memory consolidate 使用。返回 null 表示已入队。 */
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 triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
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 || launched.queued) {
267
- return result(`⏳ 整理请求已入队(${label}),前一个整理会话结束后自动开始`);
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 result = await input.execQuery(entry.query.cmd, context.channelId, bindingArgs(entry.query, context), context);
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) {