@wu529778790/open-im 1.11.8-beta.8 → 1.11.8
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 +23 -5
- package/dist/adapters/claude-sdk-adapter.d.ts +1 -0
- package/dist/adapters/claude-sdk-adapter.js +1 -0
- package/dist/adapters/codebuddy-adapter.d.ts +1 -0
- package/dist/adapters/codebuddy-adapter.js +1 -0
- package/dist/adapters/codex-adapter.d.ts +1 -0
- package/dist/adapters/codex-adapter.js +1 -0
- package/dist/adapters/opencode-adapter.d.ts +1 -0
- package/dist/adapters/opencode-adapter.js +1 -0
- package/dist/adapters/tool-adapter.interface.d.ts +2 -0
- package/dist/clawbot/client.js +45 -79
- package/dist/clawbot/qr-login.d.ts +1 -0
- package/dist/clawbot/qr-login.js +7 -0
- package/dist/codex/cli-runner.js +72 -24
- package/dist/config/types.d.ts +2 -1
- package/dist/config-web-page-i18n.d.ts +30 -0
- package/dist/config-web-page-i18n.js +30 -0
- package/dist/config-web-payload.d.ts +1 -0
- package/dist/config-web-payload.js +2 -0
- package/dist/config-web.js +28 -1
- package/dist/config.js +8 -3
- package/dist/qq/client.js +45 -53
- package/dist/shared/ai-task.js +20 -14
- package/dist/shared/connection-manager.d.ts +111 -0
- package/dist/shared/connection-manager.js +157 -0
- package/dist/telegram/event-handler.js +6 -16
- package/dist/wework/client.js +79 -127
- package/dist/workbuddy/client.js +40 -74
- package/package.json +3 -1
- package/web/dist/assets/index-DZiLymq8.js +57 -0
- package/web/dist/assets/index-u3TxtMZ1.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BpPiH_ku.js +0 -57
- package/web/dist/assets/index-CFRrY7hP.css +0 -1
package/README.md
CHANGED
|
@@ -88,12 +88,30 @@ open-im start
|
|
|
88
88
|
| `/review` | 代码审查 |
|
|
89
89
|
| `/explain` | 解释项目结构 |
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### 权限与确认
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
Claude 使用 Agent SDK 集成,open-im 默认不替 Claude 做额外的允许/拒绝协议。Claude 需要用户确认时,会按它自己的原生交互语义发问;你在 IM 里回复选项、确认或补充说明即可继续同一个会话。
|
|
94
|
+
|
|
95
|
+
如果你希望 Claude 也进入自动执行模式,可以在 Web 控制台的 **AI 工具配置 → Claude Code → 跳过 Claude 权限确认** 打开,或在配置文件里设置:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"tools": {
|
|
100
|
+
"claude": {
|
|
101
|
+
"skipPermissions": true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
也可以用环境变量临时覆盖:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
OPEN_IM_SKIP_PERMISSIONS=true open-im start # 跳过权限确认
|
|
111
|
+
OPEN_IM_SKIP_PERMISSIONS=false open-im start # 使用 Claude 原生确认
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Codex、CodeBuddy、OpenCode 仍保持原来的自动执行默认行为。
|
|
97
115
|
|
|
98
116
|
## 会话接力
|
|
99
117
|
|
|
@@ -21,6 +21,7 @@ interface ClaudeSessionMeta {
|
|
|
21
21
|
export declare function findLatestClaudeSession(workDir: string): Promise<ClaudeSessionMeta | undefined>;
|
|
22
22
|
export declare class ClaudeSDKAdapter implements ToolAdapter {
|
|
23
23
|
readonly toolId = "claude-sdk";
|
|
24
|
+
readonly interactionMode = "native";
|
|
24
25
|
/**
|
|
25
26
|
* 清理所有活跃的查询
|
|
26
27
|
*/
|
|
@@ -2,6 +2,7 @@ import type { RunCallbacks, RunHandle, RunOptions, ToolAdapter } from './tool-ad
|
|
|
2
2
|
export declare class CodeBuddyAdapter implements ToolAdapter {
|
|
3
3
|
private cliPath;
|
|
4
4
|
readonly toolId = "codebuddy";
|
|
5
|
+
readonly interactionMode = "open";
|
|
5
6
|
constructor(cliPath: string);
|
|
6
7
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
7
8
|
}
|
|
@@ -5,6 +5,7 @@ import type { RunCallbacks, RunHandle, RunOptions, ToolAdapter } from "./tool-ad
|
|
|
5
5
|
export declare class CodexAdapter implements ToolAdapter {
|
|
6
6
|
private cliPath;
|
|
7
7
|
readonly toolId = "codex";
|
|
8
|
+
readonly interactionMode = "open";
|
|
8
9
|
constructor(cliPath: string);
|
|
9
10
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
10
11
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RunCallbacks, RunHandle, RunOptions, ToolAdapter } from './tool-adapter.interface.js';
|
|
2
2
|
export declare class OpenCodeAdapter implements ToolAdapter {
|
|
3
3
|
readonly toolId = "opencode";
|
|
4
|
+
readonly interactionMode = "open";
|
|
4
5
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
5
6
|
}
|
|
@@ -2,6 +2,7 @@ import { runOpenCodeSdk } from '../opencode/sdk-runner.js';
|
|
|
2
2
|
import { isSessionInvalidMessage } from '../shared/session-invalid-detector.js';
|
|
3
3
|
export class OpenCodeAdapter {
|
|
4
4
|
toolId = 'opencode';
|
|
5
|
+
interactionMode = 'open';
|
|
5
6
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
6
7
|
const handle = runOpenCodeSdk(prompt, sessionId, workDir, {
|
|
7
8
|
onText: callbacks.onText,
|
|
@@ -40,7 +40,9 @@ export interface RunOptions {
|
|
|
40
40
|
export interface RunHandle {
|
|
41
41
|
abort: () => void;
|
|
42
42
|
}
|
|
43
|
+
export type InteractionMode = 'native' | 'open';
|
|
43
44
|
export interface ToolAdapter {
|
|
44
45
|
readonly toolId: string;
|
|
46
|
+
readonly interactionMode: InteractionMode;
|
|
45
47
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
46
48
|
}
|
package/dist/clawbot/client.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { randomBytes } from 'node:crypto';
|
|
11
11
|
import { createLogger } from '../logger.js';
|
|
12
|
-
import {
|
|
12
|
+
import { isFatalReconnectError } from '../shared/reconnect.js';
|
|
13
|
+
import { ReconnectManager, HeartbeatMonitor, StateManager } from '../shared/connection-manager.js';
|
|
13
14
|
import { cacheContextToken } from './message-sender.js';
|
|
14
15
|
import { setClawbotContextToken, clearClawbotContextToken } from '../shared/active-chats.js';
|
|
15
16
|
import { createMediaTargetPath } from '../shared/media-storage.js';
|
|
@@ -19,27 +20,30 @@ const log = createLogger('ClawBot');
|
|
|
19
20
|
const RECONNECT_DELAYS_MS = [3000, 5000, 10000, 20000, 30000];
|
|
20
21
|
const BASE_INFO = { channel_version: '0.1.0' };
|
|
21
22
|
let pollController = null;
|
|
22
|
-
let channelState = 'disconnected';
|
|
23
23
|
let messageHandler = null;
|
|
24
|
-
let stateChangeHandler = null;
|
|
25
|
-
let reconnectTimer = null;
|
|
26
|
-
let watchdogTimer = null;
|
|
27
|
-
let reconnectAttempt = 0;
|
|
28
|
-
let fatal = false;
|
|
29
24
|
let stopped = false;
|
|
30
25
|
let apiUrl = 'https://ilinkai.weixin.qq.com';
|
|
31
26
|
let apiToken = '';
|
|
32
27
|
/** Opaque cursor for getupdates pagination (replaces numeric offset) */
|
|
33
28
|
let getUpdatesBuf = '';
|
|
34
|
-
/** Timestamp of last successful poll response (for watchdog) */
|
|
35
|
-
let lastResponseAt = 0;
|
|
36
29
|
/** Per-request timeout for long-polling (ms) */
|
|
37
30
|
const POLL_REQUEST_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
38
31
|
/** Watchdog interval: force reconnect if no response for this long (ms) */
|
|
39
32
|
const WATCHDOG_INTERVAL_MS = 60_000; // check every 60s
|
|
40
33
|
const WATCHDOG_STALE_MS = 5 * 60 * 1000; // 5 minutes without response = stale
|
|
34
|
+
// Connection infrastructure (shared managers replace raw timers/counters)
|
|
35
|
+
const stateManager = new StateManager('disconnected');
|
|
36
|
+
const heartbeatMonitor = new HeartbeatMonitor();
|
|
37
|
+
const reconnectManager = new ReconnectManager({
|
|
38
|
+
name: 'ClawBot',
|
|
39
|
+
backoff: { mode: 'stepped', delays: RECONNECT_DELAYS_MS },
|
|
40
|
+
onReconnect: () => {
|
|
41
|
+
stateManager.set('connected');
|
|
42
|
+
startPolling();
|
|
43
|
+
},
|
|
44
|
+
});
|
|
41
45
|
export function getChannelState() {
|
|
42
|
-
return
|
|
46
|
+
return stateManager.current;
|
|
43
47
|
}
|
|
44
48
|
export async function initClawbot(config, eventHandler, onStateChange) {
|
|
45
49
|
const pc = config.platforms?.clawbot;
|
|
@@ -52,14 +56,14 @@ export async function initClawbot(config, eventHandler, onStateChange) {
|
|
|
52
56
|
apiUrl = pc.apiUrl ?? 'https://ilinkai.weixin.qq.com';
|
|
53
57
|
apiToken = pc.apiToken;
|
|
54
58
|
messageHandler = eventHandler;
|
|
55
|
-
|
|
59
|
+
stateManager.setOnChange(onStateChange);
|
|
56
60
|
stopped = false;
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
reconnectManager.reset();
|
|
62
|
+
reconnectManager.resume();
|
|
59
63
|
getUpdatesBuf = '';
|
|
60
64
|
// Start polling directly — no blocking connectivity check.
|
|
61
65
|
// The polling loop handles errors and reconnection internally.
|
|
62
|
-
|
|
66
|
+
stateManager.set('connected');
|
|
63
67
|
startPolling();
|
|
64
68
|
log.info('ClawBot client initialized');
|
|
65
69
|
}
|
|
@@ -82,18 +86,18 @@ function startPolling() {
|
|
|
82
86
|
base_info: BASE_INFO,
|
|
83
87
|
}, combinedSignal);
|
|
84
88
|
// Record successful response time for watchdog
|
|
85
|
-
|
|
89
|
+
heartbeatMonitor.recordResponse();
|
|
86
90
|
if (signal.aborted)
|
|
87
91
|
break;
|
|
88
92
|
if (!res.ok) {
|
|
89
93
|
// Detect fatal errors (e.g. errcode -14 "session timeout") — retrying won't help
|
|
90
94
|
if (res.errcode === -14 || isFatalReconnectError(res.error)) {
|
|
91
95
|
log.warn(`ClawBot fatal error (errcode=${res.errcode}), entering slow-probe mode`);
|
|
92
|
-
|
|
96
|
+
reconnectManager.setFatal(true);
|
|
93
97
|
getUpdatesBuf = ''; // session expired, cursor is stale
|
|
94
98
|
clearClawbotContextToken(); // session expired, token is stale
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
stateManager.set('error');
|
|
100
|
+
reconnectManager.schedule();
|
|
97
101
|
return;
|
|
98
102
|
}
|
|
99
103
|
log.warn(`ClawBot getupdates error: ${res.error ?? 'unknown'}`);
|
|
@@ -101,10 +105,9 @@ function startPolling() {
|
|
|
101
105
|
continue;
|
|
102
106
|
}
|
|
103
107
|
// Successful response — clear fatal mode and reset backoff
|
|
104
|
-
if (fatal ||
|
|
108
|
+
if (reconnectManager.fatal || reconnectManager.attemptCount > 0) {
|
|
105
109
|
log.info('ClawBot connection recovered');
|
|
106
|
-
|
|
107
|
-
reconnectAttempt = 0;
|
|
110
|
+
reconnectManager.reset();
|
|
108
111
|
}
|
|
109
112
|
// Update cursor for next poll
|
|
110
113
|
if (res.updatesBuf) {
|
|
@@ -189,69 +192,35 @@ function startPolling() {
|
|
|
189
192
|
if (err instanceof Error && err.name === 'AbortError')
|
|
190
193
|
break;
|
|
191
194
|
log.error('ClawBot polling error:', err);
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
stateManager.set('error');
|
|
196
|
+
reconnectManager.schedule();
|
|
194
197
|
return;
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
})();
|
|
198
201
|
}
|
|
199
|
-
function scheduleReconnect() {
|
|
200
|
-
if (stopped)
|
|
201
|
-
return;
|
|
202
|
-
if (reconnectTimer) {
|
|
203
|
-
clearTimeout(reconnectTimer);
|
|
204
|
-
reconnectTimer = null;
|
|
205
|
-
}
|
|
206
|
-
const baseDelay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
207
|
-
const delay = fatal ? jitteredDelay(SLOW_PROBE_MS) : jitteredDelay(baseDelay);
|
|
208
|
-
reconnectAttempt++;
|
|
209
|
-
if (fatal) {
|
|
210
|
-
log.warn(`ClawBot fatal error, slow-probe in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempt})...`);
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
log.info(`ClawBot reconnecting in ${delay}ms (attempt ${reconnectAttempt})...`);
|
|
214
|
-
}
|
|
215
|
-
reconnectTimer = setTimeout(() => {
|
|
216
|
-
reconnectTimer = null;
|
|
217
|
-
if (stopped)
|
|
218
|
-
return;
|
|
219
|
-
updateState('connected');
|
|
220
|
-
startPolling();
|
|
221
|
-
}, delay);
|
|
222
|
-
}
|
|
223
|
-
function updateState(state) {
|
|
224
|
-
channelState = state;
|
|
225
|
-
stateChangeHandler?.(state);
|
|
226
|
-
log.debug(`ClawBot state: ${state}`);
|
|
227
|
-
}
|
|
228
202
|
/**
|
|
229
203
|
* Watchdog: periodically check if the poll loop is alive.
|
|
230
204
|
* After Mac sleep or network drop, the fetch may hang without throwing.
|
|
231
205
|
* If no successful response for WATCHDOG_STALE_MS, force a reconnect.
|
|
232
206
|
*/
|
|
233
207
|
function startWatchdog() {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
watchdogTimer = setInterval(() => {
|
|
237
|
-
if (stopped)
|
|
238
|
-
return;
|
|
239
|
-
const elapsed = Date.now() - lastResponseAt;
|
|
240
|
-
if (elapsed > WATCHDOG_STALE_MS) {
|
|
241
|
-
log.warn(`ClawBot watchdog: no response for ${Math.round(elapsed / 1000)}s, forcing reconnect`);
|
|
242
|
-
if (pollController) {
|
|
243
|
-
pollController.abort();
|
|
244
|
-
pollController = null;
|
|
245
|
-
}
|
|
246
|
-
updateState('error');
|
|
247
|
-
scheduleReconnect();
|
|
248
|
-
}
|
|
249
|
-
}, WATCHDOG_INTERVAL_MS);
|
|
208
|
+
heartbeatMonitor.recordResponse();
|
|
209
|
+
heartbeatMonitor.start(WATCHDOG_INTERVAL_MS, watchdogTick);
|
|
250
210
|
}
|
|
251
|
-
function
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
211
|
+
function watchdogTick() {
|
|
212
|
+
if (stopped)
|
|
213
|
+
return;
|
|
214
|
+
if (heartbeatMonitor.isStale(WATCHDOG_STALE_MS)) {
|
|
215
|
+
const elapsed = Date.now() - heartbeatMonitor.lastResponseTime;
|
|
216
|
+
log.warn(`ClawBot watchdog: no response for ${Math.round(elapsed / 1000)}s, forcing reconnect`);
|
|
217
|
+
heartbeatMonitor.stop();
|
|
218
|
+
if (pollController) {
|
|
219
|
+
pollController.abort();
|
|
220
|
+
pollController = null;
|
|
221
|
+
}
|
|
222
|
+
stateManager.set('error');
|
|
223
|
+
reconnectManager.schedule();
|
|
255
224
|
}
|
|
256
225
|
}
|
|
257
226
|
/**
|
|
@@ -362,7 +331,7 @@ function extractTextContent(msg) {
|
|
|
362
331
|
case 3 /* MessageItemType.VOICE */: {
|
|
363
332
|
const transcript = item.voice_item?.text;
|
|
364
333
|
if (transcript)
|
|
365
|
-
return
|
|
334
|
+
return transcript;
|
|
366
335
|
return '[语音消息(无文字转录)]';
|
|
367
336
|
}
|
|
368
337
|
case 2 /* MessageItemType.IMAGE */:
|
|
@@ -441,13 +410,10 @@ export function stopClawbot() {
|
|
|
441
410
|
pollController.abort();
|
|
442
411
|
pollController = null;
|
|
443
412
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
reconnectTimer = null;
|
|
447
|
-
}
|
|
448
|
-
stopWatchdog();
|
|
413
|
+
reconnectManager.stop();
|
|
414
|
+
heartbeatMonitor.stop();
|
|
449
415
|
messageHandler = null;
|
|
450
416
|
// Don't clear context_token here — it's persisted for startup notifications
|
|
451
|
-
|
|
417
|
+
stateManager.set('disconnected');
|
|
452
418
|
log.info('ClawBot client stopped');
|
|
453
419
|
}
|
package/dist/clawbot/qr-login.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Returns bot_token on success.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import QRCode from 'qrcode';
|
|
8
9
|
import { createLogger } from '../logger.js';
|
|
9
10
|
const log = createLogger('ClawBotQR');
|
|
10
11
|
const ILINK_BASE_URL = 'https://ilinkai.weixin.qq.com';
|
|
@@ -64,10 +65,16 @@ export async function startQRLogin() {
|
|
|
64
65
|
log.info('Starting ClawBot QR login...');
|
|
65
66
|
const { qrcode, qrcodeUrl } = await fetchQRCode();
|
|
66
67
|
log.info(`QR code received, url=${qrcodeUrl}`);
|
|
68
|
+
// iLink 返回的 qrcodeUrl 是一个 HTML 中转页(X-Frame-Options: SAMEORIGIN,
|
|
69
|
+
// Content-Type: text/html),前端无法用 <img> 或 iframe 直接展示。
|
|
70
|
+
// 把这个 URL 本身编码成二维码 PNG——用户扫码后微信打开中转页完成绑定。
|
|
71
|
+
const qrcodeImage = await QRCode.toDataURL(qrcodeUrl, { width: 280, margin: 1 });
|
|
72
|
+
log.info('QR code PNG generated (base64)');
|
|
67
73
|
return {
|
|
68
74
|
sessionKey: randomUUID(),
|
|
69
75
|
qrcode,
|
|
70
76
|
qrcodeUrl,
|
|
77
|
+
qrcodeImage,
|
|
71
78
|
startedAt: Date.now(),
|
|
72
79
|
};
|
|
73
80
|
}
|
package/dist/codex/cli-runner.js
CHANGED
|
@@ -217,22 +217,27 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
|
|
|
217
217
|
const event = parseCodexEvent(line);
|
|
218
218
|
if (!event)
|
|
219
219
|
return;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
220
|
+
// Codex CLI JSONL 格式是嵌套的:顶层 type 为 "event_msg" / "response_item",
|
|
221
|
+
// 真正的子类型在 payload.type 里。旧版扁平格式也兼容。
|
|
222
|
+
const topType = event.type;
|
|
223
|
+
const payload = event.payload ?? event;
|
|
224
|
+
const type = payload.type ?? topType;
|
|
225
|
+
log.debug(`[Codex event] topType=${topType} type=${type}`);
|
|
226
|
+
// thread.started / session_meta → 拿 session ID
|
|
227
|
+
if (type === 'thread.started' || type === 'session_meta') {
|
|
228
|
+
const threadId = payload.thread_id ?? payload.session_id ?? event.thread_id ?? '';
|
|
224
229
|
if (threadId)
|
|
225
230
|
callbacks.onSessionId?.(threadId);
|
|
226
231
|
return;
|
|
227
232
|
}
|
|
228
233
|
if (type === 'turn.failed') {
|
|
229
234
|
completed = true;
|
|
230
|
-
const err = event.error;
|
|
235
|
+
const err = payload.error ?? event.error;
|
|
231
236
|
callbacks.onError(err?.message ?? 'Codex turn failed');
|
|
232
237
|
return;
|
|
233
238
|
}
|
|
234
239
|
if (type === 'error') {
|
|
235
|
-
const msg = event.message;
|
|
240
|
+
const msg = payload.message ?? event.message;
|
|
236
241
|
if (msg?.includes('Reconnecting')) {
|
|
237
242
|
return;
|
|
238
243
|
}
|
|
@@ -240,12 +245,12 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
|
|
|
240
245
|
callbacks.onError(msg ?? 'Codex stream error');
|
|
241
246
|
return;
|
|
242
247
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
// item 事件:payload 里可能有 item 字段,或者 payload 本身就是 item
|
|
249
|
+
if (topType === 'response_item' || type === 'item.started' || type === 'item.updated' || type === 'item.completed') {
|
|
250
|
+
const item = payload.item ?? payload;
|
|
251
|
+
const itemType = item.type ?? type;
|
|
252
|
+
if (itemType === 'reasoning') {
|
|
253
|
+
// Codex 的 reasoning 可能是 encrypted_content(不可读),只在有 text 时累加
|
|
249
254
|
const text = item.text;
|
|
250
255
|
if (text) {
|
|
251
256
|
accumulatedThinking += (accumulatedThinking ? '\n\n' : '') + text;
|
|
@@ -253,23 +258,41 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
|
|
|
253
258
|
}
|
|
254
259
|
return;
|
|
255
260
|
}
|
|
261
|
+
// function_call(Codex 新格式:response_item.type=function_call)
|
|
262
|
+
if (itemType === 'function_call') {
|
|
263
|
+
const name = item.name ?? 'unknown';
|
|
264
|
+
const args = item.arguments;
|
|
265
|
+
const toolName = name === 'exec_command' ? 'Bash' : name === 'apply_patch' ? 'Edit' : name;
|
|
266
|
+
toolStats[toolName] = (toolStats[toolName] || 0) + 1;
|
|
267
|
+
callbacks.onToolUse?.(toolName, args ? { arguments: args } : {});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// custom_tool_call(apply_patch 等)
|
|
271
|
+
if (itemType === 'custom_tool_call') {
|
|
272
|
+
const name = item.name ?? 'unknown';
|
|
273
|
+
const toolName = name === 'apply_patch' ? 'Edit' : name;
|
|
274
|
+
toolStats[toolName] = (toolStats[toolName] || 0) + 1;
|
|
275
|
+
callbacks.onToolUse?.(toolName, { input: item.input });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// command_execution(旧格式)
|
|
256
279
|
if (itemType === 'command_execution') {
|
|
257
280
|
const cmd = item.command;
|
|
258
|
-
if (cmd
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
callbacks.onToolUse?.(toolName, { command: cmd });
|
|
281
|
+
if (cmd) {
|
|
282
|
+
toolStats['Bash'] = (toolStats['Bash'] || 0) + 1;
|
|
283
|
+
callbacks.onToolUse?.('Bash', { command: cmd });
|
|
262
284
|
}
|
|
263
285
|
return;
|
|
264
286
|
}
|
|
265
|
-
|
|
287
|
+
// file_change(旧格式)
|
|
288
|
+
if (itemType === 'file_change') {
|
|
266
289
|
const changes = item.changes;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
callbacks.onToolUse?.(toolName, { changes });
|
|
290
|
+
toolStats['Edit'] = (toolStats['Edit'] || 0) + 1;
|
|
291
|
+
callbacks.onToolUse?.('Edit', { changes });
|
|
270
292
|
return;
|
|
271
293
|
}
|
|
272
|
-
|
|
294
|
+
// mcp_tool_call(旧格式)
|
|
295
|
+
if (itemType === 'mcp_tool_call') {
|
|
273
296
|
const tool = item.tool;
|
|
274
297
|
const server = item.server;
|
|
275
298
|
if (tool) {
|
|
@@ -279,16 +302,41 @@ export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options
|
|
|
279
302
|
}
|
|
280
303
|
return;
|
|
281
304
|
}
|
|
282
|
-
|
|
283
|
-
|
|
305
|
+
// agent_message / message — 最终回复文本
|
|
306
|
+
// Codex 新格式:event_msg.payload.type=agent_message, payload.message=文本
|
|
307
|
+
// response_item.payload.type=message, payload.content=[{type:output_text,text:...}]
|
|
308
|
+
if (itemType === 'agent_message' || itemType === 'message') {
|
|
309
|
+
let text;
|
|
310
|
+
if (itemType === 'agent_message') {
|
|
311
|
+
text = item.message ?? item.text;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// message 类型,content 是数组
|
|
315
|
+
const content = item.content;
|
|
316
|
+
if (Array.isArray(content)) {
|
|
317
|
+
text = content
|
|
318
|
+
.filter((c) => c.type === 'output_text' && c.text)
|
|
319
|
+
.map((c) => c.text)
|
|
320
|
+
.join('\n');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
284
323
|
if (text) {
|
|
324
|
+
// 只累加 final_answer 或 commentary,避免重复
|
|
325
|
+
const phase = item.phase ?? payload.phase;
|
|
285
326
|
accumulated += (accumulated ? '\n\n' : '') + text;
|
|
286
327
|
callbacks.onText(accumulated);
|
|
287
328
|
}
|
|
288
329
|
return;
|
|
289
330
|
}
|
|
290
331
|
}
|
|
291
|
-
|
|
332
|
+
// task_complete / turn.completed
|
|
333
|
+
if (type === 'task_complete' || type === 'turn.completed') {
|
|
334
|
+
// task_complete 里可能有 last_agent_message
|
|
335
|
+
const lastMsg = payload.last_agent_message;
|
|
336
|
+
if (lastMsg && !accumulated) {
|
|
337
|
+
accumulated = lastMsg;
|
|
338
|
+
callbacks.onText(accumulated);
|
|
339
|
+
}
|
|
292
340
|
completed = true;
|
|
293
341
|
callbacks.onComplete({
|
|
294
342
|
success: true,
|
package/dist/config/types.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface Config {
|
|
|
38
38
|
*/
|
|
39
39
|
claudeSessionIdleTtlMinutes: number;
|
|
40
40
|
claudeModel?: string;
|
|
41
|
-
/** 是否跳过 AI
|
|
41
|
+
/** 是否跳过 AI 工具的权限确认。未配置时由具体工具决定默认行为。 */
|
|
42
42
|
skipPermissions?: boolean;
|
|
43
43
|
logDir: string;
|
|
44
44
|
logLevel: LogLevel;
|
|
@@ -180,6 +180,7 @@ export interface FilePlatformClawbot {
|
|
|
180
180
|
export interface FileToolClaude {
|
|
181
181
|
cliPath?: string;
|
|
182
182
|
workDir?: string;
|
|
183
|
+
/** Claude SDK 是否跳过原生权限确认。默认 false,保留 Claude 自身确认流程。 */
|
|
183
184
|
skipPermissions?: boolean;
|
|
184
185
|
/** 空闲会话回收间隔(分钟),0 表示关闭 */
|
|
185
186
|
sessionIdleTtlMinutes?: number;
|
|
@@ -66,6 +66,19 @@ export declare const PAGE_TEXTS: {
|
|
|
66
66
|
readonly qrLoginFailed: "Login failed";
|
|
67
67
|
readonly qrLoginExpired: "QR code expired, please try again";
|
|
68
68
|
readonly qrScanHint: "Use WeChat to scan the QR code";
|
|
69
|
+
readonly configure: "Configure";
|
|
70
|
+
readonly rebind: "Rebind";
|
|
71
|
+
readonly platformBound: "Bound";
|
|
72
|
+
readonly platformUnbound: "Not bound";
|
|
73
|
+
readonly qrModalTitle: "Scan to Bind";
|
|
74
|
+
readonly qrModalHint: "Scan the QR code below with WeChat to bind.";
|
|
75
|
+
readonly qrModalGenerating: "Generating QR code...";
|
|
76
|
+
readonly qrModalScanning: "Waiting for scan...";
|
|
77
|
+
readonly qrModalSuccess: "Bound successfully";
|
|
78
|
+
readonly qrModalExpired: "QR code expired";
|
|
79
|
+
readonly qrModalRetry: "Scan again";
|
|
80
|
+
readonly qrModalClose: "Close";
|
|
81
|
+
readonly qrModalError: "Binding failed";
|
|
69
82
|
readonly platformCredentialsTitle: "Credentials";
|
|
70
83
|
readonly platformAccessTitle: "Routing and access";
|
|
71
84
|
readonly platformTestNote: "Checks required credentials against the platform.";
|
|
@@ -117,6 +130,8 @@ export declare const PAGE_TEXTS: {
|
|
|
117
130
|
readonly claudeAuthToken: "ANTHROPIC_AUTH_TOKEN";
|
|
118
131
|
readonly claudeBaseUrl: "ANTHROPIC_BASE_URL";
|
|
119
132
|
readonly claudeModel: "ANTHROPIC_MODEL";
|
|
133
|
+
readonly claudeSkipPermissions: "Skip Claude permission prompts";
|
|
134
|
+
readonly claudeSkipPermissionsTip: "Off by default. Claude SDK will ask for permission in its native flow; open-im just forwards the conversation. Turn this on only if you want bypassPermissions-style automation.";
|
|
120
135
|
readonly claudeProxy: "Proxy (optional)";
|
|
121
136
|
readonly hookPort: "Hook port";
|
|
122
137
|
readonly logLevel: "Log level";
|
|
@@ -282,6 +297,19 @@ export declare const PAGE_TEXTS: {
|
|
|
282
297
|
readonly qrLoginFailed: "登录失败";
|
|
283
298
|
readonly qrLoginExpired: "二维码已过期,请重试";
|
|
284
299
|
readonly qrScanHint: "请使用微信扫描二维码";
|
|
300
|
+
readonly configure: "配置";
|
|
301
|
+
readonly rebind: "重新绑定";
|
|
302
|
+
readonly platformBound: "已绑定";
|
|
303
|
+
readonly platformUnbound: "未绑定";
|
|
304
|
+
readonly qrModalTitle: "扫码绑定";
|
|
305
|
+
readonly qrModalHint: "请用微信扫描下方二维码完成绑定";
|
|
306
|
+
readonly qrModalGenerating: "正在生成二维码...";
|
|
307
|
+
readonly qrModalScanning: "等待扫码...";
|
|
308
|
+
readonly qrModalSuccess: "绑定成功";
|
|
309
|
+
readonly qrModalExpired: "二维码已过期";
|
|
310
|
+
readonly qrModalRetry: "重新扫码";
|
|
311
|
+
readonly qrModalClose: "关闭";
|
|
312
|
+
readonly qrModalError: "绑定失败";
|
|
285
313
|
readonly platformCredentialsTitle: "凭证";
|
|
286
314
|
readonly platformAccessTitle: "路由与访问";
|
|
287
315
|
readonly platformTestNote: "只会检查该平台的必填凭证是否可用。";
|
|
@@ -329,6 +357,8 @@ export declare const PAGE_TEXTS: {
|
|
|
329
357
|
readonly codexApiKey: "OPENAI_API_KEY";
|
|
330
358
|
readonly codexApiKeyTip: "设置 Codex 使用的 OpenAI API Key。也可以在下方编辑 auth 文件。";
|
|
331
359
|
readonly claudeConfigPath: "配置文件位置";
|
|
360
|
+
readonly claudeSkipPermissions: "跳过 Claude 权限确认";
|
|
361
|
+
readonly claudeSkipPermissionsTip: "默认关闭。Claude SDK 会按自己的原生流程请求权限,open-im 只负责转发对话。只有你想要 bypassPermissions 式自动执行时才打开。";
|
|
332
362
|
readonly claudeProxy: "代理(可选)";
|
|
333
363
|
readonly hookPort: "Hook 端口";
|
|
334
364
|
readonly logLevel: "日志级别";
|
|
@@ -66,6 +66,19 @@ export const PAGE_TEXTS = {
|
|
|
66
66
|
qrLoginFailed: "Login failed",
|
|
67
67
|
qrLoginExpired: "QR code expired, please try again",
|
|
68
68
|
qrScanHint: "Use WeChat to scan the QR code",
|
|
69
|
+
configure: "Configure",
|
|
70
|
+
rebind: "Rebind",
|
|
71
|
+
platformBound: "Bound",
|
|
72
|
+
platformUnbound: "Not bound",
|
|
73
|
+
qrModalTitle: "Scan to Bind",
|
|
74
|
+
qrModalHint: "Scan the QR code below with WeChat to bind.",
|
|
75
|
+
qrModalGenerating: "Generating QR code...",
|
|
76
|
+
qrModalScanning: "Waiting for scan...",
|
|
77
|
+
qrModalSuccess: "Bound successfully",
|
|
78
|
+
qrModalExpired: "QR code expired",
|
|
79
|
+
qrModalRetry: "Scan again",
|
|
80
|
+
qrModalClose: "Close",
|
|
81
|
+
qrModalError: "Binding failed",
|
|
69
82
|
platformCredentialsTitle: "Credentials",
|
|
70
83
|
platformAccessTitle: "Routing and access",
|
|
71
84
|
platformTestNote: "Checks required credentials against the platform.",
|
|
@@ -117,6 +130,8 @@ export const PAGE_TEXTS = {
|
|
|
117
130
|
claudeAuthToken: "ANTHROPIC_AUTH_TOKEN",
|
|
118
131
|
claudeBaseUrl: "ANTHROPIC_BASE_URL",
|
|
119
132
|
claudeModel: "ANTHROPIC_MODEL",
|
|
133
|
+
claudeSkipPermissions: "Skip Claude permission prompts",
|
|
134
|
+
claudeSkipPermissionsTip: "Off by default. Claude SDK will ask for permission in its native flow; open-im just forwards the conversation. Turn this on only if you want bypassPermissions-style automation.",
|
|
120
135
|
claudeProxy: "Proxy (optional)",
|
|
121
136
|
hookPort: "Hook port",
|
|
122
137
|
logLevel: "Log level",
|
|
@@ -283,6 +298,19 @@ export const PAGE_TEXTS = {
|
|
|
283
298
|
qrLoginFailed: "\u767b\u5f55\u5931\u8d25",
|
|
284
299
|
qrLoginExpired: "\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f\uff0c\u8bf7\u91cd\u8bd5",
|
|
285
300
|
qrScanHint: "\u8bf7\u4f7f\u7528\u5fae\u4fe1\u626b\u63cf\u4e8c\u7ef4\u7801",
|
|
301
|
+
configure: "\u914d\u7f6e",
|
|
302
|
+
rebind: "\u91cd\u65b0\u7ed1\u5b9a",
|
|
303
|
+
platformBound: "\u5df2\u7ed1\u5b9a",
|
|
304
|
+
platformUnbound: "\u672a\u7ed1\u5b9a",
|
|
305
|
+
qrModalTitle: "\u626b\u7801\u7ed1\u5b9a",
|
|
306
|
+
qrModalHint: "\u8bf7\u7528\u5fae\u4fe1\u626b\u63cf\u4e0b\u65b9\u4e8c\u7ef4\u7801\u5b8c\u6210\u7ed1\u5b9a",
|
|
307
|
+
qrModalGenerating: "\u6b63\u5728\u751f\u6210\u4e8c\u7ef4\u7801...",
|
|
308
|
+
qrModalScanning: "\u7b49\u5f85\u626b\u7801...",
|
|
309
|
+
qrModalSuccess: "\u7ed1\u5b9a\u6210\u529f",
|
|
310
|
+
qrModalExpired: "\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f",
|
|
311
|
+
qrModalRetry: "\u91cd\u65b0\u626b\u7801",
|
|
312
|
+
qrModalClose: "\u5173\u95ed",
|
|
313
|
+
qrModalError: "\u7ed1\u5b9a\u5931\u8d25",
|
|
286
314
|
platformCredentialsTitle: "\u51ed\u8bc1",
|
|
287
315
|
platformAccessTitle: "\u8def\u7531\u4e0e\u8bbf\u95ee",
|
|
288
316
|
platformTestNote: "\u53ea\u4f1a\u68c0\u67e5\u8be5\u5e73\u53f0\u7684\u5fc5\u586b\u51ed\u8bc1\u662f\u5426\u53ef\u7528\u3002",
|
|
@@ -330,6 +358,8 @@ export const PAGE_TEXTS = {
|
|
|
330
358
|
codexApiKey: "OPENAI_API_KEY",
|
|
331
359
|
codexApiKeyTip: "\u8bbe\u7f6e Codex \u4f7f\u7528\u7684 OpenAI API Key\u3002\u4e5f\u53ef\u4ee5\u5728\u4e0b\u65b9\u7f16\u8f91 auth \u6587\u4ef6\u3002",
|
|
332
360
|
claudeConfigPath: "\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e",
|
|
361
|
+
claudeSkipPermissions: "\u8df3\u8fc7 Claude \u6743\u9650\u786e\u8ba4",
|
|
362
|
+
claudeSkipPermissionsTip: "\u9ed8\u8ba4\u5173\u95ed\u3002Claude SDK \u4f1a\u6309\u81ea\u5df1\u7684\u539f\u751f\u6d41\u7a0b\u8bf7\u6c42\u6743\u9650\uff0copen-im \u53ea\u8d1f\u8d23\u8f6c\u53d1\u5bf9\u8bdd\u3002\u53ea\u6709\u4f60\u60f3\u8981 bypassPermissions \u5f0f\u81ea\u52a8\u6267\u884c\u65f6\u624d\u6253\u5f00\u3002",
|
|
333
363
|
claudeProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
|
|
334
364
|
hookPort: "Hook \u7aef\u53e3",
|
|
335
365
|
logLevel: "\u65e5\u5fd7\u7ea7\u522b",
|