@wu529778790/open-im 1.8.1-beta.1 → 1.8.1-beta.10
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.js +43 -1
- package/dist/channels/capabilities.js +5 -0
- package/dist/commands/handler.d.ts +1 -1
- package/dist/commands/handler.js +3 -1
- 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 +1 -0
- 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/event-handler.js +12 -1
- package/dist/feishu/event-handler.js +115 -1
- package/dist/index.js +17 -2
- package/dist/qq/event-handler.js +12 -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/telegram/client.js +24 -4
- package/dist/telegram/event-handler.js +4 -0
- package/dist/wechat/event-handler.js +4 -0
- package/dist/wework/client.js +4 -8
- package/dist/wework/event-handler.js +12 -1
- 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 +3 -1
|
@@ -39,11 +39,14 @@ function isResult(msg) {
|
|
|
39
39
|
*/
|
|
40
40
|
async function getOrCreateSession(sessionId, _workDir, // 保留参数以备将来使用
|
|
41
41
|
model, permissionMode) {
|
|
42
|
+
const resolvedModel = model?.trim() || 'claude-opus-4-5';
|
|
42
43
|
const sessionOptions = {
|
|
43
|
-
model:
|
|
44
|
+
model: resolvedModel,
|
|
44
45
|
permissionMode,
|
|
45
46
|
// 可以添加其他选项,如 hooks, allowedTools 等
|
|
46
47
|
};
|
|
48
|
+
const baseUrl = process.env.ANTHROPIC_BASE_URL ?? '(default)';
|
|
49
|
+
log.info(`[V2] getOrCreateSession model param=${String(model ?? '')} resolved=${resolvedModel} baseUrl=${baseUrl}`);
|
|
47
50
|
let session;
|
|
48
51
|
if (sessionId) {
|
|
49
52
|
// 尝试恢复已有会话
|
|
@@ -96,9 +99,19 @@ export class ClaudeSDKAdapter {
|
|
|
96
99
|
activeSessions.clear();
|
|
97
100
|
}
|
|
98
101
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
102
|
+
log.info(`[V2] run() entry model=${String(options?.model ?? '')} baseUrl=${process.env.ANTHROPIC_BASE_URL ?? '(default)'}`);
|
|
99
103
|
const abortController = new AbortController();
|
|
100
104
|
let streamClosed = false;
|
|
101
105
|
let actualSessionId;
|
|
106
|
+
let runSettled = false;
|
|
107
|
+
let timeoutId = null;
|
|
108
|
+
const timeoutMs = options?.timeoutMs ?? 600_000;
|
|
109
|
+
const clearRunTimeout = () => {
|
|
110
|
+
if (timeoutId !== null) {
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
timeoutId = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
102
115
|
const permissionMode = options?.skipPermissions
|
|
103
116
|
? 'bypassPermissions'
|
|
104
117
|
: options?.permissionMode === 'acceptEdits'
|
|
@@ -107,6 +120,15 @@ export class ClaudeSDKAdapter {
|
|
|
107
120
|
? 'plan'
|
|
108
121
|
: 'default';
|
|
109
122
|
const runSession = async () => {
|
|
123
|
+
timeoutId = setTimeout(() => {
|
|
124
|
+
if (runSettled)
|
|
125
|
+
return;
|
|
126
|
+
runSettled = true;
|
|
127
|
+
clearRunTimeout();
|
|
128
|
+
log.warn(`[ClaudeSDK] Request timeout after ${timeoutMs}ms`);
|
|
129
|
+
abortController.abort();
|
|
130
|
+
callbacks.onError(`请求超时(${Math.round(timeoutMs / 1000)}s),请重试或缩短问题。`);
|
|
131
|
+
}, timeoutMs);
|
|
110
132
|
try {
|
|
111
133
|
// 检查环境变量
|
|
112
134
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
@@ -115,6 +137,7 @@ export class ClaudeSDKAdapter {
|
|
|
115
137
|
log.warn('Claude SDK: No API credentials found in environment variables');
|
|
116
138
|
}
|
|
117
139
|
log.info(`[V2] Session: ${sessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
|
|
140
|
+
log.info(`[V2] model param=${String(options?.model ?? '')} baseUrl=${process.env.ANTHROPIC_BASE_URL ?? '(default)'}`);
|
|
118
141
|
// 获取或创建会话
|
|
119
142
|
const { session } = await getOrCreateSession(sessionId, workDir, options?.model, permissionMode);
|
|
120
143
|
// 发送用户消息
|
|
@@ -181,6 +204,8 @@ export class ClaudeSDKAdapter {
|
|
|
181
204
|
log.info(`[V2] Result: subtype=${m.subtype}, num_turns=${m.num_turns}, sessionId=${actualSessionId ?? 'unknown'}`);
|
|
182
205
|
// 检查会话错误
|
|
183
206
|
if (!success) {
|
|
207
|
+
runSettled = true;
|
|
208
|
+
clearRunTimeout();
|
|
184
209
|
const noConvErr = errs.find((e) => e.includes('No conversation found') || e.includes('session not found'));
|
|
185
210
|
if (noConvErr) {
|
|
186
211
|
log.warn(`Session ${actualSessionId} not found, may need to create new one`);
|
|
@@ -207,6 +232,8 @@ export class ClaudeSDKAdapter {
|
|
|
207
232
|
result.accumulated = accumulated;
|
|
208
233
|
result.result = accumulated;
|
|
209
234
|
}
|
|
235
|
+
runSettled = true;
|
|
236
|
+
clearRunTimeout();
|
|
210
237
|
callbacks.onComplete(result);
|
|
211
238
|
return;
|
|
212
239
|
}
|
|
@@ -214,6 +241,8 @@ export class ClaudeSDKAdapter {
|
|
|
214
241
|
// 如果流正常结束但没有收到 result 消息
|
|
215
242
|
if (!streamClosed && accumulated) {
|
|
216
243
|
log.info('Stream ended without result message, using accumulated text');
|
|
244
|
+
runSettled = true;
|
|
245
|
+
clearRunTimeout();
|
|
217
246
|
callbacks.onComplete({
|
|
218
247
|
success: true,
|
|
219
248
|
result: accumulated,
|
|
@@ -233,14 +262,27 @@ export class ClaudeSDKAdapter {
|
|
|
233
262
|
catch (err) {
|
|
234
263
|
if (abortController.signal.aborted) {
|
|
235
264
|
log.info('Session run aborted');
|
|
265
|
+
clearRunTimeout();
|
|
266
|
+
// 清理 pending tempId
|
|
267
|
+
if (actualSessionId?.startsWith('pending-')) {
|
|
268
|
+
activeSessions.delete(actualSessionId);
|
|
269
|
+
log.info(`Cleaned up pending session: ${actualSessionId}`);
|
|
270
|
+
}
|
|
236
271
|
return;
|
|
237
272
|
}
|
|
273
|
+
runSettled = true;
|
|
274
|
+
clearRunTimeout();
|
|
238
275
|
const errorObj = err;
|
|
239
276
|
const msg = errorObj.message || String(err);
|
|
240
277
|
log.error(`Claude SDK V2 error: ${msg}`);
|
|
241
278
|
if (errorObj.stack) {
|
|
242
279
|
log.error(`Error stack: ${errorObj.stack}`);
|
|
243
280
|
}
|
|
281
|
+
// 清理 pending tempId(session 在获取真实 ID 前就失败了)
|
|
282
|
+
if (actualSessionId?.startsWith('pending-')) {
|
|
283
|
+
activeSessions.delete(actualSessionId);
|
|
284
|
+
log.info(`Cleaned up pending session after error: ${actualSessionId}`);
|
|
285
|
+
}
|
|
244
286
|
callbacks.onError(msg);
|
|
245
287
|
}
|
|
246
288
|
};
|
|
@@ -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)
|
|
@@ -18,7 +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>;
|
|
21
|
+
dispatch(text: string, chatId: string, userId: string, platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
22
22
|
private getClearHistoryHint;
|
|
23
23
|
private handleHelp;
|
|
24
24
|
private handleNew;
|
package/dist/commands/handler.js
CHANGED
|
@@ -40,7 +40,9 @@ export class CommandHandler {
|
|
|
40
40
|
? '💡 提示:如需清除本对话的历史消息,请清除聊天记录'
|
|
41
41
|
: platform === 'dingtalk'
|
|
42
42
|
? '💡 提示:如需清除本对话的历史消息,请在钉钉中清空聊天记录'
|
|
43
|
-
:
|
|
43
|
+
: platform === 'workbuddy'
|
|
44
|
+
? '💡 提示:如需清除本对话的历史消息,请清除聊天记录'
|
|
45
|
+
: '💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史';
|
|
44
46
|
}
|
|
45
47
|
async handleHelp(chatId, platform) {
|
|
46
48
|
const help = [
|
|
@@ -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
|
|
package/dist/config-web.js
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LogLevel } from './logger.js';
|
|
2
|
-
export type Platform = 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework';
|
|
2
|
+
export type Platform = 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework' | 'workbuddy';
|
|
3
3
|
export type AiCommand = 'claude' | 'codex' | 'codebuddy';
|
|
4
4
|
export interface Config {
|
|
5
5
|
enabledPlatforms: Platform[];
|
|
@@ -29,6 +29,7 @@ export interface Config {
|
|
|
29
29
|
wechatAllowedUserIds: string[];
|
|
30
30
|
weworkAllowedUserIds: string[];
|
|
31
31
|
dingtalkAllowedUserIds: string[];
|
|
32
|
+
workbuddyAllowedUserIds: string[];
|
|
32
33
|
aiCommand: AiCommand;
|
|
33
34
|
codexCliPath: string;
|
|
34
35
|
codebuddyCliPath: string;
|
|
@@ -82,6 +83,17 @@ export interface Config {
|
|
|
82
83
|
allowedUserIds: string[];
|
|
83
84
|
cardTemplateId?: string;
|
|
84
85
|
};
|
|
86
|
+
workbuddy?: {
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
aiCommand?: AiCommand;
|
|
89
|
+
allowedUserIds: string[];
|
|
90
|
+
accessToken?: string;
|
|
91
|
+
refreshToken?: string;
|
|
92
|
+
userId?: string;
|
|
93
|
+
baseUrl?: string;
|
|
94
|
+
guid?: string;
|
|
95
|
+
workspacePath?: string;
|
|
96
|
+
};
|
|
85
97
|
};
|
|
86
98
|
}
|
|
87
99
|
export interface FilePlatformTelegram {
|
|
@@ -134,6 +146,17 @@ export interface FilePlatformDingtalk {
|
|
|
134
146
|
allowedUserIds?: string[];
|
|
135
147
|
cardTemplateId?: string;
|
|
136
148
|
}
|
|
149
|
+
interface FilePlatformWorkBuddy {
|
|
150
|
+
enabled?: boolean;
|
|
151
|
+
aiCommand?: AiCommand;
|
|
152
|
+
allowedUserIds?: string[];
|
|
153
|
+
accessToken?: string;
|
|
154
|
+
refreshToken?: string;
|
|
155
|
+
userId?: string;
|
|
156
|
+
baseUrl?: string;
|
|
157
|
+
guid?: string;
|
|
158
|
+
workspacePath?: string;
|
|
159
|
+
}
|
|
137
160
|
export interface FileToolClaude {
|
|
138
161
|
cliPath?: string;
|
|
139
162
|
workDir?: string;
|
|
@@ -167,6 +190,7 @@ export interface FileConfig {
|
|
|
167
190
|
wechat?: FilePlatformWechat;
|
|
168
191
|
wework?: FilePlatformWework;
|
|
169
192
|
dingtalk?: FilePlatformDingtalk;
|
|
193
|
+
workbuddy?: FilePlatformWorkBuddy;
|
|
170
194
|
};
|
|
171
195
|
env?: Record<string, string>;
|
|
172
196
|
aiCommand?: string;
|
package/dist/config.js
CHANGED
|
@@ -233,6 +233,7 @@ export function loadConfig() {
|
|
|
233
233
|
const fileWechat = file.platforms?.wechat;
|
|
234
234
|
const fileWework = file.platforms?.wework;
|
|
235
235
|
const fileDingtalk = file.platforms?.dingtalk;
|
|
236
|
+
const fileWorkBuddy = file.platforms?.workbuddy;
|
|
236
237
|
// 1. 加载各平台凭证(env 优先,其次新结构,最后旧字段)
|
|
237
238
|
const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN ??
|
|
238
239
|
fileTelegram?.botToken ??
|
|
@@ -276,6 +277,19 @@ export function loadConfig() {
|
|
|
276
277
|
fileDingtalk?.clientSecret;
|
|
277
278
|
const dingtalkCardTemplateId = process.env.DINGTALK_CARD_TEMPLATE_ID ??
|
|
278
279
|
fileDingtalk?.cardTemplateId;
|
|
280
|
+
// WorkBuddy credentials
|
|
281
|
+
const workbuddyAccessToken = process.env.WORKBUDDY_ACCESS_TOKEN ??
|
|
282
|
+
fileWorkBuddy?.accessToken;
|
|
283
|
+
const workbuddyRefreshToken = process.env.WORKBUDDY_REFRESH_TOKEN ??
|
|
284
|
+
fileWorkBuddy?.refreshToken;
|
|
285
|
+
const workbuddyUserId = process.env.WORKBUDDY_USER_ID ??
|
|
286
|
+
fileWorkBuddy?.userId;
|
|
287
|
+
const workbuddyBaseUrl = process.env.WORKBUDDY_BASE_URL ??
|
|
288
|
+
fileWorkBuddy?.baseUrl;
|
|
289
|
+
const workbuddyGuid = process.env.WORKBUDDY_GUID ??
|
|
290
|
+
fileWorkBuddy?.guid;
|
|
291
|
+
const workbuddyWorkspacePath = process.env.WORKBUDDY_WORKSPACE_PATH ??
|
|
292
|
+
fileWorkBuddy?.workspacePath;
|
|
279
293
|
// 2. 计算启用平台
|
|
280
294
|
const enabledPlatforms = [];
|
|
281
295
|
const telegramEnabledFlag = fileTelegram?.enabled;
|
|
@@ -284,6 +298,7 @@ export function loadConfig() {
|
|
|
284
298
|
const wechatEnabledFlag = fileWechat?.enabled;
|
|
285
299
|
const weworkEnabledFlag = fileWework?.enabled;
|
|
286
300
|
const dingtalkEnabledFlag = fileDingtalk?.enabled;
|
|
301
|
+
const workbuddyEnabledFlag = fileWorkBuddy?.enabled;
|
|
287
302
|
const telegramEnabled = !!telegramBotToken && (telegramEnabledFlag !== false);
|
|
288
303
|
const feishuEnabled = !!(feishuAppId && feishuAppSecret) && (feishuEnabledFlag !== false);
|
|
289
304
|
const qqEnabled = !!(qqAppId && qqSecret) && (qqEnabledFlag !== false);
|
|
@@ -294,6 +309,8 @@ export function loadConfig() {
|
|
|
294
309
|
// 企业微信只需要 corpId (botId) 和 secret
|
|
295
310
|
const weworkEnabled = !!(weworkCorpId && weworkSecret) && (weworkEnabledFlag !== false);
|
|
296
311
|
const dingtalkEnabled = !!(dingtalkClientId && dingtalkClientSecret) && (dingtalkEnabledFlag !== false);
|
|
312
|
+
// WorkBuddy 需要 OAuth 凭证
|
|
313
|
+
const workbuddyEnabled = !!(workbuddyAccessToken && workbuddyRefreshToken && workbuddyUserId) && (workbuddyEnabledFlag !== false);
|
|
297
314
|
if (telegramEnabled)
|
|
298
315
|
enabledPlatforms.push('telegram');
|
|
299
316
|
if (feishuEnabled)
|
|
@@ -306,6 +323,8 @@ export function loadConfig() {
|
|
|
306
323
|
enabledPlatforms.push('wework');
|
|
307
324
|
if (dingtalkEnabled)
|
|
308
325
|
enabledPlatforms.push('dingtalk');
|
|
326
|
+
if (workbuddyEnabled)
|
|
327
|
+
enabledPlatforms.push('workbuddy');
|
|
309
328
|
if (enabledPlatforms.length === 0) {
|
|
310
329
|
throw new Error('至少需要配置 Telegram、Feishu、WeChat、WeWork 或 DingTalk 其中一个平台(可以通过环境变量或 config.json)');
|
|
311
330
|
}
|
|
@@ -332,6 +351,9 @@ export function loadConfig() {
|
|
|
332
351
|
const dingtalkAllowedUserIds = process.env.DINGTALK_ALLOWED_USER_IDS !== undefined
|
|
333
352
|
? parseCommaSeparated(process.env.DINGTALK_ALLOWED_USER_IDS)
|
|
334
353
|
: fileDingtalk?.allowedUserIds ?? allowedUserIds;
|
|
354
|
+
const workbuddyAllowedUserIds = process.env.WORKBUDDY_ALLOWED_USER_IDS !== undefined
|
|
355
|
+
? parseCommaSeparated(process.env.WORKBUDDY_ALLOWED_USER_IDS)
|
|
356
|
+
: fileWorkBuddy?.allowedUserIds ?? allowedUserIds;
|
|
335
357
|
// 5. AI / 工作目录 / 安全配置(从 tools 读取)
|
|
336
358
|
const aiCommand = normalizeAiCommand(process.env.AI_COMMAND ?? file.aiCommand, 'claude');
|
|
337
359
|
const tc = file.tools?.claude ?? {};
|
|
@@ -579,6 +601,29 @@ export function loadConfig() {
|
|
|
579
601
|
allowedUserIds: dingtalkAllowedUserIds,
|
|
580
602
|
cardTemplateId: dingtalkCardTemplateId,
|
|
581
603
|
},
|
|
604
|
+
workbuddy: workbuddyEnabled
|
|
605
|
+
? {
|
|
606
|
+
enabled: true,
|
|
607
|
+
aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
|
|
608
|
+
allowedUserIds: workbuddyAllowedUserIds,
|
|
609
|
+
accessToken: workbuddyAccessToken,
|
|
610
|
+
refreshToken: workbuddyRefreshToken,
|
|
611
|
+
userId: workbuddyUserId,
|
|
612
|
+
baseUrl: workbuddyBaseUrl,
|
|
613
|
+
guid: workbuddyGuid,
|
|
614
|
+
workspacePath: workbuddyWorkspacePath,
|
|
615
|
+
}
|
|
616
|
+
: {
|
|
617
|
+
enabled: false,
|
|
618
|
+
aiCommand: normalizeAiCommand(file.platforms?.workbuddy?.aiCommand, aiCommand),
|
|
619
|
+
allowedUserIds: workbuddyAllowedUserIds,
|
|
620
|
+
accessToken: workbuddyAccessToken,
|
|
621
|
+
refreshToken: workbuddyRefreshToken,
|
|
622
|
+
userId: workbuddyUserId,
|
|
623
|
+
baseUrl: workbuddyBaseUrl,
|
|
624
|
+
guid: workbuddyGuid,
|
|
625
|
+
workspacePath: workbuddyWorkspacePath,
|
|
626
|
+
},
|
|
582
627
|
};
|
|
583
628
|
return {
|
|
584
629
|
enabledPlatforms,
|
|
@@ -608,6 +653,7 @@ export function loadConfig() {
|
|
|
608
653
|
wechatAllowedUserIds,
|
|
609
654
|
weworkAllowedUserIds,
|
|
610
655
|
dingtalkAllowedUserIds,
|
|
656
|
+
workbuddyAllowedUserIds,
|
|
611
657
|
aiCommand,
|
|
612
658
|
codexCliPath,
|
|
613
659
|
codebuddyCliPath,
|
package/dist/constants.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare const CARDKIT_THROTTLE_MS = 80;
|
|
|
11
11
|
export declare const TELEGRAM_THROTTLE_MS = 200;
|
|
12
12
|
/** WeChat 流式更新节流:1000ms(AGP 协议建议值) */
|
|
13
13
|
export declare const WECHAT_THROTTLE_MS = 1000;
|
|
14
|
+
/** WorkBuddy 流式更新节流:1000ms(Centrifuge 协议建议值) */
|
|
15
|
+
export declare const WORKBUDDY_THROTTLE_MS = 1000;
|
|
14
16
|
export declare const WEWORK_THROTTLE_MS = 500;
|
|
15
17
|
export declare const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
|
|
16
18
|
export declare const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
package/dist/constants.js
CHANGED
|
@@ -32,6 +32,8 @@ export const CARDKIT_THROTTLE_MS = 80;
|
|
|
32
32
|
export const TELEGRAM_THROTTLE_MS = 200;
|
|
33
33
|
/** WeChat 流式更新节流:1000ms(AGP 协议建议值) */
|
|
34
34
|
export const WECHAT_THROTTLE_MS = 1000;
|
|
35
|
+
/** WorkBuddy 流式更新节流:1000ms(Centrifuge 协议建议值) */
|
|
36
|
+
export const WORKBUDDY_THROTTLE_MS = 1000;
|
|
35
37
|
export const WEWORK_THROTTLE_MS = 500;
|
|
36
38
|
export const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
|
|
37
39
|
export const MAX_FEISHU_MESSAGE_LENGTH = 4000;
|
|
@@ -185,7 +185,18 @@ export function setupDingTalkHandlers(config, sessionManager) {
|
|
|
185
185
|
: undefined;
|
|
186
186
|
log.info(`[AI_REQUEST] Running ${aiCommand} for user ${userId}, sessionId=${sessionId ?? 'new'}`);
|
|
187
187
|
const toolId = aiCommand;
|
|
188
|
-
|
|
188
|
+
let msgId;
|
|
189
|
+
try {
|
|
190
|
+
msgId = await sendThinkingMessage(chatId, replyToMessageId, toolId, dingtalkTarget);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
log.error('Failed to send thinking message:', err);
|
|
194
|
+
try {
|
|
195
|
+
await sendTextReply(chatId, '启动 AI 处理失败,请重试。');
|
|
196
|
+
}
|
|
197
|
+
catch { /* ignore */ }
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
189
200
|
const stopTyping = startTypingLoop(chatId);
|
|
190
201
|
const taskKey = `${userId}:${msgId}`;
|
|
191
202
|
await runAITask({ config, sessionManager }, { userId, chatId, workDir, sessionId, convId, platform: 'dingtalk', taskKey }, prompt, toolAdapter, {
|