evolclaw 3.1.11 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -6,11 +6,12 @@ import { spawn, execFileSync, execFile } from 'child_process';
6
6
  import { promisify } from 'util';
7
7
  import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
8
8
  import { loadDefaults, loadAllAgents, mergeForAgent } from '../config-store.js';
9
+ import { loadEvolclawConfig } from '../evolclaw-config.js';
9
10
  import { resolveAnthropicConfig } from '../agents/resolve.js';
10
11
  import { migrateProject } from '../config-store.js';
11
- import { cmdInit } from './init.js';
12
+ import { cmdInit, needsControlAidInit, initTail } from './init.js';
12
13
  import { ipcQuery } from '../ipc.js';
13
- import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './init-channel.js';
14
+ import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
14
15
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
15
16
  import * as platform from '../utils/cross-platform.js';
16
17
  import { EventBus } from '../core/event-bus.js';
@@ -299,6 +300,19 @@ async function cmdStart() {
299
300
  await cmdInit();
300
301
  return;
301
302
  }
303
+ // 控制 AID 门禁:缺 aid 且交互式 → 只补全控制 AID + owners(不重走 baseagent 向导)。
304
+ // 非 TTY(restart-monitor/systemd/管道)不补全(无法交互),只提示后继续启动,daemon 侧 warn 兜底。
305
+ const evolclawCfgStart = loadEvolclawConfig();
306
+ if (needsControlAidInit(evolclawCfgStart.aid, !!process.stdin.isTTY)) {
307
+ console.log('⚡ 控制 AID 未配置,自动补全...\n');
308
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
309
+ suppressSdkLogs();
310
+ await initTail();
311
+ return;
312
+ }
313
+ if (!evolclawCfgStart.aid) {
314
+ console.log('⚠ 控制 AID 未配置(非交互式启动,跳过补全)。如需进程身份/远程管理,请运行 evolclaw init');
315
+ }
302
316
  // 检查至少有一个 self-agent
303
317
  const { agents, skipped } = loadAllAgents();
304
318
  if (agents.length === 0) {
@@ -968,6 +982,14 @@ async function cmdStatus() {
968
982
  }
969
983
  }
970
984
  catch { /* ignore */ }
985
+ // 控制 AID(daemon 进程身份)状态
986
+ if (status.controlAid) {
987
+ const state = status.controlAid.connected ? 'connected' : 'disconnected';
988
+ console.log(`control: ${status.controlAid.aid} [${state}]`);
989
+ }
990
+ else {
991
+ console.log('control: not configured');
992
+ }
971
993
  if (status.stats) {
972
994
  console.log('');
973
995
  console.log('📊 Last hour:');
@@ -4870,12 +4892,18 @@ export async function main(args) {
4870
4892
  --force 已存在 defaults.json 时覆盖
4871
4893
 
4872
4894
  配置渠道(先 evolclaw agent new 创建 agent):
4895
+ evolclaw init aun AUN 渠道配置(AID 创建/绑定)
4873
4896
  evolclaw init feishu 飞书扫码登录
4874
4897
  evolclaw init wechat 微信扫码登录
4875
4898
  evolclaw init dingtalk 钉钉扫码登录
4876
4899
  evolclaw init qqbot QQ 机器人扫码绑定
4877
4900
  evolclaw init wecom 企业微信 AI Bot 配置(手动输入)`);
4878
4901
  }
4902
+ else if (args[1] === 'aun') {
4903
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4904
+ suppressSdkLogs();
4905
+ await cmdInitAun();
4906
+ }
4879
4907
  else if (args[1] === 'wechat') {
4880
4908
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
4881
4909
  suppressSdkLogs();
@@ -4900,7 +4928,7 @@ export async function main(args) {
4900
4928
  await cmdInitWecom();
4901
4929
  }
4902
4930
  else if (args[1] && !args[1].startsWith('-')) {
4903
- const supported = ['feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom'];
4931
+ const supported = ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot', 'wecom'];
4904
4932
  console.error(`❌ 不支持的渠道: ${args[1]}`);
4905
4933
  console.error(` 支持的渠道: ${supported.join(', ')}`);
4906
4934
  process.exit(1);
@@ -5,16 +5,12 @@
5
5
  * - cmdInit<Channel>() — standalone `evolclaw init <channel>` entry
6
6
  * - run<Channel>QrFlow() or setupAun*() — reusable primitives for the main init wizard
7
7
  */
8
- import fs from 'fs';
9
8
  import readline from 'readline';
10
- import path from 'path';
11
9
  import crypto from 'crypto';
12
- import { aidLocalDir, aunPath as defaultAunPath } from '../paths.js';
13
10
  import { selectInstance } from './init.js';
14
- import { npmInstallGlobal } from '../utils/npm-ops.js';
15
- import { loadAllAgents, loadAgent } from '../config-store.js';
11
+ import { loadAllAgents, loadAgent, saveAgent } from '../config-store.js';
16
12
  import { agentChannelUpsert } from './agent.js';
17
- import { AUN_CORE_SDK_PKG, MIN_AUN_CORE_SDK, resolveAunCoreSdkPkg, isAunSdkVersionOk, isValidAid, aidCreate, agentmdPut, buildInitialAgentMd, } from '../aun/aid/index.js';
13
+ import { isValidAid } from '../aun/aid/index.js';
18
14
  function ask(rl, question) {
19
15
  return new Promise(resolve => rl.question(question, resolve));
20
16
  }
@@ -372,156 +368,50 @@ export async function cmdInitWechat() {
372
368
  await commitChannel(aid, channel, choice.action);
373
369
  }
374
370
  // ==================== AUN ====================
375
- //
376
- // AUN 原子操作(aidCreate, agentmdPut, downloadCaRoot, isValidAid, ...)
377
- // 已迁移至 src/channels/aun-ops.ts。本节仅保留交互式 UI 编排。
378
- export async function checkAunEnvironment(rl) {
379
- console.log('\n🔍 AUN 环境检查...\n');
380
- const minVer = MIN_AUN_CORE_SDK.join('.');
381
- const installed = resolveAunCoreSdkPkg();
382
- if (!installed) {
383
- console.log(` ✗ ${AUN_CORE_SDK_PKG} 未安装`);
384
- const answer = (await ask(rl, ` → 是否安装 ${AUN_CORE_SDK_PKG}@latest?[Y/n] `)).trim().toLowerCase();
385
- if (answer === 'n' || answer === 'no') {
386
- console.log(' 已取消');
387
- return false;
388
- }
389
- console.log(` 正在安装 ${AUN_CORE_SDK_PKG}...`);
390
- try {
391
- await npmInstallGlobal(`${AUN_CORE_SDK_PKG}@latest`);
392
- console.log(` ✓ ${AUN_CORE_SDK_PKG} 安装完成`);
393
- }
394
- catch (e) {
395
- console.log(` ✗ 安装失败: ${e.message?.slice(0, 200) || e}`);
396
- return false;
397
- }
398
- console.log('');
399
- return true;
400
- }
401
- if (isAunSdkVersionOk(installed.version)) {
402
- console.log(` ✓ ${AUN_CORE_SDK_PKG} v${installed.version}`);
403
- console.log('');
404
- return true;
405
- }
406
- console.log(` ✗ ${AUN_CORE_SDK_PKG} v${installed.version} — 需要 >= ${minVer}`);
407
- const answer = (await ask(rl, ` → 是否升级 ${AUN_CORE_SDK_PKG}?[Y/n] `)).trim().toLowerCase();
408
- if (answer === 'n' || answer === 'no') {
409
- console.log(' 已取消');
410
- return false;
411
- }
412
- console.log(` 正在升级 ${AUN_CORE_SDK_PKG}...`);
371
+ export async function cmdInitAun() {
372
+ // AUN channel agent.aid 隐式派生,AID 密钥在 `agent new` 时已创建就绪——本命令不碰 aid。
373
+ // 与其他渠道一致,职责是配置 owner;AUN 的 owner 存于 agent 顶层 owners[](见 evolagent.ts)。
374
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
413
375
  try {
414
- await npmInstallGlobal(`${AUN_CORE_SDK_PKG}@latest`);
415
- console.log(` ✓ ${AUN_CORE_SDK_PKG} 升级完成`);
416
- }
417
- catch (e) {
418
- console.log(` ✗ 升级失败: ${e.message?.slice(0, 200) || e}`);
419
- return false;
420
- }
421
- console.log('');
422
- return true;
423
- }
424
- // isValidAid, createAidSilent → 已迁移至 src/channels/aun-ops.ts
425
- // appendAunInstance → 已迁移至 src/channels/aun-ops.ts
426
- export async function setupAunAid(rl, _config) {
427
- let aid = '';
428
- // Outer loop: allows retrying with a different AID
429
- while (true) {
430
- // Ask AID with format validation
431
- aid = '';
432
- while (!aid) {
433
- aid = (await ask(rl, ' AUN Agent ID (例: mybot.agentid.pub): ')).trim();
434
- if (!aid) {
435
- console.log(' ⚠ 不能为空');
436
- continue;
437
- }
438
- if (!isValidAid(aid)) {
439
- console.log(' ⚠ 无效 AID 格式(需要合法域名,至少三级,如 alice.agentid.pub)');
440
- aid = '';
441
- }
442
- }
443
- // Check if AID exists locally
444
- const aunPath = defaultAunPath();
445
- const aidDir = path.join(aunPath, 'AIDs', aid);
446
- if (fs.existsSync(aidDir) && fs.existsSync(path.join(aidDir, 'private'))) {
447
- console.log(` ✓ AID ${aid} 已存在`);
448
- break;
449
- }
450
- const answer = (await ask(rl, ` ⚠ AID ${aid} 本地不存在,是否创建?[Y/n] `)).trim().toLowerCase();
451
- if (answer === 'n' || answer === 'no') {
452
- console.log(' 已跳过 AID 创建(启动时可能连接失败)');
453
- break;
376
+ const agentId = await pickAgentForChannel(rl);
377
+ if (!agentId)
378
+ return;
379
+ const agentConfig = loadAgent(agentId);
380
+ if (!agentConfig) {
381
+ console.error(`❌ 无法加载 agent ${agentId} 的配置`);
382
+ return;
454
383
  }
455
- // Create AID + agent.md via atomic ops
456
- console.log(' 正在创建 AID...');
457
- let failed = false;
458
- try {
459
- const result = await aidCreate(aid);
460
- console.log(` ✓ AID ${result.aid} 创建成功`);
461
- // Collect agent.md type and upload
462
- const typeInput = (await ask(rl, ' Agent 类型 human/ai [ai]: ')).trim().toLowerCase();
463
- const agentType = typeInput === 'human' ? 'human' : 'ai';
464
- const content = buildInitialAgentMd({ aid, type: agentType });
465
- try {
466
- await agentmdPut(content, { aid });
467
- console.log(' ✓ agent.md 已发布并写入本地');
468
- }
469
- catch (e) {
470
- console.log(` ⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
471
- // Still write local copy as fallback
472
- try {
473
- const localDir = aidLocalDir(aid);
474
- fs.mkdirSync(localDir, { recursive: true });
475
- fs.writeFileSync(path.join(localDir, 'agent.md'), content, 'utf-8');
476
- console.log(' ✓ agent.md 已写入本地');
384
+ console.log(`\n📡 AUN 渠道配置 agent ${agentConfig.aid}`);
385
+ const current = agentConfig.owners?.[0];
386
+ if (current)
387
+ console.log(` 当前 Owner: ${current}`);
388
+ console.log(' Owner 将接收欢迎消息并拥有管理权限\n');
389
+ let owner = '';
390
+ while (!owner) {
391
+ const input = (await ask(rl, ` Owner AID${current ? ` [${current}]` : ''}: `)).trim();
392
+ if (!input) {
393
+ if (current) {
394
+ owner = current;
395
+ break;
477
396
  }
478
- catch (we) {
479
- console.log(` ✗ agent.md 本地写入失败: ${String(we.message || we).slice(0, 100)}`);
480
- failed = true;
481
- }
482
- }
483
- try {
484
- await result.client.close();
397
+ console.log(' ⚠ Owner AID 不能为空');
398
+ continue;
485
399
  }
486
- catch { /* ignore */ }
487
- try {
488
- result.store?.close();
400
+ if (!isValidAid(input)) {
401
+ console.log(' ⚠ Owner AID 格式无效(需合法多级域名,如 alice.agentid.pub)');
402
+ continue;
489
403
  }
490
- catch { /* ignore */ }
404
+ owner = input;
491
405
  }
492
- catch (e) {
493
- const msg = e.message || String(e);
494
- console.log(` ✗ AID 创建失败: ${msg.slice(0, 200)}`);
495
- failed = true;
496
- }
497
- if (!failed)
498
- break;
499
- // Creation failed — retry or give up
500
- const retry = (await ask(rl, ' → 重新输入 (r) / 跳过 (s) / 取消 (c)?[r/s/c] ')).trim().toLowerCase();
501
- if (retry === 'c')
502
- return null;
503
- if (retry === 's')
504
- break;
505
- // default: retry with new AID
406
+ // 写入顶层 owners:新 owner 置首位(getOwner 取 owners[0]),保留其余去重
407
+ agentConfig.owners = [owner, ...(agentConfig.owners || []).filter(o => o !== owner)];
408
+ saveAgent(agentConfig);
409
+ console.log(`\n✅ AUN 渠道 Owner 已设置: ${owner}`);
410
+ console.log(`\n重启生效: evolclaw restart`);
506
411
  }
507
- // Owner 必填
508
- console.log('\n📋 Owner 配置');
509
- console.log(' Owner 将接收欢迎消息并拥有管理权限');
510
- let owner = '';
511
- while (!owner) {
512
- const ownerInput = (await ask(rl, ' Owner AID (必填): ')).trim();
513
- if (!ownerInput) {
514
- console.log(' ⚠ Owner AID 不能为空');
515
- continue;
516
- }
517
- if (!isValidAid(ownerInput)) {
518
- console.log(' ⚠ Owner AID 格式无效');
519
- continue;
520
- }
521
- owner = ownerInput;
522
- console.log(` ✓ Owner 已设置: ${owner}`);
412
+ finally {
413
+ rl.close();
523
414
  }
524
- return { aid, owner };
525
415
  }
526
416
  // ==================== DingTalk ====================
527
417
  const DINGTALK_BASE_URL = 'https://oapi.dingtalk.com';
package/dist/cli/init.js CHANGED
@@ -4,7 +4,9 @@ import readline from 'readline';
4
4
  import { resolvePaths, ensureDataDirs } from '../paths.js';
5
5
  import { commandExists } from '../utils/cross-platform.js';
6
6
  import { scanInstances } from '../utils/instance-registry.js';
7
- import { saveDefaultsSafe, loadAllAgents } from '../config-store.js';
7
+ import { saveDefaultsSafe, loadAllAgents, migrateProcessConfigIfNeeded } from '../config-store.js';
8
+ import { loadEvolclawConfig, saveEvolclawConfig } from '../evolclaw-config.js';
9
+ import { generateControlAid } from '../aun/aid/control-aid.js';
8
10
  import { isCodexSdkAvailable } from '../agents/codex-runner.js';
9
11
  // ==================== Helpers ====================
10
12
  function ask(rl, question) {
@@ -36,10 +38,35 @@ function buildDefaults(chosen, available, projectsDefaultPath) {
36
38
  function writeDefaults(chosen, available, projectsDefaultPath) {
37
39
  saveDefaultsSafe(buildDefaults(chosen, available, projectsDefaultPath));
38
40
  }
41
+ /** 启动门禁判定:缺控制 AID 且处于交互式终端时,应进 init 向导补全。
42
+ * 非 TTY(restart-monitor/systemd/管道)即使缺 aid 也不进 init(无法交互),由 daemon 侧 warn 兜底。 */
43
+ export function needsControlAidInit(aid, isTty) {
44
+ return !aid && isTty;
45
+ }
46
+ /** 解析用户输入的 owner AID 列表:按空白/逗号分隔,去空、去重、按 isValid 分流。
47
+ * 空输入 → valid:[](视为跳过)。 */
48
+ export function parseOwnerAids(raw, isValid) {
49
+ const tokens = raw.split(/[\s,]+/).map(t => t.trim()).filter(Boolean);
50
+ const valid = [];
51
+ const invalid = [];
52
+ for (const t of tokens) {
53
+ if (isValid(t)) {
54
+ if (!valid.includes(t))
55
+ valid.push(t);
56
+ }
57
+ else {
58
+ invalid.push(t);
59
+ }
60
+ }
61
+ return { valid, invalid };
62
+ }
39
63
  // ==================== Main ====================
40
64
  export async function cmdInit(options) {
41
65
  const p = resolvePaths();
42
66
  ensureDataDirs();
67
+ // config.json → evolclaw.json:init 路径也可能先于 daemon 触发 AID 生成(走 getAidStore),
68
+ // 须在任何 getAidStore 之前迁移 encryptionSeed。
69
+ migrateProcessConfigIfNeeded();
43
70
  // ── 1. 单进程互斥 ──
44
71
  const aliveMains = scanInstances().mains.filter(m => m.alive);
45
72
  if (aliveMains.length > 0) {
@@ -66,110 +93,163 @@ export async function cmdInit(options) {
66
93
  // ── 3. 非交互式分支 ──
67
94
  if (options?.nonInteractive) {
68
95
  if (exists && !options.force) {
69
- console.log(`❌ 配置已存在: ${defaultsPath}(加 --force 可覆盖)`);
70
- return;
96
+ // 配置已存在且未 --force:不重写 defaults,但仍落到共享 tail(幂等补生成控制 AID)
97
+ console.log(`配置已存在: ${defaultsPath}(加 --force 可覆盖)`);
71
98
  }
72
- let chosen;
73
- if (options.baseagent) {
74
- if (!BASEAGENT_CANDIDATES.includes(options.baseagent)) {
75
- console.log(`❌ 无效 baseagent: ${options.baseagent}(可选: ${BASEAGENT_CANDIDATES.join('/')})`);
76
- return;
99
+ else {
100
+ let chosen;
101
+ if (options.baseagent) {
102
+ if (!BASEAGENT_CANDIDATES.includes(options.baseagent)) {
103
+ console.log(`❌ 无效 baseagent: ${options.baseagent}(可选: ${BASEAGENT_CANDIDATES.join('/')})`);
104
+ return; // 硬错误:不落 tail
105
+ }
106
+ if (!available.includes(options.baseagent)) {
107
+ console.log(`❌ ${options.baseagent} 当前环境不可用(可用: ${available.join('/')})`);
108
+ return; // 硬错误:不落 tail
109
+ }
110
+ chosen = options.baseagent;
77
111
  }
78
- if (!available.includes(options.baseagent)) {
79
- console.log(`❌ ${options.baseagent} 当前环境不可用(可用: ${available.join('/')})`);
80
- return;
112
+ else {
113
+ chosen = pickDefault(available);
81
114
  }
82
- chosen = options.baseagent;
83
- }
84
- else {
85
- chosen = pickDefault(available);
86
- }
87
- writeDefaults(chosen, available);
88
- console.log(`✓ 已${exists ? '覆盖' : '创建'}: ${defaultsPath}`);
89
- console.log(` active_baseagent: ${chosen}`);
90
- const { agents } = loadAllAgents();
91
- if (agents.length === 0) {
92
- console.log('\n提示:尚无 agent,运行以下命令创建:');
93
- console.log(' evolclaw agent new <aid>.agentid.pub');
115
+ writeDefaults(chosen, available);
116
+ console.log(`✓ 已${exists ? '覆盖' : '创建'}: ${defaultsPath}`);
117
+ console.log(` active_baseagent: ${chosen}`);
94
118
  }
95
- return;
119
+ // 落到共享 tail(不 return
96
120
  }
97
- // ── 4. 交互式分支 ──
98
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
99
- async function askBaseagent() {
100
- const defaultBa = pickDefault(available);
101
- if (available.length === 1) {
102
- console.log(` baseagent: ${defaultBa}`);
103
- return defaultBa;
104
- }
105
- let chosen = null;
106
- while (chosen === null) {
107
- const input = (await ask(rl, `默认 baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
108
- if (!BASEAGENT_CANDIDATES.includes(input)) {
109
- console.log(` 无效选择,可选: ${BASEAGENT_CANDIDATES.join('/')}`);
110
- continue;
121
+ else {
122
+ // ── 4. 交互式分支(rl 生命周期封装在内部函数,tail 不引用 rl)──
123
+ await runInteractive();
124
+ }
125
+ // ── 共享 tail(单一出口):提示创建 agent + 生成控制 AID ──
126
+ await initTail();
127
+ // ── 内部函数 ──
128
+ async function runInteractive() {
129
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
130
+ async function askBaseagent() {
131
+ const defaultBa = pickDefault(available);
132
+ if (available.length === 1) {
133
+ console.log(` baseagent: ${defaultBa}`);
134
+ return defaultBa;
111
135
  }
112
- if (!available.includes(input)) {
113
- console.log(` ${input} 当前环境不可用(可用: ${available.join('/')})`);
114
- continue;
136
+ let chosen = null;
137
+ while (chosen === null) {
138
+ const input = (await ask(rl, `默认 baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
139
+ if (!BASEAGENT_CANDIDATES.includes(input)) {
140
+ console.log(` 无效选择,可选: ${BASEAGENT_CANDIDATES.join('/')}`);
141
+ continue;
142
+ }
143
+ if (!available.includes(input)) {
144
+ console.log(` ${input} 当前环境不可用(可用: ${available.join('/')})`);
145
+ continue;
146
+ }
147
+ chosen = input;
115
148
  }
116
- chosen = input;
149
+ return chosen;
117
150
  }
118
- return chosen;
119
- }
120
- async function askProjectsDefaultPath() {
121
- const defaultDir = path.join(p.root, 'projects', 'default');
122
- const input = (await ask(rl, `项目默认目录 [${defaultDir}]: `)).trim();
123
- const resolved = input || defaultDir;
124
- if (!path.isAbsolute(resolved)) {
125
- console.log(' ⚠ 需要绝对路径,已跳过');
126
- return undefined;
127
- }
128
- if (!fs.existsSync(resolved)) {
129
- const create = (await ask(rl, ` 目录不存在,是否创建?[Y/n]: `)).trim().toLowerCase();
130
- if (create === '' || create === 'y' || create === 'yes') {
131
- fs.mkdirSync(resolved, { recursive: true });
132
- console.log(` ✓ 已创建 ${resolved}`);
133
- }
134
- else {
151
+ async function askProjectsDefaultPath() {
152
+ const defaultDir = path.join(p.root, 'projects', 'default');
153
+ const input = (await ask(rl, `项目默认目录 [${defaultDir}]: `)).trim();
154
+ const resolved = input || defaultDir;
155
+ if (!path.isAbsolute(resolved)) {
156
+ console.log(' ⚠ 需要绝对路径,已跳过');
135
157
  return undefined;
136
158
  }
159
+ if (!fs.existsSync(resolved)) {
160
+ const create = (await ask(rl, ` 目录不存在,是否创建?[Y/n]: `)).trim().toLowerCase();
161
+ if (create === '' || create === 'y' || create === 'yes') {
162
+ fs.mkdirSync(resolved, { recursive: true });
163
+ console.log(` ✓ 已创建 ${resolved}`);
164
+ }
165
+ else {
166
+ return undefined;
167
+ }
168
+ }
169
+ return resolved;
137
170
  }
138
- return resolved;
139
- }
140
- try {
141
- if (exists) {
142
- const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
143
- if (ans === 'y' || ans === 'yes') {
171
+ try {
172
+ if (exists) {
173
+ const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
174
+ if (ans === 'y' || ans === 'yes') {
175
+ const chosen = await askBaseagent();
176
+ const projectsDefaultPath = await askProjectsDefaultPath();
177
+ writeDefaults(chosen, available, projectsDefaultPath);
178
+ console.log(`\n✓ 已覆盖: ${defaultsPath}`);
179
+ console.log(` active_baseagent: ${chosen}\n`);
180
+ }
181
+ else {
182
+ console.log(' 已跳过(保留现有配置)\n');
183
+ }
184
+ }
185
+ else {
144
186
  const chosen = await askBaseagent();
145
187
  const projectsDefaultPath = await askProjectsDefaultPath();
146
188
  writeDefaults(chosen, available, projectsDefaultPath);
147
- console.log(`\n✓ 已覆盖: ${defaultsPath}`);
189
+ console.log(`\n✓ 已创建: ${defaultsPath}`);
148
190
  console.log(` active_baseagent: ${chosen}\n`);
149
191
  }
150
- else {
151
- console.log(' 已跳过(保留现有配置)\n');
192
+ }
193
+ finally {
194
+ try {
195
+ rl.close();
152
196
  }
197
+ catch { /* ignore */ }
153
198
  }
154
- else {
155
- const chosen = await askBaseagent();
156
- const projectsDefaultPath = await askProjectsDefaultPath();
157
- writeDefaults(chosen, available, projectsDefaultPath);
158
- console.log(`\n✓ 已创建: ${defaultsPath}`);
159
- console.log(` active_baseagent: ${chosen}\n`);
199
+ }
200
+ }
201
+ /** 补全控制 AID + owners(可单独调用,不走 baseagent 向导)。 */
202
+ export async function initTail() {
203
+ // 提示创建 agent(两分支汇合后执行一次)
204
+ const { agents } = loadAllAgents();
205
+ if (agents.length === 0) {
206
+ console.log('\n提示:尚无 agent,运行以下命令创建:');
207
+ console.log(' evolclaw agent new <aid>.agentid.pub');
208
+ }
209
+ // 控制 AID:daemon 进程身份。缺失则生成并写回 evolclaw.json(幂等:已存在则跳过)。
210
+ const evc = loadEvolclawConfig();
211
+ if (evc.aid) {
212
+ console.log(`✓ 控制 AID 已存在: ${evc.aid}`);
213
+ }
214
+ else {
215
+ try {
216
+ const { aid } = await generateControlAid();
217
+ saveEvolclawConfig({ ...evc, $schema_version: evc.$schema_version ?? 1, aid });
218
+ console.log(`✓ 已生成控制 AID: ${aid}`);
160
219
  }
161
- // ── 5. 提示创建 agent ──
162
- const { agents } = loadAllAgents();
163
- if (agents.length === 0) {
164
- console.log('提示:尚无 agent,运行以下命令创建:');
165
- console.log(' evolclaw agent new <aid>.agentid.pub');
220
+ catch (e) {
221
+ console.error(`⚠️ 控制 AID 生成失败(Gateway 不可达?联网后重跑 evolclaw init 补全): ${e?.message || e}`);
166
222
  }
167
223
  }
168
- finally {
224
+ // 配置进程级管理者(owners):仅交互式 + aid 已配置 + owners 未配置时询问
225
+ const evcForOwners = loadEvolclawConfig();
226
+ if (process.stdin.isTTY && evcForOwners.aid && (!evcForOwners.owners || evcForOwners.owners.length === 0)) {
227
+ const { isValidAid } = await import('../aun/aid/index.js');
228
+ const rlOwners = readline.createInterface({ input: process.stdin, output: process.stdout });
169
229
  try {
170
- rl.close();
230
+ const raw = (await ask(rlOwners, '\n请输入 EvolClaw 管理者 AID: ')).trim();
231
+ if (raw) {
232
+ const { valid, invalid } = parseOwnerAids(raw, isValidAid);
233
+ if (invalid.length > 0)
234
+ console.log(` ⚠ 跳过非法 AID: ${invalid.join(', ')}`);
235
+ if (valid.length > 0) {
236
+ saveEvolclawConfig({ ...loadEvolclawConfig(), owners: valid });
237
+ console.log(` ✓ 已配置管理者: ${valid.join(', ')}`);
238
+ }
239
+ else {
240
+ console.log(' 未输入合法 AID,已跳过 owners 配置');
241
+ }
242
+ }
243
+ else {
244
+ console.log(' 已跳过 owners 配置(可日后编辑 evolclaw.json 或重跑 evolclaw init)');
245
+ }
246
+ }
247
+ finally {
248
+ try {
249
+ rlOwners.close();
250
+ }
251
+ catch { /* ignore */ }
171
252
  }
172
- catch { /* ignore */ }
173
253
  }
174
254
  }
175
255
  /**
@@ -17,6 +17,7 @@ import fs from 'fs';
17
17
  import path from 'path';
18
18
  import { resolvePaths, agentConfig as agentConfigPath, agentDir, } from './paths.js';
19
19
  import { atomicReadJson, atomicWriteJson } from './utils/atomic-write.js';
20
+ import * as evolclawConfigModule from './evolclaw-config.js';
20
21
  import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
21
22
  import { isValidChannelName } from './core/channel-loader.js';
22
23
  import { CONFIG_SCHEMA_VERSION } from './types.js';
@@ -122,14 +123,42 @@ export function saveDefaultsSafe(patch) {
122
123
  : { $schema_version: CONFIG_SCHEMA_VERSION, ...patch };
123
124
  atomicWriteJson(p, merged);
124
125
  }
125
- export function loadProcessConfig() {
126
- const raw = atomicReadJson(resolvePaths().processConfig);
126
+ // ── 进程配置迁移(旧 {root}/config.json ProcessConfig → evolclaw.json)──────
127
+ //
128
+ // ProcessConfig 类型 + loadProcessConfig/saveProcessConfig 已废弃并删除:
129
+ // 唯一有效字段 aun.encryptionSeed 已迁入 evolclaw.json(见 migrateProcessConfigIfNeeded),
130
+ // 读取源切到 loadEvolclawConfig(store.ts)。log / aun.gateway / aun.keystorePath 是死字段。
131
+ /**
132
+ * 一次性迁移:{root}/config.json(旧 ProcessConfig)→ {root}/evolclaw.json。
133
+ * - 仅搬运 aun.encryptionSeed(逐字节原样,含 null);log / aun.gateway / aun.keystorePath 是死字段,丢弃。
134
+ * - 合并写入(不覆盖 evolclaw.json 已有字段如 aid)。
135
+ * - 完成后归档 config.json → config.json.migrated(保留备份,不直接删)。
136
+ *
137
+ * ⚠️ encryptionSeed 是 AID 私钥的加密种子,迁移前后 getAidStore 拿到的 seed 必须逐字节一致,
138
+ * 否则所有已注册 AID 私钥解不开。这里只搬运不改值(含 null)。
139
+ */
140
+ export function migrateProcessConfigIfNeeded() {
141
+ const p = resolvePaths();
142
+ const oldPath = p.processConfig; // {root}/config.json
143
+ const raw = atomicReadJson(oldPath);
127
144
  if (raw === null)
128
- return {};
129
- return expandEnvRefs(raw);
130
- }
131
- export function saveProcessConfig(value) {
132
- atomicWriteJson(resolvePaths().processConfig, value);
145
+ return; // 不存在 → no-op
146
+ const { loadEvolclawConfig, saveEvolclawConfig } = evolclawConfigModule;
147
+ const evc = loadEvolclawConfig();
148
+ // 仅当旧文件确实带 aun.encryptionSeed 字段时才搬(hasOwnProperty,保 null 语义)
149
+ const didMigrateSeed = !!(raw.aun && Object.prototype.hasOwnProperty.call(raw.aun, 'encryptionSeed'));
150
+ if (didMigrateSeed) {
151
+ evc.aun = { ...(evc.aun ?? {}), encryptionSeed: raw.aun.encryptionSeed };
152
+ }
153
+ evc.$schema_version = evc.$schema_version ?? 1;
154
+ saveEvolclawConfig(evc);
155
+ // 归档旧文件(不删,留备份)
156
+ try {
157
+ fs.renameSync(oldPath, oldPath + '.migrated');
158
+ }
159
+ catch { /* ignore */ }
160
+ const what = didMigrateSeed ? 'aun.encryptionSeed 已搬运' : 'aun.encryptionSeed 不存在(无需搬运)';
161
+ logger.info(`[migrate] config.json → evolclaw.json (${what},config.json 已归档为 .migrated)`);
133
162
  }
134
163
  // ── 自动迁移 ───────────────────────────────────────────────────────────
135
164
  /**
@@ -492,6 +521,8 @@ export function mergeForAgent(agent, defaults) {
492
521
  debounce: agent.debounce ?? d.debounce,
493
522
  debug: deepMergeBlocks(d.debug, agent.debug),
494
523
  enable_rich_content: agent.enable_rich_content ?? d.enable_rich_content,
524
+ dispatch: agent.dispatch,
525
+ observable: agent.observable,
495
526
  };
496
527
  return merged;
497
528
  }