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
@@ -6,7 +6,7 @@ import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
6
6
  import { renderCommandCardAsText } from '../interaction-router.js';
7
7
  import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
8
8
  import { resolvePaths, getPackageRoot } from '../../paths.js';
9
- import { isFullAccessEnabled, loadDaemonConfig } from '../../config-store.js';
9
+ import { loadDaemonConfig } from '../../config-store.js';
10
10
  import { logger } from '../../utils/logger.js';
11
11
  import { resolvePeerRoleDetail, roleToSessionIdentity } from '../../config/peer-role-resolver.js';
12
12
  import { isManagementRole } from '../../config/builtin-roles.js';
@@ -20,7 +20,7 @@ import { applyTriggerPatch, replaceEditableTriggerDefinition } from '../../trigg
20
20
  import { definitionRevision } from '../../trigger/validation.js';
21
21
  import { tryParseChannelKey } from '../channel-loader.js';
22
22
  import { displaySessionTitle } from '../session/session-title.js';
23
- import { buildSessionTurnList } from '../session/session-turns.js';
23
+ import { buildTranscriptTurns } from '../session/session-transcript.js';
24
24
  import { resolveConfigCommand } from '../../config/resolved-config-op.js';
25
25
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommandAsync } from '../../config/config-operation-service.js';
26
26
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
@@ -28,6 +28,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
28
28
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
29
29
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
30
30
  import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
31
+ import { MAX_AVATAR_SOURCE_BYTES } from '../../utils/avatar-upload.js';
31
32
  import { executeManagedGroupOperation, isManagedGroupRelationRead, isManagedGroupRelationWrite, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
32
33
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
33
34
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
@@ -38,7 +39,7 @@ import { parsePeerKey } from '../relation/peer-identity.js';
38
39
  import { isQuickCommand } from './slash-gate.js';
39
40
  import { handleSlashCommand } from './slash-handler.js';
40
41
  import { resolveChatMode } from '../message/peer-mode.js';
41
- import { execMenuAction as menuExecMenuAction, execMenuForEcweb as menuExecMenuForEcweb, execMenuForControl as menuExecMenuForControl, execMenuQuery as menuExecMenuQuery, execMenuUpdate as menuExecMenuUpdate, getMenuItems as menuGetMenuItems, getSubMenuItems as menuGetSubMenuItems, } from './menu-handler.js';
42
+ import { execMenuAction as menuExecMenuAction, execMenuForEcweb as menuExecMenuForEcweb, execMenuForControl as menuExecMenuForControl, execMenuQuery as menuExecMenuQuery, execMenuUpdate as menuExecMenuUpdate, getMenuItems as menuGetMenuItems, getSubMenuItems as menuGetSubMenuItems, buildTopicForkSourcePayload, } from './menu-handler.js';
42
43
  import { getMenuCatalog, execMenuValueBatch, validateMenuValueBatchArgs } from './menu-catalog.js';
43
44
  export { isProcessLevelOwner } from './menu-handler.js';
44
45
  const CLI_EXEC_TIMEOUT_MS = 15_000;
@@ -124,6 +125,7 @@ export class CommandHandler {
124
125
  legacySingleRunnerMode;
125
126
  primaryRunnerKey;
126
127
  agentRegistry;
128
+ memoryConsolidationLauncher;
127
129
  agentApplicationService;
128
130
  triggerSchedulerResolver;
129
131
  triggerSchedulerFallback;
@@ -510,8 +512,6 @@ export class CommandHandler {
510
512
  if (!mode)
511
513
  return undefined;
512
514
  if (mode === 'fullaccess') {
513
- if (!isFullAccessEnabled())
514
- return 'fullaccess 功能未启用';
515
515
  return canApprovePersistentFullAccess ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
516
516
  }
517
517
  if (!role) {
@@ -929,6 +929,10 @@ export class CommandHandler {
929
929
  setStatsCollector(collector) {
930
930
  this.statsCollector = collector;
931
931
  }
932
+ /** 注入记忆整理入口(daemon 的统一整理队列),供 /memory consolidate 使用。返回 null 表示已入队。 */
933
+ setMemoryConsolidationLauncher(launcher) {
934
+ this.memoryConsolidationLauncher = launcher;
935
+ }
932
936
  registerAdapter(adapter) {
933
937
  this.adapters.set(adapter.channelName, adapter);
934
938
  }
@@ -1045,8 +1049,12 @@ export class CommandHandler {
1045
1049
  const item = {
1046
1050
  value: s.threadId,
1047
1051
  label: displayName,
1048
- lable: s.lable || null,
1052
+ topicLabel: s.label || null,
1053
+ topicStatus: s.topicStatus ?? 'pending',
1049
1054
  };
1055
+ const forkSource = buildTopicForkSourcePayload(s);
1056
+ if (forkSource)
1057
+ item.forkSource = forkSource;
1050
1058
  if (s.agentSessionId) {
1051
1059
  item.agentSessionId = s.agentSessionId;
1052
1060
  const fileInfo = this.sessionManager.getSessionFileInfo(s.projectPath, s.agentSessionId, s.baseagent, s.selfAID);
@@ -1360,8 +1368,8 @@ export class CommandHandler {
1360
1368
  }
1361
1369
  if (sub.startsWith('enable ')) {
1362
1370
  const { definition } = extractAndFindTrigger(sub, 'enable ', 'enable');
1363
- if (definition.execution.permissionMode === 'fullaccess' && (!isDaemonOwner || !isFullAccessEnabled())) {
1364
- return '❌ 只有 daemon-owner 可以启用 fullaccess Trigger,且 daemon.json.fullaccess.enabled 必须为 true';
1371
+ if (definition.execution.permissionMode === 'fullaccess' && !isDaemonOwner) {
1372
+ return '❌ 只有 daemon-owner 可以启用 fullaccess Trigger';
1365
1373
  }
1366
1374
  if (definition.execution.permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
1367
1375
  return '❌ 当前认证通道不能批准持久 fullaccess Trigger';
@@ -1571,9 +1579,7 @@ export class CommandHandler {
1571
1579
  return {
1572
1580
  ok: false,
1573
1581
  error: permissionError,
1574
- code: /fullaccess 功能未启用/.test(permissionError) ? 'NOT_SUPPORTED'
1575
- : /无权限|只有 daemon-owner/.test(permissionError) ? 'FORBIDDEN'
1576
- : undefined,
1582
+ code: /无权限|只有 daemon-owner/.test(permissionError) ? 'FORBIDDEN' : undefined,
1577
1583
  };
1578
1584
  }
1579
1585
  const runtimeFieldError = this.validateTriggerRuntimeFields(updated, triggerRole.role, triggerRole.baseagent);
@@ -1659,7 +1665,7 @@ export class CommandHandler {
1659
1665
  async handleRewindList(session, agent) {
1660
1666
  try {
1661
1667
  const messages = await agent.getSessionMessages(session.agentSessionId, session.projectPath);
1662
- const turns = buildSessionTurnList(messages);
1668
+ const turns = buildTranscriptTurns(messages);
1663
1669
  if (turns.length === 0) {
1664
1670
  return '📋 当前会话暂无对话记录';
1665
1671
  }
@@ -1679,8 +1685,11 @@ export class CommandHandler {
1679
1685
  }
1680
1686
  async handleRewind(session, agent, turnNum, mode) {
1681
1687
  try {
1688
+ if (session.threadId && turnNum === 1) {
1689
+ return '❌ 话题会话暂不支持 /rewind 1,仅开放 N>1';
1690
+ }
1682
1691
  const messages = await agent.getSessionMessages(session.agentSessionId, session.projectPath);
1683
- const turns = buildSessionTurnList(messages);
1692
+ const turns = buildTranscriptTurns(messages);
1684
1693
  if (turnNum < 1 || turnNum > turns.length) {
1685
1694
  return `❌ 轮次超出范围,当前共 ${turns.length} 轮`;
1686
1695
  }
@@ -1716,19 +1725,38 @@ export class CommandHandler {
1716
1725
  if (mode === 'chat' || mode === 'all') {
1717
1726
  const discarded = turns.length - turnNum + 1;
1718
1727
  if (agent.rollbackSessionTurns) {
1719
- const ok = await agent.rollbackSessionTurns(session.agentSessionId, session.projectPath, discarded);
1728
+ const ok = await agent.rollbackSessionTurns(session.agentSessionId, session.projectPath, discarded, rewindTarget.assistantUuid);
1720
1729
  if (!ok)
1721
1730
  return '❌ 对话回退失败';
1722
- const meta = { ...(session.metadata || {}) };
1723
- delete meta.resumeAt;
1724
- await this.sessionManager.updateSession(session.id, { metadata: meta });
1731
+ if (session.threadId) {
1732
+ await this.sessionManager.clearTopicResumeAt({
1733
+ sessionId: session.id,
1734
+ expectedThreadId: session.threadId,
1735
+ expectedAgentSessionId: session.agentSessionId,
1736
+ });
1737
+ }
1738
+ else {
1739
+ const meta = { ...(session.metadata || {}) };
1740
+ delete meta.resumeAt;
1741
+ await this.sessionManager.updateSession(session.id, { metadata: meta });
1742
+ }
1725
1743
  }
1726
1744
  else if (keepTarget) {
1727
- const meta = { ...(session.metadata || {}), resumeAt: keepTarget.assistantUuid };
1728
- await this.sessionManager.updateSession(session.id, { metadata: meta });
1745
+ if (session.threadId) {
1746
+ await this.sessionManager.setTopicResumeAt({
1747
+ sessionId: session.id,
1748
+ expectedThreadId: session.threadId,
1749
+ expectedAgentSessionId: session.agentSessionId,
1750
+ resumeAt: keepTarget.assistantUuid,
1751
+ });
1752
+ }
1753
+ else {
1754
+ const meta = { ...(session.metadata || {}), resumeAt: keepTarget.assistantUuid };
1755
+ await this.sessionManager.updateSession(session.id, { metadata: meta });
1756
+ }
1729
1757
  }
1730
1758
  else {
1731
- // N=1:撤销全部对话,清空 session 从头开始
1759
+ // N=1:撤销全部对话,清空主会话 session 从头开始
1732
1760
  const meta = { ...(session.metadata || {}) };
1733
1761
  delete meta.resumeAt;
1734
1762
  await this.sessionManager.updateSession(session.id, {
@@ -2130,15 +2158,15 @@ export class CommandHandler {
2130
2158
  if (realManagedTempPath && isSameOrDescendant(realPath, realManagedTempPath) && stat.nlink > 1) {
2131
2159
  return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar source cannot be a hard link inside managed TMPDIR' };
2132
2160
  }
2133
- if (stat.size < 1 || stat.size > 500 * 1024) {
2134
- return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be between 1 byte and 500KB' };
2161
+ if (stat.size < 1 || stat.size > MAX_AVATAR_SOURCE_BYTES) {
2162
+ return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be between 1 byte and 10 MiB' };
2135
2163
  }
2136
2164
  // Open the user-supplied node with no-follow semantics after checking
2137
2165
  // its canonical ancestors; the bytes passed to the uploader come from
2138
2166
  // this descriptor rather than from a second path lookup.
2139
2167
  fd = fs.openSync(resolvedPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
2140
2168
  const finalStat = fs.fstatSync(fd);
2141
- if (!finalStat.isFile() || finalStat.size < 1 || finalStat.size > 500 * 1024 || finalStat.nlink !== stat.nlink) {
2169
+ if (!finalStat.isFile() || finalStat.size < 1 || finalStat.size > MAX_AVATAR_SOURCE_BYTES || finalStat.nlink !== stat.nlink) {
2142
2170
  return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source changed during validation' };
2143
2171
  }
2144
2172
  avatarBuffer = fs.readFileSync(fd);
@@ -0,0 +1,270 @@
1
+ /**
2
+ * memory-slash.ts — `/memory` 斜杠命令:记忆查看、维护与整理
3
+ *
4
+ * 权限(按作用域):
5
+ * - relation:私聊里当前对端可看、admin+ 可写;群聊只有 admin+ 可看可写,且只能操作本群关系
6
+ * - agent:admin+ 可看可写(私聊)
7
+ * - owner:仅该 owner 本人(agent 配置的第一个 owner)可看可写(私聊)
8
+ * 不支持跨关系浏览:relation 永远是发起命令的这段关系。
9
+ */
10
+ import { CONSOLIDATION_MODES, MODE_SCOPE } from '../memory/consolidation-context.js';
11
+ import { MEMORY_SCOPES, isMemoryScope, memoryOverview, listMemoryFiles, formatMemoryFiles, readMemoryIndex, showMemoryFile, formatPendingNotes, showPendingNote, reindexScope, dropNote, archiveMemoryFile, restoreMemoryFile, } from '../memory/memory-admin.js';
12
+ const USAGE = [
13
+ '用法:',
14
+ ' /memory 记忆概览',
15
+ ' /memory notes 待整理 note 列表',
16
+ ' /memory note <slug> 查看 note',
17
+ ' /memory note drop <slug> 撤销 note',
18
+ ' /memory index <scope> 查看 MEMORY.md 索引',
19
+ ' /memory list <scope> [archived] 正式 / 已归档记忆列表',
20
+ ' /memory show <scope> <slug> [archived] 查看单条记忆',
21
+ ' /memory archive <scope> <slug> 归档记忆(可用 restore 恢复)',
22
+ ' /memory restore <scope> <slug> 恢复已归档记忆',
23
+ ' /memory reindex <scope> 重建索引',
24
+ ' /memory consolidate [mode] [scope] 拉起整理(mode: notes|generalize|promote|profile|forget;forget 需指定 scope)',
25
+ 'scope: relation | agent | owner',
26
+ ].join('\n');
27
+ const result = (text) => ({ kind: 'command.result', text });
28
+ const error = (text) => ({ kind: 'command.error', text: `❌ ${text}` });
29
+ export function canViewMemoryScope(ctx, scope) {
30
+ if (ctx.chatType === 'group')
31
+ return scope === 'relation' && ctx.isAdmin;
32
+ if (scope === 'relation')
33
+ return ctx.role !== 'none';
34
+ if (scope === 'agent')
35
+ return ctx.isAdmin;
36
+ return ctx.isOwnerSelf;
37
+ }
38
+ export function canWriteMemoryScope(ctx, scope) {
39
+ if (ctx.chatType === 'group')
40
+ return scope === 'relation' && ctx.isAdmin;
41
+ if (scope === 'owner')
42
+ return ctx.isOwnerSelf;
43
+ return ctx.isAdmin;
44
+ }
45
+ /** evolhelp / 菜单是否展示 /memory:至少能看一个作用域。 */
46
+ export function canUseMemorySlash(ctx) {
47
+ return MEMORY_SCOPES.some(scope => canViewMemoryScope(ctx, scope));
48
+ }
49
+ const SCOPE_LABEL = { relation: '当前关系', agent: 'Agent 级', owner: 'Owner 级' };
50
+ /**
51
+ * evolhelp「记忆管理」条目:能一键执行的都展开成无参数条目,只有需要 slug 的才带 args。
52
+ * 可见范围与 canViewMemoryScope / canWriteMemoryScope 一致。
53
+ */
54
+ export function memoryHelpEntries(ctx) {
55
+ const category = '记忆管理';
56
+ const roles = [ctx.role];
57
+ const view = MEMORY_SCOPES.filter(s => canViewMemoryScope(ctx, s));
58
+ if (view.length === 0)
59
+ return [];
60
+ const write = MEMORY_SCOPES.filter(s => canWriteMemoryScope(ctx, s));
61
+ const out = [];
62
+ const add = (command, description, args) => out.push({ command, ...(args ? { args } : {}), description, category, roles });
63
+ add('/memory', '记忆概览:条数、待整理 note、索引大小');
64
+ if (view.includes('relation'))
65
+ add('/memory notes', '待整理 note 列表');
66
+ for (const s of view)
67
+ add(`/memory list ${s}`, `${SCOPE_LABEL[s]}正式记忆列表`);
68
+ for (const s of view)
69
+ add(`/memory index ${s}`, `${SCOPE_LABEL[s]}索引 MEMORY.md`);
70
+ for (const s of view)
71
+ add(`/memory list ${s} archived`, `${SCOPE_LABEL[s]}已归档记忆`);
72
+ if (write.length > 0) {
73
+ if (write.includes('relation')) {
74
+ add('/memory consolidate notes', '整理待处理 note');
75
+ add('/memory consolidate profile', '重新生成关系画像');
76
+ }
77
+ if (write.includes('agent') && view.includes('relation'))
78
+ add('/memory consolidate generalize', '泛化为 Agent 级记忆');
79
+ if (write.includes('owner') && view.includes('relation'))
80
+ add('/memory consolidate promote', '提升为 Owner 级记忆');
81
+ for (const s of write)
82
+ add(`/memory consolidate forget ${s}`, `${SCOPE_LABEL[s]}遗忘审查`);
83
+ for (const s of write)
84
+ add(`/memory reindex ${s}`, `重建${SCOPE_LABEL[s]}索引`);
85
+ }
86
+ // 以下需要 slug,必须输入
87
+ add('/memory show', '查看单条记忆正文(slug 见 list 结果)', '<scope> <slug> [archived]');
88
+ if (view.includes('relation'))
89
+ add('/memory note', '查看单条 note', '<slug>');
90
+ if (write.length > 0) {
91
+ add('/memory archive', '归档一条记忆', '<scope> <slug>');
92
+ add('/memory restore', '恢复已归档记忆', '<scope> <slug>');
93
+ if (write.includes('relation'))
94
+ add('/memory note drop', '撤销一条 note', '<slug>');
95
+ }
96
+ add('/memory help', '全部子命令说明');
97
+ return out;
98
+ }
99
+ function denied(scope, write, chatType) {
100
+ if (chatType === 'group')
101
+ return '群聊中只有管理员可以查看和维护本群的 relation 记忆';
102
+ if (scope === 'owner')
103
+ return 'owner 级记忆仅限该 owner 本人操作';
104
+ return write ? `无权限:${scope} 级记忆的维护仅限管理员` : `无权限:${scope} 级记忆仅限管理员查看`;
105
+ }
106
+ export async function handleMemorySlash(ctx) {
107
+ if (!ctx.selfAid)
108
+ return error('无法确定当前 agent AID');
109
+ const args = ctx.content.trim().split(/\s+/).slice(1);
110
+ const sub = args[0];
111
+ const target = {
112
+ selfAid: ctx.selfAid,
113
+ peerKey: ctx.peerKey,
114
+ ownerAid: ctx.ownerAid,
115
+ memoryConfig: ctx.memoryConfig,
116
+ };
117
+ const needScope = (raw, write) => {
118
+ if (!isMemoryScope(raw))
119
+ return error(`scope 必须是 relation / agent / owner\n\n${USAGE}`);
120
+ const allowed = write ? canWriteMemoryScope(ctx, raw) : canViewMemoryScope(ctx, raw);
121
+ if (!allowed)
122
+ return error(denied(raw, write, ctx.chatType));
123
+ if (raw === 'relation' && !ctx.peerKey)
124
+ return error('当前会话没有关系上下文,无法操作 relation 记忆');
125
+ if (raw === 'owner' && !ctx.ownerAid)
126
+ return error('当前 agent 未配置 owner');
127
+ return raw;
128
+ };
129
+ const isPayload = (v) => typeof v === 'object' && v !== null;
130
+ try {
131
+ if (!sub) {
132
+ const scopes = MEMORY_SCOPES.filter(s => canViewMemoryScope(ctx, s)
133
+ && (s !== 'relation' || !!ctx.peerKey) && (s !== 'owner' || !!ctx.ownerAid));
134
+ if (scopes.length === 0)
135
+ return error('无权限查看记忆');
136
+ return result(`${memoryOverview(target, scopes)}\n\n输入 /memory help 查看全部子命令`);
137
+ }
138
+ if (sub === 'help')
139
+ return result(USAGE);
140
+ if (sub === 'notes') {
141
+ const scope = needScope('relation', false);
142
+ if (isPayload(scope))
143
+ return scope;
144
+ return result(await formatPendingNotes(target));
145
+ }
146
+ if (sub === 'note') {
147
+ if (args[1] === 'drop') {
148
+ const scope = needScope('relation', true);
149
+ if (isPayload(scope))
150
+ return scope;
151
+ if (!args[2])
152
+ return error('用法: /memory note drop <slug>');
153
+ await dropNote(target, args[2]);
154
+ return result(`✅ 已撤销 note: ${args[2]}`);
155
+ }
156
+ const scope = needScope('relation', false);
157
+ if (isPayload(scope))
158
+ return scope;
159
+ if (!args[1])
160
+ return error('用法: /memory note <slug>');
161
+ return result(showPendingNote(target, args[1]));
162
+ }
163
+ if (sub === 'index') {
164
+ const scope = needScope(args[1], false);
165
+ if (isPayload(scope))
166
+ return scope;
167
+ return result(readMemoryIndex(target, scope));
168
+ }
169
+ if (sub === 'list') {
170
+ const scope = needScope(args[1], false);
171
+ if (isPayload(scope))
172
+ return scope;
173
+ const archived = args[2] === 'archived';
174
+ return result(formatMemoryFiles(scope, listMemoryFiles(target, scope, archived), archived));
175
+ }
176
+ if (sub === 'show') {
177
+ const scope = needScope(args[1], false);
178
+ if (isPayload(scope))
179
+ return scope;
180
+ if (!args[2])
181
+ return error('用法: /memory show <scope> <slug> [archived]');
182
+ return result(await showMemoryFile(target, scope, args[2], args[3] === 'archived'));
183
+ }
184
+ if (sub === 'archive' || sub === 'restore') {
185
+ const scope = needScope(args[1], true);
186
+ if (isPayload(scope))
187
+ return scope;
188
+ if (!args[2])
189
+ return error(`用法: /memory ${sub} <scope> <slug>`);
190
+ if (sub === 'archive') {
191
+ await archiveMemoryFile(target, scope, args[2]);
192
+ return result(`✅ 已归档 ${scope}/${args[2]},索引已重建\n恢复: /memory restore ${scope} ${args[2]}`);
193
+ }
194
+ await restoreMemoryFile(target, scope, args[2]);
195
+ return result(`✅ 已恢复 ${scope}/${args[2]},索引已重建`);
196
+ }
197
+ if (sub === 'reindex') {
198
+ const scope = needScope(args[1], true);
199
+ if (isPayload(scope))
200
+ return scope;
201
+ const r = await reindexScope(target, scope);
202
+ return result(`✅ ${scope} 索引已重建(${r.indexed} 条)`);
203
+ }
204
+ if (sub === 'consolidate')
205
+ return await consolidate(ctx, target, args[1], args[2], needScope, isPayload);
206
+ return error(`未知子命令: ${sub}\n\n${USAGE}`);
207
+ }
208
+ catch (e) {
209
+ return error(e?.message || String(e));
210
+ }
211
+ }
212
+ async function consolidate(ctx, target, rawMode, rawScope, needScope, isPayload) {
213
+ const mode = (rawMode ?? 'notes');
214
+ if (!CONSOLIDATION_MODES.includes(mode))
215
+ return error(`mode 必须是 ${CONSOLIDATION_MODES.join(' / ')}`);
216
+ // 与 ipc checkMemoryEnabled 一致的开关检查
217
+ const mc = target.memoryConfig;
218
+ if (mc?.enableRelation === false)
219
+ return error('记忆系统已停用(memory.enableRelation=false)');
220
+ if (mode === 'generalize' && mc?.enableAgent === false)
221
+ return error('泛化模式已停用(memory.enableAgent=false)');
222
+ if (mode === 'promote' && mc?.enableOwner === false)
223
+ return error('提升模式已停用(memory.enableOwner=false)');
224
+ // 产出写到哪一层,就按那一层的写权限判定;整理都需要关系上下文(notes/profile 读当前关系)
225
+ let scopeOverride;
226
+ let writeScope;
227
+ if (mode === 'forget') {
228
+ if (!rawScope)
229
+ return error('forget 模式需要指定 scope: /memory consolidate forget <relation|agent|owner>');
230
+ const s = needScope(rawScope, true);
231
+ if (isPayload(s))
232
+ return s;
233
+ scopeOverride = s;
234
+ writeScope = s;
235
+ }
236
+ else {
237
+ writeScope = MODE_SCOPE[mode];
238
+ const s = needScope(writeScope, true);
239
+ if (isPayload(s))
240
+ return s;
241
+ }
242
+ if ((mode === 'notes' || mode === 'profile' || mode === 'generalize') && !ctx.peerKey) {
243
+ return error('当前会话没有关系上下文,无法整理');
244
+ }
245
+ if (!ctx.launcher)
246
+ return error('daemon 未注入整理入口,无法拉起整理会话');
247
+ const launched = await ctx.launcher({
248
+ taskCtx: {
249
+ selfAid: target.selfAid,
250
+ peerKey: ctx.peerKey,
251
+ channel: ctx.channelType,
252
+ peerId: ctx.peerId,
253
+ chatType: ctx.chatType,
254
+ threadId: ctx.threadId,
255
+ sessionId: ctx.sessionId,
256
+ baseagent: ctx.baseagent,
257
+ },
258
+ mode,
259
+ triggeredBy: 'manual',
260
+ ownerAid: ctx.ownerAid,
261
+ ownerInvolved: ctx.isOwnerSelf,
262
+ forgetAfterDays: mc?.forgetAfterDays,
263
+ ...(scopeOverride ? { scopeOverride } : {}),
264
+ });
265
+ const label = `${mode}${scopeOverride ? `(${scopeOverride})` : ''}`;
266
+ if (!launched || launched.queued) {
267
+ return result(`⏳ 整理请求已入队(${label}),前一个整理会话结束后自动开始`);
268
+ }
269
+ return result(`✅ 已拉起整理会话(${label})${launched.executionSessionId ? `\n会话: ${launched.executionSessionId}` : ''}\n完成后会在当前对话反馈结果`);
270
+ }
@@ -69,6 +69,7 @@ const ENTRIES = [
69
69
  relation('effort.relation', 'effort', '推理强度', 20, '/effort', 'model.effort.current', { options: { cmd: '/effort', operation: 'model.effort.current', scope: 'relation', mode: 'dynamic', dependsOn: ['model.relation'] }, update: { cmd: '/effort', operation: 'model.effort', scope: 'relation' }, control: 'select' }),
70
70
  relation('chatmode.relation', 'chatmode', '对话模式', 30, '/chatmode', 'chatmode.current', { options: { cmd: '/chatmode', operation: 'chatmode.current', scope: 'relation', mode: 'static', items: [{ value: 'interactive', label: '交互模式' }, { value: 'proactive', label: '主动模式' }] }, update: { cmd: '/chatmode', operation: 'chatmode.update', scope: 'relation' }, control: 'select' }),
71
71
  relation('activity.relation', 'activity', '中间输出', 40, '/activity', 'activity.current', { options: { cmd: '/activity', operation: 'activity.current', scope: 'relation', mode: 'static', items: [{ value: 'all', label: '私聊显示' }, { value: 'text', label: '仅文字进展' }, { value: 'none', label: '全部静默' }] }, update: { cmd: '/activity', operation: 'activity.update', scope: 'relation' }, control: 'select' }),
72
+ { id: 'task.activities', type: 'list', contexts: ['private_relation', 'group_relation'], name: 'task', label: '任务活动', section: 'relation_behavior', order: 45, query: { cmd: '/task', args: { view: 'activities' }, operation: 'task.activity.read', scope: 'relation', mode: 'dynamic' }, control: 'list' },
72
73
  relation('permissionMode.relation', 'permission', '权限模式', 50, '/perm', 'permission.current', { query: { cmd: '/perm', args: { scope: 'role' }, operation: 'permission.current', scope: 'relation' }, options: undefined, update: { cmd: '/perm', operation: 'permission.current', scope: 'relation', supported: false }, control: 'select' }),
73
74
  relation('mentionMode.relation', 'group', '群消息响应', 60, '/group', 'group.mentionmode.current', { contexts: ['group_relation'], query: { cmd: '/group', args: { view: 'mentionMode', scope: 'relation' }, operation: 'group.mentionmode.current', scope: 'relation' }, options: { cmd: '/group', args: { option: 'mentionMode', scope: 'relation' }, operation: 'group.mentionmode.current', scope: 'relation' }, update: { cmd: '/group', args: { resource: 'mentionMode', scope: 'relation' }, operation: 'group.mentionmode.update', scope: 'relation' }, control: 'select' }),
74
75
  relation('rulesPolicy.relation', 'group', '群规则策略', 70, '/group', 'group.rulespolicy.current', { contexts: ['group_relation'], query: { cmd: '/group', args: { view: 'rulesPolicy', scope: 'relation' }, operation: 'group.rulespolicy.current', scope: 'relation' }, options: { cmd: '/group', args: { option: 'rulesPolicy', scope: 'relation' }, operation: 'group.rulespolicy.current', scope: 'relation' }, update: { cmd: '/group', args: { resource: 'rulesPolicy', scope: 'relation' }, operation: 'group.rulespolicy.update', scope: 'relation' }, control: 'select' }),