@wu529778790/open-im 1.8.1-beta.0 → 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 +19 -3
- package/dist/config-web-page-i18n.js +19 -3
- package/dist/config-web-page-script.js +35 -7
- package/dist/config-web-page-template.js +72 -48
- package/dist/config-web.js +42 -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";
|
|
@@ -120,10 +126,12 @@ export declare const PAGE_TEXTS: {
|
|
|
120
126
|
readonly stopOk: "Bridge stopped.";
|
|
121
127
|
readonly configEditorTitle: "Config Editor";
|
|
122
128
|
readonly configEditorHint: "Edit ~/.open-im/config.json directly";
|
|
123
|
-
readonly
|
|
129
|
+
readonly claudeSettingsLabel: "~/.claude/settings.json";
|
|
130
|
+
readonly configJson: "~/.open-im/config.json";
|
|
124
131
|
readonly configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.";
|
|
125
132
|
readonly formatJson: "Format";
|
|
126
133
|
readonly resetJson: "Reset";
|
|
134
|
+
readonly saveBtn: "Save";
|
|
127
135
|
readonly jsonValid: "Valid JSON";
|
|
128
136
|
readonly jsonInvalid: "Invalid JSON: {error}";
|
|
129
137
|
};
|
|
@@ -176,6 +184,7 @@ export declare const PAGE_TEXTS: {
|
|
|
176
184
|
readonly qqSummary: "QQ 机器人 App ID 与 Secret。";
|
|
177
185
|
readonly weworkSummary: "企业微信 Corp ID 与 Secret。";
|
|
178
186
|
readonly dingtalkSummary: "钉钉 Client 凭证,可选配置卡片模板 ID。";
|
|
187
|
+
readonly workbuddySummary: "CodeBuddy OAuth 连接微信客服。";
|
|
179
188
|
readonly platformCredentialsTitle: "凭证";
|
|
180
189
|
readonly platformAccessTitle: "路由与访问";
|
|
181
190
|
readonly platformTestNote: "只会检查该平台的必填凭证是否可用。";
|
|
@@ -196,7 +205,12 @@ export declare const PAGE_TEXTS: {
|
|
|
196
205
|
readonly clientId: "Client ID / AppKey";
|
|
197
206
|
readonly clientSecret: "Client Secret / AppSecret";
|
|
198
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 连接微信客服。";
|
|
199
209
|
readonly cardTemplateId: "卡片模板 ID";
|
|
210
|
+
readonly workbuddyAccessToken: "Access Token";
|
|
211
|
+
readonly workbuddyRefreshToken: "Refresh Token";
|
|
212
|
+
readonly workbuddyUserId: "User ID";
|
|
213
|
+
readonly workbuddyBaseUrl: "基础 URL";
|
|
200
214
|
readonly optional: "可选";
|
|
201
215
|
readonly commaSeparatedIds: "多个 ID 用逗号分隔";
|
|
202
216
|
readonly aiTitle: "AI 工具配置";
|
|
@@ -242,10 +256,12 @@ export declare const PAGE_TEXTS: {
|
|
|
242
256
|
readonly stopOk: "桥接已停止。";
|
|
243
257
|
readonly configEditorTitle: "JSON 配置编辑器";
|
|
244
258
|
readonly configEditorHint: "直接编辑 ~/.open-im/config.json";
|
|
245
|
-
readonly
|
|
246
|
-
readonly
|
|
259
|
+
readonly claudeSettingsLabel: "~/.claude/settings.json";
|
|
260
|
+
readonly configJson: "~/.open-im/config.json";
|
|
261
|
+
readonly configJsonHint: "编辑配置 JSON。点击服务区的“保存配置”按钮后侘存更改。";
|
|
247
262
|
readonly formatJson: "格式化";
|
|
248
263
|
readonly resetJson: "重置";
|
|
264
|
+
readonly saveBtn: "保存";
|
|
249
265
|
readonly jsonValid: "JSON 有效";
|
|
250
266
|
readonly jsonInvalid: "JSON 无效:{error}";
|
|
251
267
|
};
|
|
@@ -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",
|
|
@@ -120,10 +126,12 @@ export const PAGE_TEXTS = {
|
|
|
120
126
|
stopOk: "Bridge stopped.",
|
|
121
127
|
configEditorTitle: "Config Editor",
|
|
122
128
|
configEditorHint: "Edit ~/.open-im/config.json directly",
|
|
123
|
-
|
|
129
|
+
claudeSettingsLabel: "~/.claude/settings.json",
|
|
130
|
+
configJson: "~/.open-im/config.json",
|
|
124
131
|
configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.",
|
|
125
132
|
formatJson: "Format",
|
|
126
133
|
resetJson: "Reset",
|
|
134
|
+
saveBtn: "Save",
|
|
127
135
|
jsonValid: "Valid JSON",
|
|
128
136
|
jsonInvalid: "Invalid JSON: {error}",
|
|
129
137
|
},
|
|
@@ -176,6 +184,7 @@ export const PAGE_TEXTS = {
|
|
|
176
184
|
qqSummary: "QQ \u673a\u5668\u4eba App ID \u4e0e Secret\u3002",
|
|
177
185
|
weworkSummary: "\u4f01\u4e1a\u5fae\u4fe1 Corp ID \u4e0e Secret\u3002",
|
|
178
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",
|
|
179
188
|
platformCredentialsTitle: "\u51ed\u8bc1",
|
|
180
189
|
platformAccessTitle: "\u8def\u7531\u4e0e\u8bbf\u95ee",
|
|
181
190
|
platformTestNote: "\u53ea\u4f1a\u68c0\u67e5\u8be5\u5e73\u53f0\u7684\u5fc5\u586b\u51ed\u8bc1\u662f\u5426\u53ef\u7528\u3002",
|
|
@@ -196,7 +205,12 @@ export const PAGE_TEXTS = {
|
|
|
196
205
|
clientId: "Client ID / AppKey",
|
|
197
206
|
clientSecret: "Client Secret / AppSecret",
|
|
198
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',
|
|
199
209
|
cardTemplateId: "\u5361\u7247\u6a21\u677f ID",
|
|
210
|
+
workbuddyAccessToken: "Access Token",
|
|
211
|
+
workbuddyRefreshToken: "Refresh Token",
|
|
212
|
+
workbuddyUserId: "User ID",
|
|
213
|
+
workbuddyBaseUrl: "\u57fa\u7840 URL",
|
|
200
214
|
optional: "\u53ef\u9009",
|
|
201
215
|
commaSeparatedIds: "\u591a\u4e2a ID \u7528\u9017\u53f7\u5206\u9694",
|
|
202
216
|
aiTitle: "AI \u5de5\u5177\u914d\u7f6e",
|
|
@@ -242,10 +256,12 @@ export const PAGE_TEXTS = {
|
|
|
242
256
|
stopOk: "\u6865\u63a5\u5df2\u505c\u6b62\u3002",
|
|
243
257
|
configEditorTitle: "JSON \u914d\u7f6e\u7f16\u8f91\u5668",
|
|
244
258
|
configEditorHint: "\u76f4\u63a5\u7f16\u8f91 ~/.open-im/config.json",
|
|
245
|
-
|
|
246
|
-
|
|
259
|
+
claudeSettingsLabel: "~/.claude/settings.json",
|
|
260
|
+
configJson: "~/.open-im/config.json",
|
|
261
|
+
configJsonHint: "\u7f16\u8f91\u914d\u7f6e JSON\u3002\u70b9\u51fb\u670d\u52a1\u533a\u7684\u201c\u4fdd\u5b58\u914d\u7f6e\u201d\u6309\u94ae\u540e\u4f98\u5b58\u66f4\u6539\u3002",
|
|
247
262
|
formatJson: "\u683c\u5f0f\u5316",
|
|
248
263
|
resetJson: "\u91cd\u7f6e",
|
|
264
|
+
saveBtn: "\u4fdd\u5b58",
|
|
249
265
|
jsonValid: "JSON \u6709\u6548",
|
|
250
266
|
jsonInvalid: "JSON \u65e0\u6548\uff1a{error}",
|
|
251
267
|
}
|
|
@@ -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"];
|
|
@@ -159,6 +160,12 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
159
160
|
{ id: "statConfiguredLabel", key: "statConfiguredLabel" },
|
|
160
161
|
{ id: "statEnabledLabel", key: "statEnabledLabel" },
|
|
161
162
|
{ id: "statServiceLabel", key: "statServiceLabel" },
|
|
163
|
+
{ id: "openImConfigSummary", key: "configJson" },
|
|
164
|
+
{ id: "claudeSettingsSummary", key: "claudeSettingsLabel" },
|
|
165
|
+
{ id: "formatJsonButtonText", key: "formatJson" },
|
|
166
|
+
{ id: "resetJsonButtonText", key: "resetJson" },
|
|
167
|
+
{ id: "saveClaudeSettingsBtnText", key: "saveBtn" },
|
|
168
|
+
{ id: "saveOpenImConfigBtnText", key: "saveBtn" },
|
|
162
169
|
],
|
|
163
170
|
platformLabels: {
|
|
164
171
|
enabled: { suffix: "-label", key: "enabled" },
|
|
@@ -380,7 +387,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
380
387
|
|
|
381
388
|
// Navigation
|
|
382
389
|
function setActiveNav(targetId) {
|
|
383
|
-
["navOverviewBtn","navPlatformsBtn","navAiBtn","navServiceBtn"
|
|
390
|
+
["navOverviewBtn","navPlatformsBtn","navAiBtn","navServiceBtn"].forEach((id) => {
|
|
384
391
|
const btn = el(id);
|
|
385
392
|
if (btn) btn.classList.toggle("active", id === targetId);
|
|
386
393
|
});
|
|
@@ -604,16 +611,37 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
604
611
|
}
|
|
605
612
|
});
|
|
606
613
|
|
|
607
|
-
//
|
|
614
|
+
// Open-im config.json: load when expanded
|
|
615
|
+
const openImConfigContainer = document.getElementById("openImConfigContainer");
|
|
616
|
+
if (openImConfigContainer && openImConfigContainer instanceof HTMLDetailsElement) {
|
|
617
|
+
openImConfigContainer.addEventListener("toggle", () => {
|
|
618
|
+
if (openImConfigContainer.open) void loadOpenImConfig();
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
// Claude settings.json: load when expanded
|
|
608
622
|
const claudeSettingsContainer = document.getElementById("claudeSettingsContainer");
|
|
609
623
|
if (claudeSettingsContainer && claudeSettingsContainer instanceof HTMLDetailsElement) {
|
|
610
624
|
claudeSettingsContainer.addEventListener("toggle", () => {
|
|
611
|
-
if (claudeSettingsContainer.open)
|
|
612
|
-
void loadClaudeSettings();
|
|
613
|
-
}
|
|
625
|
+
if (claudeSettingsContainer.open) void loadClaudeSettings();
|
|
614
626
|
});
|
|
615
627
|
}
|
|
616
628
|
|
|
629
|
+
el("saveClaudeSettingsBtn").onclick = async () => {
|
|
630
|
+
try {
|
|
631
|
+
await saveClaudeSettings();
|
|
632
|
+
} catch (e) {
|
|
633
|
+
setMessage(e && e.message ? e.message : String(e), "error");
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
el("saveOpenImConfigBtn").onclick = async () => {
|
|
637
|
+
try {
|
|
638
|
+
await saveOpenImConfig();
|
|
639
|
+
setMessage(t("saveOk"), "success");
|
|
640
|
+
} catch (e) {
|
|
641
|
+
setMessage(e && e.message ? e.message : String(e), "error");
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
617
645
|
// AI tool switcher
|
|
618
646
|
document.querySelectorAll(".tab[data-tool]").forEach((tab) => {
|
|
619
647
|
tab.addEventListener("click", () => {
|
|
@@ -648,7 +676,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
648
676
|
el("navPlatformsBtn").onclick = () => scrollToSection("configSection", "navPlatformsBtn");
|
|
649
677
|
el("navAiBtn").onclick = () => scrollToSection("aiSection", "navAiBtn");
|
|
650
678
|
el("navServiceBtn").onclick = () => scrollToSection("serviceSection", "navServiceBtn");
|
|
651
|
-
el("navConfigEditorBtn").onclick = () => scrollToSection("configEditorSection", "navConfigEditorBtn");
|
|
652
679
|
|
|
653
680
|
// Language toggle
|
|
654
681
|
el("langButton").onclick = () => {
|
|
@@ -718,7 +745,8 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
718
745
|
async function saveOpenImConfig() {
|
|
719
746
|
const textarea = document.getElementById("configJson");
|
|
720
747
|
if (!(textarea instanceof HTMLTextAreaElement)) return;
|
|
721
|
-
const json = textarea.value;
|
|
748
|
+
const json = textarea.value.trim();
|
|
749
|
+
if (!json) return;
|
|
722
750
|
|
|
723
751
|
// Validate JSON
|
|
724
752
|
try {
|
|
@@ -781,16 +781,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
781
781
|
</svg>
|
|
782
782
|
<span id="navServiceText">Service</span>
|
|
783
783
|
</button>
|
|
784
|
-
<button class="nav-item" id="navConfigEditorBtn">
|
|
785
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
786
|
-
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
787
|
-
<polyline points="14 2 14 8 20 8"/>
|
|
788
|
-
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
789
|
-
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
790
|
-
<polyline points="10 9 9 9 8 9"/>
|
|
791
|
-
</svg>
|
|
792
|
-
<span id="navConfigEditorText">Config Editor</span>
|
|
793
|
-
</button>
|
|
794
784
|
</nav>
|
|
795
785
|
</aside>
|
|
796
786
|
|
|
@@ -838,7 +828,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
838
828
|
<div class="stats-grid">
|
|
839
829
|
<div class="stat-card">
|
|
840
830
|
<div class="stat-label" id="statConfiguredLabel">Configured</div>
|
|
841
|
-
<div class="stat-value" id="statConfiguredValue">0/
|
|
831
|
+
<div class="stat-value" id="statConfiguredValue">0/6</div>
|
|
842
832
|
</div>
|
|
843
833
|
<div class="stat-card">
|
|
844
834
|
<div class="stat-label" id="statEnabledLabel">Enabled</div>
|
|
@@ -1068,6 +1058,53 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1068
1058
|
<div id="test-dingtalk-result" class="mt-4"></div>
|
|
1069
1059
|
</div>
|
|
1070
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>
|
|
1071
1108
|
</div>
|
|
1072
1109
|
</section>
|
|
1073
1110
|
|
|
@@ -1138,18 +1175,31 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1138
1175
|
<div class="form-hint" id="ai-claudeConfigPath-hint">Environment variables are saved to ~/.claude/settings.json</div>
|
|
1139
1176
|
</div>
|
|
1140
1177
|
<div class="form-group">
|
|
1141
|
-
<details id="
|
|
1142
|
-
<summary class="form-label" style="cursor: pointer;"
|
|
1143
|
-
<div
|
|
1144
|
-
|
|
1178
|
+
<details id="openImConfigContainer">
|
|
1179
|
+
<summary class="form-label" style="cursor: pointer;" id="openImConfigSummary">~/.open-im/config.json</summary>
|
|
1180
|
+
<div style="margin-top: 12px;">
|
|
1181
|
+
<div style="display:flex; justify-content:flex-end; gap:8px; margin-bottom:8px;">
|
|
1182
|
+
<button type="button" id="formatJsonButton" class="btn btn-sm btn-ghost"><span id="formatJsonButtonText">Format</span></button>
|
|
1183
|
+
<button type="button" id="resetJsonButton" class="btn btn-sm btn-ghost"><span id="resetJsonButtonText">Reset</span></button>
|
|
1184
|
+
</div>
|
|
1185
|
+
<textarea id="configJson" class="form-input mono" rows="16" style="font-family:monospace; font-size:13px; line-height:1.5; min-height:320px; resize:vertical; white-space:pre;" spellcheck="false"></textarea>
|
|
1186
|
+
<div id="jsonValidationMessage" class="message hidden" style="margin-top:6px;" aria-live="polite"></div>
|
|
1187
|
+
<div style="margin-top: 8px;">
|
|
1188
|
+
<button type="button" id="saveOpenImConfigBtn" class="btn btn-secondary btn-sm"><span id="saveOpenImConfigBtnText">Save</span></button>
|
|
1189
|
+
</div>
|
|
1145
1190
|
</div>
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1191
|
+
</details>
|
|
1192
|
+
<details id="claudeSettingsContainer" style="margin-top: 12px;">
|
|
1193
|
+
<summary class="form-label" style="cursor: pointer;" id="claudeSettingsSummary">~/.claude/settings.json</summary>
|
|
1194
|
+
<div style="margin-top: 12px;">
|
|
1195
|
+
<textarea
|
|
1196
|
+
id="claudeSettingsEditor"
|
|
1197
|
+
class="form-input mono"
|
|
1198
|
+
style="min-height: 180px; white-space: pre; font-family: var(--font-mono);"
|
|
1199
|
+
></textarea>
|
|
1200
|
+
<div style="margin-top: 8px;">
|
|
1201
|
+
<button type="button" id="saveClaudeSettingsBtn" class="btn btn-secondary btn-sm"><span id="saveClaudeSettingsBtnText">Save</span></button>
|
|
1202
|
+
</div>
|
|
1153
1203
|
</div>
|
|
1154
1204
|
</details>
|
|
1155
1205
|
</div>
|
|
@@ -1207,32 +1257,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1207
1257
|
</div>
|
|
1208
1258
|
</section>
|
|
1209
1259
|
|
|
1210
|
-
<!-- Config Editor Section -->
|
|
1211
|
-
<section class="section" id="configEditorSection" style="display:none">
|
|
1212
|
-
<div class="section-header">
|
|
1213
|
-
<h2 class="section-title" id="configEditorTitle">Config Editor</h2>
|
|
1214
|
-
<p class="section-description" id="configEditorHint">Edit ~/.open-im/config.json directly</p>
|
|
1215
|
-
</div>
|
|
1216
|
-
|
|
1217
|
-
<div class="card">
|
|
1218
|
-
<div class="card-body">
|
|
1219
|
-
<div class="form-group">
|
|
1220
|
-
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
|
1221
|
-
<label class="form-label" id="configJson-label">Configuration (JSON)</label>
|
|
1222
|
-
<div style="display:flex; gap:8px;">
|
|
1223
|
-
<button id="formatJsonButton" class="btn btn-sm btn-ghost">Format</button>
|
|
1224
|
-
<button id="resetJsonButton" class="btn btn-sm btn-ghost">Reset</button>
|
|
1225
|
-
</div>
|
|
1226
|
-
</div>
|
|
1227
|
-
<textarea id="configJson" class="form-input mono" rows="20" style="font-family:monospace; font-size:13px; line-height:1.5; min-height:400px; resize:vertical;" spellcheck="false"></textarea>
|
|
1228
|
-
<p class="form-hint" id="configJson-hint">Edit the configuration JSON. Changes will be saved when you click "Save Config" in the Service section.</p>
|
|
1229
|
-
</div>
|
|
1230
|
-
<div class="form-group">
|
|
1231
|
-
<div id="jsonValidationMessage" class="message hidden" aria-live="polite"></div>
|
|
1232
|
-
</div>
|
|
1233
|
-
</div>
|
|
1234
|
-
</div>
|
|
1235
|
-
</section>
|
|
1236
1260
|
</div>
|
|
1237
1261
|
</main>
|
|
1238
1262
|
</div>
|
package/dist/config-web.js
CHANGED
|
@@ -359,6 +359,7 @@ function createProbeConfig(values) {
|
|
|
359
359
|
wechatAllowedUserIds: [],
|
|
360
360
|
weworkAllowedUserIds: [],
|
|
361
361
|
dingtalkAllowedUserIds: [],
|
|
362
|
+
workbuddyAllowedUserIds: [],
|
|
362
363
|
aiCommand: "claude",
|
|
363
364
|
codexCliPath: "codex",
|
|
364
365
|
claudeWorkDir: process.cwd(),
|
|
@@ -673,6 +674,47 @@ export async function startWebConfigServer(options) {
|
|
|
673
674
|
});
|
|
674
675
|
return;
|
|
675
676
|
}
|
|
677
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/config/file") {
|
|
678
|
+
try {
|
|
679
|
+
let contents = "{}";
|
|
680
|
+
if (existsSync(CONFIG_PATH)) {
|
|
681
|
+
contents = readFileSync(CONFIG_PATH, "utf-8");
|
|
682
|
+
}
|
|
683
|
+
json(response, 200, { path: CONFIG_PATH, contents });
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
687
|
+
}
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (request.method === "POST" && requestUrl.pathname === "/api/config/file") {
|
|
691
|
+
try {
|
|
692
|
+
const body = await readJson(request);
|
|
693
|
+
const raw = body.contents ?? "";
|
|
694
|
+
if (!raw.trim()) {
|
|
695
|
+
json(response, 400, { error: "contents is required" });
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
JSON.parse(raw);
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
json(response, 400, { error: "Invalid JSON" });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const dir = dirname(CONFIG_PATH);
|
|
706
|
+
if (!existsSync(dir)) {
|
|
707
|
+
mkdirSync(dir, { recursive: true });
|
|
708
|
+
}
|
|
709
|
+
writeFileSync(CONFIG_PATH, raw, "utf-8");
|
|
710
|
+
loadConfig();
|
|
711
|
+
json(response, 200, { message: "Config file saved.", path: CONFIG_PATH });
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
json(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
676
718
|
if (request.method === "POST" && requestUrl.pathname === "/api/config/validate") {
|
|
677
719
|
try {
|
|
678
720
|
const body = await readJson(request);
|