imtoagent 0.3.4 → 0.3.6
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/README.md +97 -97
- package/bin/imtoagent-real +197 -153
- package/bin/imtoagent.cjs +13 -5
- package/index.ts +106 -106
- package/modules/agent/claude-adapter.ts +6 -6
- package/modules/agent/claude.ts +6 -6
- package/modules/agent/codex-adapter.ts +13 -13
- package/modules/agent/codex-exec-server.ts +11 -11
- package/modules/agent/codex.ts +29 -29
- package/modules/agent/opencode-adapter.ts +17 -17
- package/modules/agent/opencode.ts +10 -10
- package/modules/capabilities.ts +33 -33
- package/modules/cli/setup.ts +164 -164
- package/modules/core/config.ts +5 -5
- package/modules/core/error.ts +8 -8
- package/modules/core/runtime.ts +10 -10
- package/modules/core/session.ts +4 -4
- package/modules/core/stats.ts +14 -14
- package/modules/core/types.ts +7 -7
- package/modules/im/feishu.ts +56 -56
- package/modules/im/telegram.ts +23 -23
- package/modules/im/wechat.ts +54 -54
- package/modules/im/wecom.ts +50 -50
- package/modules/media/feishu-inbound-adapter.ts +4 -4
- package/modules/media/resolver.ts +11 -11
- package/modules/media/telegram-inbound-adapter.ts +8 -8
- package/modules/prompt-builder.ts +12 -12
- package/modules/proxy/anthropic-proxy.ts +31 -31
- package/modules/proxy/codex-proxy.ts +18 -18
- package/modules/utils/backend-check.ts +12 -12
- package/modules/utils/paths.ts +8 -8
- package/package.json +1 -1
- package/scripts/postinstall.cjs +10 -10
- package/scripts/postinstall.ts +13 -13
- package/templates/soul.template/identity.md +5 -5
- package/templates/soul.template/profile.md +7 -7
- package/templates/soul.template/rules.md +5 -5
- package/templates/soul.template/skills.md +2 -2
- package/templates/soul.template/workspace.md +3 -3
package/modules/agent/claude.ts
CHANGED
|
@@ -66,7 +66,7 @@ export class ClaudeAgentModule {
|
|
|
66
66
|
const session = ctx.sessions.get(chatId);
|
|
67
67
|
if (!session || session.running) return;
|
|
68
68
|
session.running = true;
|
|
69
|
-
console.log(`[${ctx.name}] Claude
|
|
69
|
+
console.log(`[${ctx.name}] Claude loop started chat=${chatId.slice(-8)}`);
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
72
|
const modelSpec = ctx.activeModel;
|
|
@@ -134,22 +134,22 @@ export class ClaudeAgentModule {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
if (result.subtype === 'error' || result.subtype === 'cancelled') {
|
|
137
|
-
await ctx.reply(chatId, `❌ ${result.error || result.result || '
|
|
137
|
+
await ctx.reply(chatId, `❌ ${result.error || result.result || 'Unknown error'}`);
|
|
138
138
|
} else if (fullResponse) {
|
|
139
139
|
await ctx.sendFormattedReply(chatId, fullResponse);
|
|
140
140
|
} else {
|
|
141
|
-
await ctx.reply(chatId, `✅
|
|
141
|
+
await ctx.reply(chatId, `✅ Completed (${toolCalls} steps)`);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
ctx.flushToolLog(chatId);
|
|
145
|
-
const costStr = callCost > 0 ?
|
|
145
|
+
const costStr = callCost > 0 ? `Cost $${callCost.toFixed(4)}\n` : '';
|
|
146
146
|
await ctx.sendProgress(chatId,
|
|
147
|
-
`✅
|
|
147
|
+
`✅ Completed (${toolCalls} steps)\nInput ${callInput.toLocaleString()} Token\nOutput ${callOutput.toLocaleString()} Token\n${costStr}Duration ${(callDur/1000).toFixed(1)}s`);
|
|
148
148
|
fullResponse = ''; toolCalls = 0;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
} catch (e: any) {
|
|
152
|
-
console.error(`[${ctx.name}] Claude
|
|
152
|
+
console.error(`[${ctx.name}] Claude error: ${e.message}`);
|
|
153
153
|
await ctx.reply(chatId, `❌ ${e.message}`);
|
|
154
154
|
} finally {
|
|
155
155
|
session.running = false;
|
|
@@ -64,12 +64,12 @@ async function spawnCodexExec(cwd: string, prompt: string): Promise<{ threadId:
|
|
|
64
64
|
let stdout = '', stderr = '';
|
|
65
65
|
try {
|
|
66
66
|
[stdout, stderr] = await Promise.all([
|
|
67
|
-
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout
|
|
68
|
-
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr
|
|
67
|
+
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
|
|
68
|
+
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
|
|
69
69
|
]);
|
|
70
70
|
} catch (ioErr: any) {
|
|
71
71
|
try { child.kill('SIGKILL'); } catch {}
|
|
72
|
-
throw new Error(`codex exec I/O
|
|
72
|
+
throw new Error(`codex exec I/O error: ${ioErr.message}`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const code = await child.exited.catch(() => -1);
|
|
@@ -87,12 +87,12 @@ async function spawnCodexResume(cwd: string, threadId: string, prompt: string):
|
|
|
87
87
|
let stdout = '', stderr = '';
|
|
88
88
|
try {
|
|
89
89
|
[stdout, stderr] = await Promise.all([
|
|
90
|
-
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout
|
|
91
|
-
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr
|
|
90
|
+
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
|
|
91
|
+
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
|
|
92
92
|
]);
|
|
93
93
|
} catch (ioErr: any) {
|
|
94
94
|
try { child.kill('SIGKILL'); } catch {}
|
|
95
|
-
throw new Error(`codex exec resume I/O
|
|
95
|
+
throw new Error(`codex exec resume I/O error: ${ioErr.message}`);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const code = await child.exited.catch(() => -1);
|
|
@@ -118,7 +118,7 @@ async function runViaAppServer(
|
|
|
118
118
|
sessionAny.codexThreadId = await client.startThread(cwd);
|
|
119
119
|
sessionAny._appServerGen = currentGen;
|
|
120
120
|
session.metadata.codexThreadId = sessionAny.codexThreadId;
|
|
121
|
-
console.log(`[CodexAdapter] app-server
|
|
121
|
+
console.log(`[CodexAdapter] app-server new thread=${sessionAny.codexThreadId.slice(-8)}${threadExpired ? ' (process restarted)' : ''}`);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
await client.sendPrompt(sessionAny.codexThreadId, prompt, cwd);
|
|
@@ -130,7 +130,7 @@ async function runViaAppServer(
|
|
|
130
130
|
|
|
131
131
|
for await (const event of client.receiveEvents()) {
|
|
132
132
|
if (Date.now() - startTime > MAX_DURATION) {
|
|
133
|
-
console.error('[CodexAdapter] app-server
|
|
133
|
+
console.error('[CodexAdapter] app-server task timed out (10min)');
|
|
134
134
|
break;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -145,7 +145,7 @@ async function runViaAppServer(
|
|
|
145
145
|
totalUsage.outputTokens += event.usage?.outputTokens || 0;
|
|
146
146
|
break;
|
|
147
147
|
case 'error':
|
|
148
|
-
throw new Error(`app-server
|
|
148
|
+
throw new Error(`app-server error: ${event.error}`);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -177,7 +177,7 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
if (session.codexMode === 'plan') {
|
|
180
|
-
effectiveText = `[
|
|
180
|
+
effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${effectiveText}`;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const isFresh = session.startFresh || !sessionAny.codexThreadId;
|
|
@@ -194,7 +194,7 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
194
194
|
execServerUsage = r.usage;
|
|
195
195
|
} catch (appErr: any) {
|
|
196
196
|
const errMsg = appErr.message || '';
|
|
197
|
-
console.error(`[CodexAdapter] app-server
|
|
197
|
+
console.error(`[CodexAdapter] app-server failed: ${errMsg}`);
|
|
198
198
|
|
|
199
199
|
if (errMsg.includes('thread not found') || errMsg.includes('Thread not found')) {
|
|
200
200
|
try {
|
|
@@ -202,7 +202,7 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
202
202
|
const r2 = await runViaAppServer(cwd, effectiveText, input.chatId, session, true);
|
|
203
203
|
response = r2.response;
|
|
204
204
|
execServerUsage = r2.usage;
|
|
205
|
-
console.error(`[CodexAdapter] app-server thread
|
|
205
|
+
console.error(`[CodexAdapter] app-server thread rebuilt successfully`);
|
|
206
206
|
} catch {
|
|
207
207
|
useExecFallback = true;
|
|
208
208
|
}
|
|
@@ -225,7 +225,7 @@ export class CodexAdapter implements AgentAdapter {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
return {
|
|
228
|
-
text: response || '✅
|
|
228
|
+
text: response || '✅ Completed',
|
|
229
229
|
usage: execServerUsage || undefined,
|
|
230
230
|
};
|
|
231
231
|
}
|
|
@@ -81,7 +81,7 @@ export class CodexAppServerClient {
|
|
|
81
81
|
if (!this._turnActive) return true;
|
|
82
82
|
this._turnToolCallCount++;
|
|
83
83
|
if (this._turnToolCallCount > _config.maxToolCallsPerTurn) {
|
|
84
|
-
console.error(`[app-server] ⚠️ tool-call loop
|
|
84
|
+
console.error(`[app-server] ⚠️ tool-call loop detected! chat=${this.chatId.slice(-8)}: ${this._turnToolCallCount} times > limit ${_config.maxToolCallsPerTurn}`);
|
|
85
85
|
return false;
|
|
86
86
|
}
|
|
87
87
|
return true;
|
|
@@ -107,7 +107,7 @@ export class CodexAppServerClient {
|
|
|
107
107
|
approvalPolicy: 'never',
|
|
108
108
|
});
|
|
109
109
|
const threadId = result?.thread?.id || '';
|
|
110
|
-
if (!threadId) throw new Error('thread/start
|
|
110
|
+
if (!threadId) throw new Error('thread/start did not return thread.id');
|
|
111
111
|
console.log(`[app-server] thread started=${threadId.slice(-8)} chat=${this.chatId.slice(-8)}`);
|
|
112
112
|
return threadId;
|
|
113
113
|
}
|
|
@@ -202,7 +202,7 @@ export class CodexAppServerClient {
|
|
|
202
202
|
const req = JSON.stringify({ jsonrpc: '2.0', id, method, params });
|
|
203
203
|
const timer = setTimeout(() => {
|
|
204
204
|
this.pendingRequests.delete(id);
|
|
205
|
-
reject(new Error(`app-server
|
|
205
|
+
reject(new Error(`app-server request timeout: ${method}`));
|
|
206
206
|
}, 300000);
|
|
207
207
|
this.pendingRequests.set(id, { resolve, reject });
|
|
208
208
|
|
|
@@ -210,7 +210,7 @@ export class CodexAppServerClient {
|
|
|
210
210
|
if (!ok) {
|
|
211
211
|
clearTimeout(timer);
|
|
212
212
|
this.pendingRequests.delete(id);
|
|
213
|
-
reject(new Error(`app-server stdin
|
|
213
|
+
reject(new Error(`app-server stdin write failed: ${method}`));
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
216
|
}
|
|
@@ -231,7 +231,7 @@ class CodexAppServerManager {
|
|
|
231
231
|
private _generation = 0; // 每次进程重启递增,用于判断 thread 是否过期
|
|
232
232
|
|
|
233
233
|
async ensureRunning(): Promise<void> {
|
|
234
|
-
if (this._shuttingDown) throw new Error('app-server
|
|
234
|
+
if (this._shuttingDown) throw new Error('app-server is shutting down');
|
|
235
235
|
if (this.process && !this.process.killed) return;
|
|
236
236
|
if (this._startPromise) { await this._startPromise; return; }
|
|
237
237
|
this._startPromise = this._spawn();
|
|
@@ -286,7 +286,7 @@ class CodexAppServerManager {
|
|
|
286
286
|
} catch {}
|
|
287
287
|
}
|
|
288
288
|
this.process = null;
|
|
289
|
-
console.log('[app-server]
|
|
289
|
+
console.log('[app-server] shut down');
|
|
290
290
|
}
|
|
291
291
|
/** 健康检查:进程存活但 readLoop 已停止 */
|
|
292
292
|
needsRestart(): boolean {
|
|
@@ -295,7 +295,7 @@ class CodexAppServerManager {
|
|
|
295
295
|
|
|
296
296
|
/** 强制重启 app-server(用于健康检查自动恢复) */
|
|
297
297
|
async forceRestart(): Promise<void> {
|
|
298
|
-
console.warn('[app-server]
|
|
298
|
+
console.warn('[app-server] Health check triggered forced restart...');
|
|
299
299
|
await this.shutdown();
|
|
300
300
|
this._shuttingDown = false;
|
|
301
301
|
this._initialized = false;
|
|
@@ -307,7 +307,7 @@ class CodexAppServerManager {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
private async _spawn(): Promise<void> {
|
|
310
|
-
console.log('[app-server]
|
|
310
|
+
console.log('[app-server] starting codex app-server (stdio)...');
|
|
311
311
|
this.process = Bun.spawn(
|
|
312
312
|
['codex', 'app-server',
|
|
313
313
|
'--listen', 'stdio://',
|
|
@@ -322,7 +322,7 @@ class CodexAppServerManager {
|
|
|
322
322
|
});
|
|
323
323
|
this.process.exited
|
|
324
324
|
.then(async (code: number | null) => {
|
|
325
|
-
console.error(`[app-server]
|
|
325
|
+
console.error(`[app-server] process exited code=${code}`);
|
|
326
326
|
this.process = null;
|
|
327
327
|
this.readLoopRunning = false;
|
|
328
328
|
this._initialized = false;
|
|
@@ -347,7 +347,7 @@ class CodexAppServerManager {
|
|
|
347
347
|
// 给 app-server 短暂时间完成内部初始化
|
|
348
348
|
await new Promise(r => setTimeout(r, 500));
|
|
349
349
|
this._startReadLoop();
|
|
350
|
-
console.log('[app-server]
|
|
350
|
+
console.log('[app-server] ready (stdio)');
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
private _startReadLoop(): void {
|
|
@@ -425,7 +425,7 @@ class CodexAppServerManager {
|
|
|
425
425
|
// 超限,发送 error 事件强制终止本轮
|
|
426
426
|
client.dispatchEvent({
|
|
427
427
|
type: 'error',
|
|
428
|
-
error: `⚠️ Tool-call loop
|
|
428
|
+
error: `⚠️ Tool-call loop detected: this turn has reached ${client.turnToolCallCount} tool calls (limit ${_config.maxToolCallsPerTurn}), forcefully terminated. Consider breaking into smaller tasks.`,
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
431
|
}
|
package/modules/agent/codex.ts
CHANGED
|
@@ -19,14 +19,14 @@ interface CodexJsonEvent {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const TOOL_NAMES: Record<string, string> = {
|
|
22
|
-
Bash: '
|
|
23
|
-
Glob: '
|
|
24
|
-
NotebookEdit: '
|
|
25
|
-
// Codex
|
|
26
|
-
command_execution: '
|
|
27
|
-
request_user_input: '
|
|
28
|
-
spawn_agent: '
|
|
29
|
-
wait_agent: '
|
|
22
|
+
Bash: 'Execute command', Read: 'Read file', Edit: 'Edit file', Write: 'Write file',
|
|
23
|
+
Glob: 'Search files', Grep: 'Search content', WebSearch: 'Web search', WebFetch: 'Fetch webpage',
|
|
24
|
+
NotebookEdit: 'Edit Notebook',
|
|
25
|
+
// Codex tool names
|
|
26
|
+
command_execution: 'Execute command', exec_command: 'Execute command', write_stdin: 'Write to stdin', update_plan: 'Update plan',
|
|
27
|
+
request_user_input: 'Request input', apply_patch: 'Apply patch', view_image: 'View image',
|
|
28
|
+
spawn_agent: 'Spawn agent', send_input: 'Send input', resume_agent: 'Resume agent',
|
|
29
|
+
wait_agent: 'Wait agent', close_agent: 'Close agent',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
// ================================================================
|
|
@@ -64,17 +64,17 @@ async function spawnCodexExec(
|
|
|
64
64
|
cwd, stdout: 'pipe', stderr: 'pipe',
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Safe read stdout/stderr: catch subprocess kill exceptions, ensure reject carries Error object
|
|
68
68
|
let stdout = '', stderr = '';
|
|
69
69
|
try {
|
|
70
70
|
[stdout, stderr] = await Promise.all([
|
|
71
|
-
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout
|
|
72
|
-
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr
|
|
71
|
+
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
|
|
72
|
+
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
|
|
73
73
|
]);
|
|
74
74
|
} catch (ioErr: any) {
|
|
75
|
-
//
|
|
75
|
+
// Subprocess may have been killed, try to get exit code
|
|
76
76
|
try { child.kill('SIGKILL'); } catch {}
|
|
77
|
-
throw new Error(`codex exec I/O
|
|
77
|
+
throw new Error(`codex exec I/O error: ${ioErr.message}`);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const code = await child.exited.catch(() => -1);
|
|
@@ -97,12 +97,12 @@ async function spawnCodexResume(
|
|
|
97
97
|
let stdout = '', stderr = '';
|
|
98
98
|
try {
|
|
99
99
|
[stdout, stderr] = await Promise.all([
|
|
100
|
-
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout
|
|
101
|
-
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr
|
|
100
|
+
new Response(child.stdout).text().catch((e: any) => { throw new Error(`stdout read failed: ${e?.message || e}`); }),
|
|
101
|
+
new Response(child.stderr).text().catch((e: any) => { throw new Error(`stderr read failed: ${e?.message || e}`); }),
|
|
102
102
|
]);
|
|
103
103
|
} catch (ioErr: any) {
|
|
104
104
|
try { child.kill('SIGKILL'); } catch {}
|
|
105
|
-
throw new Error(`codex exec resume I/O
|
|
105
|
+
throw new Error(`codex exec resume I/O error: ${ioErr.message}`);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const code = await child.exited.catch(() => -1);
|
|
@@ -129,7 +129,7 @@ async function runViaAppServer(
|
|
|
129
129
|
if (isFresh || !session.codexThreadId || threadExpired) {
|
|
130
130
|
session.codexThreadId = await client.startThread(cwd);
|
|
131
131
|
session._appServerGen = currentGen;
|
|
132
|
-
console.log(`[Codex] app-server
|
|
132
|
+
console.log(`[Codex] app-server new thread=${session.codexThreadId.slice(-8)}${threadExpired ? ' (process restarted)' : ''}`);
|
|
133
133
|
}
|
|
134
134
|
// 后续消息直接 turn/start(同线程延续上下文)
|
|
135
135
|
|
|
@@ -143,7 +143,7 @@ async function runViaAppServer(
|
|
|
143
143
|
for await (const event of client.receiveEvents()) {
|
|
144
144
|
// 超时保护
|
|
145
145
|
if (Date.now() - startTime > MAX_DURATION) {
|
|
146
|
-
console.error('[Codex] app-server
|
|
146
|
+
console.error('[Codex] app-server task timed out (10min)');
|
|
147
147
|
break;
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -160,7 +160,7 @@ async function runViaAppServer(
|
|
|
160
160
|
totalUsage.outputTokens += event.usage?.outputTokens || 0;
|
|
161
161
|
break;
|
|
162
162
|
case 'error':
|
|
163
|
-
throw new Error(`app-server
|
|
163
|
+
throw new Error(`app-server error: ${event.error}`);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -193,7 +193,7 @@ export class CodexAgentModule {
|
|
|
193
193
|
try {
|
|
194
194
|
let effectiveText = text;
|
|
195
195
|
if (session.codexMode === 'plan') {
|
|
196
|
-
effectiveText = `[
|
|
196
|
+
effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${text}`;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
const isFresh = session.startFresh || !session.codexThreadId;
|
|
@@ -201,10 +201,10 @@ export class CodexAgentModule {
|
|
|
201
201
|
let execServerUsage: { inputTokens: number; outputTokens: number } | null = null;
|
|
202
202
|
|
|
203
203
|
session.startFresh = false;
|
|
204
|
-
await ctx.sendProgress(chatId, '💭
|
|
204
|
+
await ctx.sendProgress(chatId, '💭 Thinking...');
|
|
205
205
|
|
|
206
206
|
// 优先尝试 app-server
|
|
207
|
-
console.error(`[${ctx.name}] DEBUG
|
|
207
|
+
console.error(`[${ctx.name}] DEBUG entering app-server branch, isFresh=${isFresh}, threadId=${session.codexThreadId?.slice(-8)}`);
|
|
208
208
|
let useExecFallback = false;
|
|
209
209
|
try {
|
|
210
210
|
const r = await runViaAppServer(cwd, effectiveText, chatId, session, onTool, isFresh);
|
|
@@ -212,7 +212,7 @@ export class CodexAgentModule {
|
|
|
212
212
|
execServerUsage = r.usage;
|
|
213
213
|
} catch (appErr: any) {
|
|
214
214
|
const errMsg = appErr.message || '';
|
|
215
|
-
console.error(`[${ctx.name}] app-server
|
|
215
|
+
console.error(`[${ctx.name}] app-server failed: ${errMsg}`);
|
|
216
216
|
|
|
217
217
|
// thread not found → app-server 进程内线程丢了,尝试重新创建
|
|
218
218
|
if (errMsg.includes('thread not found') || errMsg.includes('Thread not found')) {
|
|
@@ -221,7 +221,7 @@ export class CodexAgentModule {
|
|
|
221
221
|
const r2 = await runViaAppServer(cwd, effectiveText, chatId, session, onTool, true);
|
|
222
222
|
response = r2.response;
|
|
223
223
|
execServerUsage = r2.usage;
|
|
224
|
-
console.error(`[${ctx.name}] app-server thread
|
|
224
|
+
console.error(`[${ctx.name}] app-server thread rebuilt successfully`);
|
|
225
225
|
} catch {
|
|
226
226
|
useExecFallback = true;
|
|
227
227
|
}
|
|
@@ -236,7 +236,7 @@ export class CodexAgentModule {
|
|
|
236
236
|
const r = await spawnCodexExec(cwd, effectiveText, onTool);
|
|
237
237
|
session.codexThreadId = r.threadId;
|
|
238
238
|
response = r.response;
|
|
239
|
-
console.log(`[${ctx.name}]
|
|
239
|
+
console.log(`[${ctx.name}] Fresh session thread=${r.threadId.slice(-8)}`);
|
|
240
240
|
} else {
|
|
241
241
|
const r = await spawnCodexResume(cwd, session.codexThreadId, effectiveText, onTool);
|
|
242
242
|
response = r.response;
|
|
@@ -250,16 +250,16 @@ export class CodexAgentModule {
|
|
|
250
250
|
const cost = calculateCost(ctx.activeModel, usage.inputTokens, usage.outputTokens);
|
|
251
251
|
ctx.accumulateStats(session, { ...usage, costUSD: cost });
|
|
252
252
|
await ctx.sendProgress(chatId,
|
|
253
|
-
|
|
253
|
+
`Input ${usage.inputTokens.toLocaleString()} Token\nOutput ${usage.outputTokens.toLocaleString()} Token\nCost $${cost.toFixed(4)}`);
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
if (response) {
|
|
257
257
|
await ctx.sendFormattedReply(chatId, response);
|
|
258
258
|
}
|
|
259
|
-
else await ctx.reply(chatId, '✅
|
|
259
|
+
else await ctx.reply(chatId, '✅ Completed');
|
|
260
260
|
ctx.persistSession(chatId, session);
|
|
261
261
|
} catch (e: any) {
|
|
262
|
-
console.error(`[${ctx.name}] Codex
|
|
262
|
+
console.error(`[${ctx.name}] Codex error: ${e.message}`);
|
|
263
263
|
session.codexThreadId = undefined;
|
|
264
264
|
try {
|
|
265
265
|
const r = await spawnCodexExec(cwd, text, onTool);
|
|
@@ -268,7 +268,7 @@ export class CodexAgentModule {
|
|
|
268
268
|
await ctx.sendFormattedReply(chatId, r.response);
|
|
269
269
|
}
|
|
270
270
|
} catch (e2: any) {
|
|
271
|
-
await ctx.reply(chatId, `❌
|
|
271
|
+
await ctx.reply(chatId, `❌ Processing failed: ${e2.message}`);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
}
|
|
@@ -63,8 +63,8 @@ async function ocSendPrompt(
|
|
|
63
63
|
onTool?: (name: string, args: Record<string, any>) => void
|
|
64
64
|
): Promise<{ response: string; toolCalls: Array<{ name: string; summary: string }> }> {
|
|
65
65
|
const MAX_TURNS = 50;
|
|
66
|
-
const TURN_TIMEOUT = 300_000; //
|
|
67
|
-
const MAX_DURATION = 600_000; //
|
|
66
|
+
const TURN_TIMEOUT = 300_000; // 5 min per turn
|
|
67
|
+
const MAX_DURATION = 600_000; // total timeout 10 min
|
|
68
68
|
const startTime = Date.now();
|
|
69
69
|
|
|
70
70
|
let promptText = initialText;
|
|
@@ -74,7 +74,7 @@ async function ocSendPrompt(
|
|
|
74
74
|
|
|
75
75
|
while (turn < MAX_TURNS) {
|
|
76
76
|
if (Date.now() - startTime > MAX_DURATION) {
|
|
77
|
-
console.error('[OpenCodeAdapter]
|
|
77
|
+
console.error('[OpenCodeAdapter] Task timed out (10min)');
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
turn++;
|
|
@@ -118,22 +118,22 @@ async function ocSendPrompt(
|
|
|
118
118
|
|
|
119
119
|
// 有文本回复 → 任务完成(OpenCode 内部已完成多轮 agent loop)
|
|
120
120
|
if (hasText) {
|
|
121
|
-
console.log(`[OpenCodeAdapter] ✅
|
|
121
|
+
console.log(`[OpenCodeAdapter] ✅ completed at turn ${turn}/${MAX_TURNS}`);
|
|
122
122
|
break;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// 无文本且无 tool_call → 空响应,结束
|
|
126
126
|
if (!hasToolCall) {
|
|
127
|
-
console.log(`[OpenCodeAdapter] ⚠️
|
|
127
|
+
console.log(`[OpenCodeAdapter] ⚠️ empty response, ending at turn ${turn}/${MAX_TURNS}`);
|
|
128
128
|
break;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// 仅有 tool_call 无文本 → OpenCode 无法自行执行,推进下一轮
|
|
132
|
-
promptText = '
|
|
132
|
+
promptText = 'Continue executing, complete remaining tasks';
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (turn >= MAX_TURNS) {
|
|
136
|
-
console.warn(`[OpenCodeAdapter] ⚠️
|
|
136
|
+
console.warn(`[OpenCodeAdapter] ⚠️ reached max turns ${MAX_TURNS}`);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
return { response: accumulatedResponse, toolCalls: allToolCalls };
|
|
@@ -170,7 +170,7 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
if (session.codexMode === 'plan') {
|
|
173
|
-
effectiveText = `[
|
|
173
|
+
effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${effectiveText}`;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// 清理标记
|
|
@@ -181,11 +181,11 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
181
181
|
if (shouldClear || !sessionAny.ocSessionId) {
|
|
182
182
|
if (sessionAny.ocSessionId) {
|
|
183
183
|
await ocDeleteSession(serverUrl, sessionAny.ocSessionId);
|
|
184
|
-
console.log(`[OpenCodeAdapter]
|
|
184
|
+
console.log(`[OpenCodeAdapter] Cleared oc session=${sessionAny.ocSessionId.slice(-8)}`);
|
|
185
185
|
}
|
|
186
186
|
sessionAny.ocSessionId = await ocCreateSession(serverUrl, input.chatId);
|
|
187
187
|
session.metadata.ocSessionId = sessionAny.ocSessionId;
|
|
188
|
-
console.log(`[OpenCodeAdapter]
|
|
188
|
+
console.log(`[OpenCodeAdapter] Created oc session=${sessionAny.ocSessionId.slice(-8)}`);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// 构建系统提示词
|
|
@@ -209,7 +209,7 @@ export class OpenCodeAdapter implements AgentAdapter {
|
|
|
209
209
|
);
|
|
210
210
|
|
|
211
211
|
return {
|
|
212
|
-
text: response || '✅
|
|
212
|
+
text: response || '✅ Completed',
|
|
213
213
|
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
214
214
|
};
|
|
215
215
|
}
|
|
@@ -242,12 +242,12 @@ export async function startOpenCodeServer(): Promise<void> {
|
|
|
242
242
|
try {
|
|
243
243
|
const res = await fetch(`${OC_URL}/global/health`, { signal: AbortSignal.timeout(3000) });
|
|
244
244
|
if (res.ok) {
|
|
245
|
-
console.log(`[OpenCodeAdapter]
|
|
245
|
+
console.log(`[OpenCodeAdapter] Detected existing service running at ${OC_URL}, reusing`);
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
} catch {}
|
|
249
249
|
|
|
250
|
-
console.log('[OpenCodeAdapter]
|
|
250
|
+
console.log('[OpenCodeAdapter] starting opencode serve...');
|
|
251
251
|
const child = Bun.spawn(
|
|
252
252
|
['opencode', 'serve', '--port', String(OC_PORT), '--hostname', '127.0.0.1'],
|
|
253
253
|
{
|
|
@@ -279,13 +279,13 @@ export async function startOpenCodeServer(): Promise<void> {
|
|
|
279
279
|
const timeout = 15000;
|
|
280
280
|
while (Date.now() - start < timeout) {
|
|
281
281
|
if (child.exitCode !== undefined && child.exitCode !== null) {
|
|
282
|
-
throw new Error(`OpenCode
|
|
282
|
+
throw new Error(`OpenCode process exited unexpectedly, exitCode=${child.exitCode}`);
|
|
283
283
|
}
|
|
284
284
|
try {
|
|
285
285
|
const res = await fetch(`${OC_URL}/global/health`, { signal: AbortSignal.timeout(2000) });
|
|
286
286
|
if (res.ok) {
|
|
287
287
|
_ocProcess = child;
|
|
288
|
-
console.log(`[OpenCodeAdapter]
|
|
288
|
+
console.log(`[OpenCodeAdapter] Service started successfully (PID=${child.pid}, ${OC_URL})`);
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
} catch {}
|
|
@@ -294,13 +294,13 @@ export async function startOpenCodeServer(): Promise<void> {
|
|
|
294
294
|
|
|
295
295
|
// 超时
|
|
296
296
|
child.kill('SIGTERM');
|
|
297
|
-
throw new Error(`OpenCode
|
|
297
|
+
throw new Error(`OpenCode service startup timed out (${timeout}ms)`);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/** 停止 OpenCode serve 进程 */
|
|
301
301
|
export async function stopOpenCodeServer(): Promise<void> {
|
|
302
302
|
if (_ocProcess) {
|
|
303
|
-
console.log('[OpenCodeAdapter]
|
|
303
|
+
console.log('[OpenCodeAdapter] stopping OpenCode service...');
|
|
304
304
|
_ocProcess.kill('SIGTERM');
|
|
305
305
|
await new Promise(r => setTimeout(r, 1000));
|
|
306
306
|
_ocProcess = null;
|
|
@@ -96,7 +96,7 @@ async function ocSendPrompt(
|
|
|
96
96
|
|
|
97
97
|
while (turn < MAX_TURNS) {
|
|
98
98
|
if (Date.now() - startTime > MAX_DURATION) {
|
|
99
|
-
console.error('[OpenCode]
|
|
99
|
+
console.error('[OpenCode] Task timed out (10min)');
|
|
100
100
|
break;
|
|
101
101
|
}
|
|
102
102
|
turn++;
|
|
@@ -133,7 +133,7 @@ async function ocSendPrompt(
|
|
|
133
133
|
if (!hasToolCall) break;
|
|
134
134
|
|
|
135
135
|
// 有 tool_call,继续推进(空 prompt)
|
|
136
|
-
promptText = '
|
|
136
|
+
promptText = 'Continue';
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
return { response: accumulatedResponse };
|
|
@@ -176,7 +176,7 @@ export class OpenCodeAgentModule {
|
|
|
176
176
|
// ① Plan 模式处理
|
|
177
177
|
let effectiveText = text;
|
|
178
178
|
if (session.codexMode === 'plan') {
|
|
179
|
-
effectiveText = `[
|
|
179
|
+
effectiveText = `[Mode: Plan then execute] Please create a clear plan first, wait for my confirmation before executing. User request: ${text}`;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// ② 清理标记
|
|
@@ -187,14 +187,14 @@ export class OpenCodeAgentModule {
|
|
|
187
187
|
if (shouldClear || !session.ocSessionId) {
|
|
188
188
|
if (session.ocSessionId) {
|
|
189
189
|
await ocDeleteSession(session.ocSessionId);
|
|
190
|
-
console.log(`[${ctx.name}]
|
|
190
|
+
console.log(`[${ctx.name}] Cleared oc session=${session.ocSessionId.slice(-8)}`);
|
|
191
191
|
}
|
|
192
192
|
session.ocSessionId = await ocCreateSession(chatId);
|
|
193
|
-
console.log(`[${ctx.name}]
|
|
193
|
+
console.log(`[${ctx.name}] Created oc session=${session.ocSessionId.slice(-8)}`);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// ④ 发送进度提示
|
|
197
|
-
await ctx.sendProgress(chatId, '💭
|
|
197
|
+
await ctx.sendProgress(chatId, '💭 Thinking...');
|
|
198
198
|
|
|
199
199
|
// ④.⑤ 构建系统提示词
|
|
200
200
|
const systemPrompt = buildSystemPrompt({
|
|
@@ -218,7 +218,7 @@ export class OpenCodeAgentModule {
|
|
|
218
218
|
if (response) {
|
|
219
219
|
await ctx.sendFormattedReply(chatId, response);
|
|
220
220
|
} else {
|
|
221
|
-
await ctx.reply(chatId, '✅
|
|
221
|
+
await ctx.reply(chatId, '✅ Completed');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
// ⑧ 统计
|
|
@@ -228,15 +228,15 @@ export class OpenCodeAgentModule {
|
|
|
228
228
|
const cost = calculateCost(ctx.activeModel, lastUsage.inputTokens, lastUsage.outputTokens);
|
|
229
229
|
ctx.accumulateStats(session, { ...lastUsage, costUSD: cost });
|
|
230
230
|
await ctx.sendProgress(chatId,
|
|
231
|
-
|
|
231
|
+
`Input ${lastUsage.inputTokens.toLocaleString()} Token\nOutput ${lastUsage.outputTokens.toLocaleString()} Token\nCost $${cost.toFixed(4)}`);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
// ⑨ 持久化会话
|
|
235
235
|
ctx.persistSession(chatId, session);
|
|
236
236
|
|
|
237
237
|
} catch (err: any) {
|
|
238
|
-
console.error(`[${ctx.name}] OpenCode
|
|
239
|
-
await ctx.reply(chatId, `⚠️ OpenCode
|
|
238
|
+
console.error(`[${ctx.name}] OpenCode error: ${err.message}`);
|
|
239
|
+
await ctx.reply(chatId, `⚠️ OpenCode error: ${err.message}`);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|