@wu529778790/open-im 1.7.0-beta.1 → 1.7.0-beta.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/adapters/claude-sdk-adapter.d.ts +7 -7
- package/dist/adapters/claude-sdk-adapter.js +127 -65
- package/dist/commands/handler.d.ts +0 -6
- package/dist/commands/handler.js +0 -62
- package/dist/config-web.js +3 -15
- package/dist/config.d.ts +0 -10
- package/dist/config.js +1 -54
- package/dist/dingtalk/event-handler.js +2 -4
- package/dist/dingtalk/message-sender.d.ts +0 -2
- package/dist/dingtalk/message-sender.js +1 -10
- package/dist/feishu/event-handler.js +4 -157
- package/dist/feishu/message-sender.d.ts +0 -20
- package/dist/feishu/message-sender.js +0 -155
- package/dist/index.js +0 -13
- package/dist/manager.js +5 -2
- package/dist/qq/event-handler.js +2 -4
- package/dist/qq/event-handler.test.js +0 -1
- package/dist/qq/message-sender.d.ts +0 -1
- package/dist/qq/message-sender.js +1 -6
- package/dist/setup.js +3 -7
- package/dist/shared/ai-task.js +6 -25
- package/dist/shared/system-messages.d.ts +0 -2
- package/dist/shared/system-messages.js +0 -32
- package/dist/shared/system-messages.test.js +1 -8
- package/dist/telegram/event-handler.js +2 -24
- package/dist/telegram/message-sender.d.ts +0 -1
- package/dist/telegram/message-sender.js +0 -14
- package/dist/wechat/event-handler.js +2 -28
- package/dist/wechat/message-sender.d.ts +0 -2
- package/dist/wechat/message-sender.js +0 -31
- package/dist/wework/event-handler.js +2 -4
- package/dist/wework/message-sender.d.ts +0 -2
- package/dist/wework/message-sender.js +1 -23
- package/package.json +1 -1
- package/dist/hook/permission-server.d.ts +0 -38
- package/dist/hook/permission-server.js +0 -301
- package/dist/hook/permission-server.test.d.ts +0 -1
- package/dist/hook/permission-server.test.js +0 -12
- package/dist/permission-mode/session-mode.d.ts +0 -7
- package/dist/permission-mode/session-mode.js +0 -59
- package/dist/permission-mode/types.d.ts +0 -11
- package/dist/permission-mode/types.js +0 -29
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude SDK Adapter - 使用 Agent SDK
|
|
2
|
+
* Claude SDK Adapter - 使用 Agent SDK V2 Session API 实现真正的多轮对话
|
|
3
3
|
*
|
|
4
|
-
* 优势:
|
|
5
|
-
* 1. 进程内执行 - 无 fork/exec
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. 流式输出 -
|
|
4
|
+
* V2 API 优势:
|
|
5
|
+
* 1. 进程内执行 - 无 fork/exec 开销
|
|
6
|
+
* 2. 持久会话 - SDKSession 对象保持会话状态,支持真正的多轮对话
|
|
7
|
+
* 3. 流式输出 - 支持实时增量更新
|
|
8
8
|
*
|
|
9
|
-
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
9
|
+
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
10
10
|
*/
|
|
11
11
|
import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
|
|
12
12
|
export declare class ClaudeSDKAdapter implements ToolAdapter {
|
|
13
13
|
readonly toolId = "claude-sdk";
|
|
14
14
|
/**
|
|
15
|
-
* 清理所有活跃的 SDK
|
|
15
|
+
* 清理所有活跃的 SDK 会话和流
|
|
16
16
|
*/
|
|
17
17
|
static destroy(): void;
|
|
18
18
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude SDK Adapter - 使用 Agent SDK
|
|
2
|
+
* Claude SDK Adapter - 使用 Agent SDK V2 Session API 实现真正的多轮对话
|
|
3
3
|
*
|
|
4
|
-
* 优势:
|
|
5
|
-
* 1. 进程内执行 - 无 fork/exec
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. 流式输出 -
|
|
4
|
+
* V2 API 优势:
|
|
5
|
+
* 1. 进程内执行 - 无 fork/exec 开销
|
|
6
|
+
* 2. 持久会话 - SDKSession 对象保持会话状态,支持真正的多轮对话
|
|
7
|
+
* 3. 流式输出 - 支持实时增量更新
|
|
8
8
|
*
|
|
9
|
-
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
9
|
+
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { unstable_v2_createSession, unstable_v2_resumeSession } from '@anthropic-ai/claude-agent-sdk';
|
|
12
12
|
import { createLogger } from '../logger.js';
|
|
13
13
|
const log = createLogger('ClaudeSDK');
|
|
14
|
-
//
|
|
15
|
-
|
|
14
|
+
// 存储所有活跃的 SDKSession 对象,key 为 sessionId
|
|
15
|
+
// 使用 Map 而不是 Set,因为我们需要通过 sessionId 获取 session
|
|
16
|
+
const activeSessions = new Map();
|
|
17
|
+
// 存储正在进行的流式迭代器,用于中断
|
|
18
|
+
const activeStreams = new Set();
|
|
16
19
|
function isStreamEvent(msg) {
|
|
17
20
|
return msg.type === 'stream_event';
|
|
18
21
|
}
|
|
@@ -20,33 +23,82 @@ function isSystemInit(msg) {
|
|
|
20
23
|
const m = msg;
|
|
21
24
|
return m.type === 'system' && m.subtype === 'init';
|
|
22
25
|
}
|
|
26
|
+
function isAssistant(msg) {
|
|
27
|
+
return msg.type === 'assistant';
|
|
28
|
+
}
|
|
23
29
|
function isResult(msg) {
|
|
24
30
|
return msg.type === 'result';
|
|
25
31
|
}
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
/**
|
|
33
|
+
* 获取或创建 SDKSession
|
|
34
|
+
* @param sessionId 已有的 sessionId,如果为 undefined 则创建新会话
|
|
35
|
+
* @param workDir 工作目录
|
|
36
|
+
* @param model 模型名称
|
|
37
|
+
* @param permissionMode 权限模式
|
|
38
|
+
* @returns SDKSession 对象和实际的 sessionId
|
|
39
|
+
*/
|
|
40
|
+
async function getOrCreateSession(sessionId, _workDir, // 保留参数以备将来使用
|
|
41
|
+
model, permissionMode) {
|
|
42
|
+
const sessionOptions = {
|
|
43
|
+
model: model || 'claude-opus-4-5',
|
|
44
|
+
permissionMode,
|
|
45
|
+
// 可以添加其他选项,如 hooks, allowedTools 等
|
|
46
|
+
};
|
|
47
|
+
let session;
|
|
48
|
+
if (sessionId) {
|
|
49
|
+
// 尝试恢复已有会话
|
|
50
|
+
try {
|
|
51
|
+
log.info(`Attempting to resume session: ${sessionId}`);
|
|
52
|
+
session = unstable_v2_resumeSession(sessionId, sessionOptions);
|
|
53
|
+
activeSessions.set(sessionId, session);
|
|
54
|
+
log.info(`Successfully resumed session: ${sessionId}`);
|
|
55
|
+
return { session, sessionId };
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
log.warn(`Failed to resume session ${sessionId}, creating new one: ${err}`);
|
|
59
|
+
// 恢复失败,创建新会话
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 创建新会话
|
|
63
|
+
session = unstable_v2_createSession(sessionOptions);
|
|
64
|
+
// 新会话的 sessionId 需要从第一个消息中获取
|
|
65
|
+
// 暂时返回 undefined,稍后在 init 消息中获取
|
|
66
|
+
const tempId = `pending-${Date.now()}`;
|
|
67
|
+
activeSessions.set(tempId, session);
|
|
68
|
+
log.info(`Created new session (tempId: ${tempId})`);
|
|
69
|
+
return { session, sessionId: tempId };
|
|
28
70
|
}
|
|
29
71
|
export class ClaudeSDKAdapter {
|
|
30
72
|
toolId = 'claude-sdk';
|
|
31
73
|
/**
|
|
32
|
-
* 清理所有活跃的 SDK
|
|
74
|
+
* 清理所有活跃的 SDK 会话和流
|
|
33
75
|
*/
|
|
34
76
|
static destroy() {
|
|
35
|
-
for (const
|
|
77
|
+
for (const stream of activeStreams) {
|
|
36
78
|
try {
|
|
37
|
-
if (
|
|
38
|
-
|
|
79
|
+
if (stream && typeof stream.return === 'function') {
|
|
80
|
+
stream.return();
|
|
39
81
|
}
|
|
40
82
|
}
|
|
41
83
|
catch {
|
|
42
84
|
/* ignore */
|
|
43
85
|
}
|
|
44
86
|
}
|
|
45
|
-
|
|
87
|
+
activeStreams.clear();
|
|
88
|
+
for (const session of activeSessions.values()) {
|
|
89
|
+
try {
|
|
90
|
+
session.close();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* ignore */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
activeSessions.clear();
|
|
46
97
|
}
|
|
47
98
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
48
99
|
const abortController = new AbortController();
|
|
49
|
-
let
|
|
100
|
+
let streamClosed = false;
|
|
101
|
+
let actualSessionId;
|
|
50
102
|
const permissionMode = options?.skipPermissions
|
|
51
103
|
? 'bypassPermissions'
|
|
52
104
|
: options?.permissionMode === 'acceptEdits'
|
|
@@ -54,41 +106,47 @@ export class ClaudeSDKAdapter {
|
|
|
54
106
|
: options?.permissionMode === 'plan'
|
|
55
107
|
? 'plan'
|
|
56
108
|
: 'default';
|
|
57
|
-
const
|
|
109
|
+
const runSession = async () => {
|
|
58
110
|
try {
|
|
59
|
-
//
|
|
111
|
+
// 检查环境变量
|
|
60
112
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
61
113
|
const hasAuthToken = !!process.env.ANTHROPIC_AUTH_TOKEN;
|
|
62
|
-
|
|
63
|
-
if (!hasApiKey && !hasAuthToken && !hasBaseUrl) {
|
|
114
|
+
if (!hasApiKey && !hasAuthToken) {
|
|
64
115
|
log.warn('Claude SDK: No API credentials found in environment variables');
|
|
65
116
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
const q = query({
|
|
76
|
-
prompt,
|
|
77
|
-
options: opts,
|
|
78
|
-
});
|
|
79
|
-
// 将查询添加到活跃列表
|
|
80
|
-
activeQueries.add(q);
|
|
117
|
+
log.info(`[V2] Session: ${sessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
|
|
118
|
+
// 获取或创建会话
|
|
119
|
+
const { session } = await getOrCreateSession(sessionId, workDir, options?.model, permissionMode);
|
|
120
|
+
// 发送用户消息
|
|
121
|
+
await session.send(prompt);
|
|
122
|
+
// 获取响应流
|
|
123
|
+
const stream = session.stream();
|
|
124
|
+
activeStreams.add(stream);
|
|
81
125
|
let accumulated = '';
|
|
82
126
|
let accumulatedThinking = '';
|
|
83
127
|
const toolStats = {};
|
|
84
128
|
try {
|
|
85
|
-
for await (const msg of
|
|
86
|
-
if (abortController.signal.aborted)
|
|
129
|
+
for await (const msg of stream) {
|
|
130
|
+
if (abortController.signal.aborted) {
|
|
131
|
+
log.info('Stream aborted by user');
|
|
87
132
|
break;
|
|
133
|
+
}
|
|
134
|
+
// 获取实际的 sessionId(从 init 消息中)
|
|
88
135
|
if (isSystemInit(msg)) {
|
|
89
|
-
|
|
136
|
+
const newSessionId = msg.session_id;
|
|
137
|
+
if (newSessionId && newSessionId !== actualSessionId) {
|
|
138
|
+
// 更新 sessionId 映射
|
|
139
|
+
if (actualSessionId && actualSessionId.startsWith('pending-')) {
|
|
140
|
+
activeSessions.delete(actualSessionId);
|
|
141
|
+
}
|
|
142
|
+
activeSessions.set(newSessionId, session);
|
|
143
|
+
actualSessionId = newSessionId;
|
|
144
|
+
log.info(`[V2] Got actual sessionId: ${newSessionId}`);
|
|
145
|
+
callbacks.onSessionId?.(newSessionId);
|
|
146
|
+
}
|
|
90
147
|
continue;
|
|
91
148
|
}
|
|
149
|
+
// 处理流式事件
|
|
92
150
|
if (isStreamEvent(msg)) {
|
|
93
151
|
const ev = msg.event;
|
|
94
152
|
if (ev?.type === 'content_block_delta' && ev.delta) {
|
|
@@ -103,6 +161,7 @@ export class ClaudeSDKAdapter {
|
|
|
103
161
|
}
|
|
104
162
|
continue;
|
|
105
163
|
}
|
|
164
|
+
// 处理助手消息(工具调用)
|
|
106
165
|
if (isAssistant(msg)) {
|
|
107
166
|
const content = msg.message?.content;
|
|
108
167
|
for (const block of content ?? []) {
|
|
@@ -113,16 +172,22 @@ export class ClaudeSDKAdapter {
|
|
|
113
172
|
}
|
|
114
173
|
continue;
|
|
115
174
|
}
|
|
175
|
+
// 处理结果消息
|
|
116
176
|
if (isResult(msg)) {
|
|
117
|
-
|
|
177
|
+
streamClosed = true;
|
|
118
178
|
const m = msg;
|
|
119
179
|
const success = m.subtype === 'success';
|
|
120
180
|
const errs = m.errors ?? [];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
181
|
+
log.info(`[V2] Result: subtype=${m.subtype}, num_turns=${m.num_turns}, sessionId=${actualSessionId ?? 'unknown'}`);
|
|
182
|
+
// 检查会话错误
|
|
183
|
+
if (!success) {
|
|
184
|
+
const noConvErr = errs.find((e) => e.includes('No conversation found') || e.includes('session not found'));
|
|
185
|
+
if (noConvErr) {
|
|
186
|
+
log.warn(`Session ${actualSessionId} not found, may need to create new one`);
|
|
187
|
+
callbacks.onSessionInvalid?.();
|
|
188
|
+
}
|
|
189
|
+
const errMsg = errs[0] || '未知错误';
|
|
190
|
+
callbacks.onError(errMsg);
|
|
126
191
|
return;
|
|
127
192
|
}
|
|
128
193
|
const resultText = m.result ?? '';
|
|
@@ -135,58 +200,55 @@ export class ClaudeSDKAdapter {
|
|
|
135
200
|
numTurns: m.num_turns ?? 0,
|
|
136
201
|
toolStats,
|
|
137
202
|
};
|
|
138
|
-
if (!result.accumulated && result.result)
|
|
203
|
+
if (!result.accumulated && result.result) {
|
|
139
204
|
result.accumulated = result.result;
|
|
205
|
+
}
|
|
140
206
|
if (!result.accumulated && !result.result && accumulated) {
|
|
141
|
-
log.debug(`Result event had no text but accumulated=${accumulated.length} chars, using accumulated`);
|
|
142
207
|
result.accumulated = accumulated;
|
|
143
208
|
result.result = accumulated;
|
|
144
209
|
}
|
|
145
|
-
if (!result.accumulated && !result.result) {
|
|
146
|
-
const errMsg = errs[0] ?? '未知错误';
|
|
147
|
-
log.warn(`SDK result empty: subtype=${m.subtype}, errors=${JSON.stringify(errs)}`);
|
|
148
|
-
callbacks.onError(errMsg);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
210
|
callbacks.onComplete(result);
|
|
152
211
|
return;
|
|
153
212
|
}
|
|
154
213
|
}
|
|
155
|
-
|
|
214
|
+
// 如果流正常结束但没有收到 result 消息
|
|
215
|
+
if (!streamClosed && accumulated) {
|
|
216
|
+
log.info('Stream ended without result message, using accumulated text');
|
|
156
217
|
callbacks.onComplete({
|
|
157
218
|
success: true,
|
|
158
219
|
result: accumulated,
|
|
159
220
|
accumulated,
|
|
160
221
|
cost: 0,
|
|
161
222
|
durationMs: 0,
|
|
162
|
-
numTurns:
|
|
223
|
+
numTurns: 1,
|
|
163
224
|
toolStats,
|
|
164
225
|
});
|
|
165
226
|
}
|
|
166
227
|
}
|
|
167
228
|
finally {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
activeQueries.delete(q);
|
|
229
|
+
// 从活跃列表中移除流
|
|
230
|
+
activeStreams.delete(stream);
|
|
171
231
|
}
|
|
172
232
|
}
|
|
173
233
|
catch (err) {
|
|
174
|
-
if (abortController.signal.aborted)
|
|
234
|
+
if (abortController.signal.aborted) {
|
|
235
|
+
log.info('Session run aborted');
|
|
175
236
|
return;
|
|
237
|
+
}
|
|
176
238
|
const errorObj = err;
|
|
177
239
|
const msg = errorObj.message || String(err);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (stack) {
|
|
182
|
-
log.error(`Error stack: ${stack}`);
|
|
240
|
+
log.error(`Claude SDK V2 error: ${msg}`);
|
|
241
|
+
if (errorObj.stack) {
|
|
242
|
+
log.error(`Error stack: ${errorObj.stack}`);
|
|
183
243
|
}
|
|
184
244
|
callbacks.onError(msg);
|
|
185
245
|
}
|
|
186
246
|
};
|
|
187
|
-
|
|
247
|
+
// 启动会话(不等待)
|
|
248
|
+
runSession();
|
|
188
249
|
return {
|
|
189
250
|
abort: () => {
|
|
251
|
+
log.info('Aborting session run');
|
|
190
252
|
abortController.abort();
|
|
191
253
|
},
|
|
192
254
|
};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { type Config } from '../config.js';
|
|
2
2
|
import type { SessionManager } from '../session/session-manager.js';
|
|
3
3
|
import type { RequestQueue } from '../queue/request-queue.js';
|
|
4
|
-
import { type PermissionMode } from '../permission-mode/types.js';
|
|
5
4
|
import type { ThreadContext } from '../shared/types.js';
|
|
6
5
|
export type { ThreadContext };
|
|
7
6
|
export interface MessageSender {
|
|
8
7
|
sendTextReply(chatId: string, text: string, threadCtx?: ThreadContext): Promise<void>;
|
|
9
8
|
sendDirectorySelection?(chatId: string, currentDir: string, userId: string): Promise<void>;
|
|
10
|
-
sendModeCard?(chatId: string, userId: string, currentMode: PermissionMode): Promise<void>;
|
|
11
|
-
sendModeKeyboard?(chatId: string, userId: string, currentMode: PermissionMode): Promise<void>;
|
|
12
9
|
}
|
|
13
10
|
export interface CommandHandlerDeps {
|
|
14
11
|
config: Config;
|
|
@@ -22,15 +19,12 @@ export declare class CommandHandler {
|
|
|
22
19
|
private deps;
|
|
23
20
|
constructor(deps: CommandHandlerDeps);
|
|
24
21
|
dispatch(text: string, chatId: string, userId: string, platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
25
|
-
private handleMode;
|
|
26
22
|
private getClearHistoryHint;
|
|
27
23
|
private handleHelp;
|
|
28
24
|
private handleNew;
|
|
29
25
|
private handlePwd;
|
|
30
26
|
private handleStatus;
|
|
31
27
|
private handleCd;
|
|
32
|
-
private handleAllow;
|
|
33
|
-
private handleDeny;
|
|
34
28
|
private getAiVersion;
|
|
35
29
|
}
|
|
36
30
|
/**
|
package/dist/commands/handler.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
2
|
-
import { resolveLatestPermission, getPendingCount } from '../hook/permission-server.js';
|
|
3
2
|
import { escapePathForMarkdown } from '../shared/utils.js';
|
|
4
|
-
import { getPermissionMode, setPermissionMode } from '../permission-mode/session-mode.js';
|
|
5
|
-
import { MODE_LABELS, MODE_DESCRIPTIONS, parsePermissionMode } from '../permission-mode/types.js';
|
|
6
3
|
import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
|
|
7
4
|
import { execFile } from 'node:child_process';
|
|
8
5
|
import { readdirSync } from 'node:fs';
|
|
@@ -20,18 +17,12 @@ export class CommandHandler {
|
|
|
20
17
|
}
|
|
21
18
|
if (t === '/help')
|
|
22
19
|
return this.handleHelp(chatId, platform);
|
|
23
|
-
if (t === '/mode' || t.startsWith('/mode '))
|
|
24
|
-
return this.handleMode(chatId, userId, platform, t.slice(6).trim());
|
|
25
20
|
if (t === '/new')
|
|
26
21
|
return this.handleNew(chatId, userId, platform);
|
|
27
22
|
if (t === '/pwd')
|
|
28
23
|
return this.handlePwd(chatId, userId);
|
|
29
24
|
if (t === '/status')
|
|
30
25
|
return this.handleStatus(chatId, userId, platform);
|
|
31
|
-
if (t === '/allow' || t === '/y')
|
|
32
|
-
return this.handleAllow(chatId);
|
|
33
|
-
if (t === '/deny' || t === '/n')
|
|
34
|
-
return this.handleDeny(chatId);
|
|
35
26
|
if (t === '/cd' || t.startsWith('/cd ')) {
|
|
36
27
|
return this.handleCd(chatId, userId, t.slice(3).trim(), platform);
|
|
37
28
|
}
|
|
@@ -42,35 +33,6 @@ export class CommandHandler {
|
|
|
42
33
|
}
|
|
43
34
|
return false;
|
|
44
35
|
}
|
|
45
|
-
async handleMode(chatId, userId, platform, arg) {
|
|
46
|
-
const defaultMode = this.deps.config.defaultPermissionMode;
|
|
47
|
-
const currentMode = getPermissionMode(userId, defaultMode);
|
|
48
|
-
if (arg) {
|
|
49
|
-
const parsed = parsePermissionMode(arg);
|
|
50
|
-
if (parsed) {
|
|
51
|
-
setPermissionMode(userId, parsed);
|
|
52
|
-
await this.deps.sender.sendTextReply(chatId, `✅ 权限模式已切换为 **${MODE_LABELS[parsed]}**\n${MODE_DESCRIPTIONS[parsed]}`);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
await this.deps.sender.sendTextReply(chatId, `无效模式: ${arg}\n可用: ask, accept-edits, plan, yolo`);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
if (platform === 'feishu' && this.deps.sender.sendModeCard) {
|
|
59
|
-
await this.deps.sender.sendModeCard(chatId, userId, currentMode);
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
if (platform === 'telegram' && this.deps.sender.sendModeKeyboard) {
|
|
63
|
-
await this.deps.sender.sendModeKeyboard(chatId, userId, currentMode);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
const lines = [
|
|
67
|
-
`🔐 **权限模式** (当前: ${MODE_LABELS[currentMode]})`,
|
|
68
|
-
'',
|
|
69
|
-
...['ask', 'accept-edits', 'plan', 'yolo'].map((m) => `• \`/mode ${m}\` - ${MODE_LABELS[m]}: ${MODE_DESCRIPTIONS[m]}`),
|
|
70
|
-
];
|
|
71
|
-
await this.deps.sender.sendTextReply(chatId, lines.join('\n'));
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
36
|
getClearHistoryHint(platform) {
|
|
75
37
|
return platform === 'feishu'
|
|
76
38
|
? '💡 提示:如需清除本对话的历史消息,请点击飞书聊天右上角「...」→ 清除聊天记录'
|
|
@@ -85,13 +47,10 @@ export class CommandHandler {
|
|
|
85
47
|
'📋 可用命令:',
|
|
86
48
|
'',
|
|
87
49
|
'/help - 显示帮助',
|
|
88
|
-
'/mode - 切换权限模式(安全/编辑放行/只读/YOLO)',
|
|
89
50
|
'/new - 开始新会话(AI 上下文重置)',
|
|
90
51
|
'/status - 显示状态',
|
|
91
52
|
'/cd <路径> - 切换工作目录',
|
|
92
53
|
'/pwd - 当前工作目录',
|
|
93
|
-
'/allow (/y) - 允许权限请求',
|
|
94
|
-
'/deny (/n) - 拒绝权限请求',
|
|
95
54
|
'',
|
|
96
55
|
this.getClearHistoryHint(platform),
|
|
97
56
|
].join('\n');
|
|
@@ -150,27 +109,6 @@ export class CommandHandler {
|
|
|
150
109
|
}
|
|
151
110
|
return true;
|
|
152
111
|
}
|
|
153
|
-
async handleAllow(chatId) {
|
|
154
|
-
const reqId = resolveLatestPermission(chatId, 'allow');
|
|
155
|
-
if (reqId) {
|
|
156
|
-
const remaining = getPendingCount(chatId);
|
|
157
|
-
await this.deps.sender.sendTextReply(chatId, `✅ 权限已允许${remaining > 0 ? `(还有 ${remaining} 个待确认)` : ''}`);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
async handleDeny(chatId) {
|
|
165
|
-
const reqId = resolveLatestPermission(chatId, 'deny');
|
|
166
|
-
if (reqId) {
|
|
167
|
-
await this.deps.sender.sendTextReply(chatId, '❌ 权限已拒绝');
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
|
|
171
|
-
}
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
112
|
getAiVersion(aiCommand) {
|
|
175
113
|
if (aiCommand === 'claude') {
|
|
176
114
|
// Claude 使用 SDK,返回 SDK 版本
|
package/dist/config-web.js
CHANGED
|
@@ -134,7 +134,6 @@ function buildInitialPayload(file) {
|
|
|
134
134
|
ai: {
|
|
135
135
|
aiCommand: file.aiCommand ?? "claude",
|
|
136
136
|
claudeWorkDir: file.tools?.claude?.workDir ?? process.cwd(),
|
|
137
|
-
claudeSkipPermissions: file.tools?.claude?.skipPermissions ?? true,
|
|
138
137
|
claudeTimeoutMs: file.tools?.claude?.timeoutMs ?? 600000,
|
|
139
138
|
claudeConfigPath: process.platform === 'win32'
|
|
140
139
|
? getClaudeConfigHome() + "\\.claude\\settings.json"
|
|
@@ -148,8 +147,6 @@ function buildInitialPayload(file) {
|
|
|
148
147
|
codexCliPath: file.tools?.codex?.cliPath ?? "codex",
|
|
149
148
|
codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
|
|
150
149
|
codexProxy: file.tools?.codex?.proxy ?? "",
|
|
151
|
-
defaultPermissionMode: file.defaultPermissionMode ?? "ask",
|
|
152
|
-
hookPort: file.hookPort ?? 35801,
|
|
153
150
|
logDir: file.logDir ?? "",
|
|
154
151
|
logLevel: file.logLevel ?? "default",
|
|
155
152
|
},
|
|
@@ -186,8 +183,6 @@ function validatePayload(payload) {
|
|
|
186
183
|
errors.push("Codex timeout must be positive.");
|
|
187
184
|
if (!Number.isFinite(payload.ai.codebuddyTimeoutMs) || payload.ai.codebuddyTimeoutMs <= 0)
|
|
188
185
|
errors.push("CodeBuddy timeout must be positive.");
|
|
189
|
-
if (!Number.isFinite(payload.ai.hookPort) || payload.ai.hookPort <= 0)
|
|
190
|
-
errors.push("Hook port must be positive.");
|
|
191
186
|
return errors;
|
|
192
187
|
}
|
|
193
188
|
function validateConfigForPlatform(platform, config) {
|
|
@@ -266,12 +261,9 @@ function createProbeConfig(values) {
|
|
|
266
261
|
aiCommand: "claude",
|
|
267
262
|
codexCliPath: "codex",
|
|
268
263
|
claudeWorkDir: process.cwd(),
|
|
269
|
-
claudeSkipPermissions: true,
|
|
270
|
-
defaultPermissionMode: "ask",
|
|
271
264
|
claudeTimeoutMs: 600000,
|
|
272
265
|
codexTimeoutMs: 600000,
|
|
273
266
|
codebuddyTimeoutMs: 600000,
|
|
274
|
-
hookPort: 35801,
|
|
275
267
|
logDir: "",
|
|
276
268
|
logLevel: "INFO",
|
|
277
269
|
codebuddyCliPath: "codebuddy",
|
|
@@ -397,15 +389,12 @@ function toFileConfig(payload, existing) {
|
|
|
397
389
|
return {
|
|
398
390
|
...existing,
|
|
399
391
|
aiCommand: payload.ai.aiCommand,
|
|
400
|
-
defaultPermissionMode: payload.ai.defaultPermissionMode ?? existing.defaultPermissionMode ?? "ask",
|
|
401
|
-
hookPort: payload.ai.hookPort,
|
|
402
392
|
logDir: payload.ai.logDir === undefined ? existing.logDir : clean(payload.ai.logDir),
|
|
403
393
|
logLevel: payload.ai.logLevel === "default" ? undefined : payload.ai.logLevel,
|
|
404
394
|
tools: {
|
|
405
395
|
claude: {
|
|
406
396
|
...existing.tools?.claude,
|
|
407
397
|
workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
|
|
408
|
-
skipPermissions: payload.ai.claudeSkipPermissions,
|
|
409
398
|
timeoutMs: payload.ai.claudeTimeoutMs,
|
|
410
399
|
proxy: clean(payload.ai.claudeProxy),
|
|
411
400
|
// model is now saved to ~/.claude/settings.json as ANTHROPIC_MODEL
|
|
@@ -414,14 +403,12 @@ function toFileConfig(payload, existing) {
|
|
|
414
403
|
...existing.tools?.codex,
|
|
415
404
|
cliPath: clean(payload.ai.codexCliPath) ?? "codex",
|
|
416
405
|
workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
|
|
417
|
-
skipPermissions: existing.tools?.codex?.skipPermissions ?? payload.ai.claudeSkipPermissions,
|
|
418
406
|
timeoutMs: payload.ai.codexTimeoutMs,
|
|
419
407
|
proxy: clean(payload.ai.codexProxy),
|
|
420
408
|
},
|
|
421
409
|
codebuddy: {
|
|
422
410
|
...existing.tools?.codebuddy,
|
|
423
411
|
cliPath: clean(payload.ai.codebuddyCliPath) ?? "codebuddy",
|
|
424
|
-
skipPermissions: existing.tools?.codebuddy?.skipPermissions ?? payload.ai.claudeSkipPermissions,
|
|
425
412
|
timeoutMs: payload.ai.codebuddyTimeoutMs,
|
|
426
413
|
},
|
|
427
414
|
},
|
|
@@ -623,8 +610,9 @@ export async function startWebConfigServer(options) {
|
|
|
623
610
|
}
|
|
624
611
|
if (request.method === "POST" && requestUrl.pathname === "/api/service/start") {
|
|
625
612
|
try {
|
|
626
|
-
loadConfig();
|
|
627
|
-
const
|
|
613
|
+
const config = loadConfig();
|
|
614
|
+
const workDir = config.claudeWorkDir ?? options.cwd;
|
|
615
|
+
const started = startBackgroundService(workDir);
|
|
628
616
|
json(response, 200, { message: `Bridge started with pid ${started.pid}.`, pid: started.pid });
|
|
629
617
|
if (!options.persistent) {
|
|
630
618
|
setTimeout(() => finishFlow("saved"), 120);
|
package/dist/config.d.ts
CHANGED
|
@@ -38,10 +38,7 @@ export interface Config {
|
|
|
38
38
|
codexTimeoutMs: number;
|
|
39
39
|
codebuddyTimeoutMs: number;
|
|
40
40
|
claudeWorkDir: string;
|
|
41
|
-
claudeSkipPermissions: boolean;
|
|
42
|
-
defaultPermissionMode: 'ask' | 'accept-edits' | 'plan' | 'yolo';
|
|
43
41
|
claudeModel?: string;
|
|
44
|
-
hookPort: number;
|
|
45
42
|
logDir: string;
|
|
46
43
|
logLevel: LogLevel;
|
|
47
44
|
platforms: {
|
|
@@ -137,7 +134,6 @@ export interface FilePlatformDingtalk {
|
|
|
137
134
|
}
|
|
138
135
|
export interface FileToolClaude {
|
|
139
136
|
workDir?: string;
|
|
140
|
-
skipPermissions?: boolean;
|
|
141
137
|
timeoutMs?: number;
|
|
142
138
|
model?: string;
|
|
143
139
|
proxy?: string;
|
|
@@ -146,16 +142,12 @@ export interface FileToolCodex {
|
|
|
146
142
|
cliPath?: string;
|
|
147
143
|
workDir?: string;
|
|
148
144
|
timeoutMs?: number;
|
|
149
|
-
/** 是否跳过权限确认(默认 true) */
|
|
150
|
-
skipPermissions?: boolean;
|
|
151
145
|
/** HTTP/HTTPS 代理,用于访问 chatgpt.com(如 http://127.0.0.1:7890) */
|
|
152
146
|
proxy?: string;
|
|
153
147
|
}
|
|
154
148
|
export interface FileToolCodeBuddy {
|
|
155
149
|
cliPath?: string;
|
|
156
150
|
timeoutMs?: number;
|
|
157
|
-
/** 是否跳过权限确认(默认 true) */
|
|
158
|
-
skipPermissions?: boolean;
|
|
159
151
|
}
|
|
160
152
|
export interface FileConfig {
|
|
161
153
|
telegramBotToken?: string;
|
|
@@ -177,8 +169,6 @@ export interface FileConfig {
|
|
|
177
169
|
codex?: FileToolCodex;
|
|
178
170
|
codebuddy?: FileToolCodeBuddy;
|
|
179
171
|
};
|
|
180
|
-
defaultPermissionMode?: 'ask' | 'accept-edits' | 'plan' | 'yolo';
|
|
181
|
-
hookPort?: number;
|
|
182
172
|
logDir?: string;
|
|
183
173
|
logLevel?: LogLevel;
|
|
184
174
|
}
|