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.
Files changed (150) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/install-codex-managed-hooks.mjs +124 -37
  3. package/dist/agents/claude-runner.js +29 -2
  4. package/dist/agents/codex/app-server-client.js +3 -0
  5. package/dist/agents/codex/diagnostics.js +76 -44
  6. package/dist/agents/codex-runner.js +23 -19
  7. package/dist/agents/ecagent-runner.js +5 -13
  8. package/dist/aun/group/operations.js +0 -1
  9. package/dist/aun/msg/history.js +1 -0
  10. package/dist/aun/msg/p2p.js +0 -1
  11. package/dist/aun/outbox.js +50 -1
  12. package/dist/channels/aun.js +28 -2
  13. package/dist/channels/daemon.js +257 -2
  14. package/dist/cli/agent-command.js +58 -24
  15. package/dist/cli/agent.js +1 -1
  16. package/dist/cli/aun-commands.js +365 -29
  17. package/dist/cli/contact.js +8 -2
  18. package/dist/cli/daemon-commands.js +81 -12
  19. package/dist/cli/debug-mode.js +154 -0
  20. package/dist/cli/handoff-command.js +3 -0
  21. package/dist/cli/index.js +81 -64
  22. package/dist/cli/init.js +2 -2
  23. package/dist/cli/managed-command-guard.js +5 -0
  24. package/dist/cli/memory-command.js +8 -1
  25. package/dist/cli/queue-command.js +9 -4
  26. package/dist/cli/session-command.js +194 -0
  27. package/dist/cli/skill-command.js +78 -2
  28. package/dist/cli/task-context.js +1 -0
  29. package/dist/cli/thread-command.js +620 -0
  30. package/dist/cli/trigger-command.js +4 -2
  31. package/dist/config/builtin-roles.js +1 -0
  32. package/dist/config/config-field-policy.js +2 -3
  33. package/dist/config/config-manager.js +7 -2
  34. package/dist/config/resolved-config-op.js +3 -0
  35. package/dist/config-store.js +15 -0
  36. package/dist/core/auth/agent-delegation.js +25 -1
  37. package/dist/core/auth/debug-context.js +139 -0
  38. package/dist/core/auth/operation-authorizer.js +24 -1
  39. package/dist/core/auth/operation-catalog.js +33 -0
  40. package/dist/core/background-session/session-cleanup.js +212 -0
  41. package/dist/core/background-session/types.js +10 -0
  42. package/dist/core/bootstrap-messages.js +1 -1
  43. package/dist/core/bootstrap-service.js +6 -3
  44. package/dist/core/command/command-handler.js +60 -24
  45. package/dist/core/command/memory-slash.js +6 -2
  46. package/dist/core/command/menu-catalog.js +48 -1
  47. package/dist/core/command/menu-handler.js +92 -16
  48. package/dist/core/command/menu-protocol.js +24 -1
  49. package/dist/core/command/skill-slash.js +322 -0
  50. package/dist/core/command/slash-gate.js +6 -4
  51. package/dist/core/command/slash-handler.js +193 -13
  52. package/dist/core/command/topic-slash.js +528 -0
  53. package/dist/core/evolagent-registry.js +20 -5
  54. package/dist/core/handoff/runtime.js +5 -11
  55. package/dist/core/handoff/store.js +5 -0
  56. package/dist/core/ipc-registration.js +139 -2
  57. package/dist/core/memory/consolidation-context.js +49 -16
  58. package/dist/core/memory/consolidation-ops-records.js +35 -0
  59. package/dist/core/memory/consolidation-queue-format.js +21 -0
  60. package/dist/core/memory/consolidation.js +174 -180
  61. package/dist/core/message/aun-ipc-sender.js +161 -6
  62. package/dist/core/message/im-renderer.js +149 -6
  63. package/dist/core/message/message-bridge.js +13 -3
  64. package/dist/core/message/message-log.js +1 -0
  65. package/dist/core/message/peer-mode.js +6 -1
  66. package/dist/core/message/response-engine.js +349 -67
  67. package/dist/core/message/subtask-tool-projection.js +140 -0
  68. package/dist/core/model/catalog-service.js +17 -12
  69. package/dist/core/permission/ec-command-parser.js +126 -7
  70. package/dist/core/session/session-idle-monitor.js +249 -22
  71. package/dist/core/session/session-manager.js +54 -9
  72. package/dist/core/skill/command-parser.js +13 -0
  73. package/dist/core/skill/index-generator.js +4 -1
  74. package/dist/core/skill/startup.js +41 -0
  75. package/dist/core/skill/switch-resolver.js +55 -29
  76. package/dist/core/thread/command-parser.js +125 -0
  77. package/dist/core/thread/index.js +1 -0
  78. package/dist/core/thread/ipc-operation.js +95 -0
  79. package/dist/core/thread/thread-context-storage.js +131 -0
  80. package/dist/core/thread/thread-operation.js +258 -0
  81. package/dist/core/thread/thread-route.js +33 -0
  82. package/dist/core/thread/thread-service.js +777 -0
  83. package/dist/core/thread/types.js +1 -0
  84. package/dist/eck/message-renderer.js +35 -0
  85. package/dist/index.js +8 -5
  86. package/dist/ipc.js +89 -10
  87. package/dist/paths.js +23 -0
  88. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  89. package/dist/trigger/authorization.js +57 -28
  90. package/dist/trigger/feedback.js +7 -1
  91. package/dist/trigger/parser.js +63 -1
  92. package/dist/trigger/validation.js +14 -0
  93. package/dist/utils/error-utils.js +22 -1
  94. package/kits/docs/context-assembly.md +1 -0
  95. package/kits/docs/evolcore/INDEX.md +1 -0
  96. package/kits/docs/evolcore/agent-create.md +39 -28
  97. package/kits/docs/evolcore/agent.md +19 -4
  98. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  99. package/kits/docs/evolcore/group-collaboration.md +3 -5
  100. package/kits/docs/evolcore/group.md +3 -4
  101. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  102. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  103. package/kits/docs/evolcore/menu-protocol.md +7 -0
  104. package/kits/docs/evolcore/msg.md +5 -8
  105. package/kits/docs/evolcore/thread-chain.md +267 -0
  106. package/kits/docs/prompt-loading-architecture.md +1 -0
  107. package/kits/eck_manifest.json +61 -26
  108. package/kits/eck_message_manifest.json +169 -10
  109. package/kits/rules/05-venue.md +1 -1
  110. package/kits/rules/07-memory-and-thread.md +202 -0
  111. package/kits/schemas/relation-config.schema.9.json +18 -3
  112. package/kits/schemas/role-config.schema.1.json +6 -6
  113. package/kits/schemas/single-session.schema.1.json +6 -6
  114. package/kits/schemas/single-session.schema.2.json +6 -6
  115. package/kits/schemas/single-session.schema.3.json +6 -6
  116. package/kits/skills/.switches.json +12 -1
  117. package/kits/skills/ec-group/SKILL.md +234 -0
  118. package/kits/skills/ec-skill/SKILL.md +10 -1
  119. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  120. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  121. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  122. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  123. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  124. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  125. package/kits/skills/thread-ops/SKILL.md +492 -0
  126. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  127. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  128. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  129. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  130. package/kits/templates/bootstrap-welcome.md +7 -7
  131. package/kits/templates/memory-consolidation/forget.md +5 -0
  132. package/kits/templates/memory-consolidation/generalize.md +5 -0
  133. package/kits/templates/memory-consolidation/profile.md +5 -0
  134. package/kits/templates/memory-consolidation/promote.md +5 -0
  135. package/kits/templates/message-fragments/item.md +2 -1
  136. package/kits/templates/message-fragments/thread-context.md +6 -0
  137. package/kits/templates/message-fragments/trigger-session.md +2 -0
  138. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  139. package/kits/templates/roles/member.json +2 -0
  140. package/kits/templates/roles/visitor.json +2 -0
  141. package/kits/templates/system-fragments/bootstrap.md +66 -72
  142. package/kits/templates/system-fragments/channel.md +3 -6
  143. package/kits/templates/system-fragments/commands.md +1 -0
  144. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  145. package/kits/templates/system-fragments/reply.md +3 -2
  146. package/kits/templates/system-fragments/session.md +5 -14
  147. package/package.json +3 -1
  148. package/kits/docs/evolcore/group-fs.md +0 -17
  149. package/kits/docs/venues/aun-group.md +0 -45
  150. package/kits/docs/venues/group.md +0 -43
@@ -13,11 +13,13 @@ import { remoteAccessService } from '../auth/remote-access-store.js';
13
13
  import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, } from '../../config/config-manager.js';
14
14
  import { execAgentAction } from './agent-control.js';
15
15
  import { handleMemorySlash, memoryHelpEntries } from './memory-slash.js';
16
+ import { handleSkillSlash, skillHelpEntries } from './skill-slash.js';
17
+ import { handleTopicSlash, topicHelpEntries, canUseTopicSlash } from './topic-slash.js';
16
18
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
17
19
  import { authorizationNextStep, formatAuthorizationDenial } from '../auth/authorization-denial.js';
18
20
  import { resolvePermissionMode, writeScope } from '../model/config-scope.js';
19
21
  import { formatPeerKey } from '../relation/peer-identity.js';
20
- import { modelMatches, getCatalog, assertCatalogSelectable, resolveCatalogModel } from '../model/model-catalog.js';
22
+ import { modelMatches, getCatalog, assertCatalogSelectable, resolveCatalogModel, CONFIGURABLE_EFFORTS, configurableEfforts } from '../model/model-catalog.js';
21
23
  import { formatModelCheck, runModelCheck } from '../model/model-diagnostics.js';
22
24
  import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
23
25
  import { resolveRuntimeStringField } from '../role/runtime-policy.js';
@@ -30,7 +32,8 @@ import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } fr
30
32
  import { scanInstances } from '../../utils/instance-registry.js';
31
33
  import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
32
34
  import { MAX_AUN_ATTACHMENT_SIZE } from '../../aun/msg/upload.js';
33
- const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
35
+ import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS, refreshSessionLoopTarget, resolveCurrentSessionLoopTarget, sessionLoopPayload, validateIdleSeconds, writeSessionLoopConfig, } from '../session/session-idle-monitor.js';
36
+ const allEfforts = CONFIGURABLE_EFFORTS;
34
37
  const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
35
38
  function defaultSlashChatmode(field) {
36
39
  return field === 'private' ? 'interactive' : 'proactive';
@@ -144,7 +147,8 @@ function resolveSlashRelationTarget(params) {
144
147
  };
145
148
  }
146
149
  async function getAvailableEfforts(agent, model) {
147
- return (await agent.getAvailableEfforts?.(model) ?? []);
150
+ const dynamic = await agent.getAvailableEfforts?.(model);
151
+ return configurableEfforts(agent?.baseagent || agent?.name, dynamic);
148
152
  }
149
153
  function modelDisplayLabel(agent, model) {
150
154
  const full = agent.resolveModelId?.(model);
@@ -490,7 +494,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
490
494
  const isDaemonOwner = authSubject.isDaemonOwner;
491
495
  const authorizeIntent = (params) => authorizeSlashIntent({
492
496
  ...params,
493
- subject: authSubject,
497
+ subject: params.intent.operation.startsWith('session.loop.')
498
+ ? { ...authSubject, sessionId: params.session?.id }
499
+ : authSubject,
494
500
  });
495
501
  const canAccessSlashOperation = (operation, scope) => {
496
502
  const args = scope === 'relation'
@@ -534,6 +540,79 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
534
540
  const isCmd = isRecognizedSlashCommand(normalizedContent);
535
541
  if (!isCmd)
536
542
  return undefined;
543
+ if (normalizedContent === '/loop' || normalizedContent.startsWith('/loop ')) {
544
+ const rawArgs = normalizedContent.slice('/loop'.length).trim();
545
+ const parts = rawArgs ? rawArgs.split(/\s+/) : [];
546
+ if (parts.length > 2 || (parts.length > 0 && !['on', 'off'].includes(parts[0]))) {
547
+ return { kind: 'command.error', text: `❌ 用法: /loop | /loop on [${MIN_SESSION_LOOP_SECONDS}-${MAX_SESSION_LOOP_SECONDS}] | /loop off` };
548
+ }
549
+ const route = {
550
+ channel,
551
+ channelId,
552
+ selfAid: authSubject.selfAid ?? selfAID ?? '',
553
+ ...(threadId ? { threadId } : {}),
554
+ };
555
+ let target;
556
+ try {
557
+ if (!route.selfAid)
558
+ throw Object.assign(new Error('当前 agent 身份不可用'), { code: 'MISSING_AID' });
559
+ target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
560
+ }
561
+ catch (error) {
562
+ const code = error?.code;
563
+ const message = error instanceof Error ? error.message : String(error);
564
+ return { kind: 'command.error', text: `❌ ${message}${code ? ` (${code})` : ''}` };
565
+ }
566
+ const operation = parts.length === 0 ? 'session.loop.current' : 'session.loop.update';
567
+ const denied = await authorizeIntent({
568
+ intent: { operation, scope: 'relation', source: 'slash', args: { sessionId: target.session.id, peer: target.peerKey, value: parts[0] } },
569
+ identity,
570
+ session: target.session,
571
+ explicitChatType: target.session.chatType,
572
+ channel,
573
+ channelId,
574
+ userId,
575
+ selfAid: route.selfAid,
576
+ isDaemonOwner,
577
+ });
578
+ if (denied)
579
+ return denied;
580
+ if (parts.length === 0) {
581
+ try {
582
+ const current = await refreshSessionLoopTarget(this.sessionManager, route, target);
583
+ const payload = sessionLoopPayload(current);
584
+ return { kind: 'command.result', text: `当前 idle 触发(${current.level},session=${current.session.id}):${payload.value ? `开启,${payload.idleSeconds} 秒` : '关闭'}` };
585
+ }
586
+ catch (error) {
587
+ return { kind: 'command.error', text: `❌ ${error instanceof Error ? error.message : String(error)}` };
588
+ }
589
+ }
590
+ let next;
591
+ if (parts[0] === 'off') {
592
+ if (parts.length !== 1)
593
+ return { kind: 'command.error', text: '❌ /loop off 不接受秒数' };
594
+ next = { enabled: false };
595
+ }
596
+ else {
597
+ const seconds = parts.length === 2
598
+ ? /^[1-9]\d*$/.test(parts[1]) ? validateIdleSeconds(Number(parts[1])) : undefined
599
+ : DEFAULT_SESSION_LOOP_SECONDS;
600
+ if (seconds === undefined)
601
+ return { kind: 'command.error', text: `❌ 秒数必须是 ${MIN_SESSION_LOOP_SECONDS}-${MAX_SESSION_LOOP_SECONDS} 的整数` };
602
+ next = { enabled: true, idleSeconds: seconds };
603
+ }
604
+ try {
605
+ const latest = await refreshSessionLoopTarget(this.sessionManager, route, target);
606
+ const updated = writeSessionLoopConfig(this.sessionManager, latest, next);
607
+ const payload = sessionLoopPayload(updated);
608
+ if (this.shouldSuppressCardTriggerResult(source, channel))
609
+ return null;
610
+ return { kind: 'command.result', text: `✅ idle 触发(${updated.level},session=${updated.session.id})已${payload.value ? `开启(${payload.idleSeconds} 秒)` : '关闭'}` };
611
+ }
612
+ catch (error) {
613
+ return { kind: 'command.error', text: `❌ ${error instanceof Error ? error.message : String(error)}` };
614
+ }
615
+ }
537
616
  const interruptPausedSessionBeforeReplacement = async (commandSession) => {
538
617
  if (commandSession && (this.processor?.isPauseRequested?.(commandSession.id) ?? false)) {
539
618
  this.eventBus.publish({
@@ -642,6 +721,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
642
721
  };
643
722
  // /help 命令不需要会话
644
723
  if (normalizedContent === '/help') {
724
+ const canReadLoop = canAccessSlashOperation('session.loop.current', 'relation');
725
+ const canUpdateLoop = canAccessSlashOperation('session.loop.update', 'relation');
645
726
  if (groupSlash) {
646
727
  const lines = [
647
728
  '群聊可用命令:',
@@ -653,6 +734,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
653
734
  ' /baseagent - 查看 Agent 后端',
654
735
  ' /perm - 查看权限模式',
655
736
  ' /mention - 查看群聊 @ 处理模式',
737
+ ...(canReadLoop ? [` /loop${canUpdateLoop ? ' [on [seconds]|off]' : ''} - 查看${canUpdateLoop ? '或设置' : ''}当前会话的单次闲置触发`] : []),
656
738
  ' /ask <选项> - 回答待处理问题',
657
739
  ' /pause - 在下一次工具调用前暂停任务',
658
740
  ' /resume - 继续已暂停的任务',
@@ -660,6 +742,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
660
742
  ...(isAdmin ? [
661
743
  ' /compact - 压缩会话上下文',
662
744
  ...(threadId ? [' /renew - 轮换当前话题的后端会话'] : []),
745
+ ' /memory - 查看本群关系记忆',
746
+ ' /skill - 查看技能列表',
747
+ ' /thread - 查看本群话题',
663
748
  ] : []),
664
749
  '',
665
750
  '模型、推理强度、后端与权限在群聊中仅支持查询;@ 模式可由管理员切换。',
@@ -695,6 +780,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
695
780
  '',
696
781
  '💬 聊天设置:',
697
782
  ...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`] : []),
783
+ ...(canReadLoop ? [` /loop${canUpdateLoop ? ' [on [seconds]|off]' : ''} - 查看${canUpdateLoop ? '或设置' : ''}当前会话的单次闲置触发`] : []),
698
784
  ...(canReadMentionmode || canSetMentionmode ? [` /mention${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '或切换' : ''}群聊 @ 处理模式`] : []),
699
785
  ] : []),
700
786
  ...(canReadPermission || canAnswerPermission ? [
@@ -739,6 +825,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
739
825
  '💬 聊天设置:',
740
826
  ` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '或切换' : ''}会话模式`,
741
827
  ] : []),
828
+ ...(canReadLoop ? ['', ` /loop${canUpdateLoop ? ' [on [seconds]|off]' : ''} - 查看${canUpdateLoop ? '或设置' : ''}当前会话的单次闲置触发`] : []),
742
829
  ...(canReadPermission || canAnswerPermission ? [
743
830
  '',
744
831
  '🔐 权限管理:',
@@ -786,6 +873,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
786
873
  ' /activity [all|text|none] - 查看/控制中间输出显示模式',
787
874
  ...(canReadChatmode || canSetChatmode ? [` /chatmode${canSetChatmode ? ' [interactive|proactive]' : ''} - 查看${canSetChatmode ? '/切换' : ''}会话模式(被动响应或主动推进)`] : []),
788
875
  ...(canReadMentionmode || canSetMentionmode ? [` /mention${canSetMentionmode ? ' [mention-only|disabled]' : ''} - 查看${canSetMentionmode ? '/切换' : ''}群聊 @ 处理模式`] : []),
876
+ ...(canReadLoop ? [` /loop${canUpdateLoop ? ' [on [seconds]|off]' : ''} - 查看${canUpdateLoop ? '或设置' : ''}当前会话的单次闲置触发`] : []),
877
+ '',
878
+ '🧠 上下文:',
879
+ ' /memory - 记忆概览与维护',
880
+ ' /skill - 技能列表与索引刷新',
881
+ ' /thread - 话题列表、更名与 summary',
789
882
  '',
790
883
  '🔐 权限管理:',
791
884
  ...(canReadPermission ? [' /perm - 查看当前权限模式'] : []),
@@ -848,9 +941,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
848
941
  }
849
942
  // /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
850
943
  if (normalizedContent === '/evolhelp') {
944
+ const canReadLoop = canAccessSlashOperation('session.loop.current', 'relation');
945
+ const canUpdateLoop = canAccessSlashOperation('session.loop.update', 'relation');
946
+ const loopHelp = canReadLoop || canUpdateLoop ? [{
947
+ command: '/loop',
948
+ ...(canUpdateLoop ? { args: '[on [seconds]|off]' } : {}),
949
+ description: '查看或设置当前主会话/话题的单次闲置触发',
950
+ category: '聊天设置',
951
+ roles: [identity.role],
952
+ }] : [];
851
953
  if (groupSlash) {
852
954
  const canSetMentionmode = canAccessSlashOperation('mentionmode.update', 'relation');
853
955
  const commands = [
956
+ ...loopHelp,
854
957
  { command: '/help', description: '显示帮助信息', category: '帮助', roles: ['visitor', 'member', 'admin', 'owner'] },
855
958
  { command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
856
959
  { command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
@@ -867,13 +970,24 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
867
970
  if (isAdmin) {
868
971
  commands.push({ command: '/compact', description: '压缩会话上下文', category: '会话管理', roles: ['admin', 'owner'] }, ...(threadId ? [{ command: '/renew', description: '轮换当前话题 backend', category: '会话管理', roles: ['admin', 'owner'] }] : []));
869
972
  }
870
- // 记忆管理紧跟权限管理
973
+ // 记忆管理紧跟权限管理;技能 / 话题管理跟随其后
871
974
  const groupMemoryEntries = memoryHelpEntries({ chatType: 'group', role: identity.role, isAdmin, isOwnerSelf: false });
872
975
  const lastPermIdx = commands.map(c => c.category).lastIndexOf('权限管理');
873
- commands.splice(lastPermIdx >= 0 ? lastPermIdx + 1 : commands.length, 0, ...groupMemoryEntries);
976
+ const insertAt = lastPermIdx >= 0 ? lastPermIdx + 1 : commands.length;
977
+ commands.splice(insertAt, 0, ...groupMemoryEntries);
978
+ const groupExtras = [
979
+ ...skillHelpEntries({ chatType: 'group', role: identity.role, isAdmin }),
980
+ ...topicHelpEntries({
981
+ chatType: 'group',
982
+ role: identity.role,
983
+ isAdmin,
984
+ peerKey: authSubject.peerKey,
985
+ }),
986
+ ];
987
+ commands.splice(insertAt + groupMemoryEntries.length, 0, ...groupExtras);
874
988
  return { kind: 'command.result', text: JSON.stringify({ commands, categories: [...new Set(commands.map(c => c.category))] }) };
875
989
  }
876
- const cmds = [];
990
+ const cmds = [...loopHelp];
877
991
  const canReadModel = canAccessSlashOperation('model.current', 'relation')
878
992
  || canAccessSlashOperation('model.list', 'relation');
879
993
  const canUseModel = canAccessSlashOperation('model.use', 'relation');
@@ -920,6 +1034,16 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
920
1034
  isAdmin,
921
1035
  isOwnerSelf: isOwner && !!memoryOwnerForHelp && (authSubject.principalId ?? userId) === memoryOwnerForHelp,
922
1036
  }));
1037
+ // 技能管理 / 话题管理(紧随记忆管理)
1038
+ cmds.push(...skillHelpEntries({ chatType: 'private', role: identity.role, isAdmin }));
1039
+ const topicHelpCtx = {
1040
+ chatType: activeChatType === 'group' ? 'group' : 'private',
1041
+ role: identity.role,
1042
+ isAdmin,
1043
+ peerKey: authSubject.peerKey,
1044
+ };
1045
+ if (canUseTopicSlash(topicHelpCtx))
1046
+ cmds.push(...topicHelpEntries(topicHelpCtx));
923
1047
  // 运维
924
1048
  cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] });
925
1049
  cmds.push({ command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] });
@@ -3130,11 +3254,23 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3130
3254
  channelId,
3131
3255
  agentName: 'system',
3132
3256
  chatmode: 'interactive',
3133
- replyContext: restartReplyContext ?? (chatType === 'group'
3134
- ? { delivery: { chatType: 'group', groupId: channelId } }
3135
- : chatType === 'private'
3136
- ? { delivery: { chatType: 'private' } }
3137
- : undefined),
3257
+ replyContext: {
3258
+ ...(restartReplyContext ?? (chatType === 'group'
3259
+ ? { delivery: { chatType: 'group', groupId: channelId } }
3260
+ : chatType === 'private'
3261
+ ? { delivery: { chatType: 'private' } }
3262
+ : {})),
3263
+ // This notice describes the process that is about to die. If it
3264
+ // cannot be handed to the gateway before the shutdown tears the
3265
+ // transport down, the replacement process must NOT replay it:
3266
+ // it already sends its own "restart succeeded" confirmation, and
3267
+ // a late copy of the pre-restart notice is confusing rather than
3268
+ // informative.
3269
+ metadata: {
3270
+ ...(restartReplyContext?.metadata ?? {}),
3271
+ processScopedDelivery: true,
3272
+ },
3273
+ },
3138
3274
  });
3139
3275
  await adapter.send(envelope, { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' });
3140
3276
  // 等待消息发送完成后再延迟 kill
@@ -3670,6 +3806,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3670
3806
  logger.debug('[CommandHandler] Backend session rename sync failed:', error);
3671
3807
  });
3672
3808
  }
3809
+ // P1.2: 若当前是话题会话,同步更新话题索引
3810
+ if (success && session.threadId) {
3811
+ try {
3812
+ const { ThreadService } = await import('../../core/thread/index.js');
3813
+ const selfAid = session.selfAID;
3814
+ const peer = session.channelId;
3815
+ if (selfAid && peer) {
3816
+ const svc = new ThreadService(this.sessionManager);
3817
+ await svc.renameThread({ selfAid, peer, threadId: session.threadId, name: newName });
3818
+ }
3819
+ }
3820
+ catch (err) {
3821
+ logger.debug('[CommandHandler] Thread index rename sync failed:', err);
3822
+ }
3823
+ }
3673
3824
  if (!success) {
3674
3825
  return { kind: 'command.error', text: `❌ 重命名失败` };
3675
3826
  }
@@ -3886,6 +4037,35 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3886
4037
  return { kind: 'command.error', text: `❌ 修复失败: ${error.message}` };
3887
4038
  }
3888
4039
  }
4040
+ // /skill 命令:技能查看与索引刷新(写操作请走 ec skill / ec-skill 技能)
4041
+ if (normalizedContent === '/skill' || normalizedContent.startsWith('/skill ')) {
4042
+ return await handleSkillSlash({
4043
+ content: normalizedContent,
4044
+ selfAid: authSubject.selfAid,
4045
+ chatType: activeChatType === 'group' ? 'group' : 'private',
4046
+ role: identity.role,
4047
+ isAdmin,
4048
+ // 注意:getSkillIndexRefresher 是 getter,必须调用取值;只 .bind 会把这个
4049
+ // getter 本身当成 refresher 传下去,导致 /skill refresh 永远返回「未知错误」。
4050
+ refresher: this.getSkillIndexRefresher?.(),
4051
+ // manifest 的 when 判定要用同一个 lifecycle 值,否则 /skill index 会误报未加载
4052
+ lifecycle: this.agentRegistry?.get?.(authSubject.selfAid ?? '')?.lifecycle ?? null,
4053
+ });
4054
+ }
4055
+ // /thread 命令:话题查看与管理
4056
+ if (normalizedContent === '/thread' || normalizedContent.startsWith('/thread ')) {
4057
+ return await handleTopicSlash({
4058
+ content: normalizedContent,
4059
+ selfAid: authSubject.selfAid,
4060
+ peerKey: authSubject.peerKey,
4061
+ chatType: activeChatType === 'group' ? 'group' : 'private',
4062
+ role: identity.role,
4063
+ isAdmin,
4064
+ threadId,
4065
+ sessionId: activeSession?.id,
4066
+ sessionManager: this.sessionManager,
4067
+ });
4068
+ }
3889
4069
  // /trigger 命令
3890
4070
  if (normalizedContent === '/memory' || normalizedContent.startsWith('/memory ')) {
3891
4071
  const memoryAgent = this.agentRegistry?.get?.(authSubject.selfAid ?? '');
@@ -3928,7 +4108,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3928
4108
  });
3929
4109
  if (triggerAuthDenied)
3930
4110
  return triggerAuthDenied;
3931
- const text = await this.handleTrigger(normalizedContent, channel, channelId, userId ?? '', isAdmin, messageId, chatType, threadId, isDaemonOwner, authSubject.canApprovePersistentFullAccess);
4111
+ const text = await this.handleTrigger(normalizedContent, channel, channelId, userId ?? '', isAdmin, messageId, activeChatType, threadId, isDaemonOwner, authSubject.canApprovePersistentFullAccess);
3932
4112
  return { kind: 'command.result', text };
3933
4113
  }
3934
4114
  return null;