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.
- package/CHANGELOG.md +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +249 -89
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +40 -10
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/restart-monitor.js +62 -6
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +51 -23
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +276 -23
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +478 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/welcome.js +37 -8
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
package/dist/cli/data-command.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
|
130
|
-
if (
|
|
131
|
-
const message =
|
|
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
|
-
-
|
|
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 行
|