@wu529778790/open-im 1.8.1-beta.2 → 1.8.1-beta.21
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/access/access-control.js +1 -1
- package/dist/adapters/claude-sdk-adapter.js +94 -36
- package/dist/channels/capabilities.js +5 -0
- package/dist/cli.js +5 -2
- package/dist/commands/handler.d.ts +1 -2
- package/dist/commands/handler.js +6 -18
- package/dist/config-web-page-i18n.d.ts +12 -0
- package/dist/config-web-page-i18n.js +12 -0
- package/dist/config-web-page-script.js +1 -0
- package/dist/config-web-page-template.js +48 -1
- package/dist/config-web.js +110 -7
- package/dist/config.d.ts +25 -1
- package/dist/config.js +46 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/dingtalk/client.js +11 -3
- package/dist/dingtalk/event-handler.js +18 -3
- package/dist/dingtalk/message-sender.js +13 -0
- package/dist/feishu/event-handler.js +144 -10
- package/dist/index.js +26 -2
- package/dist/manager-control.js +7 -0
- package/dist/qq/client.js +111 -88
- package/dist/qq/event-handler.js +16 -2
- package/dist/qq/message-sender.js +11 -0
- package/dist/service-control.js +4 -0
- package/dist/session/session-manager.js +11 -1
- package/dist/setup.js +2 -1
- package/dist/shared/active-chats.d.ts +2 -2
- package/dist/shared/ai-task.js +13 -1
- package/dist/shared/chat-user-map.js +11 -0
- package/dist/shared/media-storage.js +27 -0
- package/dist/telegram/client.js +25 -3
- package/dist/telegram/event-handler.js +44 -8
- package/dist/telegram/message-sender.js +13 -0
- package/dist/wechat/auth/qclaw-api.js +1 -1
- package/dist/wechat/client.js +81 -4
- package/dist/wechat/event-handler.js +10 -3
- package/dist/wework/client.js +36 -14
- package/dist/wework/event-handler.js +39 -4
- package/dist/wework/message-sender.js +53 -21
- package/dist/workbuddy/centrifuge-client.d.ts +74 -0
- package/dist/workbuddy/centrifuge-client.js +272 -0
- package/dist/workbuddy/client.d.ts +27 -0
- package/dist/workbuddy/client.js +162 -0
- package/dist/workbuddy/event-handler.d.ts +11 -0
- package/dist/workbuddy/event-handler.js +118 -0
- package/dist/workbuddy/index.d.ts +8 -0
- package/dist/workbuddy/index.js +8 -0
- package/dist/workbuddy/message-sender.d.ts +16 -0
- package/dist/workbuddy/message-sender.js +51 -0
- package/dist/workbuddy/oauth.d.ts +114 -0
- package/dist/workbuddy/oauth.js +310 -0
- package/dist/workbuddy/types.d.ts +86 -0
- package/dist/workbuddy/types.js +4 -0
- package/package.json +4 -2
|
@@ -8,7 +8,7 @@ export class AccessControl {
|
|
|
8
8
|
}
|
|
9
9
|
isAllowed(userId) {
|
|
10
10
|
if (this.allowedUserIds.size === 0) {
|
|
11
|
-
log.
|
|
11
|
+
log.warn(`Allowing user ${userId} — no whitelist configured. Set allowedUserIds to restrict access.`);
|
|
12
12
|
return true;
|
|
13
13
|
}
|
|
14
14
|
const allowed = this.allowedUserIds.has(userId);
|
|
@@ -16,6 +16,21 @@ const log = createLogger('ClaudeSDK');
|
|
|
16
16
|
const activeSessions = new Map();
|
|
17
17
|
// 存储正在进行的流式迭代器,用于中断
|
|
18
18
|
const activeStreams = new Set();
|
|
19
|
+
// Mutex to serialize process.chdir() calls across concurrent users
|
|
20
|
+
let chdirMutex = Promise.resolve();
|
|
21
|
+
function withChdirMutex(fn) {
|
|
22
|
+
const previous = chdirMutex;
|
|
23
|
+
let resolve;
|
|
24
|
+
chdirMutex = new Promise((r) => { resolve = r; });
|
|
25
|
+
return previous.then(() => {
|
|
26
|
+
try {
|
|
27
|
+
return fn();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
resolve();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
19
34
|
function isStreamEvent(msg) {
|
|
20
35
|
return msg.type === 'stream_event';
|
|
21
36
|
}
|
|
@@ -37,39 +52,51 @@ function isResult(msg) {
|
|
|
37
52
|
* @param permissionMode 权限模式
|
|
38
53
|
* @returns SDKSession 对象和实际的 sessionId
|
|
39
54
|
*/
|
|
40
|
-
async function getOrCreateSession(sessionId,
|
|
41
|
-
model, permissionMode) {
|
|
55
|
+
async function getOrCreateSession(sessionId, workDir, model, permissionMode) {
|
|
42
56
|
const resolvedModel = model?.trim() || 'claude-opus-4-5';
|
|
43
57
|
const sessionOptions = {
|
|
44
58
|
model: resolvedModel,
|
|
45
59
|
permissionMode,
|
|
46
|
-
// 可以添加其他选项,如 hooks, allowedTools 等
|
|
47
60
|
};
|
|
48
61
|
const baseUrl = process.env.ANTHROPIC_BASE_URL ?? '(default)';
|
|
49
|
-
log.info(`[
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
log.info(`[V2] getOrCreateSession model param=${String(model ?? '')} resolved=${resolvedModel} baseUrl=${baseUrl} workDir=${workDir}`);
|
|
63
|
+
// Use mutex to serialize process.chdir() calls across concurrent users
|
|
64
|
+
return withChdirMutex(() => {
|
|
65
|
+
let session;
|
|
66
|
+
const originalCwd = process.cwd();
|
|
53
67
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if (workDir && workDir !== originalCwd) {
|
|
69
|
+
process.chdir(workDir);
|
|
70
|
+
}
|
|
71
|
+
if (sessionId) {
|
|
72
|
+
// 尝试恢复已有会话
|
|
73
|
+
try {
|
|
74
|
+
log.info(`Attempting to resume session: ${sessionId}`);
|
|
75
|
+
session = unstable_v2_resumeSession(sessionId, sessionOptions);
|
|
76
|
+
activeSessions.set(sessionId, session);
|
|
77
|
+
log.info(`Successfully resumed session: ${sessionId}`);
|
|
78
|
+
return { session, sessionId };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
log.warn(`Failed to resume session ${sessionId}, creating new one: ${err}`);
|
|
82
|
+
// 恢复失败,创建新会话
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 创建新会话
|
|
86
|
+
session = unstable_v2_createSession(sessionOptions);
|
|
87
|
+
// 新会话的 sessionId 需要从第一个消息中获取
|
|
88
|
+
// 暂时返回 undefined,稍后在 init 消息中获取
|
|
89
|
+
const tempId = `pending-${Date.now()}`;
|
|
90
|
+
activeSessions.set(tempId, session);
|
|
91
|
+
log.info(`Created new session (tempId: ${tempId})`);
|
|
92
|
+
return { session, sessionId: tempId };
|
|
59
93
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
finally {
|
|
95
|
+
if (workDir && workDir !== originalCwd) {
|
|
96
|
+
process.chdir(originalCwd);
|
|
97
|
+
}
|
|
63
98
|
}
|
|
64
|
-
}
|
|
65
|
-
// 创建新会话
|
|
66
|
-
session = unstable_v2_createSession(sessionOptions);
|
|
67
|
-
// 新会话的 sessionId 需要从第一个消息中获取
|
|
68
|
-
// 暂时返回 undefined,稍后在 init 消息中获取
|
|
69
|
-
const tempId = `pending-${Date.now()}`;
|
|
70
|
-
activeSessions.set(tempId, session);
|
|
71
|
-
log.info(`Created new session (tempId: ${tempId})`);
|
|
72
|
-
return { session, sessionId: tempId };
|
|
99
|
+
});
|
|
73
100
|
}
|
|
74
101
|
export class ClaudeSDKAdapter {
|
|
75
102
|
toolId = 'claude-sdk';
|
|
@@ -99,9 +126,12 @@ export class ClaudeSDKAdapter {
|
|
|
99
126
|
activeSessions.clear();
|
|
100
127
|
}
|
|
101
128
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
129
|
+
log.info(`[V2] run() entry model=${String(options?.model ?? '')} baseUrl=${process.env.ANTHROPIC_BASE_URL ?? '(default)'}`);
|
|
102
130
|
const abortController = new AbortController();
|
|
103
131
|
let streamClosed = false;
|
|
104
132
|
let actualSessionId;
|
|
133
|
+
let pendingTempId; // 记录临时 ID,用于 abort 时清理
|
|
134
|
+
let runSettled = false;
|
|
105
135
|
const permissionMode = options?.skipPermissions
|
|
106
136
|
? 'bypassPermissions'
|
|
107
137
|
: options?.permissionMode === 'acceptEdits'
|
|
@@ -118,8 +148,12 @@ export class ClaudeSDKAdapter {
|
|
|
118
148
|
log.warn('Claude SDK: No API credentials found in environment variables');
|
|
119
149
|
}
|
|
120
150
|
log.info(`[V2] Session: ${sessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
|
|
151
|
+
log.info(`[V2] model param=${String(options?.model ?? '')} baseUrl=${process.env.ANTHROPIC_BASE_URL ?? '(default)'}`);
|
|
121
152
|
// 获取或创建会话
|
|
122
|
-
const { session } = await getOrCreateSession(sessionId, workDir, options?.model, permissionMode);
|
|
153
|
+
const { session, sessionId: returnedId } = await getOrCreateSession(sessionId, workDir, options?.model, permissionMode);
|
|
154
|
+
if (returnedId.startsWith('pending-')) {
|
|
155
|
+
pendingTempId = returnedId;
|
|
156
|
+
}
|
|
123
157
|
// 发送用户消息
|
|
124
158
|
await session.send(prompt);
|
|
125
159
|
// 获取响应流
|
|
@@ -184,6 +218,7 @@ export class ClaudeSDKAdapter {
|
|
|
184
218
|
log.info(`[V2] Result: subtype=${m.subtype}, num_turns=${m.num_turns}, sessionId=${actualSessionId ?? 'unknown'}`);
|
|
185
219
|
// 检查会话错误
|
|
186
220
|
if (!success) {
|
|
221
|
+
runSettled = true;
|
|
187
222
|
const noConvErr = errs.find((e) => e.includes('No conversation found') || e.includes('session not found'));
|
|
188
223
|
if (noConvErr) {
|
|
189
224
|
log.warn(`Session ${actualSessionId} not found, may need to create new one`);
|
|
@@ -210,22 +245,32 @@ export class ClaudeSDKAdapter {
|
|
|
210
245
|
result.accumulated = accumulated;
|
|
211
246
|
result.result = accumulated;
|
|
212
247
|
}
|
|
248
|
+
runSettled = true;
|
|
213
249
|
callbacks.onComplete(result);
|
|
214
250
|
return;
|
|
215
251
|
}
|
|
216
252
|
}
|
|
217
253
|
// 如果流正常结束但没有收到 result 消息
|
|
218
|
-
if (!streamClosed
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
254
|
+
if (!streamClosed) {
|
|
255
|
+
if (accumulated) {
|
|
256
|
+
log.info('Stream ended without result message, using accumulated text');
|
|
257
|
+
runSettled = true;
|
|
258
|
+
callbacks.onComplete({
|
|
259
|
+
success: true,
|
|
260
|
+
result: accumulated,
|
|
261
|
+
accumulated,
|
|
262
|
+
cost: 0,
|
|
263
|
+
durationMs: 0,
|
|
264
|
+
numTurns: 1,
|
|
265
|
+
toolStats,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// 流结束但无 result 也无 accumulated:必须触发回调,否则 Promise 永远挂起
|
|
270
|
+
log.warn('Stream ended with no result and no accumulated text, calling onError to prevent stuck state');
|
|
271
|
+
runSettled = true;
|
|
272
|
+
callbacks.onError('AI 响应异常结束(无输出),请重试');
|
|
273
|
+
}
|
|
229
274
|
}
|
|
230
275
|
}
|
|
231
276
|
finally {
|
|
@@ -236,14 +281,27 @@ export class ClaudeSDKAdapter {
|
|
|
236
281
|
catch (err) {
|
|
237
282
|
if (abortController.signal.aborted) {
|
|
238
283
|
log.info('Session run aborted');
|
|
284
|
+
// 清理 pending tempId(abort 可能在 init 消息之前发生)
|
|
285
|
+
const idToClean = actualSessionId ?? pendingTempId;
|
|
286
|
+
if (idToClean?.startsWith('pending-')) {
|
|
287
|
+
activeSessions.delete(idToClean);
|
|
288
|
+
log.info(`Cleaned up pending session: ${idToClean}`);
|
|
289
|
+
}
|
|
239
290
|
return;
|
|
240
291
|
}
|
|
292
|
+
runSettled = true;
|
|
241
293
|
const errorObj = err;
|
|
242
294
|
const msg = errorObj.message || String(err);
|
|
243
295
|
log.error(`Claude SDK V2 error: ${msg}`);
|
|
244
296
|
if (errorObj.stack) {
|
|
245
297
|
log.error(`Error stack: ${errorObj.stack}`);
|
|
246
298
|
}
|
|
299
|
+
// 清理 pending tempId(session 在获取真实 ID 前就失败了)
|
|
300
|
+
const errIdToClean = actualSessionId ?? pendingTempId;
|
|
301
|
+
if (errIdToClean?.startsWith('pending-')) {
|
|
302
|
+
activeSessions.delete(errIdToClean);
|
|
303
|
+
log.info(`Cleaned up pending session after error: ${errIdToClean}`);
|
|
304
|
+
}
|
|
247
305
|
callbacks.onError(msg);
|
|
248
306
|
}
|
|
249
307
|
};
|
|
@@ -5,6 +5,7 @@ const PLATFORM_LABELS = {
|
|
|
5
5
|
wechat: "微信",
|
|
6
6
|
wework: "企业微信",
|
|
7
7
|
dingtalk: "钉钉",
|
|
8
|
+
workbuddy: "WorkBuddy",
|
|
8
9
|
};
|
|
9
10
|
export const CHANNEL_CAPABILITIES = {
|
|
10
11
|
telegram: {
|
|
@@ -31,6 +32,10 @@ export const CHANNEL_CAPABILITIES = {
|
|
|
31
32
|
inbound: { text: "native", image: "fallback", file: "fallback", voice: "fallback", video: "fallback" },
|
|
32
33
|
outbound: { streamEdit: "native", streamPush: "fallback", image: "fallback", card: "native", typing: "native" },
|
|
33
34
|
},
|
|
35
|
+
workbuddy: {
|
|
36
|
+
inbound: { text: "native", image: "none", file: "none", voice: "none", video: "none" },
|
|
37
|
+
outbound: { streamEdit: "none", streamPush: "none", image: "none", card: "none", typing: "none" },
|
|
38
|
+
},
|
|
34
39
|
};
|
|
35
40
|
function listPreferredPlatforms(kind) {
|
|
36
41
|
return Object.entries(CHANNEL_CAPABILITIES)
|
package/dist/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ async function cmdStart() {
|
|
|
50
50
|
console.log("\nopen-im is already running in the background.");
|
|
51
51
|
console.log(` pid: ${status.pid}`);
|
|
52
52
|
console.log(` config page: ${getWebConfigUrl()}`);
|
|
53
|
-
|
|
53
|
+
process.exit(0);
|
|
54
54
|
}
|
|
55
55
|
if (!(await ensureConfigured("start"))) {
|
|
56
56
|
process.exit(1);
|
|
@@ -67,17 +67,19 @@ async function cmdStart() {
|
|
|
67
67
|
console.log(" A one-time login URL (with login_token) has been printed by the config-web server logger.");
|
|
68
68
|
console.log(" Please use that URL (replacing 127.0.0.1 with your server IP/hostname) for the first login.");
|
|
69
69
|
}
|
|
70
|
+
process.exit(0);
|
|
70
71
|
}
|
|
71
72
|
async function cmdStop() {
|
|
72
73
|
const status = getManagerStatus();
|
|
73
74
|
if (!status.pid) {
|
|
74
75
|
console.log("open-im is not running in the background.");
|
|
75
|
-
|
|
76
|
+
process.exit(0);
|
|
76
77
|
}
|
|
77
78
|
await stopBackgroundService();
|
|
78
79
|
const result = await stopManagerProcess();
|
|
79
80
|
console.log("\nopen-im stopped.");
|
|
80
81
|
console.log(` pid: ${result.pid}`);
|
|
82
|
+
process.exit(0);
|
|
81
83
|
}
|
|
82
84
|
async function cmdRestart() {
|
|
83
85
|
const status = getManagerStatus();
|
|
@@ -98,6 +100,7 @@ async function cmdRestart() {
|
|
|
98
100
|
console.log("\nopen-im restarted in the background.");
|
|
99
101
|
console.log(` pid: ${child.pid}`);
|
|
100
102
|
console.log(` config page: ${getWebConfigUrl()}`);
|
|
103
|
+
process.exit(0);
|
|
101
104
|
}
|
|
102
105
|
async function cmdInit() {
|
|
103
106
|
console.log("\nopen-im CLI setup\n");
|
|
@@ -18,8 +18,7 @@ export type ClaudeRequestHandler = (userId: string, chatId: string, prompt: stri
|
|
|
18
18
|
export declare class CommandHandler {
|
|
19
19
|
private deps;
|
|
20
20
|
constructor(deps: CommandHandlerDeps);
|
|
21
|
-
dispatch(text: string, chatId: string, userId: string, platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
22
|
-
private getClearHistoryHint;
|
|
21
|
+
dispatch(text: string, chatId: string, userId: string, platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
23
22
|
private handleHelp;
|
|
24
23
|
private handleNew;
|
|
25
24
|
private handlePwd;
|
package/dist/commands/handler.js
CHANGED
|
@@ -16,9 +16,9 @@ export class CommandHandler {
|
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
18
|
if (t === '/help')
|
|
19
|
-
return this.handleHelp(chatId
|
|
19
|
+
return this.handleHelp(chatId);
|
|
20
20
|
if (t === '/new')
|
|
21
|
-
return this.handleNew(chatId, userId
|
|
21
|
+
return this.handleNew(chatId, userId);
|
|
22
22
|
if (t === '/pwd')
|
|
23
23
|
return this.handlePwd(chatId, userId);
|
|
24
24
|
if (t === '/status')
|
|
@@ -33,16 +33,7 @@ export class CommandHandler {
|
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
return platform === 'feishu'
|
|
38
|
-
? '💡 提示:如需清除本对话的历史消息,请点击飞书聊天右上角「...」→ 清除聊天记录'
|
|
39
|
-
: platform === 'wechat'
|
|
40
|
-
? '💡 提示:如需清除本对话的历史消息,请清除聊天记录'
|
|
41
|
-
: platform === 'dingtalk'
|
|
42
|
-
? '💡 提示:如需清除本对话的历史消息,请在钉钉中清空聊天记录'
|
|
43
|
-
: '💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史';
|
|
44
|
-
}
|
|
45
|
-
async handleHelp(chatId, platform) {
|
|
36
|
+
async handleHelp(chatId) {
|
|
46
37
|
const help = [
|
|
47
38
|
'📋 可用命令:',
|
|
48
39
|
'',
|
|
@@ -51,16 +42,14 @@ export class CommandHandler {
|
|
|
51
42
|
'/status - 显示状态',
|
|
52
43
|
'/cd <路径> - 切换工作目录',
|
|
53
44
|
'/pwd - 当前工作目录',
|
|
54
|
-
'',
|
|
55
|
-
this.getClearHistoryHint(platform),
|
|
56
45
|
].join('\n');
|
|
57
46
|
await this.deps.sender.sendTextReply(chatId, help);
|
|
58
47
|
return true;
|
|
59
48
|
}
|
|
60
|
-
async handleNew(chatId, userId
|
|
49
|
+
async handleNew(chatId, userId) {
|
|
61
50
|
const ok = this.deps.sessionManager.newSession(userId);
|
|
62
51
|
await this.deps.sender.sendTextReply(chatId, ok
|
|
63
|
-
?
|
|
52
|
+
? '✅ AI 会话已重置,下一条消息将使用全新上下文。'
|
|
64
53
|
: '当前没有活动会话。');
|
|
65
54
|
return true;
|
|
66
55
|
}
|
|
@@ -101,8 +90,7 @@ export class CommandHandler {
|
|
|
101
90
|
try {
|
|
102
91
|
const resolved = await this.deps.sessionManager.setWorkDir(userId, dir);
|
|
103
92
|
await this.deps.sender.sendTextReply(chatId, `📁 工作目录已切换到: ${escapePathForMarkdown(resolved)}\n\n` +
|
|
104
|
-
`🔄 AI
|
|
105
|
-
this.getClearHistoryHint(platform));
|
|
93
|
+
`🔄 AI 会话已重置,下一条消息将使用全新上下文。`);
|
|
106
94
|
}
|
|
107
95
|
catch (err) {
|
|
108
96
|
await this.deps.sender.sendTextReply(chatId, err instanceof Error ? err.message : String(err));
|
|
@@ -50,6 +50,7 @@ export declare const PAGE_TEXTS: {
|
|
|
50
50
|
readonly qqSummary: "App ID and secret for bot access.";
|
|
51
51
|
readonly weworkSummary: "Corp ID and secret for enterprise delivery.";
|
|
52
52
|
readonly dingtalkSummary: "Client credentials plus optional card template.";
|
|
53
|
+
readonly workbuddySummary: "CodeBuddy OAuth for WeChat customer service.";
|
|
53
54
|
readonly platformCredentialsTitle: "Credentials";
|
|
54
55
|
readonly platformAccessTitle: "Routing and access";
|
|
55
56
|
readonly platformTestNote: "Checks required credentials against the platform.";
|
|
@@ -70,8 +71,13 @@ export declare const PAGE_TEXTS: {
|
|
|
70
71
|
readonly clientId: "Client ID / AppKey";
|
|
71
72
|
readonly clientSecret: "Client Secret / AppSecret";
|
|
72
73
|
readonly dingtalkHelp: "Get credentials: Create an enterprise internal app on <a href=\"https://open-dev.dingtalk.com/\" target=\"_blank\">DingTalk Open Platform</a>, enable Stream Mode, and get Client ID / Client Secret";
|
|
74
|
+
readonly workbuddyHelp: "Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. WorkBuddy connects WeChat customer service through Centrifuge WebSocket.";
|
|
73
75
|
readonly secret: "Secret";
|
|
74
76
|
readonly cardTemplateId: "Card template ID";
|
|
77
|
+
readonly workbuddyAccessToken: "Access Token";
|
|
78
|
+
readonly workbuddyRefreshToken: "Refresh Token";
|
|
79
|
+
readonly workbuddyUserId: "User ID";
|
|
80
|
+
readonly workbuddyBaseUrl: "Base URL";
|
|
75
81
|
readonly optional: "Optional";
|
|
76
82
|
readonly commaSeparatedIds: "Comma-separated IDs";
|
|
77
83
|
readonly aiTitle: "AI Tooling";
|
|
@@ -178,6 +184,7 @@ export declare const PAGE_TEXTS: {
|
|
|
178
184
|
readonly qqSummary: "QQ 机器人 App ID 与 Secret。";
|
|
179
185
|
readonly weworkSummary: "企业微信 Corp ID 与 Secret。";
|
|
180
186
|
readonly dingtalkSummary: "钉钉 Client 凭证,可选配置卡片模板 ID。";
|
|
187
|
+
readonly workbuddySummary: "CodeBuddy OAuth 连接微信客服。";
|
|
181
188
|
readonly platformCredentialsTitle: "凭证";
|
|
182
189
|
readonly platformAccessTitle: "路由与访问";
|
|
183
190
|
readonly platformTestNote: "只会检查该平台的必填凭证是否可用。";
|
|
@@ -198,7 +205,12 @@ export declare const PAGE_TEXTS: {
|
|
|
198
205
|
readonly clientId: "Client ID / AppKey";
|
|
199
206
|
readonly clientSecret: "Client Secret / AppSecret";
|
|
200
207
|
readonly dingtalkHelp: "获取凭证:在 <a href=\"https://open-dev.dingtalk.com/\" target=\"_blank\">钉钉开放平台</a> 创建企业内部应用,启用 Stream Mode,并拿到 Client ID / Client Secret";
|
|
208
|
+
readonly workbuddyHelp: "获取凭证:通过 CodeBuddy OAuth 登录获取 access/refresh token。WorkBuddy 通过 Centrifuge WebSocket 连接微信客服。";
|
|
201
209
|
readonly cardTemplateId: "卡片模板 ID";
|
|
210
|
+
readonly workbuddyAccessToken: "Access Token";
|
|
211
|
+
readonly workbuddyRefreshToken: "Refresh Token";
|
|
212
|
+
readonly workbuddyUserId: "User ID";
|
|
213
|
+
readonly workbuddyBaseUrl: "基础 URL";
|
|
202
214
|
readonly optional: "可选";
|
|
203
215
|
readonly commaSeparatedIds: "多个 ID 用逗号分隔";
|
|
204
216
|
readonly aiTitle: "AI 工具配置";
|
|
@@ -50,6 +50,7 @@ export const PAGE_TEXTS = {
|
|
|
50
50
|
qqSummary: "App ID and secret for bot access.",
|
|
51
51
|
weworkSummary: "Corp ID and secret for enterprise delivery.",
|
|
52
52
|
dingtalkSummary: "Client credentials plus optional card template.",
|
|
53
|
+
workbuddySummary: "CodeBuddy OAuth for WeChat customer service.",
|
|
53
54
|
platformCredentialsTitle: "Credentials",
|
|
54
55
|
platformAccessTitle: "Routing and access",
|
|
55
56
|
platformTestNote: "Checks required credentials against the platform.",
|
|
@@ -70,8 +71,13 @@ export const PAGE_TEXTS = {
|
|
|
70
71
|
clientId: "Client ID / AppKey",
|
|
71
72
|
clientSecret: "Client Secret / AppSecret",
|
|
72
73
|
dingtalkHelp: 'Get credentials: Create an enterprise internal app on <a href="https://open-dev.dingtalk.com/" target="_blank">DingTalk Open Platform</a>, enable Stream Mode, and get Client ID / Client Secret',
|
|
74
|
+
workbuddyHelp: 'Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. WorkBuddy connects WeChat customer service through Centrifuge WebSocket.',
|
|
73
75
|
secret: "Secret",
|
|
74
76
|
cardTemplateId: "Card template ID",
|
|
77
|
+
workbuddyAccessToken: "Access Token",
|
|
78
|
+
workbuddyRefreshToken: "Refresh Token",
|
|
79
|
+
workbuddyUserId: "User ID",
|
|
80
|
+
workbuddyBaseUrl: "Base URL",
|
|
75
81
|
optional: "Optional",
|
|
76
82
|
commaSeparatedIds: "Comma-separated IDs",
|
|
77
83
|
aiTitle: "AI Tooling",
|
|
@@ -178,6 +184,7 @@ export const PAGE_TEXTS = {
|
|
|
178
184
|
qqSummary: "QQ \u673a\u5668\u4eba App ID \u4e0e Secret\u3002",
|
|
179
185
|
weworkSummary: "\u4f01\u4e1a\u5fae\u4fe1 Corp ID \u4e0e Secret\u3002",
|
|
180
186
|
dingtalkSummary: "\u9489\u9489 Client \u51ed\u8bc1\uff0c\u53ef\u9009\u914d\u7f6e\u5361\u7247\u6a21\u677f ID\u3002",
|
|
187
|
+
workbuddySummary: "CodeBuddy OAuth \u8fde\u63a5\u5fae\u4fe1\u5ba2\u670d\u3002",
|
|
181
188
|
platformCredentialsTitle: "\u51ed\u8bc1",
|
|
182
189
|
platformAccessTitle: "\u8def\u7531\u4e0e\u8bbf\u95ee",
|
|
183
190
|
platformTestNote: "\u53ea\u4f1a\u68c0\u67e5\u8be5\u5e73\u53f0\u7684\u5fc5\u586b\u51ed\u8bc1\u662f\u5426\u53ef\u7528\u3002",
|
|
@@ -198,7 +205,12 @@ export const PAGE_TEXTS = {
|
|
|
198
205
|
clientId: "Client ID / AppKey",
|
|
199
206
|
clientSecret: "Client Secret / AppSecret",
|
|
200
207
|
dingtalkHelp: '\u83b7\u53d6\u51ed\u8bc1\uff1a\u5728 <a href="https://open-dev.dingtalk.com/" target="_blank">\u9489\u9489\u5f00\u653e\u5e73\u53f0</a> \u521b\u5efa\u4f01\u4e1a\u5185\u90e8\u5e94\u7528\uff0c\u542f\u7528 Stream Mode\uff0c\u5e76\u62ff\u5230 Client ID / Client Secret',
|
|
208
|
+
workbuddyHelp: '\u83b7\u53d6\u51ed\u8bc1\uff1a\u901a\u8fc7 CodeBuddy OAuth \u767b\u5f55\u83b7\u53d6 access/refresh token\u3002WorkBuddy \u901a\u8fc7 Centrifuge WebSocket \u8fde\u63a5\u5fae\u4fe1\u5ba2\u670d\u3002',
|
|
201
209
|
cardTemplateId: "\u5361\u7247\u6a21\u677f ID",
|
|
210
|
+
workbuddyAccessToken: "Access Token",
|
|
211
|
+
workbuddyRefreshToken: "Refresh Token",
|
|
212
|
+
workbuddyUserId: "User ID",
|
|
213
|
+
workbuddyBaseUrl: "\u57fa\u7840 URL",
|
|
202
214
|
optional: "\u53ef\u9009",
|
|
203
215
|
commaSeparatedIds: "\u591a\u4e2a ID \u7528\u9017\u53f7\u5206\u9694",
|
|
204
216
|
aiTitle: "AI \u5de5\u5177\u914d\u7f6e",
|
|
@@ -4,6 +4,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
4
4
|
{ key: "qq", label: "QQ", fields: ["aiCommand", "appId", "secret", "allowedUserIds"], testFields: ["appId", "secret"], requiredFields: ["appId", "secret"] },
|
|
5
5
|
{ key: "wework", label: "WeWork", fields: ["aiCommand", "corpId", "secret", "allowedUserIds"], testFields: ["corpId", "secret"], requiredFields: ["corpId", "secret"] },
|
|
6
6
|
{ key: "dingtalk", label: "DingTalk", fields: ["aiCommand", "clientId", "clientSecret", "cardTemplateId", "allowedUserIds"], testFields: ["clientId", "clientSecret"], requiredFields: ["clientId", "clientSecret"] },
|
|
7
|
+
{ key: "workbuddy", label: "WorkBuddy", fields: ["aiCommand", "accessToken", "refreshToken", "userId", "baseUrl", "allowedUserIds"], testFields: ["accessToken", "refreshToken", "userId"], requiredFields: ["accessToken", "refreshToken", "userId"] },
|
|
7
8
|
];
|
|
8
9
|
const platformKeys = platformDefinitions.map((platform) => platform.key);
|
|
9
10
|
const aiTools = ["claude", "codex", "codebuddy"];
|
|
@@ -828,7 +828,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
828
828
|
<div class="stats-grid">
|
|
829
829
|
<div class="stat-card">
|
|
830
830
|
<div class="stat-label" id="statConfiguredLabel">Configured</div>
|
|
831
|
-
<div class="stat-value" id="statConfiguredValue">0/
|
|
831
|
+
<div class="stat-value" id="statConfiguredValue">0/6</div>
|
|
832
832
|
</div>
|
|
833
833
|
<div class="stat-card">
|
|
834
834
|
<div class="stat-label" id="statEnabledLabel">Enabled</div>
|
|
@@ -1058,6 +1058,53 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1058
1058
|
<div id="test-dingtalk-result" class="mt-4"></div>
|
|
1059
1059
|
</div>
|
|
1060
1060
|
</div>
|
|
1061
|
+
|
|
1062
|
+
<!-- WorkBuddy -->
|
|
1063
|
+
<div class="platform-card">
|
|
1064
|
+
<div class="platform-header">
|
|
1065
|
+
<h3 class="platform-title">WorkBuddy</h3>
|
|
1066
|
+
<label class="toggle">
|
|
1067
|
+
<input type="checkbox" id="workbuddy-enabled" class="toggle-input">
|
|
1068
|
+
<span class="toggle-switch"></span>
|
|
1069
|
+
<span class="toggle-label" id="workbuddy-label">Enabled</span>
|
|
1070
|
+
</label>
|
|
1071
|
+
</div>
|
|
1072
|
+
<div class="platform-body">
|
|
1073
|
+
<div class="form-group">
|
|
1074
|
+
<label class="form-label" id="workbuddy-aiCommand-label">AI Tool</label>
|
|
1075
|
+
<select id="workbuddy-aiCommand" class="form-select">
|
|
1076
|
+
<option value="claude">claude</option>
|
|
1077
|
+
<option value="codex">codex</option>
|
|
1078
|
+
<option value="codebuddy">codebuddy</option>
|
|
1079
|
+
</select>
|
|
1080
|
+
</div>
|
|
1081
|
+
<div class="form-group">
|
|
1082
|
+
<label class="form-label" id="workbuddy-accessToken-label">Access Token</label>
|
|
1083
|
+
<input id="workbuddy-accessToken" class="form-input mono" type="password" autocomplete="off" />
|
|
1084
|
+
</div>
|
|
1085
|
+
<div class="form-group">
|
|
1086
|
+
<label class="form-label" id="workbuddy-refreshToken-label">Refresh Token</label>
|
|
1087
|
+
<input id="workbuddy-refreshToken" class="form-input mono" type="password" autocomplete="off" />
|
|
1088
|
+
</div>
|
|
1089
|
+
<div class="form-group">
|
|
1090
|
+
<label class="form-label" id="workbuddy-userId-label">User ID</label>
|
|
1091
|
+
<input id="workbuddy-userId" class="form-input mono" type="text" />
|
|
1092
|
+
</div>
|
|
1093
|
+
<div class="form-group">
|
|
1094
|
+
<label class="form-label" id="workbuddy-baseUrl-label">Base URL (optional)</label>
|
|
1095
|
+
<input id="workbuddy-baseUrl" class="form-input mono" type="text" placeholder="https://copilot.tencent.com" />
|
|
1096
|
+
</div>
|
|
1097
|
+
<div class="form-group">
|
|
1098
|
+
<label class="form-label" id="workbuddy-allowedUserIds-label">Allowed User IDs</label>
|
|
1099
|
+
<textarea id="workbuddy-allowedUserIds" class="form-textarea mono"></textarea>
|
|
1100
|
+
</div>
|
|
1101
|
+
<div class="form-help" id="workbuddy-help">WorkBuddy uses CodeBuddy OAuth to connect WeChat customer service. Get credentials from CodeBuddy login.</div>
|
|
1102
|
+
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
|
1103
|
+
<button id="test-workbuddy" class="btn btn-secondary btn-sm" type="button">Test Configuration</button>
|
|
1104
|
+
</div>
|
|
1105
|
+
<div id="test-workbuddy-result" class="mt-4"></div>
|
|
1106
|
+
</div>
|
|
1107
|
+
</div>
|
|
1061
1108
|
</div>
|
|
1062
1109
|
</section>
|
|
1063
1110
|
|