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
@@ -99,7 +99,7 @@ export async function cmdData(args) {
99
99
  // when there is no legacy source to process.
100
100
  const existing = requestedId ? readDataMigration(root, requestedId) : undefined;
101
101
  let manifest = existing ?? planDataMigration(root);
102
- if (manifest.operations.length === 0 && Object.keys(manifest.supersededSourceHashes ?? {}).length === 0) {
102
+ if (manifest.operations.length === 0) {
103
103
  printSummary(manifest);
104
104
  console.log('No legacy data requires migration.');
105
105
  return;
@@ -119,9 +119,6 @@ export async function cmdData(args) {
119
119
  manifest = applyDataMigration(manifest);
120
120
  manifest = verifyDataMigration(manifest, { strictTargetContent: true });
121
121
  printSummary(manifest);
122
- if (manifest.state !== 'completed') {
123
- throw new Error(`migration is not fully verified; fix the listed operations and run ec data migrate --apply ${manifest.id}`);
124
- }
125
122
  }
126
123
  else {
127
124
  // A completed manifest may come from the previous copy-only workflow,
@@ -133,6 +130,9 @@ export async function cmdData(args) {
133
130
  console.log(`archive: ${finalization.archivePath}`);
134
131
  console.log(`archive-sha256: ${finalization.archiveSha256}`);
135
132
  console.log(`removed legacy sources: ${finalization.sourcePaths.length}`);
133
+ if (manifest.state !== 'completed') {
134
+ throw new Error(`migration is not fully verified; fix the listed operations and run ec data migrate --apply ${manifest.id}`);
135
+ }
136
136
  if (wasRunning) {
137
137
  console.log('Starting daemon and Agent runtimes after data migration...');
138
138
  await cmdStart();
@@ -5,6 +5,7 @@ import { HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES }
5
5
  import { HANDOFF_ID_RE } from '../core/handoff/store.js';
6
6
  import { isHelpFlag, wantsHelp } from './help.js';
7
7
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
8
+ import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
8
9
  function parseLimit(value) {
9
10
  const limit = Number(value);
10
11
  if (!value || !Number.isInteger(limit) || limit < 1 || limit > HANDOFF_QUERY_MAX_LIMIT) {
@@ -70,6 +71,7 @@ export function parseTrace(args) {
70
71
  return parsed;
71
72
  }
72
73
  export function parseReturn(args) {
74
+ args = canonicalizeEvolcoreCommandArgv(['ec', 'handoff', 'return', ...args]).slice(3);
73
75
  const dash = args.indexOf('--');
74
76
  if (dash >= 0) {
75
77
  const beforeDash = args.slice(0, dash);
package/dist/cli/index.js CHANGED
@@ -14,6 +14,7 @@ import { cmdRestartMonitor } from './restart-monitor.js';
14
14
  import { cmdAid, cmdRpc, cmdStorage, cmdMsg, cmdGroup } from './aun-commands.js';
15
15
  import { cmdAgent } from './agent-command.js';
16
16
  import { cmdCtl } from './ctl-command.js';
17
+ import { cmdMemory } from './memory-command.js';
17
18
  import { cmdQueue } from './queue-command.js';
18
19
  import { cmdStart, cmdStop, cmdRestart, cmdStatus, cmdLogs, cmdWatchCommand, cmdMv, cmdDiagnose } from './daemon-commands.js';
19
20
  import { logCliCommand } from './command-log.js';
@@ -80,7 +81,7 @@ function printLifecycleHelp(command) {
80
81
  restart: `用法: ec restart [--clear] [--diagnose|-d]\n\n --clear 同时清理跨 HOME 残留的 EvolCore 主进程\n --diagnose, -d 以诊断模式启动新 daemon`,
81
82
  status: '用法: ec status\n\n查看 daemon、agent、渠道与会话运行状态。',
82
83
  logs: `用法: ec logs [选项]\n\n --level <error|warn|info|debug> 最低日志级别\n --module <name> 只显示指定模块\n --raw 原始输出\n --no-color 禁用颜色`,
83
- watch: `用法: ec watch [logs|log|aid|msg|web|session]\n\n ec watch 打开监控选择菜单\n ec watch logs 选择日志类型后监控\n ec watch log <类型...> 直接监控指定日志类型\n ec watch aid 监控 AID 连接\n ec watch msg 打开消息 TUI\n ec watch web 安装/升级并启动 EC Web\n ec watch session ec watch web 的兼容别名`,
84
+ watch: `用法: ec watch [logs|log|aid|msg|diag|web|session]\n\n ec watch 打开监控选择菜单\n ec watch logs 选择日志类型后监控\n ec watch log <类型...> 直接监控指定日志类型\n ec watch aid 监控 AID 连接\n ec watch msg 打开消息 TUI\n ec watch diag 实时诊断消息生命周期(AUN→daemon→任务)\n ec watch web 安装/升级并启动 EC Web\n ec watch session ec watch web 的兼容别名`,
84
85
  'restart-monitor': '用法: ec restart-monitor\n\n运行内 daemon 重启与升级监控器。',
85
86
  diagnose: '用法: ec diagnose [--codex-hook]\n\n检查配置、数据库、进程和编译产物。\n\n --codex-hook 运行 Windows Codex managed hook 启动探针。',
86
87
  mv: '用法: ec mv <old-directory> <new-directory>\n\n迁移项目目录以及 Claude、Codex 和 EvolCore 会话路径。',
@@ -92,6 +93,14 @@ process.removeAllListeners('warning');
92
93
  process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
93
94
  return; process.stderr.write((w.stack ?? String(w)) + '\n'); });
94
95
  export async function main(args) {
96
+ // 托管任务里裸跑 `ec` 想问的是"有哪些命令",而不是要启动 daemon。
97
+ // 默认回落到 `start` 会让守卫抛出 "Command start is not available",
98
+ // 把用户引向进程生命周期,而真实意图是查用法。这里直接给帮助。
99
+ if (args.length === 0 && process.env.EVOLCORE_SESSION_ID) {
100
+ console.log(MAIN_HELP);
101
+ process.exitCode = 1;
102
+ return;
103
+ }
95
104
  const cmd = args[0] || 'start';
96
105
  try {
97
106
  assertManagedTopLevelCommandAllowed(cmd, args);
@@ -326,6 +335,9 @@ export async function main(args) {
326
335
  case 'ctl':
327
336
  await cmdCtl(args.slice(1));
328
337
  break;
338
+ case 'memory':
339
+ await cmdMemory(args.slice(1));
340
+ break;
329
341
  case 'queue':
330
342
  await cmdQueue(args.slice(1));
331
343
  break;
@@ -417,6 +429,11 @@ export async function main(args) {
417
429
  await cmdModel(args.slice(1));
418
430
  break;
419
431
  }
432
+ case 'skill': {
433
+ const { cmdSkill } = await import('./skill-command.js');
434
+ await cmdSkill(args.slice(1));
435
+ break;
436
+ }
420
437
  case 'response': {
421
438
  const { cmdResponse } = await import('./response.js');
422
439
  await cmdResponse(args.slice(1));
@@ -11,10 +11,20 @@ const MANAGED_TASK_COMMANDS = new Set([
11
11
  'fs',
12
12
  'group',
13
13
  'handoff',
14
+ // `memory` only works inside a managed task: both subcommands require
15
+ // EVOLCORE_SESSION_ID and authorize through their daemon IPC entrypoints.
16
+ 'memory',
14
17
  'model',
15
18
  'msg',
16
19
  'queue',
17
20
  'response',
21
+ // `skill` 管理 EC 技能(安装/开关/校验)。它的主要使用者就是 agent 自己——
22
+ // ec-skill 技能的 SKILL.md 明确指导 agent 调用它。脚本只读写
23
+ // $EVOLCORE_HOME/skills/ 与 kits/skills/ 下的技能文件,不触碰进程生命周期。
24
+ // 本白名单只负责让它进到脚本;具体某一级的读写权限由 skill.mjs 的
25
+ // assertPermission() 按会话上下文判定(群聊一律拒,Agent 级要 owner,
26
+ // 本机级要 daemonowner),故此处不额外收紧。
27
+ 'skill',
18
28
  // `stats` reads the usage database directly. It authorizes each managed call
19
29
  // against the task's relation role in `resolveManagedStatsScope`.
20
30
  'stats',
@@ -0,0 +1,441 @@
1
+ /**
2
+ * memory-command.ts — `ec memory` 子命令实现(v3)
3
+ *
4
+ * 主会话可用:
5
+ * ec memory note --slug <slug> [--tags <...>] <content>
6
+ * ec memory note update <slug> [--slug <新slug>] [--tags <...>] [<content>]
7
+ * ec memory note drop <slug>
8
+ * ec memory note list
9
+ * ec memory consolidate [--mode notes|generalize|promote|profile|forget]
10
+ *
11
+ * 整理会话(sessionType=memory)另有 create / update / archive 三个写命令,
12
+ * 实现见 core/memory/memory-writer.ts,在这里只做 CLI 分发与权限委托。
13
+ */
14
+ import { resolvePaths } from '../paths.js';
15
+ import { ipcQuery } from '../ipc.js';
16
+ import { isHelpFlag, wantsHelp } from './help.js';
17
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
18
+ export const MEMORY_MODES = ['notes', 'generalize', 'promote', 'profile', 'forget'];
19
+ const MEMORY_HELP = `用法: ec memory <command> [选项]
20
+
21
+ 命令:
22
+ note 写入 / 修改 / 撤销 / 查看临时记忆(主会话)
23
+ create 新建正式记忆(仅整理会话)
24
+ update 修改正式记忆的标签,或续期(仅整理会话)
25
+ archive 归档正式记忆(仅整理会话)
26
+ demote 把记忆降级回上一层(agent→relation,owner→agent)
27
+ consolidate 触发记忆整理(启动整理会话)
28
+
29
+ ec memory note --slug <slug> [--tags <tag1,tag2,...>] <content>
30
+
31
+ 选项:
32
+ --slug <slug> 文件名标识,字母/数字/连字符,长度 1-64(必填)
33
+ --tags <...> 标签列表,逗号分隔;取值见 \$KITS_DOCS/evolcore/memory-tags.md
34
+
35
+ ec memory note update <slug> [--slug <新slug>] [--tags <...>] [<content>]
36
+ ec memory note drop <slug>
37
+ ec memory note list
38
+
39
+ ec memory create --scope <relation|agent|owner> --slug <slug> --desc <description> \\
40
+ --tags <...> [--keywords <...>] [--replaces <path,...>] <content>
41
+
42
+ ec memory update <path> [--tags <...>] [--touch]
43
+
44
+ ec memory archive <path...>
45
+
46
+ ec memory consolidate [--mode <notes|generalize|promote|profile|forget>]
47
+
48
+ 选项:
49
+ --mode 整理模式(默认 notes)
50
+
51
+ 公共选项(所有子命令):
52
+ --debug <sessionId> 在宿主终端模拟该会话的托管环境(仅开发仓 daemon 支持);
53
+ 会话没有运行中任务时,daemon 从会话文件重建运行时上下文
54
+
55
+ 示例:
56
+ ec memory note --slug commit-convention --tags "rule,long-term" "commit 消息必须用中文"
57
+ ec memory note update commit-convention --tags "rule,done" "commit 消息必须用中文"
58
+ ec memory note drop commit-convention
59
+ ec memory note list
60
+ ec memory consolidate
61
+ ec memory consolidate --mode promote
62
+ ec memory create --scope relation --slug profile --desc "对端画像" --tags "profile" --replaces consolidated/profile.md "(画像正文)"
63
+ ec memory update consolidated/commit-convention.md --tags "rule,done"
64
+ ec memory archive consolidated/obsolete.md`;
65
+ export async function cmdMemory(args) {
66
+ if (args.length === 0 || isHelpFlag(args[0]) || wantsHelp(args)) {
67
+ console.log(MEMORY_HELP);
68
+ return;
69
+ }
70
+ const sub = args[0];
71
+ if (sub === 'note') {
72
+ await cmdMemoryNote(args.slice(1));
73
+ }
74
+ else if (sub === 'consolidate') {
75
+ await cmdMemoryConsolidate(args.slice(1));
76
+ }
77
+ else if (sub === 'create') {
78
+ await cmdMemoryCreate(args.slice(1));
79
+ }
80
+ else if (sub === 'update') {
81
+ await cmdMemoryConsolidatedUpdate(args.slice(1));
82
+ }
83
+ else if (sub === 'archive') {
84
+ await cmdMemoryArchive(args.slice(1));
85
+ }
86
+ else if (sub === 'demote') {
87
+ await cmdMemoryDemote(args.slice(1));
88
+ }
89
+ else if (sub === 'feedback') {
90
+ await cmdMemoryFeedback(args.slice(1));
91
+ }
92
+ else {
93
+ console.error(`❌ 未知子命令: ${sub}`);
94
+ console.error(MEMORY_HELP);
95
+ process.exit(1);
96
+ }
97
+ }
98
+ /**
99
+ * 解析 `--key value` 与位置参数。未识别的 `--xxx` 一律报错,避免静默忽略。
100
+ * `--debug <sessionId>` 是所有子命令的公共参数,不用每处声明。
101
+ */
102
+ function parseArgs(args, known) {
103
+ const flags = new Map();
104
+ const positional = [];
105
+ for (let i = 0; i < args.length; i++) {
106
+ const a = args[i];
107
+ if (a.startsWith('--')) {
108
+ if (a !== '--debug' && !known.includes(a)) {
109
+ console.error(`❌ 未知参数: ${a}`);
110
+ process.exit(1);
111
+ }
112
+ if (i + 1 >= args.length || args[i + 1].startsWith('--')) {
113
+ // --touch 这类布尔开关不带值
114
+ flags.set(a, '');
115
+ continue;
116
+ }
117
+ flags.set(a, args[++i]);
118
+ }
119
+ else {
120
+ positional.push(a);
121
+ }
122
+ }
123
+ return { flags, positional };
124
+ }
125
+ function parseTags(raw) {
126
+ if (raw === undefined)
127
+ return undefined;
128
+ const tags = raw.split(',').map(t => t.trim()).filter(Boolean);
129
+ return tags.length > 0 ? tags : undefined;
130
+ }
131
+ /** 各子命令的一行完整用法:缺参报错时附上,模型照抄即可重试。 */
132
+ const USAGE = {
133
+ note: 'ec memory note --slug <slug> [--tags <tag1,tag2>] [--speakers <aid1,aid2>] "<正文>"',
134
+ noteUpdate: 'ec memory note update <slug> [--slug <新slug>] [--tags <...>] ["<新正文>"]',
135
+ noteDrop: 'ec memory note drop <slug>',
136
+ create: 'ec memory create --scope <relation|agent|owner> --slug <slug> --desc "<描述>" --tags <tag1,tag2> [--keywords <...>] [--replaces <路径,...>] [--peer <peerKey>] "<正文>"',
137
+ update: 'ec memory update <路径> [--tags <tag1,tag2>] [--touch](至少给一项)',
138
+ archive: 'ec memory archive <路径...>',
139
+ };
140
+ function failWithUsage(message, usage) {
141
+ console.error(`❌ ${message}`);
142
+ console.error(`用法: ${usage}`);
143
+ process.exit(1);
144
+ }
145
+ /**
146
+ * 取当前会话。`--debug <sessionId>` 在宿主终端模拟托管环境(仅开发仓 daemon 认);
147
+ * 否则读 EVOLCORE_SESSION_ID。
148
+ */
149
+ function requireManagedSession(cmd, flags) {
150
+ const debugSessionId = flags.get('--debug');
151
+ if (flags.has('--debug') && !debugSessionId) {
152
+ console.error(`❌ --debug 需要提供 sessionId(如 --debug meta_20260910_1789013149542)`);
153
+ process.exit(1);
154
+ }
155
+ const sessionId = debugSessionId || process.env.EVOLCORE_SESSION_ID;
156
+ if (!sessionId) {
157
+ console.error(`错误: EVOLCORE_SESSION_ID 未设置(${cmd} 仅在 evolcore 托管环境中可用;调试可加 --debug <sessionId>)`);
158
+ process.exit(1);
159
+ }
160
+ if (debugSessionId)
161
+ console.error(`[debug] 模拟会话 ${debugSessionId}`);
162
+ return {
163
+ sessionId,
164
+ socketPath: resolvePaths().socket,
165
+ delegationToken: readDelegationCredential(),
166
+ ...(debugSessionId ? { debug: true } : {}),
167
+ };
168
+ }
169
+ // ── note ───────────────────────────────────────────────────────────────────
170
+ async function cmdMemoryNote(args) {
171
+ if (wantsHelp(args)) {
172
+ console.log(MEMORY_HELP);
173
+ return;
174
+ }
175
+ const sub = args[0];
176
+ if (sub === 'update')
177
+ return cmdMemoryNoteUpdate(args.slice(1));
178
+ if (sub === 'drop')
179
+ return cmdMemoryNoteDrop(args.slice(1));
180
+ if (sub === 'list')
181
+ return cmdMemoryNoteList(args.slice(1));
182
+ return cmdMemoryNoteCreate(args);
183
+ }
184
+ async function cmdMemoryNoteCreate(args) {
185
+ const { flags, positional } = parseArgs(args, ['--slug', '--tags', '--speakers']);
186
+ const content = positional.join(' ').trim();
187
+ const slug = flags.get('--slug');
188
+ const tags = parseTags(flags.get('--tags'));
189
+ const speakers = parseTags(flags.get('--speakers'));
190
+ if (!slug)
191
+ failWithUsage('必须提供 --slug', USAGE.note);
192
+ if (!content)
193
+ failWithUsage('必须提供记忆内容(content)', USAGE.note);
194
+ const { socketPath, ...session } = requireManagedSession('ec memory note', flags);
195
+ const result = await ipcQuery(socketPath, {
196
+ type: 'memory.note', ...session, slug, content, tags,
197
+ speakers: speakers && speakers.length > 0 ? speakers : undefined,
198
+ }, 15_000);
199
+ reportResult(result, (r) => {
200
+ if (r.filePath)
201
+ console.log(r.filePath);
202
+ });
203
+ }
204
+ async function cmdMemoryNoteUpdate(args) {
205
+ const { flags, positional } = parseArgs(args, ['--slug', '--tags']);
206
+ const slug = positional[0];
207
+ const content = positional.slice(1).join(' ').trim();
208
+ const newSlug = flags.get('--slug');
209
+ const tagsRaw = flags.get('--tags');
210
+ const tags = parseTags(tagsRaw);
211
+ if (!slug)
212
+ failWithUsage('必须提供要修改的 slug', USAGE.noteUpdate);
213
+ if (!newSlug && tagsRaw === undefined && !content) {
214
+ failWithUsage('至少提供一项要改的内容(--slug / --tags / 正文)', USAGE.noteUpdate);
215
+ }
216
+ const { socketPath, ...session } = requireManagedSession('ec memory note update', flags);
217
+ const result = await ipcQuery(socketPath, {
218
+ type: 'memory.note.update', ...session, slug,
219
+ newSlug, tags, tagsProvided: tagsRaw !== undefined,
220
+ content: content || undefined,
221
+ }, 15_000);
222
+ reportResult(result, (r) => {
223
+ if (r.filePath)
224
+ console.log(r.filePath);
225
+ });
226
+ }
227
+ async function cmdMemoryNoteDrop(args) {
228
+ const { flags, positional } = parseArgs(args, []);
229
+ const slug = positional[0];
230
+ if (!slug)
231
+ failWithUsage('必须提供要撤销的 slug', USAGE.noteDrop);
232
+ const { socketPath, ...session } = requireManagedSession('ec memory note drop', flags);
233
+ const result = await ipcQuery(socketPath, {
234
+ type: 'memory.note.drop', ...session, slug,
235
+ }, 15_000);
236
+ reportResult(result, (r) => {
237
+ console.log(`✓ 已撤销: ${r.filePath ?? slug}`);
238
+ });
239
+ }
240
+ async function cmdMemoryNoteList(args) {
241
+ const { flags } = parseArgs(args, []);
242
+ const { socketPath, ...session } = requireManagedSession('ec memory note list', flags);
243
+ const result = await ipcQuery(socketPath, {
244
+ type: 'memory.note.list', ...session,
245
+ }, 15_000);
246
+ reportResult(result, (r) => {
247
+ const notes = r.notes ?? [];
248
+ if (notes.length === 0) {
249
+ console.log('(没有待整理的 note)');
250
+ return;
251
+ }
252
+ for (const n of notes) {
253
+ const tags = n.tags?.length ? ` [${n.tags.join(', ')}]` : '';
254
+ console.log(`${n.slug}${tags}${n.updatedAt ? ` (${n.updatedAt})` : ''}`);
255
+ if (n.firstLine)
256
+ console.log(` ${n.firstLine}`);
257
+ }
258
+ });
259
+ }
260
+ // ── create / update / archive(仅整理会话) ─────────────────────────────────
261
+ async function cmdMemoryCreate(args) {
262
+ const { flags, positional } = parseArgs(args, [
263
+ '--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer',
264
+ ]);
265
+ const content = positional.join(' ').trim();
266
+ const scope = flags.get('--scope');
267
+ const slug = flags.get('--slug');
268
+ const desc = flags.get('--desc');
269
+ const tagsRaw = flags.get('--tags');
270
+ const keywordsRaw = flags.get('--keywords');
271
+ const replacesRaw = flags.get('--replaces');
272
+ const targetPeerKey = flags.get('--peer');
273
+ if (!scope || !['agent', 'relation', 'owner'].includes(scope)) {
274
+ failWithUsage('--scope 必须是 agent / relation / owner 之一', USAGE.create);
275
+ }
276
+ if (!slug)
277
+ failWithUsage('必须提供 --slug', USAGE.create);
278
+ if (!desc)
279
+ failWithUsage('必须提供 --desc', USAGE.create);
280
+ if (tagsRaw === undefined)
281
+ failWithUsage('必须提供 --tags', USAGE.create);
282
+ if (!content)
283
+ failWithUsage('必须提供记忆内容(content)', USAGE.create);
284
+ if (targetPeerKey !== undefined && scope !== 'relation') {
285
+ failWithUsage('--peer 只在 --scope relation 时有效', USAGE.create);
286
+ }
287
+ const { socketPath, ...session } = requireManagedSession('ec memory create', flags);
288
+ const result = await ipcQuery(socketPath, {
289
+ type: 'memory.create', ...session, scope, slug, description: desc,
290
+ content,
291
+ tags: parseTags(tagsRaw),
292
+ keywords: parseTags(keywordsRaw),
293
+ replaces: replacesRaw ? replacesRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
294
+ targetPeerKey: targetPeerKey ?? undefined,
295
+ }, 15_000);
296
+ reportResult(result, (r) => {
297
+ if (r.filePath)
298
+ console.log(r.filePath);
299
+ if (Array.isArray(r.archived) && r.archived.length > 0) {
300
+ console.log(`已归档: ${r.archived.join(', ')}`);
301
+ }
302
+ });
303
+ }
304
+ async function cmdMemoryConsolidatedUpdate(args) {
305
+ const { flags, positional } = parseArgs(args, ['--tags', '--touch']);
306
+ const filePath = positional[0];
307
+ const tagsRaw = flags.get('--tags');
308
+ const touch = flags.has('--touch');
309
+ if (!filePath)
310
+ failWithUsage('必须提供要修改的记忆文件路径', USAGE.update);
311
+ if (tagsRaw === undefined && !touch)
312
+ failWithUsage('至少提供 --tags 或 --touch', USAGE.update);
313
+ const { socketPath, ...session } = requireManagedSession('ec memory update', flags);
314
+ const result = await ipcQuery(socketPath, {
315
+ type: 'memory.update', ...session, filePath,
316
+ tags: parseTags(tagsRaw), touch,
317
+ }, 15_000);
318
+ reportResult(result, () => {
319
+ console.log(`✓ 已更新: ${filePath}`);
320
+ });
321
+ }
322
+ async function cmdMemoryArchive(args) {
323
+ const { flags, positional } = parseArgs(args, []);
324
+ if (positional.length === 0)
325
+ failWithUsage('必须提供至少一个要归档的文件路径', USAGE.archive);
326
+ const { socketPath, ...session } = requireManagedSession('ec memory archive', flags);
327
+ const result = await ipcQuery(socketPath, {
328
+ type: 'memory.archive', ...session, filePaths: positional,
329
+ }, 15_000);
330
+ reportResult(result, (r) => {
331
+ const archived = r.archived ?? positional;
332
+ console.log(`✓ 已归档 ${archived.length} 个文件:`);
333
+ for (const f of archived)
334
+ console.log(` ${f}`);
335
+ });
336
+ }
337
+ // ── consolidate ────────────────────────────────────────────────────────────
338
+ async function cmdMemoryDemote(args) {
339
+ if (wantsHelp(args)) {
340
+ console.log(MEMORY_HELP);
341
+ return;
342
+ }
343
+ const { flags, positional } = parseArgs(args, ['--scope']);
344
+ const slug = positional[0];
345
+ if (!slug) {
346
+ console.error('用法: ec memory demote <slug> [--scope agent|owner]');
347
+ process.exit(1);
348
+ }
349
+ const scope = flags.get('--scope') ?? 'agent';
350
+ if (scope !== 'agent' && scope !== 'owner') {
351
+ console.error('--scope 必须是 agent 或 owner');
352
+ process.exit(1);
353
+ }
354
+ const { socketPath, ...session } = requireManagedSession('ec memory demote', flags);
355
+ const result = await ipcQuery(socketPath, {
356
+ type: 'memory.demote', ...session, slug, scope,
357
+ }, 15_000);
358
+ reportResult(result, (r) => {
359
+ if (r.conflictArchived) {
360
+ console.log(`⚠ 目标层已有同名文件,当前记忆已归档至:`);
361
+ console.log(` ${r.conflictArchived}`);
362
+ }
363
+ else {
364
+ console.log(`✓ 已降级`);
365
+ console.log(` ${r.destPath}`);
366
+ }
367
+ });
368
+ }
369
+ async function cmdMemoryConsolidate(args) {
370
+ if (wantsHelp(args)) {
371
+ console.log(MEMORY_HELP);
372
+ return;
373
+ }
374
+ // 先于通用解析处理已废弃的 --scope,给出可操作的提示而不是「未知参数」
375
+ if (args.includes('--scope')) {
376
+ console.error('❌ `--scope` 已废弃。作用域由模式唯一决定,请改用 `--mode <notes|generalize|promote|profile|forget>`');
377
+ process.exit(1);
378
+ }
379
+ const { flags } = parseArgs(args, ['--mode']);
380
+ const mode = (flags.get('--mode') ?? 'notes');
381
+ if (!MEMORY_MODES.includes(mode)) {
382
+ console.error(`❌ --mode 必须是 ${MEMORY_MODES.join(' / ')} 之一`);
383
+ process.exit(1);
384
+ }
385
+ const { socketPath, ...session } = requireManagedSession('ec memory consolidate', flags);
386
+ console.log(`正在触发记忆整理(mode=${mode})...`);
387
+ const result = await ipcQuery(socketPath, {
388
+ type: 'memory.consolidate', ...session, mode,
389
+ }, 30_000);
390
+ reportResult(result, (r) => {
391
+ console.log(`✓ 整理会话已启动(${mode} 模式)`);
392
+ if (r.triggerId)
393
+ console.log(` triggerId: ${r.triggerId}`);
394
+ if (r.executionSessionId)
395
+ console.log(` sessionId: ${r.executionSessionId}`);
396
+ console.log(` agentSessionId: ${r.agentSessionId ?? '(会话仍在初始化,稍后用 sessionId 查)'}`);
397
+ if (r.contextFilePath)
398
+ console.log(` context: ${r.contextFilePath}`);
399
+ console.log('整理会话在后台继续运行。');
400
+ });
401
+ }
402
+ // ── feedback(整理会话反馈)─────────────────────────────────────────────────
403
+ /**
404
+ * 整理会话用 `ec memory feedback "<文本>"` 记录本次整理的反馈内容。
405
+ * 文本写入批次目录的 ops.jsonl(op=feedback-set),收尾时据此投递回发起会话。
406
+ * 未调用则不写,收尾回退到默认统计模板。
407
+ */
408
+ async function cmdMemoryFeedback(args) {
409
+ if (wantsHelp(args)) {
410
+ console.log(MEMORY_HELP);
411
+ return;
412
+ }
413
+ const { flags, positional } = parseArgs(args, []);
414
+ const text = positional.join(' ').trim();
415
+ if (!text) {
416
+ console.error('用法: ec memory feedback "<反馈文本>"');
417
+ process.exit(1);
418
+ }
419
+ const { socketPath, ...session } = requireManagedSession('ec memory feedback', flags);
420
+ const result = await ipcQuery(socketPath, {
421
+ type: 'memory.feedback', ...session, text,
422
+ }, 15_000);
423
+ reportResult(result, () => {
424
+ console.log('✓ 已记录反馈内容');
425
+ });
426
+ }
427
+ // ── 结果输出 ───────────────────────────────────────────────────────────────
428
+ function reportResult(result, ok) {
429
+ if (!result) {
430
+ console.error('错误: 无法连接 evolcore 服务');
431
+ process.exit(1);
432
+ }
433
+ const r = result;
434
+ if (r.ok) {
435
+ ok(r);
436
+ }
437
+ else {
438
+ console.error(r.error || '操作失败');
439
+ process.exit(1);
440
+ }
441
+ }
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { spawn, execFile } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import { resolvePaths, getPackageRoot, daemonControlDir } from '../paths.js';
6
- import { loadDaemonConfig } from '../config-store.js';
6
+ import { isRestartRecoveryEnabled, loadDaemonConfig } from '../config-store.js';
7
7
  import * as platform from '../utils/cross-platform.js';
8
8
  import { EventBus } from '../core/event-bus.js';
9
9
  import { applyCachedPackageUpgrades, recordPackageUpgrade } from '../utils/package-version-info.js';
@@ -13,10 +13,52 @@ import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMoni
13
13
  import { MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME } from '../product.js';
14
14
  import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
15
15
  import { rotateStdoutLog } from '../utils/log-writer.js';
16
- import { inspectDataMigrationRequirement } from '../core/data-migration.js';
16
+ import { applyDataMigration, finalizeDataMigration, planDataMigration, verifyDataMigration, } from '../core/data-migration.js';
17
17
  import { DAEMON_SHUTDOWN_TIMEOUT_MS, requestDaemonShutdown, stopCodexAppServerOrphans } from './daemon-commands.js';
18
18
  import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
19
19
  const execFileAsync = promisify(execFile);
20
+ /** Read-only check performed before stopping the current daemon. */
21
+ function preflightDataMigration(root, log) {
22
+ try {
23
+ const plan = planDataMigration(root);
24
+ const operations = plan.operations.filter(operation => operation.status !== 'acknowledged').length;
25
+ log(`Data migration preflight passed (${operations} pending operation(s))`);
26
+ for (const warning of plan.warnings)
27
+ log(`Migration preflight warning: ${warning}`);
28
+ return { ok: true, operations };
29
+ }
30
+ catch (error) {
31
+ const reason = error instanceof Error ? error.message : String(error);
32
+ log(`Data migration preflight failed: ${reason}`);
33
+ return { ok: false, reason };
34
+ }
35
+ }
36
+ /** Execute the migration transaction while the daemon is stopped. */
37
+ function applyPendingDataMigration(root, log) {
38
+ try {
39
+ const plan = planDataMigration(root);
40
+ const pending = plan.operations.filter(operation => operation.status !== 'acknowledged');
41
+ if (pending.length === 0) {
42
+ log('Data migration not required after upgrade');
43
+ return { ok: true, operations: 0 };
44
+ }
45
+ log(`Applying data migration (${pending.length} operation(s))`);
46
+ const applied = applyDataMigration(plan);
47
+ const verified = verifyDataMigration(applied, { strictTargetContent: true });
48
+ const finalization = finalizeDataMigration(root, verified);
49
+ if (verified.state !== 'completed') {
50
+ const failed = verified.operations.filter(operation => operation.status === 'error' || operation.issues?.length);
51
+ throw new Error(`migration verification failed (${failed.length} operation(s)); completed sources archived: ${finalization.archivePath}`);
52
+ }
53
+ log(`Data migration completed and legacy sources archived: ${finalization.archivePath}`);
54
+ return { ok: true, operations: pending.length };
55
+ }
56
+ catch (error) {
57
+ const reason = error instanceof Error ? error.message : String(error);
58
+ log(`Data migration failed: ${reason}`);
59
+ return { ok: false, reason };
60
+ }
61
+ }
20
62
  // Bound network notifications so they cannot hold up recovery after shutdown.
21
63
  const OWNER_NOTIFICATION_TIMEOUT_MS = 5000;
22
64
  let foregroundDaemonExit;
@@ -126,9 +168,9 @@ export async function cmdRestartMonitor() {
126
168
  const pendingFile = path.join(daemonControlDir(), 'restart-pending.json');
127
169
  // 升级成功时记下版本变化,回执里要告诉 agent 实际跑的是哪个版本。
128
170
  let upgradeVersions;
129
- const migrationRequirement = inspectDataMigrationRequirement(p.root);
130
- if (migrationRequirement.required) {
131
- const message = `Pending user-data migration (${migrationRequirement.operationCount} operations); leaving the current service running. Run ec data migrate --dry-run, then ec data migrate --apply.`;
171
+ const migrationPreflight = preflightDataMigration(p.root, log);
172
+ if (!migrationPreflight.ok) {
173
+ const message = `重启前用户数据迁移预检失败:${migrationPreflight.reason ?? 'unknown error'}`;
132
174
  log(message);
133
175
  console.error(`❌ ${message}`);
134
176
  cleanupPendingFile(pendingFile, log);
@@ -341,6 +383,13 @@ export async function cmdRestartMonitor() {
341
383
  log(`↩ ${buildOutcome.reason ?? '已回滚'};继续以上一版本启动`);
342
384
  await notifyDaemonOwners(p, `⚠️ ${buildOutcome.reason ?? '已回滚'},正在以上一版本启动服务`, log);
343
385
  }
386
+ const migration = applyPendingDataMigration(p.root, log);
387
+ if (!migration.ok) {
388
+ const message = `服务未重启:用户数据迁移失败(${migration.reason ?? 'unknown error'})`;
389
+ markRestartOutcome(pendingFile, 'migration-failed', migration.reason, log);
390
+ cleanupPendingFile(pendingFile, log);
391
+ process.exit(1);
392
+ }
344
393
  // ECWeb is owned by the daemon. Starting it here used to race with the
345
394
  // daemon's own launcher and ECWeb's stale-instance cleanup, which could
346
395
  // leave ServiceProxy pointed at a dead PID.
@@ -428,6 +477,13 @@ export async function cmdRestartMonitor() {
428
477
  cleanupPendingFile(pendingFile, log);
429
478
  process.exit(1);
430
479
  }
480
+ if (!isRestartRecoveryEnabled()) {
481
+ const message = '服务启动失败,restartRecovery.enabled 已关闭,已跳过 Claude 自动修复。';
482
+ log(`⚠️ ${message}`);
483
+ await notifyDaemonOwners(p, `⚠️ ${message}`, log);
484
+ cleanupPendingFile(pendingFile, log);
485
+ process.exit(1);
486
+ }
431
487
  // 启动失败,进入 self-heal 循环
432
488
  log('❌ Service failed to start, entering self-heal loop');
433
489
  eventBus.publish({ type: 'self-heal:started', reason: 'Service failed to start after restart' });
@@ -975,7 +1031,7 @@ async function invokeClaude(claudePath, p, attempt, maxAttempts, timeout, log) {
975
1031
  - 检查是否有旧进程仍在运行:\`ps aux | grep 'node.*dist/index.js' | grep -v grep\`,旧进程可能占用端口或锁文件
976
1032
  - 可以运行 \`EVOLCORE_HOME=${p.root} node dist/cli/index.js diagnose\` 快速检查配置和数据库
977
1033
  - 如果进程无任何输出就 exit(1),说明是 process.exit(1) 被显式调用,搜索源码中所有 process.exit(1) 位置
978
- - 配置文件使用双 rename 原子写(foo.json → foo.json_ → foo.json__),崩溃时可从 foo.json_ 恢复
1034
+ - 配置文件先写入 foo.json__,再保留旧版本到 foo.json_,最后原子切换为 foo.json;崩溃后会丢弃未完成的 foo.json__,仅在 foo.json 缺失时从 foo.json_ 恢复
979
1035
 
980
1036
  请执行以下步骤:
981
1037
  1. 读取 ${stdoutLog} 和 ${p.logs}/daemon-*.log(最新文件)的最后 50 行