@wu529778790/open-im 1.11.10-beta.1 → 1.11.10-beta.2

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.
@@ -1,4 +1,4 @@
1
- import { getConfiguredAiCommands } from '../config.js';
1
+ import { getConfiguredAiCommands, loadConfig } from '../config.js';
2
2
  import { ClaudeSDKAdapter } from './claude-sdk-adapter.js';
3
3
  import { CodexAdapter } from './codex-adapter.js';
4
4
  import { CodeBuddyAdapter } from './codebuddy-adapter.js';
@@ -37,7 +37,32 @@ export function initAdapters(config) {
37
37
  }
38
38
  }
39
39
  export function getAdapter(aiCommand) {
40
- return adapters.get(aiCommand);
40
+ const existing = adapters.get(aiCommand);
41
+ if (existing)
42
+ return existing;
43
+ // 懒加载:启动时未初始化的工具(例如启动用 claude,运行中切到 opencode)
44
+ // 按需创建并缓存,避免返回 undefined 导致消息处理失败。
45
+ const factory = ADAPTER_FACTORIES[aiCommand];
46
+ if (!factory)
47
+ return undefined;
48
+ let config;
49
+ try {
50
+ config = loadConfig();
51
+ }
52
+ catch (err) {
53
+ log.error(`Lazy-create adapter "${aiCommand}" failed to load config:`, err);
54
+ return undefined;
55
+ }
56
+ const adapter = factory(config);
57
+ adapters.set(aiCommand, adapter);
58
+ log.info(`${aiCommand} adapter lazy-created (was not enabled at startup)`);
59
+ // SDK 工具需要懒启动 server,与 initAdapters 行为一致
60
+ if (aiCommand === 'opencode' && !isOpencodeRunning()) {
61
+ startOpencode().catch((err) => {
62
+ log.warn(`OpenCode SDK server lazy-start failed (will retry on first use): ${err}`);
63
+ });
64
+ }
65
+ return adapter;
41
66
  }
42
67
  export function cleanupAdapters() {
43
68
  ClaudeSDKAdapter.destroy();
@@ -31,6 +31,7 @@ export declare class CommandHandler {
31
31
  private handleNew;
32
32
  private handlePwd;
33
33
  private handleStatus;
34
+ private handleSwitchAi;
34
35
  private handleCd;
35
36
  private handleHistory;
36
37
  private handleDelete;
@@ -1,10 +1,10 @@
1
- import { resolvePlatformAiCommand } from '../config.js';
1
+ import { resolvePlatformAiCommand, loadConfig, loadFileConfig, saveFileConfig, } from '../config.js';
2
2
  import { escapePathForMarkdown } from '../shared/utils.js';
3
3
  import { getAutopilotPendingStatus } from '../shared/ai-task.js';
4
4
  import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
5
5
  import { createLogger } from '../logger.js';
6
6
  import { ClaudeSDKAdapter } from '../adapters/claude-sdk-adapter.js';
7
- import { AI_TOOL_BY_ID } from '../adapters/tool-registry.js';
7
+ import { AI_TOOL_BY_ID, AI_TOOLS, isAiCommand } from '../adapters/tool-registry.js';
8
8
  const log = createLogger('Commands');
9
9
  function formatRelativeTime(ts) {
10
10
  const sec = Math.floor((Date.now() - ts) / 1000);
@@ -104,6 +104,8 @@ export class CommandHandler {
104
104
  return this.handlePwd(chatId, userId);
105
105
  if (t === '/status')
106
106
  return this.handleStatus(chatId, userId, platform);
107
+ if (t === '/a' || t.startsWith('/a '))
108
+ return this.handleSwitchAi(chatId, t.slice(2).trim(), platform);
107
109
  if (t === '/autopilot')
108
110
  return this.handleAutopilotStatus(chatId, userId);
109
111
  // 快捷命令 — 直接发送预设 prompt 给 AI
@@ -182,6 +184,7 @@ export class CommandHandler {
182
184
  '/plugins - 查看已安装插件',
183
185
  '/context - 查看上下文窗口占用',
184
186
  '/status - 显示状态',
187
+ '/a [工具名] - 查看或切换 AI 工具(claude/codex/codebuddy/opencode)',
185
188
  '/cd <路径> - 切换工作目录',
186
189
  '/pwd - 当前工作目录',
187
190
  '/autopilot - 查看限流自动恢复状态',
@@ -289,7 +292,9 @@ export class CommandHandler {
289
292
  return true;
290
293
  }
291
294
  async handleStatus(chatId, userId, platform) {
292
- const aiCommand = resolvePlatformAiCommand(this.deps.config, platform);
295
+ // 重新读取配置,使 /a 切换后立即反映新工具(与 AI 请求路径一致)
296
+ const config = loadConfig();
297
+ const aiCommand = resolvePlatformAiCommand(config, platform);
293
298
  const version = await this.getAiVersion(aiCommand);
294
299
  const workDir = this.deps.sessionManager.getWorkDir(userId);
295
300
  const convId = this.deps.sessionManager.getConvId(userId);
@@ -319,6 +324,45 @@ export class CommandHandler {
319
324
  await this.replySender().sendTextReply(chatId, lines.join('\n'));
320
325
  return true;
321
326
  }
327
+ async handleSwitchAi(chatId, arg, platform) {
328
+ // 无参数:显示当前工具 + 可选列表
329
+ if (!arg) {
330
+ const current = resolvePlatformAiCommand(loadConfig(), platform);
331
+ const lines = ['🔄 切换 AI 工具:', '', `当前 (${platform}): ${current}`, '', '可选:'];
332
+ for (const tool of AI_TOOLS) {
333
+ const mark = tool.id === current ? ' (当前)' : '';
334
+ lines.push(` /a ${tool.id} - ${tool.label}${mark}`);
335
+ }
336
+ lines.push('', '用法: /a <工具名>');
337
+ await this.replySender().sendTextReply(chatId, lines.join('\n'));
338
+ return true;
339
+ }
340
+ // 校验工具名
341
+ if (!isAiCommand(arg)) {
342
+ const valid = AI_TOOLS.map((t) => t.id).join(', ');
343
+ await this.replySender().sendTextReply(chatId, `❌ 未知工具: ${arg}\n\n可选: ${valid}`);
344
+ return true;
345
+ }
346
+ // 持久化到 config.json(load→mutate→save 模式,与 saveKeepaliveConfig 一致)
347
+ try {
348
+ const file = loadFileConfig();
349
+ if (!file.platforms)
350
+ file.platforms = {};
351
+ // 所有平台配置都含 aiCommand?: AiCommand,按此最小契约写入即可
352
+ const platforms = file.platforms;
353
+ const platCfg = platforms[platform] ?? {};
354
+ platCfg.aiCommand = arg;
355
+ platforms[platform] = platCfg;
356
+ saveFileConfig(file);
357
+ const label = AI_TOOL_BY_ID[arg]?.label ?? arg;
358
+ await this.replySender().sendTextReply(chatId, `✅ 已将 ${platform} 的 AI 工具切换为 ${label} (${arg})\n下一条消息生效,无需重启。`);
359
+ }
360
+ catch (err) {
361
+ log.error('Failed to switch aiCommand:', err);
362
+ await this.replySender().sendTextReply(chatId, '❌ 切换失败,请查看日志。');
363
+ }
364
+ return true;
365
+ }
322
366
  async handleCd(chatId, userId, dir, _platform) {
323
367
  // 如果 dir 为空,显示目录选择界面
324
368
  if (!dir) {
@@ -11,7 +11,7 @@
11
11
  * 4. Start typing indicator
12
12
  * 5. Run AI task via runAITask with platform-specific callbacks
13
13
  */
14
- import { resolvePlatformAiCommand } from '../config.js';
14
+ import { resolvePlatformAiCommand, loadConfig } from '../config.js';
15
15
  import { getAdapter } from '../adapters/registry.js';
16
16
  import { runAITask } from '../shared/ai-task.js';
17
17
  import { createLogger } from '../logger.js';
@@ -27,10 +27,20 @@ const log = createLogger('PlatformAI');
27
27
  * 5. run AI task with platform callbacks
28
28
  */
29
29
  export function createPlatformAIRequestHandler(deps) {
30
- const { platform, config, sessionManager, sender, throttleMs, runningTasks, minContentDeltaChars, taskKeyBuilder, onThinkingToText, extraInit, taskCallbacks, taskCallbacksFactory, } = deps;
30
+ const { platform, config: initialConfig, sessionManager, sender, throttleMs, runningTasks, minContentDeltaChars, taskKeyBuilder, onThinkingToText, extraInit, taskCallbacks, taskCallbacksFactory, } = deps;
31
31
  async function handleAIRequest(params) {
32
32
  const { userId, chatId, prompt, workDir, convId, replyToMessageId, signal } = params;
33
33
  log.info(`[${platform}] AI request: userId=${userId}, chatId=${chatId}, promptLength=${prompt.length}`);
34
+ // 每条消息重新读取配置,使平台 aiCommand 的修改无需重启即可生效。
35
+ // loadFileConfig 有 mtime 缓存,文件未变时几乎零开销;变更后自动返回新值。
36
+ let config;
37
+ try {
38
+ config = loadConfig();
39
+ }
40
+ catch (err) {
41
+ log.warn(`[${platform}] Failed to reload config, using startup snapshot:`, err);
42
+ config = initialConfig;
43
+ }
34
44
  // 1. Resolve AI command and adapter
35
45
  const aiCommand = resolvePlatformAiCommand(config, platform);
36
46
  const toolAdapter = getAdapter(aiCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.10-beta.1",
3
+ "version": "1.11.10-beta.2",
4
4
  "description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",