evolcore 0.0.27 → 0.0.29-test.0

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 (195) hide show
  1. package/CHANGELOG.md +38 -15
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +29 -14
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +13 -2
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex/diagnostics.js +4 -3
  9. package/dist/agents/codex-runner.js +444 -106
  10. package/dist/agents/gemini-runner.js +35 -17
  11. package/dist/aun/group/history.js +130 -90
  12. package/dist/aun/group/operations.js +1 -3
  13. package/dist/aun/menu/client.js +1 -0
  14. package/dist/aun/menu/transport.js +5 -3
  15. package/dist/aun/msg/managed-operation.js +70 -0
  16. package/dist/aun/msg/p2p.js +1 -3
  17. package/dist/aun/msg/upload.js +2 -2
  18. package/dist/aun/outbox.js +1 -37
  19. package/dist/channels/aun.js +262 -86
  20. package/dist/channels/daemon.js +18 -1
  21. package/dist/channels/feishu.js +1 -2
  22. package/dist/cli/agent-command.js +23 -3
  23. package/dist/cli/agent-create.js +68 -2
  24. package/dist/cli/agent.js +12 -2
  25. package/dist/cli/aun-commands.js +87 -13
  26. package/dist/cli/cli-argv.js +26 -0
  27. package/dist/cli/daemon-commands.js +116 -149
  28. package/dist/cli/data-command.js +4 -4
  29. package/dist/cli/diag.js +1 -1
  30. package/dist/cli/handoff-command.js +2 -0
  31. package/dist/cli/index.js +18 -1
  32. package/dist/cli/managed-command-guard.js +10 -0
  33. package/dist/cli/memory-command.js +441 -0
  34. package/dist/cli/menu-client.js +4 -0
  35. package/dist/cli/restart-monitor.js +127 -23
  36. package/dist/cli/skill-command.js +41 -0
  37. package/dist/cli/task-context.js +2 -23
  38. package/dist/cli/trigger-command.js +9 -1
  39. package/dist/cli/watch-msg-diag.js +209 -0
  40. package/dist/config/config-field-policy.js +52 -3
  41. package/dist/config/config-manager.js +17 -4
  42. package/dist/config/contact-book-store.js +2 -2
  43. package/dist/config/mention-mode.js +7 -2
  44. package/dist/config/merge.js +28 -9
  45. package/dist/config/resolved-config-op.js +1 -1
  46. package/dist/config/runtime-env.js +137 -0
  47. package/dist/config/schema-registry.js +11 -1
  48. package/dist/config-store.js +10 -10
  49. package/dist/core/audit/event-key.js +3 -1
  50. package/dist/core/audit/log-integrity.js +4 -0
  51. package/dist/core/auth/agent-delegation.js +2 -1
  52. package/dist/core/auth/auth-gateway.js +13 -6
  53. package/dist/core/auth/authorization-audit.js +54 -19
  54. package/dist/core/auth/operation-authorizer.js +17 -0
  55. package/dist/core/auth/operation-catalog.js +9 -0
  56. package/dist/core/causation/context.js +5 -1
  57. package/dist/core/command/agent-control.js +1 -2
  58. package/dist/core/command/command-handler.js +80 -24
  59. package/dist/core/command/diag-service.js +292 -27
  60. package/dist/core/command/memory-slash.js +270 -0
  61. package/dist/core/command/menu-catalog.js +1 -0
  62. package/dist/core/command/menu-handler.js +331 -79
  63. package/dist/core/command/menu-protocol.js +45 -4
  64. package/dist/core/command/slash-gate.js +10 -10
  65. package/dist/core/command/slash-handler.js +230 -79
  66. package/dist/core/data-migration.js +282 -505
  67. package/dist/core/event-catalog.js +26 -0
  68. package/dist/core/evolagent-registry.js +6 -0
  69. package/dist/core/evolagent.js +0 -10
  70. package/dist/core/handoff/store.js +1 -20
  71. package/dist/core/inference/text-inference.js +57 -1
  72. package/dist/core/memory/consolidation-context.js +505 -0
  73. package/dist/core/memory/consolidation-ops-records.js +124 -0
  74. package/dist/core/memory/consolidation-trigger.js +394 -0
  75. package/dist/core/memory/index-manager.js +308 -0
  76. package/dist/core/memory/launch-consolidation.js +395 -0
  77. package/dist/core/memory/memory-admin.js +234 -0
  78. package/dist/core/memory/memory-document.js +100 -0
  79. package/dist/core/memory/memory-session-policy.js +41 -0
  80. package/dist/core/memory/memory-signature.js +257 -0
  81. package/dist/core/memory/memory-writer.js +432 -0
  82. package/dist/core/memory/staleness.js +117 -0
  83. package/dist/core/message/auto-mention-gate.js +454 -0
  84. package/dist/core/message/im-renderer.js +90 -20
  85. package/dist/core/message/message-bridge.js +333 -23
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/message-queue.js +114 -27
  88. package/dist/core/message/pending-hints.js +329 -161
  89. package/dist/core/message/response-engine.js +596 -90
  90. package/dist/core/permission/ec-command-parser.js +1044 -87
  91. package/dist/core/permission/protected-paths.js +376 -126
  92. package/dist/core/permission/sandbox-runtime.js +40 -12
  93. package/dist/core/permission/tool-error-code.js +96 -13
  94. package/dist/core/permission/tool-policy.js +277 -24
  95. package/dist/core/role/runtime-policy.js +3 -7
  96. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  97. package/dist/core/session/session-fs-store.js +0 -76
  98. package/dist/core/session/session-idle-monitor.js +355 -0
  99. package/dist/core/session/session-manager.js +482 -17
  100. package/dist/core/session/session-mapper.js +33 -16
  101. package/dist/core/session/session-renew.js +217 -155
  102. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  103. package/dist/core/skill/global-watcher.js +282 -0
  104. package/dist/core/skill/index-generator.js +84 -0
  105. package/dist/core/skill/startup.js +73 -0
  106. package/dist/core/skill/switch-resolver.js +115 -0
  107. package/dist/core/task/task-activity-store.js +219 -0
  108. package/dist/eck/kit-renderer.js +30 -3
  109. package/dist/eck/manifest-engine.js +26 -4
  110. package/dist/index.js +722 -171
  111. package/dist/ipc.js +564 -2
  112. package/dist/paths.js +30 -9
  113. package/dist/product.js +1 -0
  114. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  115. package/dist/response-system/modes/single-session/index.js +3 -0
  116. package/dist/trigger/feedback.js +1 -0
  117. package/dist/trigger/manager.js +0 -10
  118. package/dist/trigger/scheduler.js +11 -0
  119. package/dist/trigger/validation.js +22 -0
  120. package/dist/utils/atomic-write.js +98 -0
  121. package/dist/utils/avatar-upload.js +59 -18
  122. package/dist/utils/cross-platform.js +87 -16
  123. package/dist/utils/error-dict.json +6 -3
  124. package/dist/utils/error-utils.js +27 -0
  125. package/dist/utils/logger.js +32 -10
  126. package/dist/utils/media-cache.js +84 -0
  127. package/dist/utils/msg-diag.js +97 -0
  128. package/dist/utils/npm-ops.js +119 -4
  129. package/dist/utils/package-version-info.js +244 -0
  130. package/dist/utils/welcome.js +37 -8
  131. package/dist/utils/windows-shell-trust.js +37 -0
  132. package/kits/docs/AUTHORING.md +314 -0
  133. package/kits/docs/GUIDE.md +2 -0
  134. package/kits/docs/INDEX.md +2 -0
  135. package/kits/docs/aun/CHEATSHEET.md +4 -0
  136. package/kits/docs/aun/SDK.md +60 -0
  137. package/kits/docs/channels/aun.md +8 -0
  138. package/kits/docs/context-assembly.md +10 -6
  139. package/kits/docs/evolcore/INDEX.md +17 -3
  140. package/kits/docs/evolcore/agent-create.md +24 -0
  141. package/kits/docs/evolcore/agent.md +5 -2
  142. package/kits/docs/evolcore/config.md +1 -1
  143. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  144. package/kits/docs/evolcore/group.md +34 -18
  145. package/kits/docs/evolcore/memory-architecture.md +78 -0
  146. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  147. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  148. package/kits/docs/evolcore/memory-tags.md +63 -0
  149. package/kits/docs/evolcore/memory.md +110 -0
  150. package/kits/docs/evolcore/menu-client.md +27 -11
  151. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  152. package/kits/docs/evolcore/menu-protocol.md +213 -0
  153. package/kits/docs/evolcore/msg.md +2 -0
  154. package/kits/docs/evolcore/trigger.md +7 -5
  155. package/kits/docs/path-registry.md +6 -8
  156. package/kits/docs/prompt-loading-architecture.md +5 -3
  157. package/kits/docs/venues/group.md +5 -4
  158. package/kits/eck_manifest.json +294 -88
  159. package/kits/eck_manifest.memory.json +257 -0
  160. package/kits/rules/03-identity.md +8 -8
  161. package/kits/rules/05-venue.md +4 -2
  162. package/kits/rules/06-channel.md +1 -1
  163. package/kits/schemas/_meta.json +276 -47
  164. package/kits/schemas/agent-config.schema.12.json +38 -2
  165. package/kits/schemas/agent-config.schema.13.json +591 -0
  166. package/kits/schemas/daemon.schema.6.json +9 -9
  167. package/kits/schemas/defaults.schema.5.json +29 -0
  168. package/kits/schemas/relation-config.schema.8.json +14 -1
  169. package/kits/schemas/relation-config.schema.9.json +129 -0
  170. package/kits/schemas/role-config.schema.1.json +3 -3
  171. package/kits/skills/.switches.json +3 -0
  172. package/kits/skills/ec-skill/SKILL.md +217 -0
  173. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  174. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  175. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  176. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  177. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  178. package/kits/templates/decision/auto-mention.md +25 -0
  179. package/kits/templates/decision/session-renew.md +22 -0
  180. package/kits/templates/memory-consolidation/forget.md +104 -0
  181. package/kits/templates/memory-consolidation/generalize.md +76 -0
  182. package/kits/templates/memory-consolidation/notes.md +133 -0
  183. package/kits/templates/memory-consolidation/profile.md +58 -0
  184. package/kits/templates/memory-consolidation/promote.md +72 -0
  185. package/kits/templates/memory-consolidation/workflow.md +75 -0
  186. package/kits/templates/system-fragments/bootstrap.md +14 -5
  187. package/kits/templates/system-fragments/commands.md +1 -0
  188. package/kits/templates/system-fragments/group-history.md +6 -0
  189. package/kits/templates/system-fragments/identity.md +0 -3
  190. package/kits/templates/system-fragments/memory.md +79 -0
  191. package/kits/templates/system-fragments/reply.md +1 -0
  192. package/kits/templates/system-fragments/venue.md +1 -1
  193. package/package.json +4 -6
  194. package/dist/trigger/legacy-session-history.js +0 -19
  195. 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';
@@ -13,17 +14,19 @@ import os from 'os';
13
14
  import crypto from 'crypto';
14
15
  import { CronExpressionParser } from 'cron-parser';
15
16
  import { parseDuration } from '../../trigger/parser.js';
16
- import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
17
+ import { isLinkedInstall, resolveGlobalPkg } from '../../utils/npm-ops.js';
18
+ import { refreshManagedPackageVersions, VERSION_INFO_PACKAGES } from '../../utils/package-version-info.js';
17
19
  import { resolveCliEntry, launchMonitorAndConfirm } from '../../utils/cross-platform.js';
18
20
  import { cachedCliVersion } from '../../utils/cross-platform.js';
19
21
  import { scanInstances } from '../../utils/instance-registry.js';
20
- import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
22
+ import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
21
23
  import { WEB_PACKAGE_NAME } from '../../product.js';
22
24
  import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
23
25
  import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
24
26
  import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
25
27
  import { displaySessionTitle } from '../session/session-title.js';
26
- import { buildSessionTurnList } from '../session/session-turns.js';
28
+ import { buildTranscriptTurns } from '../session/session-transcript.js';
29
+ import { buildForkHistorySnapshot, pageForkHistory } from '../session/session-manager.js';
27
30
  import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveCapabilityContext, updateCapabilityPolicy, } from '../capability/capability-manager.js';
28
31
  import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
29
32
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
@@ -40,18 +43,20 @@ import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
40
43
  import { logger } from '../../utils/logger.js';
41
44
  import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
42
45
  import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
43
- 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';
44
47
  import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
48
+ import { TaskActivityStore } from '../task/task-activity-store.js';
45
49
  import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
46
50
  import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
47
51
  import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
48
52
  import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
49
53
  import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
50
- import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
54
+ import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, resolveMenuCatalogContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
51
55
  import { probeSessionActivity } from './slash-gate.js';
52
56
  import { dataRootFailure, dataRootResolver } from './data-root-resolver.js';
53
57
  import { agentStorageMenuService, isAgentStorageMenuRequest } from './agent-storage-menu.js';
54
- 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';
55
60
  /**
56
61
  * 获取 baseagent CLI 的版本号(claude/gemini/codex)。
57
62
  * 失败返回 null(命令不存在或执行失败)。
@@ -61,8 +66,8 @@ function getBaseagentVersion(cmd) {
61
66
  }
62
67
  const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
63
68
  const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
64
- /** menu file: fetch 文件大小上限(与 /file 一致) */
65
- 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;
66
71
  /** menu file: query 的 sha256 仅对 ≤ 2 MB 文件计算,超过返回 null(见设计文档 §7 决策 2) */
67
72
  const FILE_HASH_MAX_SIZE = 2 * 1024 * 1024;
68
73
  const FILE_LIST_DEFAULT_LIMIT = 500;
@@ -183,12 +188,31 @@ function resolveCapabilityTarget(params) {
183
188
  function getRenameName(args) {
184
189
  return (args?.name ?? args?.title ?? args?.value ?? '').toString().trim();
185
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
+ }
186
205
  function buildSessionPayload(session, name) {
187
206
  const payload = { id: session.id, name };
188
- if (session.lable)
189
- payload.lable = session.lable;
207
+ if (session.label)
208
+ payload.label = session.label;
209
+ if (session.threadId)
210
+ payload.topicStatus = session.topicStatus ?? 'pending';
190
211
  if (session.agentSessionId)
191
212
  payload.agentSessionId = session.agentSessionId;
213
+ const forkSource = buildTopicForkSourcePayload(session);
214
+ if (forkSource)
215
+ payload.forkSource = forkSource;
192
216
  return payload;
193
217
  }
194
218
  async function findMainSessionTarget(sessionManager, channel, channelId, target, activeSession) {
@@ -388,6 +412,11 @@ async function getAvailableEfforts(agent, model) {
388
412
  const baseagent = agent?.baseagent || agent?.name;
389
413
  return (TRANSPORT_EFFORTS[baseagent] ?? []);
390
414
  }
415
+ function isDaemonControlSubject(subject) {
416
+ return subject.isDaemonOwner === true
417
+ || subject.processRole === 'daemon-owner'
418
+ || subject.processRole === 'daemon-service';
419
+ }
391
420
  function modelDisplayLabel(agent, model) {
392
421
  const full = agent.resolveModelId?.(model);
393
422
  return full && full !== model ? `${model} (${full})` : model;
@@ -641,7 +670,7 @@ function resolveMenuMentionModeTarget(params) {
641
670
  function readMenuMentionMode(target, fallback = null, fallbackSource = null) {
642
671
  try {
643
672
  const resolved = resolveEffectiveFieldWithSource(target.fieldPath, target.sel, { cache: true });
644
- if (resolved.value === 'disabled' || resolved.value === 'mention-only') {
673
+ if (resolved.value === 'disabled' || resolved.value === 'mention-only' || resolved.value === 'auto') {
645
674
  return { value: resolved.value, source: toMenuSource(resolved.source) };
646
675
  }
647
676
  }
@@ -942,12 +971,14 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
942
971
  return intent('model.effort.current', modelScope);
943
972
  if (cmdBase === '/chatmode')
944
973
  return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
945
- if (cmdBase === '/mentionmode')
974
+ if (cmdBase === '/mention')
946
975
  return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
947
976
  if (cmdBase === '/perm')
948
977
  return intent('permission.current', args?.scope === 'agent' ? 'agent' : 'relation');
949
978
  if (cmdBase === '/activity')
950
979
  return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
980
+ if (cmdBase === '/task')
981
+ return intent('task.activity.read', 'relation');
951
982
  if (cmdBase === '/observable')
952
983
  return intent('observable.current', 'agent');
953
984
  if (cmdBase === '/capability')
@@ -962,7 +993,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
962
993
  return intent(fromControlChannel && !args?.aid ? 'agent.list' : 'agent.show', fromControlChannel ? 'control' : 'agent');
963
994
  }
964
995
  if (cmdBase === '/trigger')
965
- return intent('trigger.list', 'relation');
996
+ return intent(args?.view === 'history' ? 'trigger.history'
997
+ : args?.view === 'detail' ? 'trigger.show' : 'trigger.list', 'relation');
966
998
  }
967
999
  if (verb === 'options') {
968
1000
  if (cmdBase === '/system')
@@ -987,7 +1019,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
987
1019
  return intent('model.effort.current', modelScope);
988
1020
  if (cmdBase === '/chatmode')
989
1021
  return intent('chatmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
990
- if (cmdBase === '/mentionmode')
1022
+ if (cmdBase === '/mention')
991
1023
  return intent('mentionmode.current', args?.scope === 'agent' ? 'agent' : 'relation');
992
1024
  if (cmdBase === '/activity')
993
1025
  return intent('activity.current', args?.scope === 'agent' ? 'agent' : 'relation');
@@ -1003,7 +1035,7 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
1003
1035
  return intent('model.effort', modelScope, { effort: value });
1004
1036
  if (cmdBase === '/chatmode')
1005
1037
  return intent('chatmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
1006
- if (cmdBase === '/mentionmode')
1038
+ if (cmdBase === '/mention')
1007
1039
  return intent('mentionmode.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
1008
1040
  if (cmdBase === '/activity')
1009
1041
  return intent('activity.update', args?.scope === 'agent' ? 'agent' : 'relation', { value });
@@ -1141,7 +1173,12 @@ async function authorizeMenuIntent(params) {
1141
1173
  }
1142
1174
  const source = params.source ?? 'menu';
1143
1175
  intent.source = source;
1144
- const decision = await authorizeOperation({ source, intent, subject });
1176
+ const decision = await authorizeOperation({
1177
+ source,
1178
+ intent,
1179
+ subject,
1180
+ auditMetadata: subject.auditMetadata,
1181
+ });
1145
1182
  if (!decision.allow) {
1146
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}`);
1147
1184
  if (intent.operation.startsWith('role.') || intent.operation.startsWith('connect.')) {
@@ -1430,7 +1467,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1430
1467
  { value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
1431
1468
  { value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
1432
1469
  ] } }] : []),
1433
- ...(canSetMentionmode ? [{ cmd: '/mentionmode', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1470
+ ...(canSetMentionmode ? [{ cmd: '/mention', label: '切换 @ 处理模式', desc: '控制群聊消息过滤(仅@提及或全部响应)', next: { type: 'select', items: [
1434
1471
  { value: 'mention-only', label: '@ 提及', desc: '仅在被 @ 提及时响应' },
1435
1472
  { value: 'disabled', label: '全部响应', desc: '响应群内所有消息' },
1436
1473
  ] } }] : []),
@@ -1495,6 +1532,26 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
1495
1532
  ]
1496
1533
  });
1497
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
+ }
1498
1555
  items.push({
1499
1556
  group: isAdmin ? '运维' : '其他',
1500
1557
  commands: [
@@ -1696,10 +1753,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1696
1753
  const isAdmin = isManagementRole(role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
1697
1754
  if (!triggerScheduler)
1698
1755
  return [];
1699
- const list = triggerScheduler.list({ all: scope === 'all' });
1700
- return list
1701
- .filter((definition) => this.canAccessTriggerDefinition(definition, userId ?? '', channel, isAdmin))
1702
- .map((definition) => {
1756
+ const list = triggerScheduler.list({ all: scope === 'all' })
1757
+ .filter((definition) => this.canAccessTriggerDefinition(definition, userId ?? '', channel, isAdmin));
1758
+ return list.map((definition) => {
1703
1759
  const view = this.definitionToTriggerView(definition, triggerScheduler);
1704
1760
  return {
1705
1761
  ...view,
@@ -1723,7 +1779,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1723
1779
  const agent = this.getAgent(channel, source.baseagent);
1724
1780
  if (!agent.capabilities?.forkAtTurn || !agent.getSessionMessages || !agent.forkSessionAt)
1725
1781
  return [];
1726
- const turns = buildSessionTurnList(await agent.getSessionMessages(source.agentSessionId, source.projectPath));
1782
+ const turns = buildTranscriptTurns(await agent.getSessionMessages(source.agentSessionId, source.projectPath));
1727
1783
  return turns.map((turn) => ({
1728
1784
  value: turn.assistantUuid,
1729
1785
  label: `#${turn.index} ${turn.userContent}`,
@@ -1734,13 +1790,17 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1734
1790
  userMessageId: turn.userUuid,
1735
1791
  userTextHash: turn.userTextHash,
1736
1792
  assistantTextHashes: turn.assistantTextHashes,
1793
+ assistantTextAnchors: turn.assistantTextAnchors,
1794
+ toolResultAnchors: turn.toolResultAnchors,
1795
+ forkAtMessage: agent.capabilities.forkAtMessage === true,
1796
+ forkHistoryVersion: 1,
1737
1797
  }));
1738
1798
  }
1739
- const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
1740
- let lable;
1741
- if (hasLable) {
1799
+ const hasLabel = args && (Object.hasOwn(args, 'label') || Object.hasOwn(args, 'lable'));
1800
+ let label;
1801
+ if (hasLabel) {
1742
1802
  try {
1743
- lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
1803
+ label = normalizeTopicLabel(Object.hasOwn(args, 'label') ? args.label : args.lable);
1744
1804
  }
1745
1805
  catch (error) {
1746
1806
  throw { code: 'INVALID_ARGS', message: error.message };
@@ -1748,15 +1808,23 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1748
1808
  }
1749
1809
  let archiveFilter;
1750
1810
  try {
1751
- const archived = args?.archived === undefined && isArchivedTopicLable(lable) ? true : args?.archived;
1752
- archiveFilter = resolveTopicArchiveFilter(archived);
1811
+ archiveFilter = resolveTopicArchiveFilter(args?.archived === undefined && args?.topicStatus === 'archived' ? true : args?.archived);
1753
1812
  }
1754
1813
  catch (error) {
1755
1814
  throw { code: 'INVALID_ARGS', message: error.message };
1756
1815
  }
1757
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
+ }
1758
1826
  return sessions
1759
- .filter((s) => !!s.threadId && (!hasLable || s.lable === lable))
1827
+ .filter((s) => !!s.threadId && (!hasLabel || s.label === label) && (!topicStatus || s.topicStatus === topicStatus))
1760
1828
  .map((s) => this.buildTopicMenuItem(s));
1761
1829
  }
1762
1830
  if (cmd === '/s' || cmd === '/session' || cmd === '/del') {
@@ -1827,7 +1895,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1827
1895
  agent = this.getAgent(channel, target.baseagent);
1828
1896
  }
1829
1897
  catch (error) {
1830
- if (subject.processRole === 'daemon-service') {
1898
+ if (isDaemonControlSubject(subject)) {
1831
1899
  return [{ value: target.baseagent, label: target.baseagent, selected: true }];
1832
1900
  }
1833
1901
  throw error;
@@ -1898,7 +1966,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1898
1966
  agent = this.getAgent(channel, target.baseagent);
1899
1967
  }
1900
1968
  catch (error) {
1901
- if (subject.processRole === 'daemon-service') {
1969
+ if (isDaemonControlSubject(subject)) {
1902
1970
  return ['low', 'medium', 'high', 'auto'].map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e }));
1903
1971
  }
1904
1972
  throw error;
@@ -1930,7 +1998,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1930
1998
  { value: 'proactive', label: '主动模式', selected: currentMode === 'proactive' },
1931
1999
  ];
1932
2000
  }
1933
- if (cmd === '/mentionmode') {
2001
+ if (cmd === '/mention') {
1934
2002
  const target = resolveMenuMentionModeTarget.call(this, {
1935
2003
  args,
1936
2004
  session,
@@ -1948,6 +2016,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1948
2016
  return [
1949
2017
  { value: 'mention-only', label: '@提及时响应', selected: currentMode === 'mention-only' },
1950
2018
  { value: 'disabled', label: '所有消息响应', selected: currentMode === 'disabled' },
2019
+ { value: 'auto', label: '根据上下文自动判断', selected: currentMode === 'auto' },
1951
2020
  ];
1952
2021
  }
1953
2022
  if (cmd === '/perm') {
@@ -2061,6 +2130,59 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2061
2130
  explicitChatType, channel, channelId, userId, fromControlChannel, source,
2062
2131
  });
2063
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
+ }
2064
2186
  const authDenied = await authorizeMenuIntent.call(this, {
2065
2187
  intent: buildMenuIntent('query', cmdBase, args, undefined, undefined, fromControlChannel),
2066
2188
  identity,
@@ -2075,6 +2197,40 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2075
2197
  });
2076
2198
  if (authDenied)
2077
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
+ }
2078
2234
  if (cmdBase === '/agent-data-root') {
2079
2235
  if (args && Object.keys(args).length > 0) {
2080
2236
  return { error: 'agent-data-root does not accept args', code: 'INVALID_ARGUMENT' };
@@ -2271,6 +2427,69 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2271
2427
  const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
2272
2428
  if (!topic)
2273
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
+ }
2274
2493
  if (args?.view === 'subtasks') {
2275
2494
  return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(topic.id) } };
2276
2495
  }
@@ -2292,7 +2511,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2292
2511
  const data = {
2293
2512
  threadId: topic.threadId,
2294
2513
  name: topic.name || null,
2295
- lable: topic.lable || null,
2514
+ label: topic.label || null,
2515
+ topicStatus: topic.topicStatus ?? 'pending',
2296
2516
  agentSessionId: topic.agentSessionId || null,
2297
2517
  status: isProcessing ? 'processing' : 'idle',
2298
2518
  createdAt: topic.createdAt,
@@ -2414,7 +2634,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2414
2634
  },
2415
2635
  };
2416
2636
  }
2417
- if (cmdBase === '/mentionmode') {
2637
+ if (cmdBase === '/mention') {
2418
2638
  const chatType = session?.chatType ?? explicitChatType ?? 'private';
2419
2639
  if (chatType !== 'group') {
2420
2640
  return { error: 'mentionMode 仅在群聊会话中有效', code: 'NOT_APPLICABLE' };
@@ -3085,8 +3305,8 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
3085
3305
  },
3086
3306
  };
3087
3307
  }
3088
- if (cmdBase === '/mentionmode') {
3089
- if (arg !== 'mention-only' && arg !== 'disabled' && arg !== 'clear') {
3308
+ if (cmdBase === '/mention') {
3309
+ if (arg !== 'mention-only' && arg !== 'disabled' && arg !== 'auto' && arg !== 'clear') {
3090
3310
  return { error: `无效模式: ${arg}`, code: 'INVALID_VALUE' };
3091
3311
  }
3092
3312
  const chatType = session?.chatType;
@@ -3520,8 +3740,6 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3520
3740
  if (!trigger)
3521
3741
  return { error: '触发器不存在或无权限', code: 'NOT_FOUND' };
3522
3742
  if (action === 'enable' && trigger.execution.permissionMode === 'fullaccess') {
3523
- if (!isFullAccessEnabled())
3524
- return { error: 'fullaccess 功能未启用', code: 'NOT_SUPPORTED' };
3525
3743
  if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')
3526
3744
  return { error: '只有 daemon-owner 可以启用 fullaccess Trigger', code: 'FORBIDDEN' };
3527
3745
  if (subject.canApprovePersistentFullAccess === false) {
@@ -3618,11 +3836,11 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3618
3836
  return { error: `不支持的 trigger action: ${action}`, code: 'INVALID_ARGS' };
3619
3837
  }
3620
3838
  if (cmdBase === '/topic') {
3621
- const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
3622
- let lable;
3623
- 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')) {
3624
3842
  try {
3625
- lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
3843
+ label = normalizeTopicLabel(Object.hasOwn(args, 'label') ? args.label : args.lable);
3626
3844
  }
3627
3845
  catch (error) {
3628
3846
  return { error: error.message, code: 'INVALID_ARGS' };
@@ -3651,7 +3869,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3651
3869
  : null;
3652
3870
  if (requestId
3653
3871
  && forkSource?.requestId === requestId
3654
- && forkSource.assistantMessageId === sourceAssistantMessageId
3872
+ && (forkSource.requestedAssistantMessageId ?? forkSource.assistantMessageId) === sourceAssistantMessageId
3655
3873
  && forkSource.threadId === requestedSourceThreadId) {
3656
3874
  return {
3657
3875
  data: {
@@ -3703,25 +3921,33 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3703
3921
  try {
3704
3922
  const executeFork = async (current, currentAgent) => {
3705
3923
  const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
3706
- const turns = buildSessionTurnList(messages);
3707
- // fork-turns resolves bubble aliases to this canonical, complete-turn
3708
- // anchor. Never fall back to a runner iteration or a guessed index.
3709
- const forkTurn = turns.find(turn => turn.assistantUuid === sourceAssistantMessageId);
3710
- if (!forkTurn)
3711
- return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
3712
- 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 });
3713
3938
  const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
3714
3939
  name: name || undefined,
3715
- ...(hasLable ? { lable: lable ?? null } : {}),
3940
+ ...(hasLabel ? { label: label ?? null } : {}),
3716
3941
  creatorPeerId: userId,
3717
- sourceAssistantMessageId: forkTurn.assistantUuid,
3942
+ sourceAssistantMessageId: actualSourceMessageId,
3718
3943
  sourceTurn: forkTurn.index,
3944
+ historySnapshot,
3719
3945
  requestId,
3720
3946
  });
3721
3947
  await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
3722
3948
  evolcoreSessionId: topic.id,
3723
3949
  sourceSessionId: current.id,
3724
- sourceAssistantMessageId: forkTurn.assistantUuid,
3950
+ sourceAssistantMessageId: actualSourceMessageId,
3725
3951
  sourceTurn: forkTurn.index,
3726
3952
  }).catch((error) => {
3727
3953
  logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
@@ -3732,7 +3958,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3732
3958
  sourceSessionId: current.id,
3733
3959
  name: topic.name,
3734
3960
  threadId: topic.threadId,
3735
- sourceAssistantMessageId: forkTurn.assistantUuid,
3961
+ sourceAssistantMessageId: actualSourceMessageId,
3736
3962
  sourceTurn: forkTurn.index,
3737
3963
  });
3738
3964
  return {
@@ -3744,7 +3970,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3744
3970
  threadId: topic.threadId,
3745
3971
  sourceSessionId: current.id,
3746
3972
  sourceThreadId: current.threadId || null,
3747
- sourceAssistantMessageId: forkTurn.assistantUuid,
3973
+ sourceAssistantMessageId: actualSourceMessageId,
3748
3974
  sourceTurn: forkTurn.index,
3749
3975
  },
3750
3976
  },
@@ -3833,7 +4059,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3833
4059
  topicForkTargetsInFlight.delete(targetKey);
3834
4060
  }
3835
4061
  }
3836
- if (action !== 'delete' && action !== 'rename' && action !== 'label') {
4062
+ if (action !== 'delete' && action !== 'rename' && action !== 'label' && action !== 'status') {
3837
4063
  return { error: `不支持的 topic action: ${action}`, code: 'NOT_SUPPORTED' };
3838
4064
  }
3839
4065
  const target = (args?.target ?? '').toString().trim();
@@ -3842,8 +4068,17 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3842
4068
  const renameName = action === 'rename' ? getRenameName(args) : '';
3843
4069
  if (action === 'rename' && !renameName)
3844
4070
  return { error: '缺少 args.name', code: 'MISSING_VALUE' };
3845
- if (action === 'label' && !hasLable)
3846
- 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
+ }
3847
4082
  const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
3848
4083
  if (!topic)
3849
4084
  return { error: '话题不存在', code: 'NOT_FOUND' };
@@ -3853,19 +4088,25 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3853
4088
  ? 'private'
3854
4089
  : this.resolveMenuChatType(channel, channelId, explicitChatType);
3855
4090
  if (!this.canDeleteTopic(identity.role, chatType, topic, userId)) {
3856
- return { error: action === 'label' ? '无权限修改话题分类' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
4091
+ return { error: action === 'label' ? '无权限修改话题分类' : action === 'status' ? '无权限修改话题状态' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
3857
4092
  }
3858
4093
  if (action === 'label') {
3859
- const success = await this.sessionManager.setTopicLable(topic.id, lable ?? null);
4094
+ const success = await this.sessionManager.setTopicLabel(topic.id, label ?? null);
3860
4095
  if (!success)
3861
4096
  return { error: '话题不存在', code: 'NOT_FOUND' };
3862
4097
  return {
3863
4098
  data: {
3864
4099
  action: 'label', success: true,
3865
- topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, lable: lable ?? null },
4100
+ topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, label: label ?? null },
3866
4101
  },
3867
4102
  };
3868
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
+ }
3869
4110
  if (action === 'rename') {
3870
4111
  const newName = renameName;
3871
4112
  const existing = await this.sessionManager.getSessionByName?.(channel, channelId, newName);
@@ -4115,27 +4356,23 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
4115
4356
  }
4116
4357
  if (action === 'upgrade') {
4117
4358
  const devMode = isLinkedInstall();
4118
- const localEvolcore = getLocalVersion();
4119
- // fastaun 本地版本:从 node_modules 读取(与 menu.query name=system 一致)
4120
- let localFastaun = null;
4121
- try {
4122
- const fp = path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json');
4123
- localFastaun = JSON.parse(fs.readFileSync(fp, 'utf-8'))?.version ?? null;
4124
- }
4125
- catch { }
4126
- const [evolcoreRemote, fastaunRemote, ecwebRemote] = await Promise.all([
4127
- checkLatestVersion('evolcore'),
4128
- checkLatestVersion('@agentunion/fastaun'),
4129
- checkLatestVersion(WEB_PACKAGE_NAME),
4130
- ]);
4131
- const cmp = (local, remote) => !!(local && remote && compareVersions(local, remote) < 0);
4359
+ const results = await refreshManagedPackageVersions(VERSION_INFO_PACKAGES, { source: 'menu-upgrade' });
4360
+ const [evolcore, fastaunWeb, web] = [results[0], results[2], results[1]];
4361
+ const item = (result) => ({
4362
+ local: result.entry.currentVersion,
4363
+ remote: result.status === 'ok' ? result.entry.latestVersion ?? null : null,
4364
+ hasUpdate: result.status === 'ok' && result.hasUpdate,
4365
+ status: result.status,
4366
+ checkedAt: result.status === 'ok' ? result.entry.checkedAt ?? null : null,
4367
+ lastCheckedAt: result.entry.checkedAt ?? null,
4368
+ ...(result.error ? { error: result.error } : {}),
4369
+ });
4132
4370
  return {
4133
4371
  data: {
4134
4372
  devMode,
4135
- evolcore: { local: localEvolcore, remote: evolcoreRemote, hasUpdate: cmp(localEvolcore, evolcoreRemote) },
4136
- fastaun: { local: localFastaun, remote: fastaunRemote, hasUpdate: cmp(localFastaun, fastaunRemote), managedBy: 'evolcore' },
4137
- // ecweb 本地版本由 ECWeb 进程自身注入(data.ecwebVersion),此处仅给 remote
4138
- ecweb: { remote: ecwebRemote },
4373
+ evolcore: item(evolcore),
4374
+ fastaun: { ...item(fastaunWeb), managedBy: 'evolcore' },
4375
+ ecweb: item(web),
4139
4376
  },
4140
4377
  };
4141
4378
  }
@@ -4516,6 +4753,14 @@ async function execMenuForSystemControl(payload, context) {
4516
4753
  ecwebManagement: context.source === 'ecweb',
4517
4754
  canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
4518
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,
4519
4764
  });
4520
4765
  const trustedIdentity = trustedSubject.identity;
4521
4766
  const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
@@ -4663,6 +4908,10 @@ export async function execMenuForEcweb(payload, trusted) {
4663
4908
  const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
4664
4909
  ? trusted.actorAid
4665
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);
4666
4915
  return execMenuForSystemControl.call(this, payload, {
4667
4916
  source: 'ecweb',
4668
4917
  actorAid: userId,
@@ -4674,6 +4923,9 @@ export async function execMenuForEcweb(payload, trusted) {
4674
4923
  // approve persistent fullaccess without owner-bound token metadata.
4675
4924
  canApprovePersistentFullAccess: true,
4676
4925
  owners,
4926
+ ecwebRequestId,
4927
+ endpoint: trusted.endpoint ?? 'ECWeb menu.exec',
4928
+ logicalToolName,
4677
4929
  });
4678
4930
  }
4679
4931
  /** Control AID entry: daemon-owner and valid ECWeb token modes share the same