evolcore 0.0.2 → 0.0.3

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 (124) hide show
  1. package/CHANGELOG.md +44 -793
  2. package/dist/agents/claude-runner.js +197 -17
  3. package/dist/agents/codex-runner.js +46 -3
  4. package/dist/aun/outbox.js +8 -0
  5. package/dist/channels/aun.js +21 -4
  6. package/dist/channels/contact-bind-code.js +134 -0
  7. package/dist/channels/dingtalk.js +979 -149
  8. package/dist/channels/feishu.js +130 -54
  9. package/dist/channels/wecom-card.js +101 -0
  10. package/dist/channels/wecom-onboarding.js +82 -0
  11. package/dist/channels/wecom-state.js +191 -0
  12. package/dist/channels/wecom.js +755 -163
  13. package/dist/cli/agent-command.js +2 -1
  14. package/dist/cli/aun-commands.js +88 -33
  15. package/dist/cli/bench.js +2 -2
  16. package/dist/cli/contact.js +71 -0
  17. package/dist/cli/ctl-command.js +2 -2
  18. package/dist/cli/daemon-commands.js +77 -214
  19. package/dist/cli/handoff-command.js +2 -2
  20. package/dist/cli/help.js +9 -5
  21. package/dist/cli/index.js +132 -116
  22. package/dist/cli/init-channel.js +92 -97
  23. package/dist/cli/init.js +63 -26
  24. package/dist/cli/model.js +2 -1
  25. package/dist/cli/net-check.js +2 -2
  26. package/dist/cli/queue-command.js +30 -6
  27. package/dist/cli/raw-key-input.js +25 -0
  28. package/dist/cli/response.js +5 -6
  29. package/dist/cli/restart-monitor.js +25 -1
  30. package/dist/cli/stats.js +6 -4
  31. package/dist/cli/trigger-command.js +55 -15
  32. package/dist/cli/version.js +6 -1
  33. package/dist/config/builtin-role-templates.js +22 -10
  34. package/dist/config/builtin-roles.js +7 -1
  35. package/dist/config/config-manager.js +221 -17
  36. package/dist/config/contact-alias.js +68 -0
  37. package/dist/config/contact-book-store.js +454 -0
  38. package/dist/config/contact-book-v2-startup.js +35 -0
  39. package/dist/config/contact-book.js +156 -303
  40. package/dist/config/contact-operation-service.js +110 -0
  41. package/dist/config/peer-role-resolver.js +133 -54
  42. package/dist/config/role-ranks.js +18 -0
  43. package/dist/config/role-service.js +16 -19
  44. package/dist/config/role-store.js +16 -5
  45. package/dist/config/roles.js +10 -1
  46. package/dist/config-store.js +0 -2
  47. package/dist/core/auth/authorization-audit.js +10 -1
  48. package/dist/core/auth/operation-authorizer.js +2 -0
  49. package/dist/core/auth/operation-catalog.js +56 -0
  50. package/dist/core/command/command-handler.js +105 -10
  51. package/dist/core/command/connect-menu.js +374 -0
  52. package/dist/core/command/menu-handler.js +114 -16
  53. package/dist/core/command/role-menu.js +128 -29
  54. package/dist/core/command/slash-handler.js +28 -18
  55. package/dist/core/daemon-file-cache.js +12 -6
  56. package/dist/core/event-catalog.js +70 -0
  57. package/dist/core/evolagent-registry.js +0 -1
  58. package/dist/core/evolagent.js +20 -9
  59. package/dist/core/message/im-renderer.js +2 -0
  60. package/dist/core/message/message-bridge.js +79 -8
  61. package/dist/core/message/message-queue.js +10 -0
  62. package/dist/core/message/message-utils.js +8 -2
  63. package/dist/core/message/response-engine.js +269 -25
  64. package/dist/core/message/send-receipt.js +24 -0
  65. package/dist/core/message/stream-debouncer.js +11 -2
  66. package/dist/core/permission/tool-policy.js +47 -15
  67. package/dist/core/protected-paths.js +2 -0
  68. package/dist/core/session/session-fs-store.js +44 -3
  69. package/dist/core/session/session-manager.js +61 -5
  70. package/dist/index.js +62 -6
  71. package/dist/ipc.js +34 -5
  72. package/dist/stats/billing.js +20 -8
  73. package/dist/trigger/manager.js +3 -0
  74. package/dist/trigger/parser.js +77 -2
  75. package/dist/trigger/patch.js +8 -1
  76. package/dist/trigger/scheduler.js +3 -1
  77. package/dist/trigger/validation.js +13 -0
  78. package/dist/utils/aid-bind.js +43 -29
  79. package/dist/utils/instance-registry.js +14 -7
  80. package/dist/utils/log-writer.js +46 -0
  81. package/dist/utils/media-cache.js +4 -1
  82. package/dist/utils/model-prices.jsonl +6 -3
  83. package/dist/utils/restart-safety.js +31 -0
  84. package/dist/utils/system-memory.js +62 -0
  85. package/kits/docs/INDEX.md +2 -1
  86. package/kits/docs/evolcore/INDEX.md +5 -3
  87. package/kits/docs/evolcore/agent.md +9 -1
  88. package/kits/docs/evolcore/aid.md +5 -2
  89. package/kits/docs/evolcore/contact.md +57 -0
  90. package/kits/docs/evolcore/fs.md +9 -0
  91. package/kits/docs/evolcore/group.md +12 -3
  92. package/kits/docs/evolcore/model.md +4 -1
  93. package/kits/docs/evolcore/msg.md +9 -3
  94. package/kits/docs/evolcore/response.md +16 -21
  95. package/kits/docs/evolcore/rpc.md +2 -0
  96. package/kits/docs/evolcore/stats.md +15 -2
  97. package/kits/docs/evolcore/storage.md +1 -0
  98. package/kits/docs/evolcore/trigger.md +17 -2
  99. package/kits/eck_manifest.json +12 -0
  100. package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
  101. package/kits/schemas/_meta.json +7 -4
  102. package/kits/schemas/agent-config.schema.6.json +322 -0
  103. package/kits/schemas/contact-book.schema.2.json +43 -0
  104. package/kits/schemas/relation-config.schema.5.json +47 -0
  105. package/kits/schemas/role-config.schema.1.json +1 -0
  106. package/kits/schemas/role-registry.schema.1.json +2 -2
  107. package/kits/templates/roles/admin.json +1 -0
  108. package/kits/templates/roles/member.json +1 -0
  109. package/kits/templates/roles/owner.json +1 -0
  110. package/kits/templates/roles/visitor.json +1 -0
  111. package/kits/templates/system-fragments/commands.md +3 -1
  112. package/package.json +4 -4
  113. package/assets/brand/evolcore/README.md +0 -19
  114. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  115. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  116. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  117. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  118. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  119. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  120. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  121. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  122. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  123. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  124. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
package/dist/cli/index.js CHANGED
@@ -3,18 +3,82 @@
3
3
  process.env.AUN_LOG_INI_DISABLE = '1';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
- import { resolvePaths, getPackageRoot } from '../paths.js';
6
+ import { getPackageRoot } from '../paths.js';
7
7
  import { cmdInit } from './init.js';
8
8
  import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
9
- import { isHelpFlag, getArgValue } from './help.js';
9
+ import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
10
10
  import * as platform from '../utils/cross-platform.js';
11
11
  import { cmdRestartMonitor } from './restart-monitor.js';
12
12
  import { cmdAid, cmdRpc, cmdStorage, cmdMsg, cmdGroup } from './aun-commands.js';
13
13
  import { cmdAgent } from './agent-command.js';
14
14
  import { cmdCtl } from './ctl-command.js';
15
15
  import { cmdQueue } from './queue-command.js';
16
- import { cmdStart, cmdStop, cmdRestart, cmdStatus, cmdLogs, cmdWatchCommand, cmdDev, cmdMv, cmdDiagnose } from './daemon-commands.js';
16
+ import { cmdStart, cmdStop, cmdRestart, cmdStatus, cmdLogs, cmdWatchCommand, cmdMv, cmdDiagnose } from './daemon-commands.js';
17
17
  import { logCliCommand } from './command-log.js';
18
+ const MAIN_HELP = `用法: ec <command> [选项]
19
+
20
+ 服务:
21
+ init 初始化默认配置、owner、控制 AID 与 EC Web
22
+ start 启动服务
23
+ stop 停止服务
24
+ restart 重启服务并检查更新
25
+ status 查看运行状态
26
+ logs 持续查看 daemon 日志
27
+ watch 打开日志、AID、消息或 EC Web 监控
28
+
29
+ Agent 与会话:
30
+ agent 管理 EvolAgent
31
+ ctl 当前托管会话的控制命令
32
+ queue 查询或操作 agent 消息队列
33
+ trigger 管理定时与事件触发器
34
+ handoff 查询和完成跨会话回流
35
+ model 查看或设置模型与推理强度
36
+ response 查看或设置响应模式
37
+
38
+ AUN:
39
+ aid 管理 AID 身份与 agent.md
40
+ msg 私聊消息收发、撤回、在线状态与历史
41
+ group 群消息与群管理
42
+ fs AUN 文件系统
43
+ storage 底层对象存储命令
44
+ rpc 调用底层 AUN RPC
45
+ contact 联系人黑名单管理
46
+ net AUN 网络链路诊断
47
+
48
+ 配置与诊断:
49
+ config 读取、修改和检查分层配置
50
+ stats Token 用量与费用统计
51
+ diagnose 检查启动环境
52
+ version 显示组件版本
53
+ mv 迁移项目目录和会话路径
54
+
55
+ 维护命令:
56
+ mode 切换开发源码与 npm 包运行模式
57
+ restart-monitor 运行 daemon 重启监控器
58
+ bench AUN 消息性能基准测试
59
+ link-rules 连接 ECK rules 到项目目录
60
+
61
+ 运行 ec <command> --help 查看该命令的完整参数。
62
+
63
+ 环境变量:
64
+ EVOLCORE_HOME 数据目录(默认: ~/.evolcore)
65
+ LOG_LEVEL 日志级别(默认: INFO)
66
+ MESSAGE_LOG 消息日志(默认: true)
67
+ EVENT_LOG 事件日志(默认: true)`;
68
+ function printLifecycleHelp(command) {
69
+ const help = {
70
+ start: `用法: ec start [--foreground] [--diagnose|-d]\n\n --foreground 前台运行,适合 systemd 或容器直接管理\n --diagnose, -d 以诊断模式启动 daemon`,
71
+ stop: '用法: ec stop\n\n停止当前 EVOLCORE_HOME 下运行的 EvolCore 服务。',
72
+ restart: `用法: ec restart [--clear] [--diagnose|-d]\n\n --clear 同时清理跨 HOME 残留的 EvolCore 主进程\n --diagnose, -d 以诊断模式启动新 daemon`,
73
+ status: '用法: ec status\n\n查看 daemon、agent、渠道与会话运行状态。',
74
+ logs: `用法: ec logs [选项]\n\n --level <error|warn|info|debug> 最低日志级别\n --module <name> 只显示指定模块\n --raw 原始输出\n --no-color 禁用颜色`,
75
+ 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 的兼容别名`,
76
+ 'restart-monitor': '用法: ec restart-monitor\n\n运行内 daemon 重启与升级监控器。',
77
+ diagnose: '用法: ec diagnose\n\n检查配置、数据库、进程和编译产物。',
78
+ mv: '用法: ec mv <old-directory> <new-directory>\n\n迁移项目目录以及 Claude、Codex 和 EvolCore 会话路径。',
79
+ };
80
+ console.log(help[command]);
81
+ }
18
82
  // Suppress Node.js ExperimentalWarning (e.g. SQLite) from cluttering CLI output
19
83
  process.removeAllListeners('warning');
20
84
  process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
@@ -27,9 +91,13 @@ export async function main(args) {
27
91
  console.log(pkg.version);
28
92
  return;
29
93
  }
94
+ if (isHelpFlag(cmd)) {
95
+ console.log(MAIN_HELP);
96
+ return;
97
+ }
30
98
  switch (cmd) {
31
99
  case 'init':
32
- if (isHelpFlag(args[1])) {
100
+ if (wantsHelp(args.slice(1))) {
33
101
  console.log(`用法: ec init [渠道] [选项]
34
102
 
35
103
  仅初始化 defaults.json:
@@ -43,7 +111,7 @@ export async function main(args) {
43
111
  --owner <aid> 必填。唯一 daemon owner AID
44
112
  --baseagent <claude|codex|gemini> 可选。默认从 PATH 检测
45
113
  --projectpath <abs-path> 可选。默认项目目录(必须绝对路径,自动创建)
46
- --ecweb 可选。启用 ECWeb 自启动
114
+ --ecweb 可选。安装 EC Web 并启用自启动
47
115
  --format json 可选但推荐。stdout 输出 init.result JSON
48
116
  --force 已有不同 owner 时强制覆盖
49
117
 
@@ -53,7 +121,7 @@ export async function main(args) {
53
121
  ec init wechat 微信扫码登录
54
122
  ec init dingtalk 钉钉扫码登录
55
123
  ec init qqbot QQ 机器人扫码绑定
56
- ec init wecom 企业微信 AI Bot 配置(手动输入)`);
124
+ ec init wecom 企业微信 AI Bot 扫码创建/手工配置`);
57
125
  }
58
126
  else if (args[1] === 'aun') {
59
127
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
@@ -105,36 +173,84 @@ export async function main(args) {
105
173
  }
106
174
  break;
107
175
  case 'start':
176
+ if (wantsHelp(args.slice(1))) {
177
+ printLifecycleHelp('start');
178
+ break;
179
+ }
108
180
  await cmdStart({
109
181
  diagnose: args.includes('--diagnose') || args.includes('-d'),
110
182
  foreground: args.includes('--foreground'),
111
183
  });
112
184
  break;
113
185
  case 'stop':
186
+ if (wantsHelp(args.slice(1))) {
187
+ printLifecycleHelp('stop');
188
+ break;
189
+ }
114
190
  await cmdStop();
115
191
  break;
116
192
  case 'restart':
193
+ if (wantsHelp(args.slice(1))) {
194
+ printLifecycleHelp('restart');
195
+ break;
196
+ }
117
197
  await cmdRestart({ clear: args.includes('--clear'), diagnose: args.includes('--diagnose') || args.includes('-d') });
118
198
  break;
119
199
  case 'status':
200
+ if (wantsHelp(args.slice(1))) {
201
+ printLifecycleHelp('status');
202
+ break;
203
+ }
120
204
  await cmdStatus();
121
205
  break;
122
206
  case 'logs':
207
+ if (wantsHelp(args.slice(1))) {
208
+ printLifecycleHelp('logs');
209
+ break;
210
+ }
123
211
  cmdLogs(args.slice(1));
124
212
  break;
125
213
  case 'watch':
214
+ if (wantsHelp(args.slice(1))) {
215
+ printLifecycleHelp('watch');
216
+ break;
217
+ }
126
218
  await cmdWatchCommand(args.slice(1));
127
219
  break;
128
220
  case 'restart-monitor':
221
+ if (wantsHelp(args.slice(1))) {
222
+ printLifecycleHelp('restart-monitor');
223
+ break;
224
+ }
129
225
  await cmdRestartMonitor();
130
226
  break;
131
- case 'dev':
132
- await cmdDev(args.slice(1));
227
+ case 'mode':
228
+ try {
229
+ // 开发源码仓可提供 mode.js;正式 npm 包可以不携带该内部命令。
230
+ const { cmdMode } = await import('./mode.js');
231
+ await cmdMode(args.slice(1));
232
+ }
233
+ catch (error) {
234
+ if (error?.code === 'ERR_MODULE_NOT_FOUND' && /(?:^|[\\/])mode\.js(?:$|')/.test(String(error?.message ?? ''))) {
235
+ console.error('❌ 无效指令: mode');
236
+ process.exitCode = 1;
237
+ break;
238
+ }
239
+ throw error;
240
+ }
133
241
  break;
134
242
  case 'mv':
243
+ if (wantsHelp(args.slice(1))) {
244
+ printLifecycleHelp('mv');
245
+ break;
246
+ }
135
247
  await cmdMv(args[1], args[2]);
136
248
  break;
137
249
  case 'diagnose':
250
+ if (wantsHelp(args.slice(1))) {
251
+ printLifecycleHelp('diagnose');
252
+ break;
253
+ }
138
254
  await cmdDiagnose();
139
255
  break;
140
256
  case 'net': {
@@ -217,6 +333,11 @@ export async function main(args) {
217
333
  await cmdConfig(args.slice(1));
218
334
  break;
219
335
  }
336
+ case 'contact': {
337
+ const { cmdContact } = await import('./contact.js');
338
+ await cmdContact(args.slice(1));
339
+ break;
340
+ }
220
341
  case 'stats': {
221
342
  const { handleStats } = await import('./stats.js');
222
343
  await handleStats(args.slice(1));
@@ -242,114 +363,9 @@ export async function main(args) {
242
363
  break;
243
364
  }
244
365
  default:
245
- console.log(`Usage: ec {init|start|stop|restart|status|logs|watch|ctl|queue|trigger|handoff|diagnose|net|mv}
246
-
247
- Commands:
248
- init 初始化 evolcore home (${resolvePaths().defaultsConfig})
249
- init feishu 飞书扫码登录并写入配置
250
- init wechat 微信扫码登录并写入配置
251
- init dingtalk 钉钉扫码登录并写入配置
252
- init qqbot QQ 机器人扫码绑定并写入配置
253
- init wecom 企业微信 AI Bot 配置(手动输入 Bot ID + Secret)
254
- start 启动服务 (默认)
255
- --foreground 前台运行,适合 systemd/Docker 直接管理进程
256
- stop 停止服务
257
- restart 重启服务
258
- --clear 顺带 SIGKILL 跨 HOME 残留的 evolcore 主进程
259
- status 查看状态
260
- logs 查看日志 (tail -f, 着色渲染)
261
- --level error|warn 只显示指定级别及以上
262
- --module <name> 只显示指定模块(如 feishu、AgentRunner)
263
- --raw 原始输出,不着色
264
- watch 监控面板选择菜单(↑↓ 选择 log/aid/msg)
265
- watch logs 勾选日志类型后实时监控(记忆上次选择,存入 daemon.json)
266
- watch log <类型...> 直接监控指定类型(如 evolcore aun),不读写偏好
267
- watch aid AID 连接状态实时监控(显示各 AID 在线/离线/重连状态)
268
- watch msg 消息监控(三面板交互式 TUI:AID 列表 / 对端统计 / 消息流)
269
- ctl 当前会话工具(发送消息/文件、名称、队列、路径、状态)
270
- ec ctl help 查看完整命令列表
271
- trigger daemon 级触发器管理(list/show/history/create/update/run)
272
- handoff 跨会话任务回流
273
- handoff return [id] [content] 确认当前 handoff;required 策略需提供内容
274
- handoff list/trace 查询实例和审计事件
275
- queue 消息队列查询与操作
276
- queue --agent <aid> 查看 agent 的所有队列
277
- queue --agent <aid> --clear 清空待处理消息
278
- queue --agent <aid> --interrupt --sessionkey <key> 打断处理中任务
279
- agent 管理 EvolAgent
280
- agent 列出所有 agent
281
- agent <name> 查看指定 agent 详情
282
- agent new <name> 创建新 agent(交互式)
283
- agent new <name> --non-interactive ... 非交互创建(自动化)
284
- 必填: --project <absolute path>
285
- 可选: --baseagent <claude|codex|gemini> (默认: PATH 中第一个可用)
286
- --owner <aid>
287
- --name <display-name>
288
- --description <text>
289
- --force (覆盖已有 config.json)
290
- agent reload 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
291
- agent reload <n> 热重载指定 agent 配置
292
- model 模型管理(agent/关系可写,支持角色策略预览)
293
- model list 列出可用模型并显示当前生效模型
294
- model current 显示实际生效的模型、强度和来源
295
- model info <id> 查看模型详情
296
- model use <id> 设置模型(写操作需要 --self)
297
- model effort <lv> 设置推理强度
298
- model reset 清除当前作用域的模型和强度设置
299
- model check 诊断网关与模型可用性
300
- response 响应模式管理(查看/切换/配置,作用域:全局/agent/关系)
301
- response list 列出所有响应模式
302
- response current 显示当前作用域生效的默认模式
303
- response info <id> 查看单个模式详情(场景/配置参数)
304
- response set <id> 设置默认模式(--scene private/group)
305
- response config 查看/修改模式配置参数
306
- response reset 清除指定作用域设置
307
- response help 查看完整用法
308
- aid AID 身份管理
309
- aid list 列出本地所有 AID
310
- aid show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
311
- aid new <aid> 创建新 AID 身份
312
- aid lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
313
- aid agentmd put <aid> 签名并上传 agent.md
314
- aid agentmd get <aid> 下载并验签 agent.md
315
- aid delete <aid> 删除指定 AID
316
- aid delete --orphan 清理无私钥的外部 AID 缓存
317
- aid delete --unrecoverable 清理云端公钥已变更、不可恢复的 AID
318
- 默认 dry-run,加 --yes 执行
319
- fs AUN 文件系统统一入口
320
- fs ls|stat|cat|find <AID>:/path
321
- fs cp <src> <dst> 上传/下载/远程复制
322
- fs mv <src> <dst> 移动/改名(同后端)
323
- fs rm [-r] <AID>:/path
324
- fs mkdir [-p] <AID>:/path
325
- fs ln -s <target> <AID>:/path
326
- fs chmod +r|o-r <AID>:/path
327
- fs setfacl|getfacl <AID>:/path
328
- fs token issue|revoke|ls <AID>:/path
329
- fs df <AID>:
330
- fs mount <target> --volume <id>|--source <src>
331
- net 网络链路诊断
332
- net check [<aid>] 10 步链路检测(DNS→Discovery→TCP→TLS→WSS→Auth→Ping→Echo)
333
- net help 查看详细帮助
334
- rpc AUN RPC 调用
335
- rpc --as <aid> --params <json|jsonl|file>
336
- storage 文件存储
337
- storage upload <aid> <file> <path> [--public]
338
- storage download <aid> <url> [local-path]
339
- storage ls <aid> [prefix]
340
- storage rm <aid> <path>
341
- storage quota <aid>
342
- diagnose 诊断启动环境(配置、数据库、进程)
343
- mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolCore 会话)
344
- bench AUN 消息性能基准测试
345
- bench --aids 5 --rounds 10 --concurrency 5
346
-
347
- Environment:
348
- EVOLCORE_HOME 数据目录 (默认: ~/.evolcore)
349
- LOG_LEVEL 日志级别 (默认: INFO)
350
- MESSAGE_LOG 消息日志 (默认: true)
351
- EVENT_LOG 事件日志 (默认: true)`);
352
- process.exit(1);
366
+ console.error(`未知命令: ${cmd}`);
367
+ console.log(MAIN_HELP);
368
+ process.exitCode = 1;
353
369
  }
354
370
  }
355
371
  // 直接运行时自动执行(node dist/cli/index.js ...)
@@ -17,6 +17,8 @@ import { cmdStart } from './daemon-commands.js';
17
17
  import * as platform from '../utils/cross-platform.js';
18
18
  import { cleanupInstances } from '../utils/instance-registry.js';
19
19
  import { bindContactAlias } from '../config/contact-book.js';
20
+ import { listStaticAgentAdmins } from '../config/peer-role-resolver.js';
21
+ import { setupRawKeyListener } from './raw-key-input.js';
20
22
  function ask(rl, question) {
21
23
  return new Promise(resolve => rl.question(question, resolve));
22
24
  }
@@ -198,26 +200,12 @@ async function pollBindStatusWithKeyboard(taskId, expiresAt) {
198
200
  let quit = false;
199
201
  let skip = false;
200
202
  const p = resolvePaths();
201
- const setupKeyListener = () => {
202
- if (!process.stdin.isTTY)
203
- return () => { };
204
- process.stdin.setRawMode(true);
205
- process.stdin.resume();
206
- process.stdin.setEncoding('utf8');
207
- const handler = (key) => {
208
- if (key === 'q' || key === '\u0003')
209
- quit = true;
210
- if (key === 's' || key === 'S')
211
- skip = true;
212
- };
213
- process.stdin.on('data', handler);
214
- return () => {
215
- process.stdin.removeListener('data', handler);
216
- process.stdin.setRawMode(false);
217
- // Don't pause stdin - let the next readline/prompt handle it naturally
218
- };
219
- };
220
- const cleanup = setupKeyListener();
203
+ const cleanup = setupRawKeyListener((key) => {
204
+ if (key === 'q' || key === '\u0003')
205
+ quit = true;
206
+ if (key === 's' || key === 'S')
207
+ skip = true;
208
+ });
221
209
  try {
222
210
  while (Date.now() < expiresAt) {
223
211
  if (quit)
@@ -305,26 +293,12 @@ async function runQrRegistrationFlow() {
305
293
  console.log('请用飞书/Lark 扫描上方二维码...\n');
306
294
  console.log('按 q 退出 | 按 s 跳过扫码手动输入 appId/appSecret\n');
307
295
  let userAction = null;
308
- const setupKeyListener = () => {
309
- if (!process.stdin.isTTY)
310
- return () => { };
311
- process.stdin.setRawMode(true);
312
- process.stdin.resume();
313
- process.stdin.setEncoding('utf8');
314
- const handler = (key) => {
315
- if (key === 'q' || key === '\u0003')
316
- userAction = QUIT;
317
- if (key === 's')
318
- userAction = SKIP;
319
- };
320
- process.stdin.on('data', handler);
321
- return () => {
322
- process.stdin.removeListener('data', handler);
323
- process.stdin.setRawMode(false);
324
- // Don't pause stdin - let the next readline/prompt handle it naturally
325
- };
326
- };
327
- const cleanup = setupKeyListener();
296
+ const cleanup = setupRawKeyListener((key) => {
297
+ if (key === 'q' || key === '\u0003')
298
+ userAction = QUIT;
299
+ if (key === 's')
300
+ userAction = SKIP;
301
+ });
328
302
  const startedAt = Date.now();
329
303
  let pollIntervalSeconds = Number(beginResult.interval ?? 5);
330
304
  const expireInSeconds = Number(beginResult.expires_in ?? beginResult.expire_in ?? 600);
@@ -473,7 +447,7 @@ export async function cmdInitFeishu() {
473
447
  bindQrActorToContact(aid, contactBinding, 'feishu', result.openId, commitResult.channelKey);
474
448
  // Show unified init success message
475
449
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
476
- console.log(generateInitSuccessMessage('feishu', false));
450
+ console.log(generateInitSuccessMessage('feishu', contactBinding !== null));
477
451
  }
478
452
  // ==================== WeChat ====================
479
453
  const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
@@ -610,7 +584,7 @@ export async function cmdInitWechat() {
610
584
  bindQrActorToContact(aid, contactBinding, 'wechat', result.userId, commitResult.channelKey);
611
585
  // Show unified init success message
612
586
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
613
- console.log(generateInitSuccessMessage('wechat', false));
587
+ console.log(generateInitSuccessMessage('wechat', contactBinding !== null));
614
588
  }
615
589
  // ==================== AUN ====================
616
590
  export async function cmdInitAun() {
@@ -752,26 +726,12 @@ async function runDingtalkQrFlow() {
752
726
  console.log('提示: 扫码页面标注 "OpenClaw" 是钉钉生态接入桥,可放心使用。\n');
753
727
  console.log('按 q 退出 | 按 s 跳过扫码手动输入\n');
754
728
  let userAction = null;
755
- const setupKeyListener = () => {
756
- if (!process.stdin.isTTY)
757
- return () => { };
758
- process.stdin.setRawMode(true);
759
- process.stdin.resume();
760
- process.stdin.setEncoding('utf8');
761
- const handler = (key) => {
762
- if (key === 'q' || key === '\u0003')
763
- userAction = QUIT;
764
- if (key === 's')
765
- userAction = SKIP;
766
- };
767
- process.stdin.on('data', handler);
768
- return () => {
769
- process.stdin.removeListener('data', handler);
770
- process.stdin.setRawMode(false);
771
- // Don't pause stdin - let the next readline/prompt handle it naturally
772
- };
773
- };
774
- const cleanup = setupKeyListener();
729
+ const cleanup = setupRawKeyListener((key) => {
730
+ if (key === 'q' || key === '\u0003')
731
+ userAction = QUIT;
732
+ if (key === 's')
733
+ userAction = SKIP;
734
+ });
775
735
  const pollInterval = Math.max(Number(beginData.interval ?? 3), 2);
776
736
  const expiresIn = Number(beginData.expires_in ?? 7200);
777
737
  const startedAt = Date.now();
@@ -904,7 +864,7 @@ export async function cmdInitDingtalk() {
904
864
  console.log(' ⚠ 服务未运行或热重载失败,无法注册钉钉绑定码;请启动服务后重新执行 ec init dingtalk 并再次扫码绑定。');
905
865
  }
906
866
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
907
- console.log(generateInitSuccessMessage('dingtalk', false));
867
+ console.log(generateInitSuccessMessage('dingtalk', contactBinding !== null));
908
868
  }
909
869
  // ==================== QQBot ====================
910
870
  const QQBOT_PORTAL_HOST = 'q.qq.com';
@@ -968,26 +928,12 @@ async function runQQBotBindFlow() {
968
928
  console.log('请用 QQ 扫描上方二维码绑定机器人...\n');
969
929
  console.log('按 q 退出 | 按 s 跳过扫码手动输入\n');
970
930
  let userAction = null;
971
- const setupKeyListener = () => {
972
- if (!process.stdin.isTTY)
973
- return () => { };
974
- process.stdin.setRawMode(true);
975
- process.stdin.resume();
976
- process.stdin.setEncoding('utf8');
977
- const handler = (key) => {
978
- if (key === 'q' || key === '\u0003')
979
- userAction = QUIT;
980
- if (key === 's')
981
- userAction = SKIP;
982
- };
983
- process.stdin.on('data', handler);
984
- return () => {
985
- process.stdin.removeListener('data', handler);
986
- process.stdin.setRawMode(false);
987
- // Don't pause stdin - let the next readline/prompt handle it naturally
988
- };
989
- };
990
- const cleanup = setupKeyListener();
931
+ const cleanup = setupRawKeyListener((key) => {
932
+ if (key === 'q' || key === '\u0003')
933
+ userAction = QUIT;
934
+ if (key === 's')
935
+ userAction = SKIP;
936
+ });
991
937
  const startedAt = Date.now();
992
938
  try {
993
939
  // Step 3: Poll for bind result
@@ -1122,7 +1068,7 @@ export async function cmdInitQQBot() {
1122
1068
  console.log(` App ID: ${result.appId}`);
1123
1069
  bindQrActorToContact(aid, contactBinding, 'qqbot', result.userOpenId, commitResult.channelKey);
1124
1070
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
1125
- console.log(generateInitSuccessMessage('qqbot', false));
1071
+ console.log(generateInitSuccessMessage('qqbot', contactBinding !== null));
1126
1072
  }
1127
1073
  // ==================== WeCom (企业微信) ====================
1128
1074
  export async function cmdInitWecom() {
@@ -1130,6 +1076,7 @@ export async function cmdInitWecom() {
1130
1076
  let aid = null;
1131
1077
  let agentConfig = null;
1132
1078
  let choice = null;
1079
+ let contactBinding = null;
1133
1080
  let botId = '';
1134
1081
  let secret = '';
1135
1082
  try {
@@ -1145,8 +1092,43 @@ export async function cmdInitWecom() {
1145
1092
  choice = await pickInstanceWithinAgent(rl, 'wecom', existing);
1146
1093
  if (choice === null)
1147
1094
  return;
1095
+ contactBinding = await pickOwnerAdminForChannelBinding(rl, agentConfig, 'wecom');
1096
+ if (!contactBinding)
1097
+ return;
1148
1098
  console.log('\n企业微信 AI Bot 配置');
1149
- console.log('请在企业微信管理后台 AI Bot 页面获取 Bot ID Secret\n');
1099
+ const method = (await ask(rl, ' 接入方式:1. 扫码创建(推荐) 2. 手工输入 [1]: ')).trim() || '1';
1100
+ if (method === '1') {
1101
+ try {
1102
+ const { createWecomQrSession, pollWecomQrCredentials } = await import('../channels/wecom-onboarding.js');
1103
+ const session = await createWecomQrSession();
1104
+ const qrterm = await import('qrcode-terminal');
1105
+ console.log('\n请使用企业微信扫描以下二维码:\n');
1106
+ await new Promise(resolve => {
1107
+ qrterm.default.generate(session.authUrl, { small: true }, (qr) => {
1108
+ console.log(qr);
1109
+ resolve();
1110
+ });
1111
+ });
1112
+ console.log(`也可在浏览器打开:${session.browserUrl}`);
1113
+ console.log('等待扫码确认,二维码 5 分钟内有效...\n');
1114
+ const credentials = await pollWecomQrCredentials(session.scode, {
1115
+ onStatus: status => {
1116
+ if (status !== 'waiting')
1117
+ console.log(` 扫码状态: ${status}`);
1118
+ },
1119
+ });
1120
+ botId = credentials.botId;
1121
+ secret = credentials.secret;
1122
+ console.log(' ✓ 已取得 Bot ID 和 Secret');
1123
+ }
1124
+ catch (error) {
1125
+ console.log(` ⚠ 扫码接入失败: ${error instanceof Error ? error.message : String(error)}`);
1126
+ console.log(' 将切换到手工输入模式。\n');
1127
+ }
1128
+ }
1129
+ if (!botId || !secret) {
1130
+ console.log('请在企业微信管理后台 → AI Bot 页面获取 Bot ID 和 Secret\n');
1131
+ }
1150
1132
  while (!botId) {
1151
1133
  botId = (await ask(rl, ' Bot ID: ')).trim();
1152
1134
  if (!botId)
@@ -1168,10 +1150,18 @@ export async function cmdInitWecom() {
1168
1150
  botId,
1169
1151
  secret,
1170
1152
  };
1171
- await commitChannel(aid, channel, choice.action);
1153
+ const commitResult = await commitChannel(aid, channel, choice.action);
1154
+ if (!commitResult)
1155
+ return;
1172
1156
  console.log(` Bot ID: ${botId}`);
1157
+ if (commitResult.reloaded) {
1158
+ await registerWecomContactBind(aid, commitResult.channelKey, contactBinding);
1159
+ }
1160
+ else {
1161
+ console.log(' ⚠ 服务未运行或热重载失败,无法注册企微绑定码;请启动服务后重新执行 ec init wecom。');
1162
+ }
1173
1163
  const { generateInitSuccessMessage } = await import('../utils/welcome.js');
1174
- console.log(generateInitSuccessMessage('wecom', false));
1164
+ console.log(generateInitSuccessMessage('wecom', contactBinding !== null));
1175
1165
  }
1176
1166
  // ==================== Shared helpers for per-agent init <channel> ====================
1177
1167
  /**
@@ -1221,8 +1211,8 @@ async function pickInstanceWithinAgent(rl, channelType, existing) {
1221
1211
  async function pickOwnerAdminForChannelBinding(rl, agentConfig, channelType) {
1222
1212
  const candidates = ownerAdminContactCandidates(agentConfig);
1223
1213
  if (candidates.length === 0) {
1224
- console.log(`\n❌ agent ${agentConfig.aid} 尚未配置 owners/admins,无法为 ${channelType} 扫码账号建立身份映射。`);
1225
- console.log(' 请先通过 AUN 绑定 agent owner,或在 agent config.json 中配置 owners/admins 后重试。');
1214
+ console.log(`\n❌ agent ${agentConfig.aid} 尚未配置 owner/admin,无法为 ${channelType} 扫码账号建立身份映射。`);
1215
+ console.log(' 请先通过 AUN 绑定 agent owner,或由 owner AUN 私聊 relation 中分配 admin 后重试。');
1226
1216
  return null;
1227
1217
  }
1228
1218
  console.log(`\n请选择 ${channelType} 扫码账号要绑定到的 owner/admin AID:`);
@@ -1255,7 +1245,7 @@ function ownerAdminContactCandidates(agentConfig) {
1255
1245
  };
1256
1246
  for (const owner of agentConfig.owners ?? [])
1257
1247
  add(owner, 'owner');
1258
- for (const admin of agentConfig.admins ?? [])
1248
+ for (const admin of listStaticAgentAdmins(agentConfig.aid))
1259
1249
  add(admin, 'admin');
1260
1250
  return [...labels.entries()].map(([primaryId, roles]) => ({
1261
1251
  primaryId,
@@ -1273,7 +1263,7 @@ function bindQrActorToContact(aid, binding, channelType, actorId, channelKey) {
1273
1263
  try {
1274
1264
  const changed = bindContactAlias(aid, binding.primaryId, channelType, normalizedActor, channelKey);
1275
1265
  const action = changed ? '已写入' : '已存在';
1276
- console.log(` ✓ 身份映射${action}: ${channelType}:${normalizedActor} -> ${binding.primaryId}`);
1266
+ console.log(` ✓ 身份映射${action}: ${channelKey}:${encodeURIComponent(normalizedActor)} -> ${binding.primaryId}`);
1277
1267
  }
1278
1268
  catch (error) {
1279
1269
  const message = error instanceof Error ? error.message : String(error);
@@ -1281,23 +1271,28 @@ function bindQrActorToContact(aid, binding, channelType, actorId, channelKey) {
1281
1271
  }
1282
1272
  }
1283
1273
  async function registerDingtalkContactBind(aid, channelName, binding) {
1274
+ await registerChannelContactBind('dingtalk', '钉钉', aid, channelName, binding);
1275
+ }
1276
+ async function registerWecomContactBind(aid, channelName, binding) {
1277
+ await registerChannelContactBind('wecom', '企微', aid, channelName, binding);
1278
+ }
1279
+ async function registerChannelContactBind(channelType, displayName, aid, channelName, binding) {
1284
1280
  if (!binding)
1285
1281
  return;
1286
1282
  const result = await ipcQuery(resolvePaths().socket, {
1287
- type: 'dingtalk.contact-bind.register',
1283
+ type: `${channelType}.contact-bind.register`,
1288
1284
  selfAid: aid,
1289
1285
  channelName,
1290
1286
  primaryId: binding.primaryId,
1291
1287
  }, 3000);
1292
1288
  if (!result?.ok) {
1293
- console.log(` ⚠ 钉钉绑定码注册失败: ${result?.error || 'daemon 不可用'}`);
1294
- console.log(' 请重新执行 ec init dingtalk 并再次扫码绑定。');
1289
+ console.log(` ⚠ ${displayName}绑定码注册失败: ${result?.error || 'daemon 不可用'}`);
1290
+ console.log(` 请重新执行 ec init ${channelType} 并再次绑定。`);
1295
1291
  return;
1296
1292
  }
1297
1293
  const expiresAt = new Date(Number(result.expiresAt)).toLocaleString();
1298
- console.log(` ✓ 钉钉身份绑定码: ${result.code}`);
1299
- console.log(` 请在 10 分钟内私聊钉钉机器人,直接发送这 6 位数字绑定码。有效期至: ${expiresAt}`);
1300
- console.log(' 非纯数字或位数不对只会提醒格式,不计入错误次数;6 位数字输错最多 5 次。');
1294
+ console.log(` ✓ ${displayName}身份绑定码: ${result.code}`);
1295
+ console.log(` 请在 10 分钟内私聊${displayName}机器人,直接发送这 6 位数字绑定码。有效期至: ${expiresAt}`);
1301
1296
  }
1302
1297
  function firstNonEmpty(...values) {
1303
1298
  for (const value of values) {