evolclaw 2.8.2 → 2.8.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.
- package/dist/agents/claude-runner.js +18 -7
- package/dist/agents/codex-runner.js +16 -5
- package/dist/agents/gemini-runner.js +15 -4
- package/dist/channels/aun.js +22 -0
- package/dist/config.js +59 -44
- package/dist/core/agent-loader.js +34 -19
- package/dist/core/command-handler.js +273 -32
- package/dist/core/evolagent-registry.js +12 -1
- package/dist/core/message/message-processor.js +25 -11
- package/dist/core/message/message-queue.js +2 -2
- package/dist/index.js +21 -13
- package/package.json +1 -1
|
@@ -988,15 +988,26 @@ export class AgentRunner {
|
|
|
988
988
|
// Plugin implementation
|
|
989
989
|
export class ClaudeAgentPlugin {
|
|
990
990
|
name = 'claude';
|
|
991
|
-
isEnabled(
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
991
|
+
isEnabled(_globalConfig, agent) {
|
|
992
|
+
// Only instantiate this baseagent for agents that declare it.
|
|
993
|
+
return !!agent.config.agents?.claude;
|
|
994
|
+
}
|
|
995
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
996
|
+
// Per-agent override: read from agent.json's agents.claude block first.
|
|
997
|
+
const override = agent.config.agents?.claude;
|
|
998
|
+
const anthropic = resolveAnthropicConfig(globalConfig, override);
|
|
999
|
+
// Merge per-agent claude block into config so runner reads useSettingSources etc.
|
|
1000
|
+
const merged = {
|
|
1001
|
+
...globalConfig,
|
|
1002
|
+
agents: {
|
|
1003
|
+
...(globalConfig.agents || {}),
|
|
1004
|
+
claude: { ...(globalConfig.agents?.claude || {}), ...(override || {}) },
|
|
1005
|
+
},
|
|
1006
|
+
};
|
|
1007
|
+
const agentRunner = new AgentRunner(anthropic.apiKey, anthropic.model, callbacks.onSessionIdUpdate, anthropic.baseUrl, merged);
|
|
997
1008
|
if (anthropic.effort) {
|
|
998
1009
|
agentRunner.setEffort(anthropic.effort);
|
|
999
1010
|
}
|
|
1000
|
-
return { agent: agentRunner };
|
|
1011
|
+
return { evolagentName: agent.name, baseagent: 'claude', agent: agentRunner };
|
|
1001
1012
|
}
|
|
1002
1013
|
}
|
|
@@ -301,17 +301,28 @@ export class CodexRunner {
|
|
|
301
301
|
// ── Plugin ──
|
|
302
302
|
export class CodexAgentPlugin {
|
|
303
303
|
name = 'codex';
|
|
304
|
-
isEnabled(
|
|
304
|
+
isEnabled(globalConfig, agent) {
|
|
305
|
+
if (!agent.config.agents?.codex)
|
|
306
|
+
return false;
|
|
305
307
|
try {
|
|
306
|
-
const
|
|
308
|
+
const override = agent.config.agents.codex;
|
|
309
|
+
const resolved = resolveOpenaiConfig(globalConfig, override);
|
|
307
310
|
return !!resolved.apiKey;
|
|
308
311
|
}
|
|
309
312
|
catch {
|
|
310
313
|
return false;
|
|
311
314
|
}
|
|
312
315
|
}
|
|
313
|
-
createAgent(
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
317
|
+
const override = agent.config.agents?.codex;
|
|
318
|
+
// Synthesize a per-agent config view so CodexRunner sees its own credentials.
|
|
319
|
+
const merged = {
|
|
320
|
+
...globalConfig,
|
|
321
|
+
agents: {
|
|
322
|
+
...(globalConfig.agents || {}),
|
|
323
|
+
codex: { ...(globalConfig.agents?.codex || {}), ...(override || {}) },
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks) };
|
|
316
327
|
}
|
|
317
328
|
}
|
|
@@ -405,16 +405,27 @@ export class GeminiRunner {
|
|
|
405
405
|
// ── Plugin ──
|
|
406
406
|
export class GeminiAgentPlugin {
|
|
407
407
|
name = 'gemini';
|
|
408
|
-
isEnabled(
|
|
408
|
+
isEnabled(globalConfig, agent) {
|
|
409
|
+
if (!agent.config.agents?.gemini)
|
|
410
|
+
return false;
|
|
409
411
|
try {
|
|
410
|
-
const
|
|
412
|
+
const override = agent.config.agents.gemini;
|
|
413
|
+
const resolved = resolveGoogleConfig(globalConfig, override);
|
|
411
414
|
return !!resolved.cliPath;
|
|
412
415
|
}
|
|
413
416
|
catch {
|
|
414
417
|
return false;
|
|
415
418
|
}
|
|
416
419
|
}
|
|
417
|
-
createAgent(
|
|
418
|
-
|
|
420
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
421
|
+
const override = agent.config.agents?.gemini;
|
|
422
|
+
const merged = {
|
|
423
|
+
...globalConfig,
|
|
424
|
+
agents: {
|
|
425
|
+
...(globalConfig.agents || {}),
|
|
426
|
+
gemini: { ...(globalConfig.agents?.gemini || {}), ...(override || {}) },
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
return { evolagentName: agent.name, baseagent: 'gemini', agent: new GeminiRunner(merged, callbacks) };
|
|
419
430
|
}
|
|
420
431
|
}
|
package/dist/channels/aun.js
CHANGED
|
@@ -80,6 +80,11 @@ export class AUNChannel {
|
|
|
80
80
|
*/
|
|
81
81
|
async callAndTrace(method, params, opts) {
|
|
82
82
|
this.trace('OUT', method, params);
|
|
83
|
+
// [DIAG-STALE] 记录调用瞬间 SDK 内部 _state,证明是否在 reconnecting 中误发
|
|
84
|
+
const sdkStateBefore = this.client?._state ?? 'no-client';
|
|
85
|
+
if (sdkStateBefore !== 'connected') {
|
|
86
|
+
logger.warn(`[AUN][DIAG-STALE] callAndTrace ${method} on non-connected SDK: sdk_state=${sdkStateBefore} evolclaw_connected=${this.connected}`);
|
|
87
|
+
}
|
|
83
88
|
try {
|
|
84
89
|
const result = await this.client.call(method, params);
|
|
85
90
|
if (!opts?.silentOk) {
|
|
@@ -97,6 +102,9 @@ export class AUNChannel {
|
|
|
97
102
|
code: e?.code,
|
|
98
103
|
name: e?.name,
|
|
99
104
|
});
|
|
105
|
+
// [DIAG-STALE] 失败时再记录一次 SDK _state,看错误类型是否为 ConnectionError
|
|
106
|
+
const sdkStateAfter = this.client?._state ?? 'no-client';
|
|
107
|
+
logger.warn(`[AUN][DIAG-STALE] callAndTrace ${method} FAILED: err_name=${e?.name ?? '?'} err_code=${e?.code ?? '?'} sdk_state_before=${sdkStateBefore} sdk_state_after=${sdkStateAfter} evolclaw_connected=${this.connected}`);
|
|
100
108
|
logger.warn(`${this.logPrefix()} rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
101
109
|
throw e;
|
|
102
110
|
}
|
|
@@ -887,6 +895,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
887
895
|
if (!data || typeof data !== 'object')
|
|
888
896
|
return;
|
|
889
897
|
const state = data.state ?? '';
|
|
898
|
+
// [DIAG-STALE] 记录状态切换瞬间 evolclaw 的 connected 标志和 SDK 的内部 _state,
|
|
899
|
+
// 用于证明"reconnecting 时 connected 保持 true,导致 sendMessage 误放行"的假设
|
|
900
|
+
const sdkState = this.client?._state ?? 'no-client';
|
|
901
|
+
const connectedBefore = this.connected;
|
|
902
|
+
logger.info(`[AUN][DIAG-STALE] connection.state event: state=${state} attempt=${data.attempt ?? '-'} | connected_before=${connectedBefore} sdk_state=${sdkState}`);
|
|
890
903
|
if (state === 'connected') {
|
|
891
904
|
this.connected = true;
|
|
892
905
|
this.reconnectAttempt = 0;
|
|
@@ -952,6 +965,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
952
965
|
this.recallHandler = handler;
|
|
953
966
|
}
|
|
954
967
|
async sendMessage(channelId, text, context) {
|
|
968
|
+
// [DIAG-STALE] 进入 sendMessage 时记录 evolclaw connected 标志和 SDK _state,
|
|
969
|
+
// 用于检测两者是否一致:若 connected=true 但 sdk_state != 'connected',即为 stale 状态
|
|
970
|
+
const sdkStateOnEntry = this.client?._state ?? 'no-client';
|
|
971
|
+
if (this.connected !== (sdkStateOnEntry === 'connected')) {
|
|
972
|
+
logger.warn(`[AUN][DIAG-STALE] sendMessage entry MISMATCH: connected=${this.connected} sdk_state=${sdkStateOnEntry} channel=${channelId} text=${text.slice(0, 40)}`);
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
logger.debug(`[AUN][DIAG-STALE] sendMessage entry: connected=${this.connected} sdk_state=${sdkStateOnEntry} channel=${channelId}`);
|
|
976
|
+
}
|
|
955
977
|
if (!this.connected || !this.client) {
|
|
956
978
|
logger.warn(`${this.logPrefix()} Cannot send: not connected`);
|
|
957
979
|
return;
|
package/dist/config.js
CHANGED
|
@@ -48,82 +48,97 @@ function loadCodexSettings() {
|
|
|
48
48
|
catch { }
|
|
49
49
|
return {};
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Resolve anthropic credentials with optional override (from agent.json).
|
|
53
|
+
*
|
|
54
|
+
* Priority: override > globalConfig.agents.claude > env > ~/.claude/settings.json
|
|
55
|
+
*
|
|
56
|
+
* Override is matched against the same shape as `config.agents.claude` so
|
|
57
|
+
* EvolAgent's `agents.claude` block is wired in directly.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveAnthropicConfig(config, override) {
|
|
52
60
|
const settings = loadClaudeSettings();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
62
|
+
// apiKey: override → global → env → settings.json
|
|
63
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
64
|
+
const globalApiKey = isPlaceholder(config.agents?.claude?.apiKey) ? undefined : config.agents?.claude?.apiKey;
|
|
65
|
+
const apiKey = overrideApiKey
|
|
66
|
+
|| globalApiKey
|
|
59
67
|
|| process.env.ANTHROPIC_AUTH_TOKEN
|
|
60
68
|
|| settings.env?.ANTHROPIC_AUTH_TOKEN;
|
|
61
69
|
if (!apiKey) {
|
|
62
|
-
throw new Error('No API key found. Set one of: agents.claude.apiKey, env ANTHROPIC_AUTH_TOKEN, or ~/.claude/settings.json env.ANTHROPIC_AUTH_TOKEN');
|
|
70
|
+
throw new Error('No API key found. Set one of: agents.claude.apiKey (per-agent or global), env ANTHROPIC_AUTH_TOKEN, or ~/.claude/settings.json env.ANTHROPIC_AUTH_TOKEN');
|
|
63
71
|
}
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const baseUrl =
|
|
72
|
+
const isPlaceholderUrl = (v) => !v || v.includes('api.anthropic.com');
|
|
73
|
+
const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
|
|
74
|
+
const globalBaseUrl = isPlaceholderUrl(config.agents?.claude?.baseUrl) ? undefined : config.agents?.claude?.baseUrl;
|
|
75
|
+
const baseUrl = overrideBaseUrl
|
|
76
|
+
|| globalBaseUrl
|
|
68
77
|
|| process.env.ANTHROPIC_BASE_URL
|
|
69
78
|
|| settings.env?.ANTHROPIC_BASE_URL;
|
|
70
|
-
const model =
|
|
79
|
+
const model = override?.model
|
|
80
|
+
|| config.agents?.claude?.model
|
|
71
81
|
|| settings.model
|
|
72
82
|
|| 'sonnet';
|
|
73
|
-
const effort =
|
|
83
|
+
const effort = override?.effort
|
|
84
|
+
|| config.agents?.claude?.effort
|
|
74
85
|
|| settings.effortLevel
|
|
75
86
|
|| undefined;
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
87
|
+
const pickExec = (v) => (!v || v.includes('your-') || v.includes('placeholder')) ? undefined : v;
|
|
88
|
+
const pathToClaudeCodeExecutable = pickExec(override?.pathToClaudeCodeExecutable)
|
|
89
|
+
|| pickExec(config.agents?.claude?.pathToClaudeCodeExecutable);
|
|
79
90
|
return { apiKey, baseUrl, model, effort, pathToClaudeCodeExecutable };
|
|
80
91
|
}
|
|
81
|
-
export function resolveOpenaiConfig(config) {
|
|
92
|
+
export function resolveOpenaiConfig(config, override) {
|
|
82
93
|
const codexSettings = loadCodexSettings();
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const apiKey = (isPlaceholderKey ? null : configApiKey)
|
|
94
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
95
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
96
|
+
const globalApiKey = isPlaceholder(config.agents?.codex?.apiKey) ? undefined : config.agents?.codex?.apiKey;
|
|
97
|
+
const apiKey = overrideApiKey
|
|
98
|
+
|| globalApiKey
|
|
89
99
|
|| process.env.OPENAI_API_KEY
|
|
90
100
|
|| codexSettings.apiKey;
|
|
91
101
|
if (!apiKey) {
|
|
92
|
-
throw new Error('No OpenAI API key found. Set one of: agents.codex.apiKey, env OPENAI_API_KEY, or ~/.codex/auth.json');
|
|
102
|
+
throw new Error('No OpenAI API key found. Set one of: agents.codex.apiKey (per-agent or global), env OPENAI_API_KEY, or ~/.codex/auth.json');
|
|
93
103
|
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const baseUrl =
|
|
104
|
+
const isPlaceholderUrl = (v) => !v || v.includes('api.openai.com');
|
|
105
|
+
const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
|
|
106
|
+
const globalBaseUrl = isPlaceholderUrl(config.agents?.codex?.baseUrl) ? undefined : config.agents?.codex?.baseUrl;
|
|
107
|
+
const baseUrl = overrideBaseUrl
|
|
108
|
+
|| globalBaseUrl
|
|
98
109
|
|| process.env.OPENAI_BASE_URL
|
|
99
110
|
|| codexSettings.baseUrl
|
|
100
111
|
|| undefined;
|
|
101
|
-
const model =
|
|
112
|
+
const model = override?.model
|
|
113
|
+
|| config.agents?.codex?.model
|
|
102
114
|
|| codexSettings.model
|
|
103
115
|
|| 'gpt-5.2-codex';
|
|
104
|
-
const effort =
|
|
116
|
+
const effort = override?.effort
|
|
117
|
+
|| override?.reasoning
|
|
118
|
+
|| config.agents?.codex?.effort
|
|
119
|
+
|| config.agents?.codex?.reasoning
|
|
120
|
+
|| undefined;
|
|
105
121
|
return { apiKey, baseUrl, model, effort };
|
|
106
122
|
}
|
|
107
|
-
export function resolveGoogleConfig(config) {
|
|
123
|
+
export function resolveGoogleConfig(config, override) {
|
|
108
124
|
const googleCfg = config.agents?.gemini;
|
|
109
|
-
|
|
110
|
-
let cliPath = googleCfg?.cliPath || '';
|
|
125
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
126
|
+
let cliPath = override?.cliPath || googleCfg?.cliPath || '';
|
|
111
127
|
if (!cliPath) {
|
|
112
128
|
cliPath = commandExists('gemini') ? 'gemini' : '';
|
|
113
129
|
}
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
const apiKey = (isPlaceholder ? undefined : configApiKey)
|
|
130
|
+
const model = override?.model || googleCfg?.model || 'gemini-2.5-flash';
|
|
131
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
132
|
+
const globalApiKey = isPlaceholder(googleCfg?.apiKey) ? undefined : googleCfg?.apiKey;
|
|
133
|
+
const apiKey = overrideApiKey
|
|
134
|
+
|| globalApiKey
|
|
120
135
|
|| process.env.GEMINI_API_KEY
|
|
121
136
|
|| process.env.GOOGLE_API_KEY
|
|
122
137
|
|| undefined;
|
|
123
|
-
const mode = googleCfg?.mode || 'cli';
|
|
124
|
-
const useVertex = googleCfg?.useVertex
|
|
125
|
-
const project = googleCfg?.project || process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
|
126
|
-
const location = googleCfg?.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
|
|
138
|
+
const mode = override?.mode || googleCfg?.mode || 'cli';
|
|
139
|
+
const useVertex = override?.useVertex ?? googleCfg?.useVertex ?? false;
|
|
140
|
+
const project = override?.project || googleCfg?.project || process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
|
141
|
+
const location = override?.location || googleCfg?.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
|
|
127
142
|
return { cliPath, model, apiKey, mode, useVertex, project, location };
|
|
128
143
|
}
|
|
129
144
|
export function loadConfig(configPath = resolvePaths().config) {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent Plugin System
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Per-EvolAgent runner instantiation: each (EvolAgent × baseagent) gets its
|
|
5
|
+
* own runner so that runtime state (model/effort/permissionMode/credentials)
|
|
6
|
+
* is fully isolated.
|
|
5
7
|
*/
|
|
6
8
|
import { logger } from '../utils/logger.js';
|
|
7
|
-
/**
|
|
8
|
-
* Agent Loader
|
|
9
|
-
*
|
|
10
|
-
* Manages agent plugin registration and creation.
|
|
11
|
-
*/
|
|
9
|
+
/** Agent Loader — produces one runner per (EvolAgent × baseagent). */
|
|
12
10
|
export class AgentLoader {
|
|
13
11
|
plugins = new Map();
|
|
14
12
|
register(plugin) {
|
|
@@ -18,20 +16,37 @@ export class AgentLoader {
|
|
|
18
16
|
this.plugins.set(plugin.name, plugin);
|
|
19
17
|
logger.debug(`Registered agent plugin: ${plugin.name}`);
|
|
20
18
|
}
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Iterate over all EvolAgents (DefaultAgent + each named agent in registry)
|
|
21
|
+
* × all registered plugins. Each successful (agent, plugin) pair yields one
|
|
22
|
+
* runner instance.
|
|
23
|
+
*/
|
|
24
|
+
createAll(globalConfig, registry, callbacks) {
|
|
22
25
|
const instances = [];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
const allAgents = [];
|
|
27
|
+
const def = registry.get('[default]');
|
|
28
|
+
if (def)
|
|
29
|
+
allAgents.push(def);
|
|
30
|
+
for (const a of registry.runnableAgents())
|
|
31
|
+
allAgents.push(a);
|
|
32
|
+
for (const agent of allAgents) {
|
|
33
|
+
for (const [pluginName, plugin] of this.plugins) {
|
|
34
|
+
if (!plugin.isEnabled(globalConfig, agent)) {
|
|
35
|
+
logger.debug(`Plugin '${pluginName}' disabled for agent '${agent.name}', skipping`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const instance = plugin.createAgent(globalConfig, agent, callbacks);
|
|
40
|
+
if (!instance) {
|
|
41
|
+
logger.debug(`Plugin '${pluginName}' returned null for agent '${agent.name}', skipping`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
instances.push(instance);
|
|
45
|
+
logger.info(`✓ Runner created: agent=${instance.evolagentName} baseagent=${instance.baseagent}`);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.error(`✗ Failed to create runner for agent='${agent.name}' baseagent='${pluginName}':`, error);
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
51
|
}
|
|
37
52
|
return instances;
|
|
@@ -104,7 +104,7 @@ function formatIdleTime(ms) {
|
|
|
104
104
|
return '刚刚';
|
|
105
105
|
}
|
|
106
106
|
// 支持的命令列表
|
|
107
|
-
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode', '/ask'];
|
|
107
|
+
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode', '/ask', '/resume'];
|
|
108
108
|
// 命令别名映射
|
|
109
109
|
const aliases = {
|
|
110
110
|
'/p': '/project',
|
|
@@ -113,7 +113,7 @@ const aliases = {
|
|
|
113
113
|
'/rw': '/rewind'
|
|
114
114
|
};
|
|
115
115
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
116
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd', '/ask'];
|
|
116
|
+
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd', '/ask', '/resume'];
|
|
117
117
|
export class CommandHandler {
|
|
118
118
|
sessionManager;
|
|
119
119
|
config;
|
|
@@ -131,11 +131,38 @@ export class CommandHandler {
|
|
|
131
131
|
agentMap;
|
|
132
132
|
defaultAgentId;
|
|
133
133
|
agentRegistry;
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Get the runner for a (channel, baseagent) pair.
|
|
136
|
+
*
|
|
137
|
+
* Resolves the owning EvolAgent via the registry; falls back to default key.
|
|
138
|
+
* `baseagent` typically comes from `session.agentId` (e.g. 'claude').
|
|
139
|
+
*/
|
|
140
|
+
getAgent(channel, baseagent) {
|
|
141
|
+
if (channel && baseagent) {
|
|
142
|
+
const evolName = this.agentRegistry?.resolveByChannel(channel)?.name || '[default]';
|
|
143
|
+
const key = `${evolName}::${baseagent}`;
|
|
144
|
+
if (this.agentMap.has(key))
|
|
145
|
+
return this.agentMap.get(key);
|
|
146
|
+
}
|
|
147
|
+
if (this.agentMap.has(this.defaultAgentId))
|
|
148
|
+
return this.agentMap.get(this.defaultAgentId);
|
|
149
|
+
return this.agentMap.values().next().value;
|
|
150
|
+
}
|
|
151
|
+
/** Return the list of baseagents available to a given channel (per-EvolAgent isolation). */
|
|
152
|
+
getAvailableBaseagents(channel) {
|
|
153
|
+
const evolName = this.agentRegistry?.resolveByChannel(channel)?.name || '[default]';
|
|
154
|
+
const prefix = `${evolName}::`;
|
|
155
|
+
const result = [];
|
|
156
|
+
for (const key of this.agentMap.keys()) {
|
|
157
|
+
if (key.startsWith(prefix))
|
|
158
|
+
result.push(key.slice(prefix.length));
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
/** Extract the baseagent component from `defaultAgentId` (e.g. `[default]::claude` → `claude`). */
|
|
163
|
+
parseDefaultBaseagent() {
|
|
164
|
+
const idx = this.defaultAgentId.indexOf('::');
|
|
165
|
+
return idx >= 0 ? this.defaultAgentId.slice(idx + 2) : this.defaultAgentId;
|
|
139
166
|
}
|
|
140
167
|
constructor(sessionManager, agentRunnerOrMap, config, messageCache, eventBus, defaultAgentId) {
|
|
141
168
|
this.sessionManager = sessionManager;
|
|
@@ -144,11 +171,12 @@ export class CommandHandler {
|
|
|
144
171
|
this.eventBus = eventBus;
|
|
145
172
|
if (agentRunnerOrMap instanceof Map) {
|
|
146
173
|
this.agentMap = agentRunnerOrMap;
|
|
147
|
-
this.defaultAgentId = defaultAgentId || 'claude';
|
|
174
|
+
this.defaultAgentId = defaultAgentId || '[default]::claude';
|
|
148
175
|
}
|
|
149
176
|
else {
|
|
150
|
-
|
|
151
|
-
this.
|
|
177
|
+
// Backward-compat single-runner path: treat as DefaultAgent's claude.
|
|
178
|
+
this.agentMap = new Map([[`[default]::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
179
|
+
this.defaultAgentId = `[default]::${agentRunnerOrMap.name}`;
|
|
152
180
|
}
|
|
153
181
|
}
|
|
154
182
|
/** 注入 EvolAgentRegistry,用于判断通道是否被 EvolAgent 管理 */
|
|
@@ -685,10 +713,10 @@ export class CommandHandler {
|
|
|
685
713
|
return Object.entries(list).map(([name, path]) => ({ value: name, label: name, desc: path }));
|
|
686
714
|
}
|
|
687
715
|
if (cmd === '/agent') {
|
|
688
|
-
return
|
|
716
|
+
return this.getAvailableBaseagents(channel).map(name => ({ value: name, label: name }));
|
|
689
717
|
}
|
|
690
718
|
if (cmd === '/model') {
|
|
691
|
-
const agent = this.getAgent(session?.agentId);
|
|
719
|
+
const agent = this.getAgent(channel, session?.agentId);
|
|
692
720
|
if (hasModelSwitcher(agent) && agent.listModels) {
|
|
693
721
|
const models = await agent.listModels() ?? [];
|
|
694
722
|
if (models.length > 0)
|
|
@@ -737,7 +765,7 @@ export class CommandHandler {
|
|
|
737
765
|
const identity = this.sessionManager.resolveIdentity(channel, userId);
|
|
738
766
|
if (identity.role !== 'owner')
|
|
739
767
|
return { error: '无权限' };
|
|
740
|
-
const permAgent = this.getAgent(session.agentId);
|
|
768
|
+
const permAgent = this.getAgent(channel, session.agentId);
|
|
741
769
|
const validModes = hasPermissionController(permAgent)
|
|
742
770
|
? permAgent.listModes().filter(m => m.available).map(m => m.key)
|
|
743
771
|
: ['auto', 'bypass', 'plan', 'edit', 'request', 'noask'];
|
|
@@ -779,7 +807,7 @@ export class CommandHandler {
|
|
|
779
807
|
const policy = this.getPolicy(channel);
|
|
780
808
|
// 按当前会话选择 agent 后端
|
|
781
809
|
const activeSession = await this.sessionManager.getActiveSession(channel, channelId);
|
|
782
|
-
const agent = this.getAgent(activeSession?.agentId);
|
|
810
|
+
const agent = this.getAgent(channel, activeSession?.agentId);
|
|
783
811
|
// 规范化命令(将别名转换为完整命令)
|
|
784
812
|
let normalizedContent = content;
|
|
785
813
|
for (const [alias, full] of Object.entries(aliases)) {
|
|
@@ -821,8 +849,9 @@ export class CommandHandler {
|
|
|
821
849
|
if (normalizedContent.startsWith('/')) {
|
|
822
850
|
// guest 在群聊和私聊中均可访问的只读命令:纯查询形态(带参写操作由各 handler 内部守卫拦截)
|
|
823
851
|
const guestGroupCommands = [
|
|
824
|
-
'/status', '/help', '/check', '/chatmode',
|
|
825
|
-
'/model', '/effort', '/agent', '/perm', '/activity', '/safe', '/stop',
|
|
852
|
+
'/status', '/help', '/evolhelp', '/check', '/chatmode',
|
|
853
|
+
'/model', '/setmodel', '/effort', '/agent', '/perm', '/activity', '/safe', '/stop',
|
|
854
|
+
'/resume',
|
|
826
855
|
];
|
|
827
856
|
const userCommands = activeChatType === 'group' && !isAdmin
|
|
828
857
|
? guestGroupCommands
|
|
@@ -853,7 +882,7 @@ export class CommandHandler {
|
|
|
853
882
|
// 话题中:检查话题 session 是否在处理(不创建)
|
|
854
883
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
855
884
|
if (threadSession) {
|
|
856
|
-
const threadAgent = this.getAgent(threadSession.agentId);
|
|
885
|
+
const threadAgent = this.getAgent(channel, threadSession.agentId);
|
|
857
886
|
if (threadAgent.hasActiveStream(threadSession.id)) {
|
|
858
887
|
return '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试';
|
|
859
888
|
}
|
|
@@ -961,6 +990,64 @@ export class CommandHandler {
|
|
|
961
990
|
];
|
|
962
991
|
return lines.join('\n');
|
|
963
992
|
}
|
|
993
|
+
// /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
|
|
994
|
+
if (normalizedContent === '/evolhelp') {
|
|
995
|
+
const cmds = [];
|
|
996
|
+
// 项目管理
|
|
997
|
+
cmds.push({ command: '/pwd', description: '显示当前项目路径', category: '项目管理', roles: ['admin', 'owner'] });
|
|
998
|
+
cmds.push({ command: '/p', aliases: ['/project', '/plist'], args: '[name|path]', description: '列出或切换项目', category: '项目管理', roles: ['admin', 'owner'] });
|
|
999
|
+
if (isOwner) {
|
|
1000
|
+
cmds.push({ command: '/bind', args: '<path>', description: '绑定新项目目录', category: '项目管理', roles: ['owner'] });
|
|
1001
|
+
}
|
|
1002
|
+
// 会话管理
|
|
1003
|
+
cmds.push({ command: '/new', args: '[名称]', description: '创建新会话(清空历史请用此命令,可选命名)', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1004
|
+
cmds.push({ command: '/s', aliases: ['/session', '/slist'], args: '[cli|名称|序号|uuid]', description: '列出或切换会话', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1005
|
+
cmds.push({ command: '/name', aliases: ['/rename'], args: '<新名称>', description: '重命名当前会话', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1006
|
+
cmds.push({ command: '/del', args: '<名称>', description: '删除指定会话(仅解绑,不删除文件)', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1007
|
+
if (isAdmin) {
|
|
1008
|
+
cmds.push({ command: '/fork', args: '[名称]', description: '分支当前会话(从当前对话点创建分支)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1009
|
+
cmds.push({ command: '/rewind', aliases: ['/rw'], args: '[N] [chat|file|all]', description: '查看历史/撤销指定轮次', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1010
|
+
cmds.push({ command: '/compact', description: '压缩会话上下文(减少 token 用量)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1011
|
+
}
|
|
1012
|
+
// Agent 与模型
|
|
1013
|
+
if (isAdmin) {
|
|
1014
|
+
cmds.push({ command: '/agent', args: '[name]', description: '查看或切换 Agent 后端', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1015
|
+
cmds.push({ command: '/model', args: '[model]', description: '查看或切换模型', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1016
|
+
cmds.push({ command: '/setmodel', description: '返回 JSON 格式的模型列表(供程序解析)', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1017
|
+
cmds.push({ command: '/effort', args: '[level]', description: '查看或切换推理强度', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1018
|
+
}
|
|
1019
|
+
// 权限管理
|
|
1020
|
+
if (isAdmin) {
|
|
1021
|
+
cmds.push({ command: '/perm', args: isOwner ? '<auto|bypass|request|edit|plan|noask>' : undefined, description: '查看当前权限模式', category: '权限管理', roles: ['admin', 'owner'] });
|
|
1022
|
+
cmds.push({ command: '/perm', args: 'allow|always|deny', description: '审批权限请求', category: '权限管理', roles: ['admin', 'owner'] });
|
|
1023
|
+
}
|
|
1024
|
+
// 运维
|
|
1025
|
+
cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1026
|
+
cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
|
|
1027
|
+
cmds.push({ command: '/check', description: '检查渠道状态', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1028
|
+
if (isAdmin) {
|
|
1029
|
+
cmds.push({ command: '/activity', args: '[all|dm|owner|none]', description: '查看/控制中间输出显示模式', category: '运维', roles: ['admin', 'owner'] });
|
|
1030
|
+
cmds.push({ command: '/restart', args: '<channel>', description: '重连指定渠道', category: '运维', roles: ['admin', 'owner'] });
|
|
1031
|
+
}
|
|
1032
|
+
if (isOwner) {
|
|
1033
|
+
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['owner'] });
|
|
1034
|
+
cmds.push({ command: '/file', args: '[channel] <path>', description: '发送项目内文件', category: '运维', roles: ['owner'] });
|
|
1035
|
+
cmds.push({ command: '/aid', args: '[list|new <aid>]', description: 'AID 管理', category: '运维', roles: ['owner'] });
|
|
1036
|
+
cmds.push({ command: '/agentmd', args: '[put|set <内容>]', description: '管理 agent.md', category: '运维', roles: ['owner'] });
|
|
1037
|
+
}
|
|
1038
|
+
// 会话模式
|
|
1039
|
+
if (isAdmin) {
|
|
1040
|
+
cmds.push({ command: '/chatmode', args: '[interactive|proactive]', description: '查看/切换会话模式(被动响应或主动推进)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1041
|
+
}
|
|
1042
|
+
// 交互
|
|
1043
|
+
cmds.push({ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1044
|
+
cmds.push({ command: '/resume', description: '查看当前项目的 Claude 会话记录', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1045
|
+
// 帮助
|
|
1046
|
+
cmds.push({ command: '/help', description: '显示帮助信息', category: '帮助', roles: ['guest', 'admin', 'owner'] });
|
|
1047
|
+
cmds.push({ command: '/evolhelp', description: '返回 JSON 格式命令列表', category: '帮助', roles: ['guest', 'admin', 'owner'] });
|
|
1048
|
+
const categories = [...new Set(cmds.map(c => c.category))];
|
|
1049
|
+
return JSON.stringify({ commands: cmds, categories });
|
|
1050
|
+
}
|
|
964
1051
|
// /perm 命令:权限模式切换 + 权限审批(快速路径,不进入消息队列)
|
|
965
1052
|
if (normalizedContent.startsWith('/perm')) {
|
|
966
1053
|
const args = normalizedContent.slice(5).trim();
|
|
@@ -969,7 +1056,7 @@ export class CommandHandler {
|
|
|
969
1056
|
if ('error' in permResult)
|
|
970
1057
|
return permResult.error;
|
|
971
1058
|
const { session: permSession } = permResult;
|
|
972
|
-
const permAgent = this.getAgent(permSession.agentId);
|
|
1059
|
+
const permAgent = this.getAgent(channel, permSession.agentId);
|
|
973
1060
|
// /perm(无参数):显示当前模式和可选模式
|
|
974
1061
|
if (!args) {
|
|
975
1062
|
if (!hasPermissionController(permAgent)) {
|
|
@@ -1108,6 +1195,89 @@ export class CommandHandler {
|
|
|
1108
1195
|
this.interactionRouter.handle({ type: 'interaction.response', id: targetId, action: args, operatorId: userId });
|
|
1109
1196
|
return `✓ 已回答`;
|
|
1110
1197
|
}
|
|
1198
|
+
// /resume 命令:返回当前项目的 Claude 会话记录(JSON)
|
|
1199
|
+
if (normalizedContent === '/resume' || normalizedContent.startsWith('/resume ')) {
|
|
1200
|
+
const resumeResult = await this.ensureSession(channel, channelId, threadId);
|
|
1201
|
+
if ('error' in resumeResult)
|
|
1202
|
+
return resumeResult.error;
|
|
1203
|
+
const { session: resumeSession } = resumeResult;
|
|
1204
|
+
try {
|
|
1205
|
+
const { encodePath } = await import('../utils/cross-platform.js');
|
|
1206
|
+
const homeDir = os.homedir();
|
|
1207
|
+
const encodedPath = encodePath(resumeSession.projectPath);
|
|
1208
|
+
const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
|
|
1209
|
+
if (!fs.existsSync(projectDir)) {
|
|
1210
|
+
return '❌ 未找到 Claude 会话记录目录';
|
|
1211
|
+
}
|
|
1212
|
+
const jsonlFiles = fs.readdirSync(projectDir).filter(f => f.endsWith('.jsonl'));
|
|
1213
|
+
if (jsonlFiles.length === 0) {
|
|
1214
|
+
return '❌ 当前项目没有 Claude 会话记录';
|
|
1215
|
+
}
|
|
1216
|
+
const sessions = [];
|
|
1217
|
+
for (const file of jsonlFiles) {
|
|
1218
|
+
const filePath = path.join(projectDir, file);
|
|
1219
|
+
const sessionId = file.replace('.jsonl', '');
|
|
1220
|
+
let lastTimestamp = '';
|
|
1221
|
+
let firstUserMessage = '';
|
|
1222
|
+
let model = '';
|
|
1223
|
+
let branch = '';
|
|
1224
|
+
let turns = 0;
|
|
1225
|
+
try {
|
|
1226
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1227
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
1228
|
+
for (const line of lines) {
|
|
1229
|
+
const event = JSON.parse(line);
|
|
1230
|
+
if (event.timestamp && event.timestamp > lastTimestamp) {
|
|
1231
|
+
lastTimestamp = event.timestamp;
|
|
1232
|
+
}
|
|
1233
|
+
if (event.gitBranch && !branch) {
|
|
1234
|
+
branch = event.gitBranch;
|
|
1235
|
+
}
|
|
1236
|
+
if (event.type === 'user' && event.message?.role === 'user') {
|
|
1237
|
+
const msgContent = event.message.content;
|
|
1238
|
+
const isToolResult = Array.isArray(msgContent) && msgContent.every((c) => c.type === 'tool_result');
|
|
1239
|
+
if (!isToolResult) {
|
|
1240
|
+
turns++;
|
|
1241
|
+
if (!firstUserMessage) {
|
|
1242
|
+
if (typeof msgContent === 'string') {
|
|
1243
|
+
firstUserMessage = msgContent.slice(0, 100);
|
|
1244
|
+
}
|
|
1245
|
+
else if (Array.isArray(msgContent)) {
|
|
1246
|
+
const textBlock = msgContent.find((c) => c.type === 'text');
|
|
1247
|
+
if (textBlock?.text) {
|
|
1248
|
+
firstUserMessage = textBlock.text.slice(0, 100);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (event.type === 'assistant' && event.message?.model && !model) {
|
|
1255
|
+
model = event.message.model;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (!lastTimestamp)
|
|
1263
|
+
continue;
|
|
1264
|
+
sessions.push({
|
|
1265
|
+
sessionId,
|
|
1266
|
+
lastMessageTime: lastTimestamp,
|
|
1267
|
+
firstUserMessage: firstUserMessage || '(无消息)',
|
|
1268
|
+
model: model || 'unknown',
|
|
1269
|
+
turns,
|
|
1270
|
+
branch: branch || 'unknown',
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
sessions.sort((a, b) => b.lastMessageTime.localeCompare(a.lastMessageTime));
|
|
1274
|
+
return JSON.stringify(sessions, null, 2);
|
|
1275
|
+
}
|
|
1276
|
+
catch (error) {
|
|
1277
|
+
logger.error('[CommandHandler] /resume failed:', error);
|
|
1278
|
+
return `❌ 读取会话记录失败: ${error instanceof Error ? error.message : '未知错误'}`;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1111
1281
|
// /agent 命令:查看或切换 Agent 后端
|
|
1112
1282
|
if (normalizedContent === '/agent' || normalizedContent.startsWith('/agent ')) {
|
|
1113
1283
|
const args = normalizedContent.slice(6).trim();
|
|
@@ -1115,9 +1285,12 @@ export class CommandHandler {
|
|
|
1115
1285
|
if (args && (activeChatType === 'group' ? !isOwner : !isAdmin)) {
|
|
1116
1286
|
return '❌ 无权限:此命令仅限管理员使用';
|
|
1117
1287
|
}
|
|
1118
|
-
const available =
|
|
1288
|
+
const available = this.getAvailableBaseagents(channel);
|
|
1119
1289
|
if (!args) {
|
|
1120
|
-
|
|
1290
|
+
// currentAgent: 当前 session 的 baseagent,或该 channel 所属 evolagent 的 baseagent
|
|
1291
|
+
const currentAgent = activeSession?.agentId
|
|
1292
|
+
|| this.agentRegistry?.resolveByChannel(channel)?.baseagent
|
|
1293
|
+
|| this.parseDefaultBaseagent();
|
|
1121
1294
|
// 尝试发送交互卡片
|
|
1122
1295
|
if (this.interactionRouter && available.length > 1) {
|
|
1123
1296
|
const requestId = `agent-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
@@ -1163,7 +1336,7 @@ export class CommandHandler {
|
|
|
1163
1336
|
}
|
|
1164
1337
|
return `当前 Agent: ${currentAgent}`;
|
|
1165
1338
|
}
|
|
1166
|
-
if (!
|
|
1339
|
+
if (!available.includes(args)) {
|
|
1167
1340
|
return `❌ 未知 Agent: ${args}\n可用: ${available.join(', ')}`;
|
|
1168
1341
|
}
|
|
1169
1342
|
const result = await this.ensureSession(channel, channelId, threadId);
|
|
@@ -1184,6 +1357,74 @@ export class CommandHandler {
|
|
|
1184
1357
|
let agentSwitchResponse = `✓ 已切换 Agent: ${args}\n 项目: ${projectName}\n 会话: ${newSession.name || '(未命名)'}\n ${hasExistingSession}`;
|
|
1185
1358
|
return agentSwitchResponse;
|
|
1186
1359
|
}
|
|
1360
|
+
// /setmodel 命令:返回 JSON 格式的模型列表(供程序解析)
|
|
1361
|
+
if (normalizedContent === '/setmodel' || normalizedContent.startsWith('/setmodel ')) {
|
|
1362
|
+
const setmodelResult = await this.ensureSession(channel, channelId, threadId);
|
|
1363
|
+
if ('error' in setmodelResult)
|
|
1364
|
+
return setmodelResult.error;
|
|
1365
|
+
const { session: setmodelSession } = setmodelResult;
|
|
1366
|
+
const setmodelAgent = this.getAgent(channel, setmodelSession.agentId);
|
|
1367
|
+
const currentModel = hasModelSwitcher(setmodelAgent) ? setmodelAgent.getModel() : setmodelAgent.name;
|
|
1368
|
+
const efforts = getAvailableEfforts(setmodelAgent, currentModel);
|
|
1369
|
+
const currentEffort = setmodelAgent.getEffort?.() || 'auto';
|
|
1370
|
+
// 获取 API URL 用于请求 /models
|
|
1371
|
+
let apiBaseUrl;
|
|
1372
|
+
try {
|
|
1373
|
+
const configBaseUrl = this.config.agents?.claude?.baseUrl;
|
|
1374
|
+
const isPlaceholderUrl = configBaseUrl?.includes('api.anthropic.com');
|
|
1375
|
+
if (configBaseUrl && !isPlaceholderUrl) {
|
|
1376
|
+
apiBaseUrl = configBaseUrl;
|
|
1377
|
+
}
|
|
1378
|
+
else if (process.env.ANTHROPIC_BASE_URL) {
|
|
1379
|
+
apiBaseUrl = process.env.ANTHROPIC_BASE_URL;
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
1383
|
+
if (fs.existsSync(claudeSettingsPath)) {
|
|
1384
|
+
const claudeSettings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));
|
|
1385
|
+
if (claudeSettings.env?.ANTHROPIC_BASE_URL) {
|
|
1386
|
+
apiBaseUrl = claudeSettings.env.ANTHROPIC_BASE_URL;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
catch { }
|
|
1392
|
+
let modelListData = null;
|
|
1393
|
+
if (apiBaseUrl) {
|
|
1394
|
+
try {
|
|
1395
|
+
const modelsUrl = apiBaseUrl.replace(/\/+$/, '') + '/v1/models';
|
|
1396
|
+
const controller = new AbortController();
|
|
1397
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
1398
|
+
const resp = await fetch(modelsUrl, {
|
|
1399
|
+
signal: controller.signal,
|
|
1400
|
+
headers: { 'Authorization': `Bearer ${this.config.agents?.claude?.apiKey || process.env.ANTHROPIC_AUTH_TOKEN || ''}` },
|
|
1401
|
+
});
|
|
1402
|
+
clearTimeout(timeout);
|
|
1403
|
+
if (resp.ok) {
|
|
1404
|
+
modelListData = await resp.json();
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
catch { }
|
|
1408
|
+
}
|
|
1409
|
+
// 兜底模型列表
|
|
1410
|
+
if (!modelListData || !modelListData.data || modelListData.data.length === 0) {
|
|
1411
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1412
|
+
modelListData = {
|
|
1413
|
+
object: 'list',
|
|
1414
|
+
data: [
|
|
1415
|
+
{ id: 'claude-opus-4-7', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1416
|
+
{ id: 'claude-opus-4-6', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1417
|
+
{ id: 'claude-sonnet-4-6', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1418
|
+
],
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
return JSON.stringify({
|
|
1422
|
+
current_model: currentModel,
|
|
1423
|
+
current_effort: currentEffort,
|
|
1424
|
+
available_efforts: efforts,
|
|
1425
|
+
models: modelListData,
|
|
1426
|
+
}, null, 2);
|
|
1427
|
+
}
|
|
1187
1428
|
// /model 命令:查看或切换模型/推理强度
|
|
1188
1429
|
if (normalizedContent.startsWith('/model')) {
|
|
1189
1430
|
const args = normalizedContent.slice(6).trim();
|
|
@@ -1192,7 +1433,7 @@ export class CommandHandler {
|
|
|
1192
1433
|
if ('error' in modelResult)
|
|
1193
1434
|
return modelResult.error;
|
|
1194
1435
|
const { session: modelSession } = modelResult;
|
|
1195
|
-
const modelAgent = this.getAgent(modelSession.agentId);
|
|
1436
|
+
const modelAgent = this.getAgent(channel, modelSession.agentId);
|
|
1196
1437
|
const models = hasModelSwitcher(modelAgent) ? modelAgent.listModels() : [];
|
|
1197
1438
|
if (!args) {
|
|
1198
1439
|
const currentModel = hasModelSwitcher(modelAgent) ? modelAgent.getModel() : modelAgent.name;
|
|
@@ -1326,7 +1567,7 @@ export class CommandHandler {
|
|
|
1326
1567
|
if ('error' in effortResult)
|
|
1327
1568
|
return effortResult.error;
|
|
1328
1569
|
const { session: effortSession } = effortResult;
|
|
1329
|
-
const effortAgent = this.getAgent(effortSession.agentId);
|
|
1570
|
+
const effortAgent = this.getAgent(channel, effortSession.agentId);
|
|
1330
1571
|
const currentModel = hasModelSwitcher(effortAgent) ? effortAgent.getModel() : effortAgent.name;
|
|
1331
1572
|
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1332
1573
|
const currentEffort = effortAgent.getEffort?.() || 'auto';
|
|
@@ -1649,7 +1890,7 @@ export class CommandHandler {
|
|
|
1649
1890
|
if (threadId) {
|
|
1650
1891
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
1651
1892
|
if (threadSession) {
|
|
1652
|
-
const threadAgent = this.getAgent(threadSession.agentId);
|
|
1893
|
+
const threadAgent = this.getAgent(channel, threadSession.agentId);
|
|
1653
1894
|
if (threadAgent.hasActiveStream(threadSession.id)) {
|
|
1654
1895
|
return '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试';
|
|
1655
1896
|
}
|
|
@@ -1667,7 +1908,7 @@ export class CommandHandler {
|
|
|
1667
1908
|
if ('error' in stopResult)
|
|
1668
1909
|
return '当前没有正在处理的任务';
|
|
1669
1910
|
const { session: stopSession } = stopResult;
|
|
1670
|
-
const stopAgent = this.getAgent(stopSession.agentId);
|
|
1911
|
+
const stopAgent = this.getAgent(channel, stopSession.agentId);
|
|
1671
1912
|
const sessionKey = stopSession.id;
|
|
1672
1913
|
const queueLength = this.messageQueue.getQueueLength(sessionKey);
|
|
1673
1914
|
const hasActive = stopAgent.hasActiveStream(sessionKey);
|
|
@@ -1692,7 +1933,7 @@ export class CommandHandler {
|
|
|
1692
1933
|
if ('error' in result)
|
|
1693
1934
|
return result.error;
|
|
1694
1935
|
const { session } = result;
|
|
1695
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
1936
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1696
1937
|
if (!sessionAgent.capabilities?.clear) {
|
|
1697
1938
|
return `❌ 当前 Agent (${sessionAgent.name}) 不支持 /clear\n\n可使用 /new 创建新会话替代`;
|
|
1698
1939
|
}
|
|
@@ -1724,7 +1965,7 @@ export class CommandHandler {
|
|
|
1724
1965
|
if ('error' in result)
|
|
1725
1966
|
return result.error;
|
|
1726
1967
|
const { session } = result;
|
|
1727
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
1968
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1728
1969
|
if (!sessionAgent.capabilities?.compact) {
|
|
1729
1970
|
return `❌ 当前 Agent (${sessionAgent.name}) 不支持 /compact`;
|
|
1730
1971
|
}
|
|
@@ -1775,7 +2016,7 @@ export class CommandHandler {
|
|
|
1775
2016
|
return `❌ 无法创建会话,请检查配置`;
|
|
1776
2017
|
}
|
|
1777
2018
|
const sessionKey = this.getQueueKey(session, channel, channelId);
|
|
1778
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
2019
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1779
2020
|
const isCurrentlyProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
1780
2021
|
const queueLength = this.messageQueue.getQueueLength(sessionKey);
|
|
1781
2022
|
const isThread = !!session.threadId;
|
|
@@ -2776,7 +3017,7 @@ export class CommandHandler {
|
|
|
2776
3017
|
return `❌ 删除失败`;
|
|
2777
3018
|
}
|
|
2778
3019
|
this.eventBus.publish({ type: 'session:deleted', sessionId: targetSession.id });
|
|
2779
|
-
const targetAgent = this.getAgent(targetSession.agentId);
|
|
3020
|
+
const targetAgent = this.getAgent(channel, targetSession.agentId);
|
|
2780
3021
|
await targetAgent.closeSession(targetSession.id);
|
|
2781
3022
|
return `✓ 已删除会话: ${targetSession.name || sessionName}\n会话文件已保留,可通过 CLI 访问`;
|
|
2782
3023
|
}
|
|
@@ -2789,7 +3030,7 @@ export class CommandHandler {
|
|
|
2789
3030
|
if (!session.agentSessionId) {
|
|
2790
3031
|
return `❌ 当前会话尚未初始化对话,无法分支\n\n请先发送一条消息,然后再使用 /fork`;
|
|
2791
3032
|
}
|
|
2792
|
-
const forkAgent = this.getAgent(session.agentId);
|
|
3033
|
+
const forkAgent = this.getAgent(channel, session.agentId);
|
|
2793
3034
|
if (!forkAgent.capabilities?.fork) {
|
|
2794
3035
|
return `❌ 当前 Agent (${forkAgent.name}) 不支持 /fork\n\n可使用 /new 创建新会话替代`;
|
|
2795
3036
|
}
|
|
@@ -2810,7 +3051,7 @@ export class CommandHandler {
|
|
|
2810
3051
|
if ('error' in result)
|
|
2811
3052
|
return result.error;
|
|
2812
3053
|
const { session } = result;
|
|
2813
|
-
const rewindAgent = this.getAgent(session.agentId);
|
|
3054
|
+
const rewindAgent = this.getAgent(channel, session.agentId);
|
|
2814
3055
|
if (rewindAgent.name !== 'claude') {
|
|
2815
3056
|
return '❌ /rewind 仅支持 Claude 后端';
|
|
2816
3057
|
}
|
|
@@ -2847,7 +3088,7 @@ export class CommandHandler {
|
|
|
2847
3088
|
if ('error' in repairResult)
|
|
2848
3089
|
return repairResult.error;
|
|
2849
3090
|
const { session: repairSession } = repairResult;
|
|
2850
|
-
const repairAgent = this.getAgent(repairSession.agentId);
|
|
3091
|
+
const repairAgent = this.getAgent(channel, repairSession.agentId);
|
|
2851
3092
|
const { checkSessionFile, backupSessionFile } = await import('./session/session-file-health.js');
|
|
2852
3093
|
try {
|
|
2853
3094
|
if (!repairSession.agentSessionId) {
|
|
@@ -105,10 +105,21 @@ export class EvolAgentRegistry {
|
|
|
105
105
|
buildDefaultAgent(globalConfig) {
|
|
106
106
|
const agents = globalConfig.agents || {};
|
|
107
107
|
const defaultName = agents.defaultAgent || 'claude';
|
|
108
|
+
// Include ALL declared baseagents (not just defaultName) so that
|
|
109
|
+
// AgentLoader creates runners for each, enabling /agent switching.
|
|
110
|
+
const baseagentBlock = {};
|
|
111
|
+
const KNOWN_BASEAGENTS = ['claude', 'codex', 'gemini', 'hermes'];
|
|
112
|
+
for (const ba of KNOWN_BASEAGENTS) {
|
|
113
|
+
if (agents[ba] !== undefined)
|
|
114
|
+
baseagentBlock[ba] = agents[ba];
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(baseagentBlock).length === 0) {
|
|
117
|
+
baseagentBlock[defaultName] = agents[defaultName] || {};
|
|
118
|
+
}
|
|
108
119
|
const cfg = {
|
|
109
120
|
name: '[default]',
|
|
110
121
|
enabled: true,
|
|
111
|
-
agents:
|
|
122
|
+
agents: baseagentBlock,
|
|
112
123
|
channels: globalConfig.channels || {},
|
|
113
124
|
projects: { defaultPath: globalConfig.projects?.defaultPath || process.cwd() },
|
|
114
125
|
chatmode: globalConfig.chatmode,
|
|
@@ -32,11 +32,25 @@ export class MessageProcessor {
|
|
|
32
32
|
interactionRouter;
|
|
33
33
|
messageQueue;
|
|
34
34
|
skillsEnsured = false; // 全局 SKILLS.md 是否已确保
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Get the runner for a given (channel, baseagent) pair.
|
|
37
|
+
*
|
|
38
|
+
* - `channel` is used to look up the owning EvolAgent (via registry).
|
|
39
|
+
* - `baseagent` (e.g. 'claude') comes from `session.agentId`.
|
|
40
|
+
*
|
|
41
|
+
* Falls back to `defaultAgentId` (a composite key, e.g. `[default]::claude`)
|
|
42
|
+
* when no match is found.
|
|
43
|
+
*/
|
|
44
|
+
getAgent(channel, baseagent) {
|
|
45
|
+
if (channel && baseagent) {
|
|
46
|
+
const evolName = this.agentRegistry?.resolveByChannel(channel)?.name || '[default]';
|
|
47
|
+
const key = `${evolName}::${baseagent}`;
|
|
48
|
+
if (this.agentMap.has(key))
|
|
49
|
+
return this.agentMap.get(key);
|
|
50
|
+
}
|
|
51
|
+
if (this.agentMap.has(this.defaultAgentId))
|
|
52
|
+
return this.agentMap.get(this.defaultAgentId);
|
|
53
|
+
return this.agentMap.values().next().value;
|
|
40
54
|
}
|
|
41
55
|
/** 获取可用 agent 列表 */
|
|
42
56
|
getAvailableAgents() {
|
|
@@ -57,12 +71,12 @@ export class MessageProcessor {
|
|
|
57
71
|
this.commandHandler = commandHandler;
|
|
58
72
|
if (agentRunnerOrMap instanceof Map) {
|
|
59
73
|
this.agentMap = agentRunnerOrMap;
|
|
60
|
-
this.defaultAgentId = defaultAgentId || 'claude';
|
|
74
|
+
this.defaultAgentId = defaultAgentId || '[default]::claude';
|
|
61
75
|
}
|
|
62
76
|
else {
|
|
63
|
-
//
|
|
64
|
-
this.agentMap = new Map([[agentRunnerOrMap.name
|
|
65
|
-
this.defaultAgentId = agentRunnerOrMap.name
|
|
77
|
+
// Backward-compat single-runner path.
|
|
78
|
+
this.agentMap = new Map([[`[default]::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
79
|
+
this.defaultAgentId = `[default]::${agentRunnerOrMap.name}`;
|
|
66
80
|
}
|
|
67
81
|
// 监听中断事件,标记被中断的 session
|
|
68
82
|
this.eventBus.subscribe('message:interrupted', (event) => {
|
|
@@ -176,7 +190,7 @@ export class MessageProcessor {
|
|
|
176
190
|
logger.debug(`[MessageProcessor] Agent context resolved: ${agentContext.name} (${agentContext.baseagent})`);
|
|
177
191
|
}
|
|
178
192
|
// 按 session.agentId 选择 agent 后端
|
|
179
|
-
const agent = this.getAgent(session.agentId);
|
|
193
|
+
const agent = this.getAgent(channelKey, session.agentId);
|
|
180
194
|
const monitorEnabled = this.config.idleMonitor?.enabled !== false;
|
|
181
195
|
const showIdleMonitor = policy.showIdleMonitor(chatType, identityRole);
|
|
182
196
|
// 计算是否抑制中间输出(工具活动 + 流式文本)
|
|
@@ -302,7 +316,7 @@ export class MessageProcessor {
|
|
|
302
316
|
return;
|
|
303
317
|
}
|
|
304
318
|
const { adapter, options } = channelInfo;
|
|
305
|
-
const agent = this.getAgent(session.agentId);
|
|
319
|
+
const agent = this.getAgent(channelKey, session.agentId);
|
|
306
320
|
const streamKey = session.id;
|
|
307
321
|
// 为本次任务处理生成唯一 task_id(客户端生成,格式 task-{10hex})
|
|
308
322
|
const taskId = `task-${crypto.randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
|
@@ -98,7 +98,7 @@ export class MessageQueue {
|
|
|
98
98
|
agentName: this.processingAgent.get(queueKey),
|
|
99
99
|
});
|
|
100
100
|
if (this.interruptCallback) {
|
|
101
|
-
this.interruptCallback(sessionKey, this.currentAgentId).catch(() => { });
|
|
101
|
+
this.interruptCallback(sessionKey, this.currentAgentId, this.processingAgent.get(queueKey)).catch(() => { });
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
@@ -282,7 +282,7 @@ export class MessageQueue {
|
|
|
282
282
|
agentName: this.processingAgent.get(this.currentSessionKey),
|
|
283
283
|
});
|
|
284
284
|
if (this.interruptCallback) {
|
|
285
|
-
this.interruptCallback(sessionKey, this.currentAgentId).catch(() => { });
|
|
285
|
+
this.interruptCallback(sessionKey, this.currentAgentId, this.processingAgent.get(this.currentSessionKey)).catch(() => { });
|
|
286
286
|
}
|
|
287
287
|
return true;
|
|
288
288
|
}
|
package/dist/index.js
CHANGED
|
@@ -132,27 +132,28 @@ async function main() {
|
|
|
132
132
|
sessionManager.registerFileAdapter(new ClaudeSessionFileAdapter());
|
|
133
133
|
sessionManager.registerFileAdapter(new CodexSessionFileAdapter());
|
|
134
134
|
sessionManager.registerFileAdapter(new GeminiSessionFileAdapter());
|
|
135
|
-
// Agent
|
|
135
|
+
// Agent 插件系统:每个 EvolAgent × 每个 baseagent 一个独立 runner(H1/H2 修复)
|
|
136
136
|
const agentLoader = new AgentLoader();
|
|
137
137
|
agentLoader.register(new ClaudeAgentPlugin());
|
|
138
138
|
agentLoader.register(new CodexAgentPlugin());
|
|
139
139
|
agentLoader.register(new GeminiAgentPlugin());
|
|
140
|
-
const agentInstances = agentLoader.createAll(config, {
|
|
140
|
+
const agentInstances = agentLoader.createAll(config, agentRegistry, {
|
|
141
141
|
onSessionIdUpdate: async (sessionId, agentSessionId) => {
|
|
142
142
|
await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId);
|
|
143
143
|
},
|
|
144
144
|
});
|
|
145
|
-
//
|
|
145
|
+
// agentMap 复合键:${evolagentName}::${baseagent}
|
|
146
146
|
const agentMap = new Map();
|
|
147
147
|
for (const inst of agentInstances) {
|
|
148
|
-
agentMap.set(inst.
|
|
148
|
+
agentMap.set(`${inst.evolagentName}::${inst.baseagent}`, inst.agent);
|
|
149
149
|
}
|
|
150
150
|
const defaultAgent = config.agents?.defaultAgent || 'claude';
|
|
151
|
-
const
|
|
151
|
+
const defaultAgentKey = `[default]::${defaultAgent}`;
|
|
152
|
+
const agentRunner = agentMap.get(defaultAgentKey) || agentInstances[0]?.agent;
|
|
152
153
|
if (!agentRunner) {
|
|
153
|
-
throw new Error('No agent backend available. Check agents config.');
|
|
154
|
+
throw new Error('No agent backend available. Check agents config (no runners created).');
|
|
154
155
|
}
|
|
155
|
-
logger.info(`✓
|
|
156
|
+
logger.info(`✓ Runners ready (default key: ${defaultAgentKey}, total: ${agentMap.size}, keys: ${[...agentMap.keys()].join(', ')})`);
|
|
156
157
|
// 权限审批网关
|
|
157
158
|
const permissionGateway = new PermissionGateway();
|
|
158
159
|
permissionGateway.setEventBus(eventBus);
|
|
@@ -222,7 +223,7 @@ async function main() {
|
|
|
222
223
|
// 启动迁移:将 sessions.channel 从 channelType 回填为实例名
|
|
223
224
|
sessionManager.migrateChannelToInstanceName();
|
|
224
225
|
// 创建命令处理器
|
|
225
|
-
const cmdHandler = new CommandHandler(sessionManager, agentMap, config, messageCache, eventBus,
|
|
226
|
+
const cmdHandler = new CommandHandler(sessionManager, agentMap, config, messageCache, eventBus, defaultAgentKey);
|
|
226
227
|
cmdHandler.setPermissionGateway(permissionGateway);
|
|
227
228
|
cmdHandler.setInteractionRouter(interactionRouter);
|
|
228
229
|
cmdHandler.setStatsCollector(statsCollector);
|
|
@@ -237,7 +238,7 @@ async function main() {
|
|
|
237
238
|
}
|
|
238
239
|
};
|
|
239
240
|
return cmdHandler.handle(content, channel, channelId, sendFn, userId, threadId);
|
|
240
|
-
},
|
|
241
|
+
}, defaultAgentKey);
|
|
241
242
|
// 回填 processor 和 messageQueue 的引用
|
|
242
243
|
cmdHandler.setProcessor(processor);
|
|
243
244
|
// Inject EvolAgentRegistry (methods added by T6/T7)
|
|
@@ -260,8 +261,11 @@ async function main() {
|
|
|
260
261
|
await processor.processMessage(message);
|
|
261
262
|
});
|
|
262
263
|
// 设置中断回调(精确中断正在处理的 agent)
|
|
263
|
-
messageQueue.setInterruptCallback(async (sessionKey, agentId) => {
|
|
264
|
-
const
|
|
264
|
+
messageQueue.setInterruptCallback(async (sessionKey, agentId, evolagentName) => {
|
|
265
|
+
const baseagent = agentId || defaultAgent;
|
|
266
|
+
const evol = evolagentName || '[default]';
|
|
267
|
+
const agent = agentMap.get(`${evol}::${baseagent}`)
|
|
268
|
+
|| agentMap.get(defaultAgentKey);
|
|
265
269
|
if (agent?.hasActiveStream(sessionKey)) {
|
|
266
270
|
await agent.interrupt(sessionKey);
|
|
267
271
|
}
|
|
@@ -522,12 +526,16 @@ async function main() {
|
|
|
522
526
|
sessionManager.clearProcessing(session.id);
|
|
523
527
|
continue;
|
|
524
528
|
}
|
|
525
|
-
|
|
529
|
+
// 复合键:${evolagentName}::${baseagent},从 channel 反查 evolagent
|
|
530
|
+
const owningAgent = agentRegistry.resolveByChannel(session.channel);
|
|
531
|
+
const evolName = owningAgent?.name || '[default]';
|
|
532
|
+
const baseagentName = session.agentId || defaultAgent;
|
|
533
|
+
const agent = agentMap.get(`${evolName}::${baseagentName}`) || agentMap.get(defaultAgentKey);
|
|
526
534
|
if (!agent) {
|
|
527
535
|
sessionManager.clearProcessing(session.id);
|
|
528
536
|
continue;
|
|
529
537
|
}
|
|
530
|
-
logger.info(`[Resume] Resuming session: ${session.id} (agent: ${
|
|
538
|
+
logger.info(`[Resume] Resuming session: ${session.id} (agent: ${evolName}::${baseagentName})`);
|
|
531
539
|
const resumeMessage = {
|
|
532
540
|
channel: session.channel,
|
|
533
541
|
channelId: session.channelId,
|
package/package.json
CHANGED