evolclaw 3.2.0 → 3.3.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/CHANGELOG.md +17 -0
- package/README.md +1 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -0
- package/dist/aun/aid/store.js +1 -1
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +406 -293
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +97 -150
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +8 -5
- package/dist/cli/index.js +177 -44
- package/dist/cli/init.js +33 -6
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +12 -6
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +473 -114
- package/dist/core/evolagent-registry.js +1 -0
- package/dist/core/evolagent.js +1 -1
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +63 -1
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +49 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +295 -35
- package/dist/core/message/message-queue.js +2 -2
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +2 -2
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/manifest-engine.js +20 -1
- package/dist/{agents → eck}/message-renderer.js +24 -1
- package/dist/index.js +130 -8
- package/dist/ipc.js +17 -1
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/evolclaw-config.js +0 -11
- package/dist/utils/channel-helpers.js +0 -46
- /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
- /package/dist/{agents → eck}/kit-renderer.js +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ec version — 显示所有组件版本和构建时间戳,便于诊断。
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
10
|
+
const BOLD = '\x1b[1m';
|
|
11
|
+
const DIM = '\x1b[2m';
|
|
12
|
+
const GREEN = '\x1b[32m';
|
|
13
|
+
const CYAN = '\x1b[36m';
|
|
14
|
+
function readPkgJson(dir) {
|
|
15
|
+
const f = path.join(dir, 'package.json');
|
|
16
|
+
if (!fs.existsSync(f))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getFileModTime(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs.statSync(filePath);
|
|
28
|
+
return stat.mtime.toISOString().replace('T', ' ').slice(0, 19);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function findInstalledVersion(pkgName) {
|
|
35
|
+
// 从 PACKAGE_ROOT/node_modules 查找
|
|
36
|
+
const dir = path.join(PACKAGE_ROOT, 'node_modules', ...pkgName.split('/'));
|
|
37
|
+
const pkg = readPkgJson(dir);
|
|
38
|
+
if (!pkg)
|
|
39
|
+
return null;
|
|
40
|
+
// 取 dist/index.js 修改时间作为构建时间戳参考
|
|
41
|
+
const mainFile = path.join(dir, 'dist', 'index.js');
|
|
42
|
+
const ts = getFileModTime(mainFile) || getFileModTime(path.join(dir, 'index.js'));
|
|
43
|
+
return { name: pkg.name || pkgName, version: pkg.version || '?', buildTs: ts || undefined, path: dir };
|
|
44
|
+
}
|
|
45
|
+
export function handleVersion(args) {
|
|
46
|
+
const isJson = args.includes('--format') && args.includes('json') || args.includes('--json');
|
|
47
|
+
// 主包
|
|
48
|
+
const mainPkg = readPkgJson(PACKAGE_ROOT);
|
|
49
|
+
const mainEntry = path.join(PACKAGE_ROOT, 'dist', 'cli', 'index.js');
|
|
50
|
+
const mainTs = getFileModTime(mainEntry);
|
|
51
|
+
// ecweb 包
|
|
52
|
+
const ecwebDir = path.join(PACKAGE_ROOT, 'ecweb');
|
|
53
|
+
const ecwebPkg = readPkgJson(ecwebDir);
|
|
54
|
+
const ecwebEntry = path.join(ecwebDir, 'dist', 'index.js');
|
|
55
|
+
const ecwebTs = getFileModTime(ecwebEntry);
|
|
56
|
+
const ecwebStaticTs = getFileModTime(path.join(ecwebDir, 'dist', 'static', 'index.html'));
|
|
57
|
+
// 关键依赖
|
|
58
|
+
const deps = [
|
|
59
|
+
'@agentunion/fastaun',
|
|
60
|
+
'ws',
|
|
61
|
+
];
|
|
62
|
+
const components = [
|
|
63
|
+
{ name: 'evolclaw', version: mainPkg?.version || '?', built: mainTs || undefined, note: 'main package' },
|
|
64
|
+
{ name: 'evolclaw-web', version: ecwebPkg?.version || '?', built: ecwebTs || undefined, note: `static: ${ecwebStaticTs || '?'}` },
|
|
65
|
+
];
|
|
66
|
+
for (const dep of deps) {
|
|
67
|
+
const info = findInstalledVersion(dep);
|
|
68
|
+
if (info) {
|
|
69
|
+
components.push({ name: info.name, version: info.version, built: info.buildTs || undefined });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Node.js 版本
|
|
73
|
+
components.push({ name: 'node', version: process.version, note: process.platform + '/' + process.arch });
|
|
74
|
+
if (isJson) {
|
|
75
|
+
console.log(JSON.stringify(components, null, 2));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(`\n${BOLD}🔧 EvolClaw Component Versions${RESET}\n`);
|
|
79
|
+
const nameW = Math.max(...components.map(c => c.name.length), 8);
|
|
80
|
+
const verW = Math.max(...components.map(c => c.version.length), 7);
|
|
81
|
+
for (const c of components) {
|
|
82
|
+
const builtStr = c.built ? `${DIM}built: ${c.built}${RESET}` : '';
|
|
83
|
+
const noteStr = c.note ? `${DIM}(${c.note})${RESET}` : '';
|
|
84
|
+
console.log(` ${CYAN}${c.name.padEnd(nameW)}${RESET} ${GREEN}${c.version.padEnd(verW)}${RESET} ${builtStr} ${noteStr}`);
|
|
85
|
+
}
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
package/dist/cli/watch-msg.js
CHANGED
|
@@ -12,6 +12,7 @@ const GREEN = isTTY ? '\x1b[32m' : '';
|
|
|
12
12
|
const BLUE = isTTY ? '\x1b[34m' : '';
|
|
13
13
|
const ORANGE = isTTY ? '\x1b[38;5;208m' : '';
|
|
14
14
|
const MAGENTA = isTTY ? '\x1b[35m' : '';
|
|
15
|
+
const YELLOW = isTTY ? '\x1b[33m' : '';
|
|
15
16
|
const BG_SEL = isTTY ? '\x1b[48;5;236m' : ''; // dark gray background for selected row
|
|
16
17
|
// ==================== Helpers ====================
|
|
17
18
|
function visualWidth(s) {
|
|
@@ -314,8 +315,10 @@ function renderMessagesPanel(state, width, height) {
|
|
|
314
315
|
const encLabel = m.encrypt ? '密文' : '明文';
|
|
315
316
|
const modeLabel = m.chatmode === 'proactive' ? '自主' : '响应';
|
|
316
317
|
const metaTags = (m.encrypt != null || m.chatmode) ? `${MAGENTA}[${encLabel}|${modeLabel}]${RST}` : '';
|
|
318
|
+
// observer 插话:醒目标记,与对端真实消息区分
|
|
319
|
+
const injectTag = m.source === 'owner-inject' ? `${YELLOW}[插话]${RST}` : '';
|
|
317
320
|
let typeTag = '';
|
|
318
|
-
if (m.dir === 'out') {
|
|
321
|
+
if (m.dir === 'out' && m.source !== 'owner-inject') {
|
|
319
322
|
const rawSource = m.source;
|
|
320
323
|
// 4 种来源: daemon | ctl | msg | cli
|
|
321
324
|
const source = (rawSource === 'ctl' || rawSource === 'msg' || rawSource === 'cli') ? rawSource : 'daemon';
|
|
@@ -326,7 +329,7 @@ function renderMessagesPanel(state, width, height) {
|
|
|
326
329
|
const lenTag = `${DIM}${formatNumber(byteLen)}B${RST}`;
|
|
327
330
|
const fromDisplay = isGroup && m.groupId && m.dir === 'in' ? m.groupId : m.from.split('.')[0];
|
|
328
331
|
const toDisplay = isGroup && m.groupId && m.dir === 'out' ? m.groupId : m.to.split('.')[0];
|
|
329
|
-
const header = `${DIM}${time}${RST} ${dir}${chatTag}${metaTags}${typeTag} ${ORANGE}${fromDisplay}${RST}${DIM}→${RST}${GREEN}${toDisplay}${RST} ${lenTag}`;
|
|
332
|
+
const header = `${DIM}${time}${RST} ${dir}${chatTag}${injectTag}${metaTags}${typeTag} ${ORANGE}${fromDisplay}${RST}${DIM}→${RST}${GREEN}${toDisplay}${RST} ${lenTag}`;
|
|
330
333
|
const out = [padRight(header, msgWidth)];
|
|
331
334
|
const rawContent = m.content.replace(/\n/g, ' ');
|
|
332
335
|
const wrappedLines = wrapText(rawContent, contentLineWidth, maxContentLines);
|
package/dist/config-store.js
CHANGED
|
@@ -17,11 +17,19 @@ 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';
|
|
21
20
|
import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
|
|
22
21
|
import { isValidChannelName } from './core/channel-loader.js';
|
|
23
22
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
24
23
|
import { logger } from './utils/logger.js';
|
|
24
|
+
/** 读 {root}/evolclaw.json。文件不存在返回 {},不报错。 */
|
|
25
|
+
export function loadEvolclawConfig() {
|
|
26
|
+
const raw = atomicReadJson(resolvePaths().evolclawJson);
|
|
27
|
+
return raw ?? {};
|
|
28
|
+
}
|
|
29
|
+
/** 原子写入 {root}/evolclaw.json。调用方负责传完整对象(含要保留的字段)。 */
|
|
30
|
+
export function saveEvolclawConfig(value) {
|
|
31
|
+
atomicWriteJson(resolvePaths().evolclawJson, value);
|
|
32
|
+
}
|
|
25
33
|
const SUPPORTED_CHANNEL_TYPES = new Set([
|
|
26
34
|
'aun', 'feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom',
|
|
27
35
|
]);
|
|
@@ -143,7 +151,6 @@ export function migrateProcessConfigIfNeeded() {
|
|
|
143
151
|
const raw = atomicReadJson(oldPath);
|
|
144
152
|
if (raw === null)
|
|
145
153
|
return; // 不存在 → no-op
|
|
146
|
-
const { loadEvolclawConfig, saveEvolclawConfig } = evolclawConfigModule;
|
|
147
154
|
const evc = loadEvolclawConfig();
|
|
148
155
|
// 仅当旧文件确实带 aun.encryptionSeed 字段时才搬(hasOwnProperty,保 null 语义)
|
|
149
156
|
const didMigrateSeed = !!(raw.aun && Object.prototype.hasOwnProperty.call(raw.aun, 'encryptionSeed'));
|
|
@@ -601,10 +608,9 @@ export function migrateIdentitiesIfNeeded() {
|
|
|
601
608
|
}
|
|
602
609
|
// ── Project Migration ────────────────────────────────────────────────────────
|
|
603
610
|
import os from 'os';
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
611
|
+
// 复用与 Claude SDK 对齐的统一编码(resolve→realpath→NFC→非字母数字替换为 -),
|
|
612
|
+
// 避免本地实现规则不一致导致中文/非 ASCII 路径迁移时找不到 SDK 会话目录。
|
|
613
|
+
import { encodePath } from './utils/cross-platform.js';
|
|
608
614
|
/** 查找最新的 ~/.codex/state_*.sqlite */
|
|
609
615
|
function findCodexDb() {
|
|
610
616
|
const codexHome = path.join(os.homedir(), '.codex');
|
|
@@ -6,11 +6,21 @@
|
|
|
6
6
|
* The main service (index.ts) handles registration and message flow wiring.
|
|
7
7
|
*/
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
9
|
+
/** Resolve showActivities for a single instance (instance overrides default). */
|
|
10
|
+
export function resolveShowActivities(inst) {
|
|
11
|
+
return inst.showActivities ?? 'all';
|
|
12
|
+
}
|
|
13
|
+
/** Standard showMiddleResult / showIdleMonitor policy function. */
|
|
14
|
+
export function showActivitiesPolicy(mode, chatType, identity) {
|
|
15
|
+
if (mode === 'none')
|
|
16
|
+
return false;
|
|
17
|
+
if (mode === 'dm-only')
|
|
18
|
+
return chatType === 'private';
|
|
19
|
+
if (mode === 'owner-dm-only')
|
|
20
|
+
return chatType === 'private' && identity === 'owner';
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// ── ChannelLoader ──────────────────────────────────────────────────────────
|
|
14
24
|
export class ChannelLoader {
|
|
15
25
|
plugins = new Map();
|
|
16
26
|
register(plugin) {
|
|
@@ -20,81 +30,70 @@ export class ChannelLoader {
|
|
|
20
30
|
this.plugins.set(plugin.name, plugin);
|
|
21
31
|
logger.debug(`Registered channel plugin: ${plugin.name}`);
|
|
22
32
|
}
|
|
33
|
+
/** Look up a registered plugin by channel type (used by reload hooks). */
|
|
34
|
+
getPlugin(type) {
|
|
35
|
+
return this.plugins.get(type);
|
|
36
|
+
}
|
|
23
37
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 内部把 ChannelInstance[] 翻成各 plugin 期望的 dict 形态(`{ type: [instances...] }`),
|
|
27
|
-
* 然后调用现有 plugin.createChannels / createChannel。
|
|
28
|
-
*
|
|
29
|
-
* 当所有 channel plugin 重写为直接吃 ChannelInstance[] 后,本方法可简化。
|
|
38
|
+
* Create all runtime channels for an agent directly from its config.channels[].
|
|
39
|
+
* AUN is always created implicitly from agent.aid (no explicit entry required).
|
|
30
40
|
*/
|
|
31
41
|
async createForAgent(agent) {
|
|
32
|
-
const
|
|
33
|
-
|
|
42
|
+
const ctx = {
|
|
43
|
+
agentName: agent.aid,
|
|
44
|
+
defaultProjectPath: agent.config.projects?.defaultPath ?? process.cwd(),
|
|
45
|
+
enableRichContent: agent.config.enable_rich_content,
|
|
46
|
+
debug: agent.config.debug,
|
|
47
|
+
};
|
|
48
|
+
// Build the full list of config instances to create.
|
|
49
|
+
// AUN is synthesised from agent.aid; any explicit aun entry in channels[] is skipped.
|
|
34
50
|
const aunEffName = agent.effectiveChannelName('aun', 'main');
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}];
|
|
44
|
-
// 其它 channels(非 AUN)从 config.channels[] 取
|
|
51
|
+
const aunInst = {
|
|
52
|
+
type: 'aun',
|
|
53
|
+
name: aunEffName,
|
|
54
|
+
aid: agent.aid,
|
|
55
|
+
enabled: true,
|
|
56
|
+
owner: agent.config.owners?.[0],
|
|
57
|
+
};
|
|
58
|
+
const configInsts = [aunInst];
|
|
45
59
|
for (const inst of agent.config.channels) {
|
|
46
60
|
if (inst.type === 'aun')
|
|
47
|
-
continue;
|
|
61
|
+
continue;
|
|
48
62
|
const effName = agent.effectiveChannelName(inst.type, inst.name);
|
|
49
|
-
|
|
50
|
-
(rewrittenChannels[inst.type] ??= []).push(rewritten);
|
|
63
|
+
configInsts.push({ ...inst, name: effName });
|
|
51
64
|
}
|
|
52
|
-
|
|
53
|
-
// 新 schema 字段命名为 snake_case,这里转 camelCase 透传。
|
|
54
|
-
const syntheticConfig = {
|
|
55
|
-
agents: agent.config.baseagents,
|
|
56
|
-
channels: rewrittenChannels,
|
|
57
|
-
projects: agent.config.projects,
|
|
58
|
-
chatmode: agent.config.chatmode,
|
|
59
|
-
debug: agent.config.debug,
|
|
60
|
-
showActivities: agent.config.show_activities,
|
|
61
|
-
flushDelay: agent.config.flush_delay,
|
|
62
|
-
debounce: agent.config.debounce,
|
|
63
|
-
enableRichContent: agent.config.enable_rich_content,
|
|
64
|
-
};
|
|
65
|
-
return this.createAll(syntheticConfig);
|
|
65
|
+
return this._buildInstances(configInsts, ctx);
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
/** Build runtime instances for a list of config instances + context. */
|
|
68
|
+
async _buildInstances(configInsts, ctx) {
|
|
69
|
+
const result = [];
|
|
70
|
+
for (const inst of configInsts) {
|
|
71
|
+
const plugin = this.plugins.get(inst.type);
|
|
72
|
+
if (!plugin) {
|
|
73
|
+
logger.debug(`No plugin for channel type '${inst.type}', skipping`);
|
|
72
74
|
continue;
|
|
73
75
|
}
|
|
74
76
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
logger.info(`✓ Channel '${name}'
|
|
77
|
+
const runtime = await plugin.createInstance(inst, ctx);
|
|
78
|
+
if (runtime) {
|
|
79
|
+
result.push(runtime);
|
|
80
|
+
logger.info(`✓ Channel '${inst.name}' (${inst.type}) created`);
|
|
79
81
|
}
|
|
80
82
|
else {
|
|
81
|
-
|
|
82
|
-
instances.push(instance);
|
|
83
|
-
logger.info(`✓ Channel '${name}' instance created`);
|
|
83
|
+
logger.info(`Channel '${inst.name}' (${inst.type}) disabled or invalid credentials, skipping`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
catch (
|
|
87
|
-
logger.error(`✗ Failed to create channel '${name}':`,
|
|
86
|
+
catch (err) {
|
|
87
|
+
logger.error(`✗ Failed to create channel '${inst.name}' (${inst.type}):`, err);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
return
|
|
90
|
+
return result;
|
|
91
91
|
}
|
|
92
92
|
async connectAll(instances, { concurrency = 3, intervalMs = 50 } = {}) {
|
|
93
93
|
const connected = [];
|
|
94
94
|
const failed = [];
|
|
95
95
|
const inflight = new Set();
|
|
96
96
|
for (const inst of instances) {
|
|
97
|
-
// 等待并发数降到 concurrency 以下
|
|
98
97
|
while (inflight.size >= concurrency) {
|
|
99
98
|
await Promise.race(inflight);
|
|
100
99
|
}
|
|
@@ -110,12 +109,10 @@ export class ChannelLoader {
|
|
|
110
109
|
})();
|
|
111
110
|
const tracked = task.then(() => { inflight.delete(tracked); });
|
|
112
111
|
inflight.add(tracked);
|
|
113
|
-
// 间隔发起,避免瞬间并发冲击网关
|
|
114
112
|
if (intervalMs > 0) {
|
|
115
113
|
await new Promise(r => setTimeout(r, intervalMs));
|
|
116
114
|
}
|
|
117
115
|
}
|
|
118
|
-
// 等待所有剩余任务完成
|
|
119
116
|
await Promise.allSettled(inflight);
|
|
120
117
|
if (failed.length > 0) {
|
|
121
118
|
logger.warn(`[connectAll] ${failed.length} channel(s) failed initial connect (will retry in background): ${failed.map(f => f.name).join(', ')}`);
|
|
@@ -123,7 +120,7 @@ export class ChannelLoader {
|
|
|
123
120
|
return connected;
|
|
124
121
|
}
|
|
125
122
|
async disconnectAll(instances) {
|
|
126
|
-
await Promise.allSettled(instances.map(
|
|
123
|
+
await Promise.allSettled(instances.map(inst => inst.disconnect()));
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
const SEP = '#';
|
|
@@ -197,34 +194,39 @@ export function buildReloadHooks(deps) {
|
|
|
197
194
|
}
|
|
198
195
|
},
|
|
199
196
|
async startChannel(agent, channelName) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
// The implicit AUN channel is synthesised from agent.aid (not in config.channels[]).
|
|
198
|
+
// Reconstruct it the same way createForAgent does before falling back to config scan.
|
|
199
|
+
const aid = agent.aid ?? agent.config?.aid;
|
|
200
|
+
const aunEffName = agent.effectiveChannelName?.('aun', 'main') ?? 'aun-main';
|
|
201
|
+
const isImplicitAun = channelName === aunEffName;
|
|
202
|
+
// Find config instance: implicit AUN gets a synthetic entry; others scan channels[].
|
|
203
|
+
const cfgInst = isImplicitAun
|
|
204
|
+
? { type: 'aun', name: aunEffName, aid, enabled: true, owner: agent.config?.owners?.[0] }
|
|
205
|
+
: (() => {
|
|
206
|
+
const agentChannels = agent.config?.channels ?? [];
|
|
207
|
+
return agentChannels.find((i) => {
|
|
208
|
+
const effName = agent.effectiveChannelName?.(i.type, i.name) ?? i.name;
|
|
209
|
+
return effName === channelName;
|
|
210
|
+
}) ?? null;
|
|
211
|
+
})();
|
|
212
|
+
if (!cfgInst) {
|
|
213
|
+
const msg = `[Reload] Channel ${channelName} not found in agent config`;
|
|
216
214
|
logger.error(msg);
|
|
217
215
|
throw new Error(msg);
|
|
218
216
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
const ctx = {
|
|
218
|
+
agentName: agent.aid ?? agent.config?.aid,
|
|
219
|
+
defaultProjectPath: agent.config?.projects?.defaultPath ?? process.cwd(),
|
|
220
|
+
enableRichContent: agent.config?.enable_rich_content,
|
|
221
|
+
debug: agent.config?.debug,
|
|
223
222
|
};
|
|
224
|
-
const
|
|
225
|
-
|
|
223
|
+
const plugin = channelLoader.getPlugin(cfgInst.type);
|
|
224
|
+
if (!plugin)
|
|
225
|
+
throw new Error(`[Reload] No plugin for channel type '${cfgInst.type}'`);
|
|
226
|
+
const effInst = { ...cfgInst, name: channelName };
|
|
227
|
+
const newInst = await plugin.createInstance(effInst, ctx);
|
|
226
228
|
if (!newInst)
|
|
227
|
-
throw new Error(`[Reload]
|
|
229
|
+
throw new Error(`[Reload] createInstance returned null for ${channelName}`);
|
|
228
230
|
registerChannelInstance(newInst);
|
|
229
231
|
await newInst.connect();
|
|
230
232
|
channelInstances.push(newInst);
|