imtoagent 0.3.18 → 0.3.20

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/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  // ============================================================
4
4
 
5
5
  import * as fs from 'fs';
6
+ import * as os from 'os';
6
7
  import * as path from 'path';
7
8
 
8
9
  // ===== 重启信号文件路径(统一固定,不依赖 getDataDir) =====
@@ -315,7 +316,7 @@ class Bot {
315
316
  this.backend = cfg.backend;
316
317
  this.appId = cfg.appId;
317
318
  this.appSecret = cfg.appSecret;
318
- this.defaultCwd = cfg.cwd || globalConfig.system?.defaultProjectDir || '/Users/keyi/Projects';
319
+ this.defaultCwd = cfg.cwd || globalConfig.system?.defaultProjectDir || path.join(os.homedir(), 'Projects');
319
320
  this.config = globalConfig;
320
321
 
321
322
  // Bot 级模型配置
@@ -874,7 +875,7 @@ async function main() {
874
875
  process.exit(0);
875
876
  }
876
877
 
877
- const DEFAULT_PROJECT_DIR = config.system?.defaultProjectDir || '/Users/keyi/Projects';
878
+ const DEFAULT_PROJECT_DIR = config.system?.defaultProjectDir || path.join(os.homedir(), 'Projects');
878
879
 
879
880
  if (config.modelAliases) sharedState.modelAliases = config.modelAliases;
880
881
  const { providers: _providers, defaultModel: DEFAULT_MODEL_SPEC } = loadProviders();
@@ -69,7 +69,7 @@ export class ClaudeAdapter implements AgentAdapter {
69
69
  private ctx: ClaudeAdapterContext;
70
70
  private activeControllers: AbortController[] = [];
71
71
  /** 单次调用最大超时(毫秒),0 = 不限制 */
72
- static MAX_CALL_TIMEOUT_MS = 5 * 60 * 1000; // 5 分钟
72
+ static MAX_CALL_TIMEOUT_MS = 15 * 60 * 1000; // 15 分钟
73
73
 
74
74
  constructor(ctx: ClaudeAdapterContext) {
75
75
  this.ctx = ctx;
@@ -64,12 +64,12 @@ async function ocSendPrompt(
64
64
  initialText: string,
65
65
  system: string,
66
66
  defaultModel: { providerID: string; modelID: string },
67
- onTool?: (name: string, args: Record<string, any>) => void,
67
+ onProgress?: (text: string) => void,
68
68
  cancelSignal?: AbortSignal
69
69
  ): Promise<{ response: string; toolCalls: Array<{ name: string; summary: string }> }> {
70
70
  const MAX_TURNS = 50;
71
- const TURN_TIMEOUT = 300_000; // 5 min per turn
72
- const MAX_DURATION = 600_000; // total timeout 10 min
71
+ const TURN_TIMEOUT = 900_000; // 15 min per turn
72
+ const MAX_DURATION = 3_600_000; // total timeout 60 min
73
73
  const startTime = Date.now();
74
74
 
75
75
  let promptText = initialText;
@@ -79,7 +79,8 @@ async function ocSendPrompt(
79
79
 
80
80
  while (turn < MAX_TURNS) {
81
81
  if (Date.now() - startTime > MAX_DURATION) {
82
- console.error('[OpenCodeAdapter] Task timed out (10min)');
82
+ console.error(`[OpenCodeAdapter] Task timed out (${MAX_DURATION / 60000}min)`);
83
+ if (onProgress) onProgress('⚠️ 任务超时,已停止');
83
84
  break;
84
85
  }
85
86
  turn++;
@@ -130,7 +131,7 @@ async function ocSendPrompt(
130
131
  const summary = args.command || args.cmd || args.file_path || args.query
131
132
  || JSON.stringify(args).slice(0, 80);
132
133
  allToolCalls.push({ name, summary });
133
- if (onTool) onTool(name, args);
134
+ if (onProgress) onProgress(`🔧 正在执行: ${name} — ${summary.slice(0, 60)}`);
134
135
  console.log(`[OpenCodeAdapter] 🔧 turn ${turn}: ${name} ${summary.slice(0, 60)}`);
135
136
  }
136
137
  }
@@ -138,6 +139,7 @@ async function ocSendPrompt(
138
139
  // 有文本回复 → 任务完成(OpenCode 内部已完成多轮 agent loop)
139
140
  if (hasText) {
140
141
  console.log(`[OpenCodeAdapter] ✅ completed at turn ${turn}/${MAX_TURNS}`);
142
+ if (onProgress) onProgress(`✅ 第 ${turn} 轮处理完成`);
141
143
  break;
142
144
  }
143
145
 
@@ -147,7 +149,8 @@ async function ocSendPrompt(
147
149
  break;
148
150
  }
149
151
 
150
- // 仅有 tool_call 无文本 → OpenCode 无法自行执行,推进下一轮
152
+ // 仅有 tool_call 无文本 → 推进下一轮
153
+ if (onProgress) onProgress(`⏳ 第 ${turn}/${MAX_TURNS} 轮,继续推进...`);
151
154
  promptText = 'Continue executing, complete remaining tasks';
152
155
  }
153
156
 
@@ -215,16 +218,18 @@ export class OpenCodeAdapter implements AgentAdapter {
215
218
  console.log(`[OpenCodeAdapter] 📝 system prompt built (${systemPrompt.length} chars)`);
216
219
 
217
220
  // 发送 prompt(多轮循环:自动推进 tool_call → 纯文本响应)
221
+ // 注意:ocSendPrompt 不直接依赖 ctx,由 handleMessage 传入 onProgress 回调
222
+ const onProgress = async (text: string) => {
223
+ try { await input.sendProgress?.(text); } catch {}
224
+ };
225
+
218
226
  const { response, toolCalls } = await ocSendPrompt(
219
227
  serverUrl,
220
228
  sessionAny.ocSessionId,
221
229
  effectiveText,
222
230
  systemPrompt,
223
231
  defaultModel,
224
- // onTool 回调:适配器层不依赖外部 ctx,直接记日志
225
- (name, args) => {
226
- // 工具调用日志由 Runtime 层统一格式化
227
- },
232
+ onProgress,
228
233
  input.cancelSignal
229
234
  );
230
235
 
@@ -281,19 +286,34 @@ export async function startOpenCodeServer(): Promise<void> {
281
286
 
282
287
  console.log(`[OpenCodeAdapter] Using opencode binary: ${ocBinPath}`);
283
288
 
284
- const child = Bun.spawn(
285
- [ocBinPath, 'serve', '--port', String(OC_PORT), '--hostname', '127.0.0.1'],
286
- {
287
- cwd: getDataDir(),
288
- env: {
289
- ...process.env,
290
- // 环形通信无需真实 key,但 OpenCode 的 Anthropic provider 要求此变量存在
291
- ANTHROPIC_API_KEY: 'imtoagent-local',
289
+ const dataDir = getDataDir();
290
+ if (!fs.existsSync(dataDir)) {
291
+ console.log(`[OpenCodeAdapter] Creating data directory: ${dataDir}`);
292
+ fs.mkdirSync(dataDir, { recursive: true });
293
+ }
294
+ console.log(`[OpenCodeAdapter] Working directory: ${dataDir}`);
295
+
296
+ let child;
297
+ try {
298
+ child = Bun.spawn(
299
+ [ocBinPath, 'serve', '--port', String(OC_PORT), '--hostname', '127.0.0.1'],
300
+ {
301
+ cwd: dataDir,
302
+ env: {
303
+ ...process.env,
304
+ // 环形通信无需真实 key,但 OpenCode 的 Anthropic provider 要求此变量存在
305
+ ANTHROPIC_API_KEY: 'imtoagent-local',
306
+ },
307
+ stdout: 'pipe',
308
+ stderr: 'pipe',
292
309
  },
293
- stdout: 'pipe',
294
- stderr: 'pipe',
295
- },
296
- );
310
+ );
311
+ } catch (err: any) {
312
+ console.error(`[OpenCodeAdapter] Failed to start opencode serve: ${err.message}`);
313
+ console.error(` Binary: ${ocBinPath}`);
314
+ console.error(` Work dir: ${dataDir} (exists: ${fs.existsSync(dataDir)})`);
315
+ throw new Error(`opencode serve failed: ${err.message}`);
316
+ }
297
317
 
298
318
  // 后台收集日志
299
319
  (async () => {
@@ -157,6 +157,7 @@ export class AgentRuntime {
157
157
  systemPrompt: ctx.systemPrompt,
158
158
  model: ctx.model,
159
159
  cancelSignal: ctx.cancelSignal,
160
+ sendProgress: (t: string) => ctx.sendProgress(t),
160
161
  };
161
162
 
162
163
  const output = await adapter.handleMessage(input);
@@ -115,6 +115,8 @@ export interface AgentInput {
115
115
  model: string;
116
116
  /** 外部取消信号(用于 /stop 等中断命令) */
117
117
  cancelSignal?: AbortSignal;
118
+ /** 进度回调(适配器向用户发送中间状态,如工具调用、思考进度) */
119
+ sendProgress?: (text: string) => Promise<void>;
118
120
  }
119
121
 
120
122
  /** Agent 输出 */
@@ -39,8 +39,8 @@ export function getDataDir(): string {
39
39
  // ====== 第 1 步:查找已有的配置文件 ======
40
40
  const candidates: { dir: string; label: string }[] = [];
41
41
 
42
- // IMTOAGENT_HOME(优先检查,但不强制)
43
- if (envHome && fs.existsSync(path.join(envHome, 'config.json'))) {
42
+ // IMTOAGENT_HOME(优先检查,但不强制;目录不存在则忽略)
43
+ if (envHome && fs.existsSync(envHome) && fs.existsSync(path.join(envHome, 'config.json'))) {
44
44
  candidates.push({ dir: envHome, label: 'IMTOAGENT_HOME' });
45
45
  }
46
46
 
@@ -83,7 +83,7 @@ function initDataDir(dotDir: string, envHome: string): string {
83
83
  let sourceDir: string | null = null;
84
84
  let sourceLabel = '';
85
85
 
86
- if (envHome && fs.existsSync(path.join(envHome, 'config.json'))) {
86
+ if (envHome && fs.existsSync(envHome) && fs.existsSync(path.join(envHome, 'config.json'))) {
87
87
  sourceDir = envHome;
88
88
  sourceLabel = 'IMTOAGENT_HOME';
89
89
  } else if (fs.existsSync(path.join(process.cwd(), 'config.json'))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imtoagent",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "description": "IM ↔ Agent 统一网关 — 飞书/Telegram/微信/企业微信对接 Claude Code/Codex/OpenCode",
5
5
  "type": "module",
6
6
  "bin": {