evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -1,3 +1,4 @@
1
+ import { memoryHelpEntries } from './memory-slash.js';
1
2
  import { getBaseagentSnapshot, loadedBaseagentIds } from '../../agents/baseagent-availability.js';
2
3
  import { normalizePeer, writeScope } from '../model/config-scope.js';
3
4
  import { formatPeerKey } from '../relation/peer-identity.js';
@@ -18,13 +19,14 @@ import { refreshManagedPackageVersions, VERSION_INFO_PACKAGES } from '../../util
18
19
  import { resolveCliEntry, launchMonitorAndConfirm } from '../../utils/cross-platform.js';
19
20
  import { cachedCliVersion } from '../../utils/cross-platform.js';
20
21
  import { scanInstances } from '../../utils/instance-registry.js';
21
- import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
22
+ import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
22
23
  import { WEB_PACKAGE_NAME } from '../../product.js';
23
24
  import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
24
25
  import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
25
26
  import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
26
27
  import { displaySessionTitle } from '../session/session-title.js';
27
- import { buildSessionTurnList } from '../session/session-turns.js';
28
+ import { buildTranscriptTurns } from '../session/session-transcript.js';
29
+ import { buildForkHistorySnapshot, pageForkHistory } from '../session/session-manager.js';
28
30
  import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveCapabilityContext, updateCapabilityPolicy, } from '../capability/capability-manager.js';
29
31
  import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
30
32
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
@@ -41,18 +43,20 @@ import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
41
43
  import { logger } from '../../utils/logger.js';
42
44
  import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
43
45
  import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
44
- import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
46
+ import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateTaskActivityQueryArgs, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
45
47
  import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
48
+ import { TaskActivityStore } from '../task/task-activity-store.js';
46
49
  import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
47
50
  import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
48
51
  import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
49
52
  import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
50
53
  import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
51
- import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
54
+ import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, resolveMenuCatalogContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
52
55
  import { probeSessionActivity } from './slash-gate.js';
53
56
  import { dataRootFailure, dataRootResolver } from './data-root-resolver.js';
54
57
  import { agentStorageMenuService, isAgentStorageMenuRequest } from './agent-storage-menu.js';
55
- import { isArchivedTopicLable, normalizeTopicLable, resolveTopicArchiveFilter, } from '../session/session-mapper.js';
58
+ import { MAX_AUN_ATTACHMENT_SIZE } from '../../aun/msg/upload.js';
59
+ import { normalizeTopicLabel, normalizeTopicStatus, resolveTopicArchiveFilter, } from '../session/session-mapper.js';
56
60
  /**
57
61
  * 获取 baseagent CLI 的版本号(claude/gemini/codex)。
58
62
  * 失败返回 null(命令不存在或执行失败)。
@@ -62,8 +66,8 @@ function getBaseagentVersion(cmd) {
62
66
  }
63
67
  const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
64
68
  const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
65
- /** menu file: fetch 文件大小上限(与 /file 一致) */
66
- const FILE_FETCH_MAX_SIZE = 10 * 1024 * 1024;
69
+ /** menu file: fetch 文件大小上限(与 /file 和 AUN sendFile 一致) */
70
+ const FILE_FETCH_MAX_SIZE = MAX_AUN_ATTACHMENT_SIZE;
67
71
  /** menu file: query 的 sha256 仅对 ≤ 2 MB 文件计算,超过返回 null(见设计文档 §7 决策 2) */
68
72
  const FILE_HASH_MAX_SIZE = 2 * 1024 * 1024;
69
73
  const FILE_LIST_DEFAULT_LIMIT = 500;
@@ -184,12 +188,31 @@ function resolveCapabilityTarget(params) {
184
188
  function getRenameName(args) {
185
189
  return (args?.name ?? args?.title ?? args?.value ?? '').toString().trim();
186
190
  }
191
+ export function buildTopicForkSourcePayload(session) {
192
+ const source = session.metadata?.forkSource;
193
+ if (!source)
194
+ return undefined;
195
+ return {
196
+ sourceThreadId: source.threadId,
197
+ sourceAssistantMessageId: source.assistantMessageId,
198
+ sourceTurn: source.turn,
199
+ createdAt: source.createdAt,
200
+ historyVersion: source.historyVersion ?? null,
201
+ historyCount: source.historyCount ?? null,
202
+ historyDigest: source.historyDigest ?? null,
203
+ };
204
+ }
187
205
  function buildSessionPayload(session, name) {
188
206
  const payload = { id: session.id, name };
189
- if (session.lable)
190
- payload.lable = session.lable;
207
+ if (session.label)
208
+ payload.label = session.label;
209
+ if (session.threadId)
210
+ payload.topicStatus = session.topicStatus ?? 'pending';
191
211
  if (session.agentSessionId)
192
212
  payload.agentSessionId = session.agentSessionId;
213
+ const forkSource = buildTopicForkSourcePayload(session);
214
+ if (forkSource)
215
+ payload.forkSource = forkSource;
193
216
  return payload;
194
217
  }
195
218
  async function findMainSessionTarget(sessionManager, channel, channelId, target, activeSession) {
@@ -389,6 +412,11 @@ async function getAvailableEfforts(agent, model) {
389
412
  const baseagent = agent?.baseagent || agent?.name;
390
413
  return (TRANSPORT_EFFORTS[baseagent] ?? []);
391
414
  }
415
+ function isDaemonControlSubject(subject) {
416
+ return subject.isDaemonOwner === true
417
+ || subject.processRole === 'daemon-owner'
418
+ || subject.processRole === 'daemon-service';
419
+ }
392
420
  function modelDisplayLabel(agent, model) {
393
421
  const full = agent.resolveModelId?.(model);
394
422
  return full && full !== model ? `${model} (${full})` : model;
@@ -642,7 +670,7 @@ function resolveMenuMentionModeTarget(params) {
642
670
  function readMenuMentionMode(target, fallback = null, fallbackSource = null) {
643
671
  try {
644
672
  const resolved = resolveEffectiveFieldWithSource(target.fieldPath, target.sel, { cache: true });
645
- if (resolved.value === 'disabled' || resolved.value === 'mention-only') {
673
+ if (resolved.value === 'disabled' || resolved.value === 'mention-only' || resolved.value === 'auto') {
646
674
  return { value: resolved.value, source: toMenuSource(resolved.source) };
647
675
  }
648
676
  }
@@ -943,12 +971,14 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
943
971
  return intent('model.effort.current', modelScope);
944
972
  if (cmdBase === '/chatmode')
945
973
  return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
946
- if (cmdBase === '/mentionmode')
974
+ if (cmdBase === '/mention')
947
975
  return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
948
976
  if (cmdBase === '/perm')
949
977
  return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
950
978
  if (cmdBase === '/activity')
951
979
  return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
980
+ if (cmdBase === '/task')
981
+ return intent('task.activity.read', 'relation');
952
982
  if (cmdBase === '/observable')
953
983
  return intent('observable.current', 'agent');
954
984
  if (cmdBase === '/capability')
@@ -963,7 +993,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
963
993
  return intent(fromControlChannel && !args?.aid ? 'agent.list' : 'agent.show', fromControlChannel ? 'control' : 'agent');
964
994
  }
965
995
  if (cmdBase === '/trigger')
966
- return intent('trigger.list', 'relation');
996
+ return intent(args?.view === 'history' ? 'trigger.history'
997
+ : args?.view === 'detail' ? 'trigger.show' : 'trigger.list', 'relation');
967
998
  }
968
999
  if (verb === 'options') {
969
1000
  if (cmdBase === '/system')
@@ -988,7 +1019,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
988
1019
  return intent('model.effort.current', modelScope);
989
1020
  if (cmdBase === '/chatmode')
990
1021
  return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
991
- if (cmdBase === '/mentionmode')
1022
+ if (cmdBase === '/mention')
992
1023
  return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
993
1024
  if (cmdBase === '/activity')
994
1025
  return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
@@ -1004,7 +1035,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
1004
1035
  return intent('model.effort', modelScope, { effort: value });
1005
1036
  if (cmdBase === '/chatmode')
1006
1037
  return intent('chatmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
1007
- if (cmdBase === '/mentionmode')
1038
+ if (cmdBase === '/mention')
1008
1039
  return intent('mentionmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
1009
1040
  if (cmdBase === '/activity')
1010
1041
  return intent('activity.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
@@ -1142,7 +1173,12 @@ async function authorizeMenuIntent(params) {
1142
1173
  }
1143
1174
  const source = params.source ?? 'menu';
1144
1175
  intent.source = source;
1145
- const decision = await authorizeOperation({ source, intent, subject });
1176
+ const decision = await authorizeOperation({
1177
+ source,
1178
+ intent,
1179
+ subject,
1180
+ auditMetadata: subject.auditMetadata,
1181
+ });
1146
1182
  if (!decision.allow) {
1147
1183
  logger.warn(`[MenuAuth] denied operation=${intent.operation} scope=${intent.scope} source=${source} selfAid=${subject.selfAid ?? '<none>'} actor=${subject.actorId ?? '<none>'} role=${subject.role} processRole=${subject.processRole} dataScope=${subject.dataScope} daemonOwner=${subject.isDaemonOwner} control=${subject.fromControlChannel} reason=${decision.reason}`);
1148
1184
  if (intent.operation.startsWith('role.') || intent.operation.startsWith('connect.')) {
@@ -1431,7 +1467,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1431
1467
  { value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
1432
1468
  { value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
1433
1469
  ] } }] : []),
1434
- ...(canSetMentionmode ? [{ cmd: '/mentionmode', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1470
+ ...(canSetMentionmode ? [{ cmd: '/mention', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1435
1471
  { value: 'mention-only', label: '@ 提及', desc: '仅在被 @ 提及时响应' },
1436
1472
  { value: 'disabled', label: '全部响应', desc: '响应群内所有消息' },
1437
1473
  ] } }] : []),
@@ -1496,6 +1532,26 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1496
1532
  ]
1497
1533
  });
1498
1534
  }
1535
+ // 记忆管理(紧跟权限管理):条目与 evolhelp 同源,无参数的一点即执行,需要 slug 的弹输入框
1536
+ const memoryOwnerAid = authSubject?.selfAid
1537
+ ? this?.agentRegistry?.get?.(authSubject.selfAid)?.config?.owners?.[0]
1538
+ : undefined;
1539
+ const memoryEntries = isControlScope ? [] : memoryHelpEntries({
1540
+ chatType: normalizedChatType,
1541
+ role,
1542
+ isAdmin,
1543
+ isOwnerSelf: isOwner && !!memoryOwnerAid && authSubject?.principalId === memoryOwnerAid,
1544
+ });
1545
+ if (memoryEntries.length > 0) {
1546
+ items.push({
1547
+ group: '记忆管理',
1548
+ commands: memoryEntries.map(e => ({
1549
+ cmd: e.command,
1550
+ label: e.description,
1551
+ ...(e.args ? { args: e.args, next: { type: 'text' } } : {}),
1552
+ })),
1553
+ });
1554
+ }
1499
1555
  items.push({
1500
1556
  group: isAdmin ? '运维' : '其他',
1501
1557
  commands: [
@@ -1697,10 +1753,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1697
1753
  const isAdmin = isManagementRole(role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
1698
1754
  if (!triggerScheduler)
1699
1755
  return [];
1700
- const list = triggerScheduler.list({ all: scope === 'all' });
1701
- return list
1702
- .filter((definition) => this.canAccessTriggerDefinition(definition, userId ?? '', channel, isAdmin))
1703
- .map((definition) => {
1756
+ const list = triggerScheduler.list({ all: scope === 'all' })
1757
+ .filter((definition) => this.canAccessTriggerDefinition(definition, userId ?? '', channel, isAdmin));
1758
+ return list.map((definition) => {
1704
1759
  const view = this.definitionToTriggerView(definition, triggerScheduler);
1705
1760
  return {
1706
1761
  ...view,
@@ -1724,7 +1779,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1724
1779
  const agent = this.getAgent(channel, source.baseagent);
1725
1780
  if (!agent.capabilities?.forkAtTurn || !agent.getSessionMessages || !agent.forkSessionAt)
1726
1781
  return [];
1727
- const turns = buildSessionTurnList(await agent.getSessionMessages(source.agentSessionId, source.projectPath));
1782
+ const turns = buildTranscriptTurns(await agent.getSessionMessages(source.agentSessionId, source.projectPath));
1728
1783
  return turns.map((turn) => ({
1729
1784
  value: turn.assistantUuid,
1730
1785
  label: `#${turn.index} ${turn.userContent}`,
@@ -1735,13 +1790,17 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1735
1790
  userMessageId: turn.userUuid,
1736
1791
  userTextHash: turn.userTextHash,
1737
1792
  assistantTextHashes: turn.assistantTextHashes,
1793
+ assistantTextAnchors: turn.assistantTextAnchors,
1794
+ toolResultAnchors: turn.toolResultAnchors,
1795
+ forkAtMessage: agent.capabilities.forkAtMessage === true,
1796
+ forkHistoryVersion: 1,
1738
1797
  }));
1739
1798
  }
1740
- const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
1741
- let lable;
1742
- if (hasLable) {
1799
+ const hasLabel = args && (Object.hasOwn(args, 'label') || Object.hasOwn(args, 'lable'));
1800
+ let label;
1801
+ if (hasLabel) {
1743
1802
  try {
1744
- lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
1803
+ label = normalizeTopicLabel(Object.hasOwn(args, 'label') ? args.label : args.lable);
1745
1804
  }
1746
1805
  catch (error) {
1747
1806
  throw { code: 'INVALID_ARGS', message: error.message };
@@ -1749,15 +1808,23 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1749
1808
  }
1750
1809
  let archiveFilter;
1751
1810
  try {
1752
- const archived = args?.archived === undefined && isArchivedTopicLable(lable) ? true : args?.archived;
1753
- archiveFilter = resolveTopicArchiveFilter(archived);
1811
+ archiveFilter = resolveTopicArchiveFilter(args?.archived === undefined && args?.topicStatus === 'archived' ? true : args?.archived);
1754
1812
  }
1755
1813
  catch (error) {
1756
1814
  throw { code: 'INVALID_ARGS', message: error.message };
1757
1815
  }
1758
1816
  const sessions = await this.sessionManager.listSessions(channel, channelId, { archiveFilter });
1817
+ let topicStatus;
1818
+ if (args?.topicStatus !== undefined) {
1819
+ try {
1820
+ topicStatus = normalizeTopicStatus(args.topicStatus);
1821
+ }
1822
+ catch (error) {
1823
+ throw { code: 'INVALID_ARGS', message: error.message };
1824
+ }
1825
+ }
1759
1826
  return sessions
1760
- .filter((s) => !!s.threadId && (!hasLable || s.lable === lable))
1827
+ .filter((s) => !!s.threadId && (!hasLabel || s.label === label) && (!topicStatus || s.topicStatus === topicStatus))
1761
1828
  .map((s) => this.buildTopicMenuItem(s));
1762
1829
  }
1763
1830
  if (cmd === '/s' || cmd === '/session' || cmd === '/del') {
@@ -1828,7 +1895,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1828
1895
  agent = this.getAgent(channel, target.baseagent);
1829
1896
  }
1830
1897
  catch (error) {
1831
- if (subject.processRole === 'daemon-service') {
1898
+ if (isDaemonControlSubject(subject)) {
1832
1899
  return [{ value: target.baseagent, label: target.baseagent, selected: true }];
1833
1900
  }
1834
1901
  throw error;
@@ -1899,7 +1966,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1899
1966
  agent = this.getAgent(channel, target.baseagent);
1900
1967
  }
1901
1968
  catch (error) {
1902
- if (subject.processRole === 'daemon-service') {
1969
+ if (isDaemonControlSubject(subject)) {
1903
1970
  return ['low', 'medium', 'high', 'auto'].map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e }));
1904
1971
  }
1905
1972
  throw error;
@@ -1931,7 +1998,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1931
1998
  { value: 'proactive', label: '主动模式', selected: currentMode === 'proactive' },
1932
1999
  ];
1933
2000
  }
1934
- if (cmd === '/mentionmode') {
2001
+ if (cmd === '/mention') {
1935
2002
  const target = resolveMenuMentionModeTarget.call(this, {
1936
2003
  args,
1937
2004
  session,
@@ -1949,6 +2016,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1949
2016
  return [
1950
2017
  { value: 'mention-only', label: '@提及时响应', selected: currentMode === 'mention-only' },
1951
2018
  { value: 'disabled', label: '所有消息响应', selected: currentMode === 'disabled' },
2019
+ { value: 'auto', label: '根据上下文自动判断', selected: currentMode === 'auto' },
1952
2020
  ];
1953
2021
  }
1954
2022
  if (cmd === '/perm') {
@@ -2062,6 +2130,59 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2062
2130
  explicitChatType, channel, channelId, userId, fromControlChannel, source,
2063
2131
  });
2064
2132
  }
2133
+ if (cmdBase === '/task') {
2134
+ const invalid = validateTaskActivityQueryArgs(args);
2135
+ if (invalid)
2136
+ return { error: invalid.message, code: invalid.code };
2137
+ if (fromControlChannel || !session || !subject.selfAid || !subject.actorId || !subject.allowAccess) {
2138
+ return { error: 'Task is not available in this session', code: 'FORBIDDEN' };
2139
+ }
2140
+ try {
2141
+ let boundSubject = subject;
2142
+ if (session.chatType === 'group') {
2143
+ const groupId = session.metadata?.groupId || session.channelId;
2144
+ const context = await resolveMenuCatalogContext({
2145
+ subject,
2146
+ channelType: subject.channelType || channel.split('#')[0],
2147
+ channelScope: 'agent',
2148
+ channelId,
2149
+ request: { kind: 'group_relation', groupId },
2150
+ });
2151
+ if (context.groupId !== groupId)
2152
+ return { error: 'Task is not available in this group', code: 'FORBIDDEN' };
2153
+ boundSubject = context.subject;
2154
+ }
2155
+ const authDenied = await authorizeMenuIntent.call(this, {
2156
+ intent: buildMenuIntent('query', cmdBase, args, undefined, undefined, false),
2157
+ identity, subject: boundSubject, session, explicitChatType, channel, channelId,
2158
+ userId, fromControlChannel, source,
2159
+ });
2160
+ if (authDenied)
2161
+ return authDenied;
2162
+ const store = new TaskActivityStore(subject.selfAid);
2163
+ const meta = store.getMeta(args.taskId);
2164
+ if (!meta)
2165
+ return { error: 'Task not found', code: 'NOT_FOUND' };
2166
+ const groupId = session.metadata?.groupId || session.channelId;
2167
+ if (meta.selfAid !== subject.selfAid || meta.sessionId !== session.id
2168
+ || meta.channel !== (subject.channelType || channel.split('#')[0])
2169
+ || meta.chatType !== session.chatType
2170
+ || (meta.chatType === 'group'
2171
+ ? meta.groupId !== groupId || meta.channelId !== groupId
2172
+ : meta.peerId !== subject.actorId || meta.channelId !== subject.actorId)) {
2173
+ return { error: 'Task is not available in this session', code: 'FORBIDDEN' };
2174
+ }
2175
+ return { data: args.view === 'activities'
2176
+ ? store.readList(args.taskId, args.offset ?? 0, args.limit ?? 50)
2177
+ : store.readDetail(args.taskId, args.activityId) };
2178
+ }
2179
+ catch (error) {
2180
+ if (error?.code === 'INVALID_CONTEXT') {
2181
+ return { error: 'Task is not available in this group', code: 'FORBIDDEN' };
2182
+ }
2183
+ return menuResultFailure(error);
2184
+ }
2185
+ }
2065
2186
  const authDenied = await authorizeMenuIntent.call(this, {
2066
2187
  intent: buildMenuIntent('query', cmdBase, args, undefined, undefined, fromControlChannel),
2067
2188
  identity,
@@ -2076,6 +2197,40 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2076
2197
  });
2077
2198
  if (authDenied)
2078
2199
  return authDenied;
2200
+ if (cmdBase === '/trigger') {
2201
+ if (args?.view !== 'detail' && args?.view !== 'history') {
2202
+ return { error: 'Unsupported trigger query view', code: 'INVALID_ARGS' };
2203
+ }
2204
+ const triggerId = args?.triggerId;
2205
+ if (typeof triggerId !== 'string' || !triggerId.trim()) {
2206
+ return { error: 'triggerId is required', code: 'INVALID_ARGS' };
2207
+ }
2208
+ const limit = args?.limit ?? 50;
2209
+ if (args.view === 'history' && (!Number.isInteger(limit) || limit < 1 || limit > 200)) {
2210
+ return { error: 'limit must be between 1 and 200', code: 'INVALID_ARGS' };
2211
+ }
2212
+ const scheduler = this.getTriggerSchedulerForChannel?.(channel);
2213
+ if (!scheduler)
2214
+ return { error: 'Trigger is unavailable', code: 'NOT_SUPPORTED' };
2215
+ const role = identity.role;
2216
+ const isAdmin = isManagementRole(role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
2217
+ const definition = scheduler.list({ all: true }).find((item) => item.id === triggerId);
2218
+ if (!definition || !this.canAccessTriggerDefinition(definition, userId ?? '', channel, isAdmin)) {
2219
+ return { error: 'Trigger not found', code: 'NOT_FOUND' };
2220
+ }
2221
+ if (args.view === 'detail') {
2222
+ const details = scheduler.show(triggerId, { includeScriptPreview: false });
2223
+ return { data: {
2224
+ trigger: this.definitionToTriggerView(definition, scheduler),
2225
+ active: details.active,
2226
+ recentRuns: details.recentRuns,
2227
+ } };
2228
+ }
2229
+ return { data: {
2230
+ triggerId,
2231
+ runs: scheduler.recentRuns(triggerId, limit),
2232
+ } };
2233
+ }
2079
2234
  if (cmdBase === '/agent-data-root') {
2080
2235
  if (args && Object.keys(args).length > 0) {
2081
2236
  return { error: 'agent-data-root does not accept args', code: 'INVALID_ARGUMENT' };
@@ -2272,6 +2427,69 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2272
2427
  const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
2273
2428
  if (!topic)
2274
2429
  return { error: '话题不存在', code: 'NOT_FOUND' };
2430
+ if (args?.view === 'fork-history') {
2431
+ const source = topic.metadata?.forkSource;
2432
+ if (topic.chatType !== 'private' || !source) {
2433
+ return { error: '该话题没有分叉历史', code: 'NOT_SUPPORTED' };
2434
+ }
2435
+ const limit = args?.limit === undefined ? 20 : args.limit;
2436
+ const before = args?.before;
2437
+ if (!Number.isInteger(limit) || limit < 1 || limit > 20
2438
+ || (before !== undefined && (!Number.isInteger(before) || before < 1))) {
2439
+ return { error: '分叉历史分页参数无效', code: 'INVALID_ARGUMENT' };
2440
+ }
2441
+ try {
2442
+ let current = topic;
2443
+ let snapshot = this.sessionManager.readForkHistory(current);
2444
+ if (!snapshot) {
2445
+ const recovered = await this.sessionManager.attachForkHistory(topic.id);
2446
+ if (recovered) {
2447
+ current = recovered;
2448
+ snapshot = this.sessionManager.readForkHistory(current);
2449
+ }
2450
+ }
2451
+ if (!snapshot) {
2452
+ const candidate = async (session) => {
2453
+ if (!session?.agentSessionId || session.channel !== channel || session.channelId !== channelId)
2454
+ return undefined;
2455
+ const agent = this.getAgent(channel, session.baseagent);
2456
+ if (!agent.getSessionMessages)
2457
+ return undefined;
2458
+ try {
2459
+ return buildForkHistorySnapshot(await agent.getSessionMessages(session.agentSessionId, session.projectPath), source.assistantMessageId);
2460
+ }
2461
+ catch {
2462
+ return undefined;
2463
+ }
2464
+ };
2465
+ const projected = await candidate(topic)
2466
+ ?? await candidate(await this.sessionManager.getSessionById(source.sessionId));
2467
+ if (!projected)
2468
+ return { error: '无法从现有会话恢复分叉历史', code: 'INVALID_STATE' };
2469
+ current = await this.sessionManager.attachForkHistory(topic.id, projected) ?? topic;
2470
+ snapshot = this.sessionManager.readForkHistory(current);
2471
+ }
2472
+ if (!snapshot)
2473
+ return { error: '分叉历史未持久化', code: 'INVALID_STATE' };
2474
+ if (before !== undefined && before > snapshot.items.length) {
2475
+ return { error: '分叉历史分页游标无效', code: 'INVALID_ARGUMENT' };
2476
+ }
2477
+ const page = pageForkHistory(snapshot, before, limit);
2478
+ const forkSource = current.metadata?.forkSource;
2479
+ return { data: {
2480
+ threadId: current.threadId,
2481
+ sourceAssistantMessageId: source.assistantMessageId,
2482
+ historyVersion: 1,
2483
+ historyDigest: forkSource?.historyDigest,
2484
+ createdAt: source.createdAt,
2485
+ ...page,
2486
+ } };
2487
+ }
2488
+ catch (error) {
2489
+ logger.warn(`[MenuHandler] Fork history read failed: ${error}`);
2490
+ return { error: '分叉历史读取失败,请稍后重试', code: 'INVALID_STATE' };
2491
+ }
2492
+ }
2275
2493
  if (args?.view === 'subtasks') {
2276
2494
  return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(topic.id) } };
2277
2495
  }
@@ -2293,7 +2511,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2293
2511
  const data = {
2294
2512
  threadId: topic.threadId,
2295
2513
  name: topic.name || null,
2296
- lable: topic.lable || null,
2514
+ label: topic.label || null,
2515
+ topicStatus: topic.topicStatus ?? 'pending',
2297
2516
  agentSessionId: topic.agentSessionId || null,
2298
2517
  status: isProcessing ? 'processing' : 'idle',
2299
2518
  createdAt: topic.createdAt,
@@ -2415,7 +2634,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2415
2634
  },
2416
2635
  };
2417
2636
  }
2418
- if (cmdBase === '/mentionmode') {
2637
+ if (cmdBase === '/mention') {
2419
2638
  const chatType = session?.chatType ?? explicitChatType ?? 'private';
2420
2639
  if (chatType !== 'group') {
2421
2640
  return { error: 'mentionMode 仅在群聊会话中有效', code: 'NOT_APPLICABLE' };
@@ -3086,8 +3305,8 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
3086
3305
  },
3087
3306
  };
3088
3307
  }
3089
- if (cmdBase === '/mentionmode') {
3090
- if (arg !== 'mention-only' && arg !== 'disabled' && arg !== 'clear') {
3308
+ if (cmdBase === '/mention') {
3309
+ if (arg !== 'mention-only' && arg !== 'disabled' && arg !== 'auto' && arg !== 'clear') {
3091
3310
  return { error: `无效模式: ${arg}`, code: 'INVALID_VALUE' };
3092
3311
  }
3093
3312
  const chatType = session?.chatType;
@@ -3521,8 +3740,6 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3521
3740
  if (!trigger)
3522
3741
  return { error: '触发器不存在或无权限', code: 'NOT_FOUND' };
3523
3742
  if (action === 'enable' && trigger.execution.permissionMode === 'fullaccess') {
3524
- if (!isFullAccessEnabled())
3525
- return { error: 'fullaccess 功能未启用', code: 'NOT_SUPPORTED' };
3526
3743
  if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')
3527
3744
  return { error: '只有 daemon-owner 可以启用 fullaccess Trigger', code: 'FORBIDDEN' };
3528
3745
  if (subject.canApprovePersistentFullAccess === false) {
@@ -3619,11 +3836,11 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3619
3836
  return { error: `不支持的 trigger action: ${action}`, code: 'INVALID_ARGS' };
3620
3837
  }
3621
3838
  if (cmdBase === '/topic') {
3622
- const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
3623
- let lable;
3624
- if (hasLable && (action === 'fork' || action === 'label')) {
3839
+ const hasLabel = args && (Object.hasOwn(args, 'label') || Object.hasOwn(args, 'lable'));
3840
+ let label;
3841
+ if (hasLabel && (action === 'fork' || action === 'label')) {
3625
3842
  try {
3626
- lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
3843
+ label = normalizeTopicLabel(Object.hasOwn(args, 'label') ? args.label : args.lable);
3627
3844
  }
3628
3845
  catch (error) {
3629
3846
  return { error: error.message, code: 'INVALID_ARGS' };
@@ -3652,7 +3869,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3652
3869
  : null;
3653
3870
  if (requestId
3654
3871
  && forkSource?.requestId === requestId
3655
- && forkSource.assistantMessageId === sourceAssistantMessageId
3872
+ && (forkSource.requestedAssistantMessageId ?? forkSource.assistantMessageId) === sourceAssistantMessageId
3656
3873
  && forkSource.threadId === requestedSourceThreadId) {
3657
3874
  return {
3658
3875
  data: {
@@ -3704,25 +3921,33 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3704
3921
  try {
3705
3922
  const executeFork = async (current, currentAgent) => {
3706
3923
  const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
3707
- const turns = buildSessionTurnList(messages);
3708
- // fork-turns resolves bubble aliases to this canonical, complete-turn
3709
- // anchor. Never fall back to a runner iteration or a guessed index.
3710
- const forkTurn = turns.find(turn => turn.assistantUuid === sourceAssistantMessageId);
3711
- if (!forkTurn)
3712
- return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
3713
- const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, forkTurn.assistantUuid, name || undefined, { sessionId: current.id, permissionMode: subject.permissionMode, role: subject.role });
3924
+ const turns = buildTranscriptTurns(messages);
3925
+ const matchingTurns = turns.filter(turn => turn.userUuid === sourceAssistantMessageId
3926
+ || turn.assistantUuids?.includes(sourceAssistantMessageId)
3927
+ || turn.toolResultAnchors.some(anchor => anchor.messageId === sourceAssistantMessageId));
3928
+ if (matchingTurns.length !== 1)
3929
+ return { error: '分叉点已失效或无法唯一定位,请刷新历史后重试', code: 'NOT_FOUND' };
3930
+ const forkTurn = matchingTurns[0];
3931
+ if (!currentAgent.capabilities?.forkAtMessage
3932
+ && sourceAssistantMessageId !== forkTurn.assistantUuid) {
3933
+ return { error: '当前 Agent 无法在所选消息处精确分叉', code: 'NOT_SUPPORTED' };
3934
+ }
3935
+ const actualSourceMessageId = sourceAssistantMessageId;
3936
+ const historySnapshot = buildForkHistorySnapshot(messages, actualSourceMessageId);
3937
+ const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, actualSourceMessageId, name || undefined, { sessionId: current.id, permissionMode: subject.permissionMode, role: subject.role });
3714
3938
  const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
3715
3939
  name: name || undefined,
3716
- ...(hasLable ? { lable: lable ?? null } : {}),
3940
+ ...(hasLabel ? { label: label ?? null } : {}),
3717
3941
  creatorPeerId: userId,
3718
- sourceAssistantMessageId: forkTurn.assistantUuid,
3942
+ sourceAssistantMessageId: actualSourceMessageId,
3719
3943
  sourceTurn: forkTurn.index,
3944
+ historySnapshot,
3720
3945
  requestId,
3721
3946
  });
3722
3947
  await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
3723
3948
  evolcoreSessionId: topic.id,
3724
3949
  sourceSessionId: current.id,
3725
- sourceAssistantMessageId: forkTurn.assistantUuid,
3950
+ sourceAssistantMessageId: actualSourceMessageId,
3726
3951
  sourceTurn: forkTurn.index,
3727
3952
  }).catch((error) => {
3728
3953
  logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
@@ -3733,7 +3958,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3733
3958
  sourceSessionId: current.id,
3734
3959
  name: topic.name,
3735
3960
  threadId: topic.threadId,
3736
- sourceAssistantMessageId: forkTurn.assistantUuid,
3961
+ sourceAssistantMessageId: actualSourceMessageId,
3737
3962
  sourceTurn: forkTurn.index,
3738
3963
  });
3739
3964
  return {
@@ -3745,7 +3970,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3745
3970
  threadId: topic.threadId,
3746
3971
  sourceSessionId: current.id,
3747
3972
  sourceThreadId: current.threadId || null,
3748
- sourceAssistantMessageId: forkTurn.assistantUuid,
3973
+ sourceAssistantMessageId: actualSourceMessageId,
3749
3974
  sourceTurn: forkTurn.index,
3750
3975
  },
3751
3976
  },
@@ -3834,7 +4059,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3834
4059
  topicForkTargetsInFlight.delete(targetKey);
3835
4060
  }
3836
4061
  }
3837
- if (action !== 'delete' && action !== 'rename' && action !== 'label') {
4062
+ if (action !== 'delete' && action !== 'rename' && action !== 'label' && action !== 'status') {
3838
4063
  return { error: `不支持的 topic action: ${action}`, code: 'NOT_SUPPORTED' };
3839
4064
  }
3840
4065
  const target = (args?.target ?? '').toString().trim();
@@ -3843,8 +4068,17 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3843
4068
  const renameName = action === 'rename' ? getRenameName(args) : '';
3844
4069
  if (action === 'rename' && !renameName)
3845
4070
  return { error: '缺少 args.name', code: 'MISSING_VALUE' };
3846
- if (action === 'label' && !hasLable)
3847
- return { error: '缺少 args.lable', code: 'MISSING_VALUE' };
4071
+ if (action === 'label' && !hasLabel)
4072
+ return { error: '缺少 args.label', code: 'MISSING_VALUE' };
4073
+ let topicStatus;
4074
+ if (action === 'status') {
4075
+ try {
4076
+ topicStatus = normalizeTopicStatus(args?.topicStatus);
4077
+ }
4078
+ catch (error) {
4079
+ return { error: error.message, code: 'INVALID_ARGS' };
4080
+ }
4081
+ }
3848
4082
  const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
3849
4083
  if (!topic)
3850
4084
  return { error: '话题不存在', code: 'NOT_FOUND' };
@@ -3854,19 +4088,25 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3854
4088
  ? 'private'
3855
4089
  : this.resolveMenuChatType(channel, channelId, explicitChatType);
3856
4090
  if (!this.canDeleteTopic(identity.role, chatType, topic, userId)) {
3857
- return { error: action === 'label' ? '无权限修改话题分类' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
4091
+ return { error: action === 'label' ? '无权限修改话题分类' : action === 'status' ? '无权限修改话题状态' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
3858
4092
  }
3859
4093
  if (action === 'label') {
3860
- const success = await this.sessionManager.setTopicLable(topic.id, lable ?? null);
4094
+ const success = await this.sessionManager.setTopicLabel(topic.id, label ?? null);
3861
4095
  if (!success)
3862
4096
  return { error: '话题不存在', code: 'NOT_FOUND' };
3863
4097
  return {
3864
4098
  data: {
3865
4099
  action: 'label', success: true,
3866
- topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, lable: lable ?? null },
4100
+ topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, label: label ?? null },
3867
4101
  },
3868
4102
  };
3869
4103
  }
4104
+ if (action === 'status') {
4105
+ const success = await this.sessionManager.setTopicStatus(topic.id, topicStatus);
4106
+ if (!success)
4107
+ return { error: '话题不存在', code: 'NOT_FOUND' };
4108
+ return { data: { action: 'status', success: true, topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, topicStatus } } };
4109
+ }
3870
4110
  if (action === 'rename') {
3871
4111
  const newName = renameName;
3872
4112
  const existing = await this.sessionManager.getSessionByName?.(channel, channelId, newName);
@@ -4513,6 +4753,14 @@ async function execMenuForSystemControl(payload, context) {
4513
4753
  ecwebManagement: context.source === 'ecweb',
4514
4754
  canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
4515
4755
  trustedProcessRole: context.source === 'ecweb' ? 'daemon-owner' : undefined,
4756
+ auditMetadata: context.source === 'ecweb'
4757
+ ? {
4758
+ ecwebRequestId: context.ecwebRequestId,
4759
+ targetAgentAid: targetAid,
4760
+ endpoint: context.endpoint,
4761
+ logicalToolName: context.logicalToolName,
4762
+ }
4763
+ : undefined,
4516
4764
  });
4517
4765
  const trustedIdentity = trustedSubject.identity;
4518
4766
  const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
@@ -4660,6 +4908,10 @@ export async function execMenuForEcweb(payload, trusted) {
4660
4908
  const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
4661
4909
  ? trusted.actorAid
4662
4910
  : owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
4911
+ const ecwebRequestId = trusted.ecwebRequestId
4912
+ ?? (id !== undefined && id !== null && String(id) ? String(id) : crypto.randomUUID());
4913
+ const logicalToolName = trusted.logicalToolName
4914
+ ?? (typeof name === 'string' && name.trim() ? name.trim() : menuCommandToken(payload?.cmd) ?? payload?.type);
4663
4915
  return execMenuForSystemControl.call(this, payload, {
4664
4916
  source: 'ecweb',
4665
4917
  actorAid: userId,
@@ -4671,6 +4923,9 @@ export async function execMenuForEcweb(payload, trusted) {
4671
4923
  // approve persistent fullaccess without owner-bound token metadata.
4672
4924
  canApprovePersistentFullAccess: true,
4673
4925
  owners,
4926
+ ecwebRequestId,
4927
+ endpoint: trusted.endpoint ?? 'ECWeb menu.exec',
4928
+ logicalToolName,
4674
4929
  });
4675
4930
  }
4676
4931
  /** Control AID entry: daemon-owner and valid ECWeb token modes share the same