@wu529778790/open-im 1.7.0-beta.2 → 1.7.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/handler.d.ts +0 -6
- package/dist/commands/handler.js +0 -62
- package/dist/config-web.js +3 -15
- package/dist/config.d.ts +0 -10
- package/dist/config.js +1 -54
- package/dist/dingtalk/event-handler.js +2 -4
- package/dist/dingtalk/message-sender.d.ts +0 -2
- package/dist/dingtalk/message-sender.js +1 -10
- package/dist/feishu/event-handler.js +4 -157
- package/dist/feishu/message-sender.d.ts +0 -20
- package/dist/feishu/message-sender.js +0 -155
- package/dist/index.js +0 -13
- package/dist/manager.js +5 -2
- package/dist/qq/event-handler.js +2 -4
- package/dist/qq/event-handler.test.js +0 -1
- package/dist/qq/message-sender.d.ts +0 -1
- package/dist/qq/message-sender.js +1 -6
- package/dist/setup.js +3 -7
- package/dist/shared/ai-task.js +2 -25
- package/dist/shared/system-messages.d.ts +0 -2
- package/dist/shared/system-messages.js +0 -32
- package/dist/shared/system-messages.test.js +1 -8
- package/dist/telegram/event-handler.js +2 -24
- package/dist/telegram/message-sender.d.ts +0 -1
- package/dist/telegram/message-sender.js +0 -14
- package/dist/wechat/event-handler.js +2 -28
- package/dist/wechat/message-sender.d.ts +0 -2
- package/dist/wechat/message-sender.js +0 -31
- package/dist/wework/event-handler.js +2 -4
- package/dist/wework/message-sender.d.ts +0 -2
- package/dist/wework/message-sender.js +1 -23
- package/package.json +1 -1
- package/dist/hook/permission-server.d.ts +0 -38
- package/dist/hook/permission-server.js +0 -301
- package/dist/hook/permission-server.test.d.ts +0 -1
- package/dist/hook/permission-server.test.js +0 -12
- package/dist/permission-mode/session-mode.d.ts +0 -7
- package/dist/permission-mode/session-mode.js +0 -59
- package/dist/permission-mode/types.d.ts +0 -11
- package/dist/permission-mode/types.js +0 -29
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { type Config } from '../config.js';
|
|
2
2
|
import type { SessionManager } from '../session/session-manager.js';
|
|
3
3
|
import type { RequestQueue } from '../queue/request-queue.js';
|
|
4
|
-
import { type PermissionMode } from '../permission-mode/types.js';
|
|
5
4
|
import type { ThreadContext } from '../shared/types.js';
|
|
6
5
|
export type { ThreadContext };
|
|
7
6
|
export interface MessageSender {
|
|
8
7
|
sendTextReply(chatId: string, text: string, threadCtx?: ThreadContext): Promise<void>;
|
|
9
8
|
sendDirectorySelection?(chatId: string, currentDir: string, userId: string): Promise<void>;
|
|
10
|
-
sendModeCard?(chatId: string, userId: string, currentMode: PermissionMode): Promise<void>;
|
|
11
|
-
sendModeKeyboard?(chatId: string, userId: string, currentMode: PermissionMode): Promise<void>;
|
|
12
9
|
}
|
|
13
10
|
export interface CommandHandlerDeps {
|
|
14
11
|
config: Config;
|
|
@@ -22,15 +19,12 @@ export declare class CommandHandler {
|
|
|
22
19
|
private deps;
|
|
23
20
|
constructor(deps: CommandHandlerDeps);
|
|
24
21
|
dispatch(text: string, chatId: string, userId: string, platform: 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wechat' | 'wework', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
25
|
-
private handleMode;
|
|
26
22
|
private getClearHistoryHint;
|
|
27
23
|
private handleHelp;
|
|
28
24
|
private handleNew;
|
|
29
25
|
private handlePwd;
|
|
30
26
|
private handleStatus;
|
|
31
27
|
private handleCd;
|
|
32
|
-
private handleAllow;
|
|
33
|
-
private handleDeny;
|
|
34
28
|
private getAiVersion;
|
|
35
29
|
}
|
|
36
30
|
/**
|
package/dist/commands/handler.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
2
|
-
import { resolveLatestPermission, getPendingCount } from '../hook/permission-server.js';
|
|
3
2
|
import { escapePathForMarkdown } from '../shared/utils.js';
|
|
4
|
-
import { getPermissionMode, setPermissionMode } from '../permission-mode/session-mode.js';
|
|
5
|
-
import { MODE_LABELS, MODE_DESCRIPTIONS, parsePermissionMode } from '../permission-mode/types.js';
|
|
6
3
|
import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
|
|
7
4
|
import { execFile } from 'node:child_process';
|
|
8
5
|
import { readdirSync } from 'node:fs';
|
|
@@ -20,18 +17,12 @@ export class CommandHandler {
|
|
|
20
17
|
}
|
|
21
18
|
if (t === '/help')
|
|
22
19
|
return this.handleHelp(chatId, platform);
|
|
23
|
-
if (t === '/mode' || t.startsWith('/mode '))
|
|
24
|
-
return this.handleMode(chatId, userId, platform, t.slice(6).trim());
|
|
25
20
|
if (t === '/new')
|
|
26
21
|
return this.handleNew(chatId, userId, platform);
|
|
27
22
|
if (t === '/pwd')
|
|
28
23
|
return this.handlePwd(chatId, userId);
|
|
29
24
|
if (t === '/status')
|
|
30
25
|
return this.handleStatus(chatId, userId, platform);
|
|
31
|
-
if (t === '/allow' || t === '/y')
|
|
32
|
-
return this.handleAllow(chatId);
|
|
33
|
-
if (t === '/deny' || t === '/n')
|
|
34
|
-
return this.handleDeny(chatId);
|
|
35
26
|
if (t === '/cd' || t.startsWith('/cd ')) {
|
|
36
27
|
return this.handleCd(chatId, userId, t.slice(3).trim(), platform);
|
|
37
28
|
}
|
|
@@ -42,35 +33,6 @@ export class CommandHandler {
|
|
|
42
33
|
}
|
|
43
34
|
return false;
|
|
44
35
|
}
|
|
45
|
-
async handleMode(chatId, userId, platform, arg) {
|
|
46
|
-
const defaultMode = this.deps.config.defaultPermissionMode;
|
|
47
|
-
const currentMode = getPermissionMode(userId, defaultMode);
|
|
48
|
-
if (arg) {
|
|
49
|
-
const parsed = parsePermissionMode(arg);
|
|
50
|
-
if (parsed) {
|
|
51
|
-
setPermissionMode(userId, parsed);
|
|
52
|
-
await this.deps.sender.sendTextReply(chatId, `✅ 权限模式已切换为 **${MODE_LABELS[parsed]}**\n${MODE_DESCRIPTIONS[parsed]}`);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
await this.deps.sender.sendTextReply(chatId, `无效模式: ${arg}\n可用: ask, accept-edits, plan, yolo`);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
if (platform === 'feishu' && this.deps.sender.sendModeCard) {
|
|
59
|
-
await this.deps.sender.sendModeCard(chatId, userId, currentMode);
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
if (platform === 'telegram' && this.deps.sender.sendModeKeyboard) {
|
|
63
|
-
await this.deps.sender.sendModeKeyboard(chatId, userId, currentMode);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
const lines = [
|
|
67
|
-
`🔐 **权限模式** (当前: ${MODE_LABELS[currentMode]})`,
|
|
68
|
-
'',
|
|
69
|
-
...['ask', 'accept-edits', 'plan', 'yolo'].map((m) => `• \`/mode ${m}\` - ${MODE_LABELS[m]}: ${MODE_DESCRIPTIONS[m]}`),
|
|
70
|
-
];
|
|
71
|
-
await this.deps.sender.sendTextReply(chatId, lines.join('\n'));
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
36
|
getClearHistoryHint(platform) {
|
|
75
37
|
return platform === 'feishu'
|
|
76
38
|
? '💡 提示:如需清除本对话的历史消息,请点击飞书聊天右上角「...」→ 清除聊天记录'
|
|
@@ -85,13 +47,10 @@ export class CommandHandler {
|
|
|
85
47
|
'📋 可用命令:',
|
|
86
48
|
'',
|
|
87
49
|
'/help - 显示帮助',
|
|
88
|
-
'/mode - 切换权限模式(安全/编辑放行/只读/YOLO)',
|
|
89
50
|
'/new - 开始新会话(AI 上下文重置)',
|
|
90
51
|
'/status - 显示状态',
|
|
91
52
|
'/cd <路径> - 切换工作目录',
|
|
92
53
|
'/pwd - 当前工作目录',
|
|
93
|
-
'/allow (/y) - 允许权限请求',
|
|
94
|
-
'/deny (/n) - 拒绝权限请求',
|
|
95
54
|
'',
|
|
96
55
|
this.getClearHistoryHint(platform),
|
|
97
56
|
].join('\n');
|
|
@@ -150,27 +109,6 @@ export class CommandHandler {
|
|
|
150
109
|
}
|
|
151
110
|
return true;
|
|
152
111
|
}
|
|
153
|
-
async handleAllow(chatId) {
|
|
154
|
-
const reqId = resolveLatestPermission(chatId, 'allow');
|
|
155
|
-
if (reqId) {
|
|
156
|
-
const remaining = getPendingCount(chatId);
|
|
157
|
-
await this.deps.sender.sendTextReply(chatId, `✅ 权限已允许${remaining > 0 ? `(还有 ${remaining} 个待确认)` : ''}`);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
async handleDeny(chatId) {
|
|
165
|
-
const reqId = resolveLatestPermission(chatId, 'deny');
|
|
166
|
-
if (reqId) {
|
|
167
|
-
await this.deps.sender.sendTextReply(chatId, '❌ 权限已拒绝');
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
await this.deps.sender.sendTextReply(chatId, 'ℹ️ 没有待确认的权限请求');
|
|
171
|
-
}
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
112
|
getAiVersion(aiCommand) {
|
|
175
113
|
if (aiCommand === 'claude') {
|
|
176
114
|
// Claude 使用 SDK,返回 SDK 版本
|
package/dist/config-web.js
CHANGED
|
@@ -134,7 +134,6 @@ function buildInitialPayload(file) {
|
|
|
134
134
|
ai: {
|
|
135
135
|
aiCommand: file.aiCommand ?? "claude",
|
|
136
136
|
claudeWorkDir: file.tools?.claude?.workDir ?? process.cwd(),
|
|
137
|
-
claudeSkipPermissions: file.tools?.claude?.skipPermissions ?? true,
|
|
138
137
|
claudeTimeoutMs: file.tools?.claude?.timeoutMs ?? 600000,
|
|
139
138
|
claudeConfigPath: process.platform === 'win32'
|
|
140
139
|
? getClaudeConfigHome() + "\\.claude\\settings.json"
|
|
@@ -148,8 +147,6 @@ function buildInitialPayload(file) {
|
|
|
148
147
|
codexCliPath: file.tools?.codex?.cliPath ?? "codex",
|
|
149
148
|
codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
|
|
150
149
|
codexProxy: file.tools?.codex?.proxy ?? "",
|
|
151
|
-
defaultPermissionMode: file.defaultPermissionMode ?? "ask",
|
|
152
|
-
hookPort: file.hookPort ?? 35801,
|
|
153
150
|
logDir: file.logDir ?? "",
|
|
154
151
|
logLevel: file.logLevel ?? "default",
|
|
155
152
|
},
|
|
@@ -186,8 +183,6 @@ function validatePayload(payload) {
|
|
|
186
183
|
errors.push("Codex timeout must be positive.");
|
|
187
184
|
if (!Number.isFinite(payload.ai.codebuddyTimeoutMs) || payload.ai.codebuddyTimeoutMs <= 0)
|
|
188
185
|
errors.push("CodeBuddy timeout must be positive.");
|
|
189
|
-
if (!Number.isFinite(payload.ai.hookPort) || payload.ai.hookPort <= 0)
|
|
190
|
-
errors.push("Hook port must be positive.");
|
|
191
186
|
return errors;
|
|
192
187
|
}
|
|
193
188
|
function validateConfigForPlatform(platform, config) {
|
|
@@ -266,12 +261,9 @@ function createProbeConfig(values) {
|
|
|
266
261
|
aiCommand: "claude",
|
|
267
262
|
codexCliPath: "codex",
|
|
268
263
|
claudeWorkDir: process.cwd(),
|
|
269
|
-
claudeSkipPermissions: true,
|
|
270
|
-
defaultPermissionMode: "ask",
|
|
271
264
|
claudeTimeoutMs: 600000,
|
|
272
265
|
codexTimeoutMs: 600000,
|
|
273
266
|
codebuddyTimeoutMs: 600000,
|
|
274
|
-
hookPort: 35801,
|
|
275
267
|
logDir: "",
|
|
276
268
|
logLevel: "INFO",
|
|
277
269
|
codebuddyCliPath: "codebuddy",
|
|
@@ -397,15 +389,12 @@ function toFileConfig(payload, existing) {
|
|
|
397
389
|
return {
|
|
398
390
|
...existing,
|
|
399
391
|
aiCommand: payload.ai.aiCommand,
|
|
400
|
-
defaultPermissionMode: payload.ai.defaultPermissionMode ?? existing.defaultPermissionMode ?? "ask",
|
|
401
|
-
hookPort: payload.ai.hookPort,
|
|
402
392
|
logDir: payload.ai.logDir === undefined ? existing.logDir : clean(payload.ai.logDir),
|
|
403
393
|
logLevel: payload.ai.logLevel === "default" ? undefined : payload.ai.logLevel,
|
|
404
394
|
tools: {
|
|
405
395
|
claude: {
|
|
406
396
|
...existing.tools?.claude,
|
|
407
397
|
workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
|
|
408
|
-
skipPermissions: payload.ai.claudeSkipPermissions,
|
|
409
398
|
timeoutMs: payload.ai.claudeTimeoutMs,
|
|
410
399
|
proxy: clean(payload.ai.claudeProxy),
|
|
411
400
|
// model is now saved to ~/.claude/settings.json as ANTHROPIC_MODEL
|
|
@@ -414,14 +403,12 @@ function toFileConfig(payload, existing) {
|
|
|
414
403
|
...existing.tools?.codex,
|
|
415
404
|
cliPath: clean(payload.ai.codexCliPath) ?? "codex",
|
|
416
405
|
workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
|
|
417
|
-
skipPermissions: existing.tools?.codex?.skipPermissions ?? payload.ai.claudeSkipPermissions,
|
|
418
406
|
timeoutMs: payload.ai.codexTimeoutMs,
|
|
419
407
|
proxy: clean(payload.ai.codexProxy),
|
|
420
408
|
},
|
|
421
409
|
codebuddy: {
|
|
422
410
|
...existing.tools?.codebuddy,
|
|
423
411
|
cliPath: clean(payload.ai.codebuddyCliPath) ?? "codebuddy",
|
|
424
|
-
skipPermissions: existing.tools?.codebuddy?.skipPermissions ?? payload.ai.claudeSkipPermissions,
|
|
425
412
|
timeoutMs: payload.ai.codebuddyTimeoutMs,
|
|
426
413
|
},
|
|
427
414
|
},
|
|
@@ -623,8 +610,9 @@ export async function startWebConfigServer(options) {
|
|
|
623
610
|
}
|
|
624
611
|
if (request.method === "POST" && requestUrl.pathname === "/api/service/start") {
|
|
625
612
|
try {
|
|
626
|
-
loadConfig();
|
|
627
|
-
const
|
|
613
|
+
const config = loadConfig();
|
|
614
|
+
const workDir = config.claudeWorkDir ?? options.cwd;
|
|
615
|
+
const started = startBackgroundService(workDir);
|
|
628
616
|
json(response, 200, { message: `Bridge started with pid ${started.pid}.`, pid: started.pid });
|
|
629
617
|
if (!options.persistent) {
|
|
630
618
|
setTimeout(() => finishFlow("saved"), 120);
|
package/dist/config.d.ts
CHANGED
|
@@ -38,10 +38,7 @@ export interface Config {
|
|
|
38
38
|
codexTimeoutMs: number;
|
|
39
39
|
codebuddyTimeoutMs: number;
|
|
40
40
|
claudeWorkDir: string;
|
|
41
|
-
claudeSkipPermissions: boolean;
|
|
42
|
-
defaultPermissionMode: 'ask' | 'accept-edits' | 'plan' | 'yolo';
|
|
43
41
|
claudeModel?: string;
|
|
44
|
-
hookPort: number;
|
|
45
42
|
logDir: string;
|
|
46
43
|
logLevel: LogLevel;
|
|
47
44
|
platforms: {
|
|
@@ -137,7 +134,6 @@ export interface FilePlatformDingtalk {
|
|
|
137
134
|
}
|
|
138
135
|
export interface FileToolClaude {
|
|
139
136
|
workDir?: string;
|
|
140
|
-
skipPermissions?: boolean;
|
|
141
137
|
timeoutMs?: number;
|
|
142
138
|
model?: string;
|
|
143
139
|
proxy?: string;
|
|
@@ -146,16 +142,12 @@ export interface FileToolCodex {
|
|
|
146
142
|
cliPath?: string;
|
|
147
143
|
workDir?: string;
|
|
148
144
|
timeoutMs?: number;
|
|
149
|
-
/** 是否跳过权限确认(默认 true) */
|
|
150
|
-
skipPermissions?: boolean;
|
|
151
145
|
/** HTTP/HTTPS 代理,用于访问 chatgpt.com(如 http://127.0.0.1:7890) */
|
|
152
146
|
proxy?: string;
|
|
153
147
|
}
|
|
154
148
|
export interface FileToolCodeBuddy {
|
|
155
149
|
cliPath?: string;
|
|
156
150
|
timeoutMs?: number;
|
|
157
|
-
/** 是否跳过权限确认(默认 true) */
|
|
158
|
-
skipPermissions?: boolean;
|
|
159
151
|
}
|
|
160
152
|
export interface FileConfig {
|
|
161
153
|
telegramBotToken?: string;
|
|
@@ -177,8 +169,6 @@ export interface FileConfig {
|
|
|
177
169
|
codex?: FileToolCodex;
|
|
178
170
|
codebuddy?: FileToolCodeBuddy;
|
|
179
171
|
};
|
|
180
|
-
defaultPermissionMode?: 'ask' | 'accept-edits' | 'plan' | 'yolo';
|
|
181
|
-
hookPort?: number;
|
|
182
172
|
logDir?: string;
|
|
183
173
|
logLevel?: LogLevel;
|
|
184
174
|
}
|
package/dist/config.js
CHANGED
|
@@ -19,7 +19,7 @@ const CODEX_AUTH_PATHS = [
|
|
|
19
19
|
join(homedir(), 'AppData', 'Roaming', 'codex', 'auth.json'),
|
|
20
20
|
];
|
|
21
21
|
const OLD_ROOT_KEYS = [
|
|
22
|
-
'claudeWorkDir',
|
|
22
|
+
'claudeWorkDir',
|
|
23
23
|
'claudeTimeoutMs', 'claudeModel',
|
|
24
24
|
];
|
|
25
25
|
function hasOldConfigFormat(raw) {
|
|
@@ -54,7 +54,6 @@ function migrateToNewConfigFormat(raw) {
|
|
|
54
54
|
claude: {
|
|
55
55
|
...tc,
|
|
56
56
|
workDir: tc.workDir ?? raw.claudeWorkDir ?? process.cwd(),
|
|
57
|
-
skipPermissions: tc.skipPermissions ?? raw.claudeSkipPermissions ?? true,
|
|
58
57
|
timeoutMs: tc.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
|
|
59
58
|
model: tc.model ?? raw.claudeModel,
|
|
60
59
|
},
|
|
@@ -62,14 +61,12 @@ function migrateToNewConfigFormat(raw) {
|
|
|
62
61
|
...tcod,
|
|
63
62
|
cliPath: tcod.cliPath ?? 'codex',
|
|
64
63
|
workDir: tcod.workDir ?? raw.claudeWorkDir ?? process.cwd(),
|
|
65
|
-
skipPermissions: tcod.skipPermissions ?? raw.claudeSkipPermissions ?? true,
|
|
66
64
|
timeoutMs: tcod.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
|
|
67
65
|
proxy: tcod.proxy,
|
|
68
66
|
},
|
|
69
67
|
codebuddy: {
|
|
70
68
|
...tcb,
|
|
71
69
|
cliPath: tcb.cliPath ?? 'codebuddy',
|
|
72
|
-
skipPermissions: tcb.skipPermissions ?? raw.claudeSkipPermissions ?? true,
|
|
73
70
|
timeoutMs: tcb.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
|
|
74
71
|
},
|
|
75
72
|
};
|
|
@@ -78,36 +75,6 @@ function migrateToNewConfigFormat(raw) {
|
|
|
78
75
|
}
|
|
79
76
|
return migrated;
|
|
80
77
|
}
|
|
81
|
-
/** 确保 codex/codebuddy 有 skipPermissions(缺失时从 claude 继承并写回) */
|
|
82
|
-
function ensureToolsSkipPermissions(raw) {
|
|
83
|
-
const tools = raw.tools;
|
|
84
|
-
if (!tools || typeof tools !== 'object')
|
|
85
|
-
return false;
|
|
86
|
-
const tc = tools.claude || {};
|
|
87
|
-
const fallback = tc.skipPermissions ?? true;
|
|
88
|
-
let changed = false;
|
|
89
|
-
if (tools.codex && typeof tools.codex === 'object') {
|
|
90
|
-
const cod = tools.codex;
|
|
91
|
-
if (cod.skipPermissions === undefined) {
|
|
92
|
-
cod.skipPermissions = fallback;
|
|
93
|
-
changed = true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (tools.codebuddy && typeof tools.codebuddy === 'object') {
|
|
97
|
-
const codebuddy = tools.codebuddy;
|
|
98
|
-
if (codebuddy.skipPermissions === undefined) {
|
|
99
|
-
codebuddy.skipPermissions = fallback;
|
|
100
|
-
changed = true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (changed) {
|
|
104
|
-
const dir = dirname(CONFIG_PATH);
|
|
105
|
-
if (!existsSync(dir))
|
|
106
|
-
mkdirSync(dir, { recursive: true });
|
|
107
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(raw, null, 2), 'utf-8');
|
|
108
|
-
}
|
|
109
|
-
return changed;
|
|
110
|
-
}
|
|
111
78
|
export function loadFileConfig() {
|
|
112
79
|
try {
|
|
113
80
|
const raw = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
@@ -121,7 +88,6 @@ export function loadFileConfig() {
|
|
|
121
88
|
writeFileSync(CONFIG_PATH, JSON.stringify(migrated, null, 2), 'utf-8');
|
|
122
89
|
return migrated;
|
|
123
90
|
}
|
|
124
|
-
ensureToolsSkipPermissions(raw);
|
|
125
91
|
return raw;
|
|
126
92
|
}
|
|
127
93
|
catch {
|
|
@@ -401,19 +367,6 @@ export function loadConfig() {
|
|
|
401
367
|
}
|
|
402
368
|
}
|
|
403
369
|
const claudeWorkDir = process.env.CLAUDE_WORK_DIR ?? tc.workDir ?? process.cwd();
|
|
404
|
-
// 按当前 AI 工具选择 skipPermissions:claude 用 tools.claude,其他 CLI 工具优先读各自配置,再回退到 claude
|
|
405
|
-
const claudeSkipPermissions = (() => {
|
|
406
|
-
if (process.env.CLAUDE_SKIP_PERMISSIONS !== undefined)
|
|
407
|
-
return process.env.CLAUDE_SKIP_PERMISSIONS === 'true';
|
|
408
|
-
if (process.env.CODEBUDDY_SKIP_PERMISSIONS !== undefined && aiCommand === 'codebuddy')
|
|
409
|
-
return process.env.CODEBUDDY_SKIP_PERMISSIONS === 'true';
|
|
410
|
-
if (aiCommand === 'codex')
|
|
411
|
-
return tcod.skipPermissions ?? tc.skipPermissions ?? true;
|
|
412
|
-
if (aiCommand === 'codebuddy')
|
|
413
|
-
return tcb.skipPermissions ?? tc.skipPermissions ?? true;
|
|
414
|
-
return tc.skipPermissions ?? true;
|
|
415
|
-
})();
|
|
416
|
-
const defaultPermissionMode = (file.defaultPermissionMode ?? 'ask');
|
|
417
370
|
const claudeTimeoutMs = process.env.CLAUDE_TIMEOUT_MS !== undefined
|
|
418
371
|
? parseInt(process.env.CLAUDE_TIMEOUT_MS, 10) || 600000
|
|
419
372
|
: tc.timeoutMs ?? 600000;
|
|
@@ -423,9 +376,6 @@ export function loadConfig() {
|
|
|
423
376
|
const codebuddyTimeoutMs = process.env.CODEBUDDY_TIMEOUT_MS !== undefined
|
|
424
377
|
? parseInt(process.env.CODEBUDDY_TIMEOUT_MS, 10) || 600000
|
|
425
378
|
: tcb.timeoutMs ?? 600000;
|
|
426
|
-
const hookPort = process.env.HOOK_PORT !== undefined
|
|
427
|
-
? parseInt(process.env.HOOK_PORT, 10) || 35801
|
|
428
|
-
: file.hookPort ?? 35801;
|
|
429
379
|
// 6. 校验 Claude API 凭证(SDK 模式需要)
|
|
430
380
|
// 支持:官方 API Key、Auth Token、或自定义 API(第三方模型等,BASE_URL + token)
|
|
431
381
|
if (aiCommand === 'claude') {
|
|
@@ -655,13 +605,10 @@ export function loadConfig() {
|
|
|
655
605
|
codebuddyCliPath,
|
|
656
606
|
codexProxy,
|
|
657
607
|
claudeWorkDir,
|
|
658
|
-
claudeSkipPermissions,
|
|
659
|
-
defaultPermissionMode,
|
|
660
608
|
claudeTimeoutMs,
|
|
661
609
|
codexTimeoutMs,
|
|
662
610
|
codebuddyTimeoutMs,
|
|
663
611
|
claudeModel: process.env.CLAUDE_MODEL ?? tc.model,
|
|
664
|
-
hookPort,
|
|
665
612
|
logDir,
|
|
666
613
|
logLevel,
|
|
667
614
|
platforms,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
2
2
|
import { AccessControl } from '../access/access-control.js';
|
|
3
3
|
import { RequestQueue } from '../queue/request-queue.js';
|
|
4
|
-
import { configureDingTalkMessageSender, sendThinkingMessage, updateMessage, sendFinalMessages, sendErrorMessage, sendTextReply, sendImageReply, startTypingLoop,
|
|
4
|
+
import { configureDingTalkMessageSender, sendThinkingMessage, updateMessage, sendFinalMessages, sendErrorMessage, sendTextReply, sendImageReply, startTypingLoop, sendDirectorySelection, } from './message-sender.js';
|
|
5
5
|
import { ackMessage, downloadRobotMessageFile, registerSessionWebhook } from './client.js';
|
|
6
|
-
import { registerPermissionSender } from '../hook/permission-server.js';
|
|
7
6
|
import { CommandHandler } from '../commands/handler.js';
|
|
8
7
|
import { getAdapter } from '../adapters/registry.js';
|
|
9
8
|
import { runAITask } from '../shared/ai-task.js';
|
|
@@ -163,10 +162,9 @@ export function setupDingTalkHandlers(config, sessionManager) {
|
|
|
163
162
|
config,
|
|
164
163
|
sessionManager,
|
|
165
164
|
requestQueue,
|
|
166
|
-
sender: { sendTextReply,
|
|
165
|
+
sender: { sendTextReply, sendDirectorySelection },
|
|
167
166
|
getRunningTasksSize: () => runningTasks.size,
|
|
168
167
|
});
|
|
169
|
-
registerPermissionSender('dingtalk', { sendTextReply, sendPermissionCard });
|
|
170
168
|
async function enqueuePrompt(userId, chatId, prompt, dingtalkTarget) {
|
|
171
169
|
const workDir = sessionManager.getWorkDir(userId);
|
|
172
170
|
const convId = sessionManager.getConvId(userId);
|
|
@@ -14,8 +14,6 @@ export declare function sendErrorMessage(chatId: string, messageId: string, erro
|
|
|
14
14
|
export declare function sendTextReply(chatId: string, text: string, _threadCtx?: ThreadContext | string): Promise<void>;
|
|
15
15
|
export declare function sendImageReply(chatId: string, imagePath: string): Promise<void>;
|
|
16
16
|
export declare function sendProactiveTextReply(target: string | DingTalkActiveTarget, text: string): Promise<void>;
|
|
17
|
-
export declare function sendPermissionCard(chatId: string, requestId: string, toolName: string, toolInput: string): Promise<void>;
|
|
18
|
-
export declare function sendModeCard(chatId: string, _userId: string, currentMode: string): Promise<void>;
|
|
19
17
|
export declare function sendDirectorySelection(chatId: string, currentDir: string, userId: string): Promise<void>;
|
|
20
18
|
export declare function startTypingLoop(_chatId: string): () => void;
|
|
21
19
|
export {};
|
|
@@ -8,7 +8,7 @@ import { MAX_DINGTALK_MESSAGE_LENGTH } from '../constants.js';
|
|
|
8
8
|
import { buildImageFallbackMessage } from '../channels/capabilities.js';
|
|
9
9
|
import { buildMessageTitle, OPEN_IM_SYSTEM_TITLE } from '../shared/message-title.js';
|
|
10
10
|
import { buildTextNote } from '../shared/message-note.js';
|
|
11
|
-
import { buildDirectoryMessage,
|
|
11
|
+
import { buildDirectoryMessage, } from '../shared/system-messages.js';
|
|
12
12
|
const log = createLogger('DingTalkSender');
|
|
13
13
|
const STATUS_ICONS = {
|
|
14
14
|
thinking: '🔵',
|
|
@@ -344,15 +344,6 @@ export async function sendProactiveTextReply(target, text) {
|
|
|
344
344
|
const targetId = typeof target === 'string' ? target : target.chatId;
|
|
345
345
|
log.info(`Proactive text sent to DingTalk chat ${targetId}`);
|
|
346
346
|
}
|
|
347
|
-
export async function sendPermissionCard(chatId, requestId, toolName, toolInput) {
|
|
348
|
-
const message = buildPermissionRequestMessage(toolName, toolInput, requestId);
|
|
349
|
-
await sendTextWithRetry(chatId, message);
|
|
350
|
-
}
|
|
351
|
-
export async function sendModeCard(chatId, _userId, currentMode) {
|
|
352
|
-
const { MODE_LABELS } = await import('../permission-mode/types.js');
|
|
353
|
-
const message = buildModeMessage(MODE_LABELS[currentMode] || currentMode);
|
|
354
|
-
await sendTextWithRetry(chatId, message);
|
|
355
|
-
}
|
|
356
347
|
export async function sendDirectorySelection(chatId, currentDir, userId) {
|
|
357
348
|
const directories = listDirectories(currentDir);
|
|
358
349
|
const dirName = basename(currentDir) || currentDir;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
2
2
|
import { AccessControl } from '../access/access-control.js';
|
|
3
3
|
import { RequestQueue } from '../queue/request-queue.js';
|
|
4
|
-
import { sendTextReply,
|
|
5
|
-
import { registerPermissionSender, resolvePermissionById } from '../hook/permission-server.js';
|
|
6
|
-
import { setPermissionMode } from '../permission-mode/session-mode.js';
|
|
7
|
-
import { MODE_LABELS } from '../permission-mode/types.js';
|
|
4
|
+
import { sendTextReply, startTypingLoop, sendImageReply, sendThinkingCard, streamContentUpdate, sendFinalCards, sendErrorCard, } from './message-sender.js';
|
|
8
5
|
import { CommandHandler } from '../commands/handler.js';
|
|
9
6
|
import { getAdapter } from '../adapters/registry.js';
|
|
10
7
|
import { runAITask } from '../shared/ai-task.js';
|
|
@@ -32,50 +29,6 @@ async function downloadFeishuMessageResource(client, messageId, fileKey, type, o
|
|
|
32
29
|
await response.writeFile(targetPath);
|
|
33
30
|
return targetPath;
|
|
34
31
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Send permission prompt card with interactive buttons
|
|
37
|
-
*/
|
|
38
|
-
async function sendPermissionCard(chatId, requestId, toolName, toolInput) {
|
|
39
|
-
const { getClient } = await import('./client.js');
|
|
40
|
-
const client = getClient();
|
|
41
|
-
// Format tool input for display
|
|
42
|
-
let formattedInput;
|
|
43
|
-
if (toolInput.length > 300) {
|
|
44
|
-
formattedInput = toolInput.slice(0, 300) + '...';
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
formattedInput = toolInput;
|
|
48
|
-
}
|
|
49
|
-
const content = `**工具:** \`${toolName}\`
|
|
50
|
-
|
|
51
|
-
**参数:**
|
|
52
|
-
\`\`\`
|
|
53
|
-
${formattedInput}
|
|
54
|
-
\`\`\`
|
|
55
|
-
|
|
56
|
-
**请求 ID:** \`${requestId.slice(-8)}\`
|
|
57
|
-
|
|
58
|
-
请点击下方按钮进行处理。`;
|
|
59
|
-
const cardContent = createFeishuButtonCard('权限请求', content, [
|
|
60
|
-
{ label: '允许', value: `allow_${requestId}`, type: 'primary' },
|
|
61
|
-
{ label: '拒绝', value: `deny_${requestId}`, type: 'default' },
|
|
62
|
-
]);
|
|
63
|
-
try {
|
|
64
|
-
await client.im.message.create({
|
|
65
|
-
data: {
|
|
66
|
-
receive_id: chatId,
|
|
67
|
-
msg_type: 'interactive',
|
|
68
|
-
content: cardContent,
|
|
69
|
-
},
|
|
70
|
-
params: { receive_id_type: 'chat_id' },
|
|
71
|
-
});
|
|
72
|
-
log.info(`Permission card sent for request ${requestId}`);
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
log.error('Failed to send permission card:', err);
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
32
|
export function setupFeishuHandlers(config, sessionManager) {
|
|
80
33
|
const accessControl = new AccessControl(config.feishuAllowedUserIds);
|
|
81
34
|
const requestQueue = new RequestQueue();
|
|
@@ -85,10 +38,9 @@ export function setupFeishuHandlers(config, sessionManager) {
|
|
|
85
38
|
config,
|
|
86
39
|
sessionManager,
|
|
87
40
|
requestQueue,
|
|
88
|
-
sender: { sendTextReply
|
|
41
|
+
sender: { sendTextReply },
|
|
89
42
|
getRunningTasksSize: () => runningTasks.size,
|
|
90
43
|
});
|
|
91
|
-
registerPermissionSender('feishu', { sendTextReply, sendPermissionCard });
|
|
92
44
|
async function handleAIRequest(userId, chatId, prompt, workDir, convId, _threadCtx, replyToMessageId) {
|
|
93
45
|
log.info(`[AI_REQUEST] userId=${userId}, chatId=${chatId}, promptLength=${prompt.length}`);
|
|
94
46
|
log.info(`[AI_REQUEST] Full prompt: "${prompt}"`);
|
|
@@ -142,41 +94,6 @@ export function setupFeishuHandlers(config, sessionManager) {
|
|
|
142
94
|
sendImage: (path) => sendImageReply(chatId, path),
|
|
143
95
|
});
|
|
144
96
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Parse permission button value from card action (兼容多种格式)
|
|
147
|
-
*/
|
|
148
|
-
function parsePermissionActionValue(raw) {
|
|
149
|
-
if (!raw)
|
|
150
|
-
return null;
|
|
151
|
-
let buttonValue;
|
|
152
|
-
if (typeof raw === 'string') {
|
|
153
|
-
try {
|
|
154
|
-
const parsed = JSON.parse(raw);
|
|
155
|
-
if (parsed.action === 'permission' && parsed.value)
|
|
156
|
-
buttonValue = parsed.value;
|
|
157
|
-
else if (raw.startsWith('allow_') || raw.startsWith('deny_'))
|
|
158
|
-
buttonValue = raw;
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
if (raw.startsWith('allow_') || raw.startsWith('deny_'))
|
|
162
|
-
buttonValue = raw;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
else if (typeof raw === 'object' && raw !== null) {
|
|
166
|
-
const obj = raw;
|
|
167
|
-
if (obj.action === 'permission' && obj.value)
|
|
168
|
-
buttonValue = obj.value;
|
|
169
|
-
}
|
|
170
|
-
if (!buttonValue)
|
|
171
|
-
return null;
|
|
172
|
-
if (buttonValue.startsWith('allow_')) {
|
|
173
|
-
return { decision: 'allow', requestId: buttonValue.slice(6) };
|
|
174
|
-
}
|
|
175
|
-
if (buttonValue.startsWith('deny_')) {
|
|
176
|
-
return { decision: 'deny', requestId: buttonValue.slice(5) };
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
97
|
/**
|
|
181
98
|
* 解析 action value(兼容对象、JSON 字符串)
|
|
182
99
|
*/
|
|
@@ -265,61 +182,8 @@ export function setupFeishuHandlers(config, sessionManager) {
|
|
|
265
182
|
}
|
|
266
183
|
return { toast: { type: 'success', content: '已停止' } };
|
|
267
184
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (modeAv?.action === 'mode' && modeAv.value) {
|
|
271
|
-
const mode = modeAv.value;
|
|
272
|
-
if (['ask', 'accept-edits', 'plan', 'yolo'].includes(mode)) {
|
|
273
|
-
setPermissionMode(userId, mode);
|
|
274
|
-
const toastContent = `✅ 已切换为 ${MODE_LABELS[mode]}`;
|
|
275
|
-
const label = MODE_LABELS[mode];
|
|
276
|
-
// 异步发送文本回复,不阻塞 3 秒内返回
|
|
277
|
-
const sendReply = () => {
|
|
278
|
-
if (chatId)
|
|
279
|
-
return sendTextReply(chatId, toastContent);
|
|
280
|
-
if (userId)
|
|
281
|
-
return sendTextReplyByOpenId(userId, toastContent);
|
|
282
|
-
log.warn('[handleCardAction] No chatId/userId, cannot send text reply');
|
|
283
|
-
};
|
|
284
|
-
const p = sendReply();
|
|
285
|
-
if (p)
|
|
286
|
-
p.catch((e) => log.warn('[handleCardAction] Send reply failed:', e));
|
|
287
|
-
// 同步只返回 toast,避免 200672(同步返回 card 格式易出错)
|
|
288
|
-
const cardToken = extractCardToken(data);
|
|
289
|
-
const readOnlyCard = createFeishuModeCardReadOnly(label);
|
|
290
|
-
if (cardToken && userId) {
|
|
291
|
-
// 延时更新:异步替换为只读卡片,防止二次点击
|
|
292
|
-
delayUpdateCard(cardToken, readOnlyCard, [userId]).catch((e) => log.warn('[handleCardAction] delayUpdateCard failed:', e));
|
|
293
|
-
}
|
|
294
|
-
else if (!cardToken) {
|
|
295
|
-
log.debug('[handleCardAction] No card token in event, cannot delay-update card');
|
|
296
|
-
}
|
|
297
|
-
return { toast: { type: 'success', content: toastContent } };
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
const parsed = parsePermissionActionValue(actionValue);
|
|
301
|
-
if (!parsed) {
|
|
302
|
-
log.info('[handleCardAction] Unrecognized action value, returning default toast');
|
|
303
|
-
return { toast: { type: 'warning', content: '未知操作' } };
|
|
304
|
-
}
|
|
305
|
-
const { decision, requestId } = parsed;
|
|
306
|
-
log.info(`[handleCardAction] Permission button: ${decision} for ${requestId}, chatId=${chatId}`);
|
|
307
|
-
const resolved = resolvePermissionById(requestId, decision);
|
|
308
|
-
const toastContent = resolved
|
|
309
|
-
? decision === 'allow'
|
|
310
|
-
? '✅ 权限已允许'
|
|
311
|
-
: '❌ 权限已拒绝'
|
|
312
|
-
: '⚠️ 权限请求已过期或不存在';
|
|
313
|
-
const sendPermReply = () => {
|
|
314
|
-
if (chatId)
|
|
315
|
-
return sendTextReply(chatId, toastContent);
|
|
316
|
-
if (userId)
|
|
317
|
-
return sendTextReplyByOpenId(userId, toastContent);
|
|
318
|
-
};
|
|
319
|
-
const permP = sendPermReply();
|
|
320
|
-
if (permP)
|
|
321
|
-
permP.catch((err) => log.warn('Failed to send permission reply:', err));
|
|
322
|
-
return { toast: { type: resolved ? 'success' : 'warning', content: toastContent } };
|
|
185
|
+
log.info('[handleCardAction] Unrecognized action value');
|
|
186
|
+
return { toast: { type: 'warning', content: '未知操作' } };
|
|
323
187
|
}
|
|
324
188
|
async function handleEvent(data) {
|
|
325
189
|
log.info('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
|
|
@@ -336,23 +200,6 @@ export function setupFeishuHandlers(config, sessionManager) {
|
|
|
336
200
|
// 2. 消息接收 (im.message.receive_v1)
|
|
337
201
|
if (eventType === 'im.message.receive_v1') {
|
|
338
202
|
log.info('[handleEvent] Processing im.message.receive_v1 event');
|
|
339
|
-
// 兼容:部分场景下卡片点击可能通过 im.message 携带 action
|
|
340
|
-
if (event?.action?.value) {
|
|
341
|
-
const parsed = parsePermissionActionValue(event.action.value);
|
|
342
|
-
if (parsed) {
|
|
343
|
-
const { decision, requestId } = parsed;
|
|
344
|
-
const chatId = event.message?.chat_id ?? '';
|
|
345
|
-
log.info(`[handleEvent] Permission (via msg): ${decision} for ${requestId}`);
|
|
346
|
-
const resolved = resolvePermissionById(requestId, decision);
|
|
347
|
-
if (resolved) {
|
|
348
|
-
await sendTextReply(chatId, decision === 'allow' ? '✅ 权限已允许' : '❌ 权限已拒绝');
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
await sendTextReply(chatId, '⚠️ 权限请求已过期或不存在');
|
|
352
|
-
}
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
203
|
const message = event?.message;
|
|
357
204
|
if (!message) {
|
|
358
205
|
log.warn('No message data in event');
|
|
@@ -3,26 +3,6 @@ export interface CardHandle {
|
|
|
3
3
|
cardId: string;
|
|
4
4
|
}
|
|
5
5
|
export type MessageStatus = 'thinking' | 'streaming' | 'done' | 'error';
|
|
6
|
-
/**
|
|
7
|
-
* Create Feishu card with action buttons
|
|
8
|
-
* Used for permission prompts and other interactive requests
|
|
9
|
-
*/
|
|
10
|
-
export declare function createFeishuButtonCard(title: string, content: string, buttons: Array<{
|
|
11
|
-
label: string;
|
|
12
|
-
value: string;
|
|
13
|
-
type?: 'primary' | 'default';
|
|
14
|
-
}>): string;
|
|
15
|
-
/** 只读模式卡片(无按钮,用于回调后替换原卡片防止二次点击) */
|
|
16
|
-
export declare function createFeishuModeCardReadOnly(currentMode: string): Record<string, unknown>;
|
|
17
|
-
/**
|
|
18
|
-
* 延时更新消息卡片(POST /open-apis/im/v1/cards/update)
|
|
19
|
-
* 用于在卡片回调 3 秒内无法完成时,异步替换卡片为只读版本,防止二次点击
|
|
20
|
-
* @param token 从卡片交互事件中获取的 token(格式 c-xxxx)
|
|
21
|
-
* @param card 卡片内容 { config, header, elements }
|
|
22
|
-
* @param openIds 非共享卡片需指定更新的用户 open_id 列表
|
|
23
|
-
*/
|
|
24
|
-
export declare function delayUpdateCard(token: string, card: Record<string, unknown>, openIds?: string[]): Promise<void>;
|
|
25
|
-
export declare function sendModeCard(chatId: string, _userId: string, currentMode: string): Promise<void>;
|
|
26
6
|
export declare function sendThinkingMessage(chatId: string, replyToMessageId: string | undefined, toolId?: string): Promise<string>;
|
|
27
7
|
/** CardKit 打字机:发送思考卡片并返回 cardId + messageId */
|
|
28
8
|
export declare function sendThinkingCard(chatId: string, toolId?: string): Promise<CardHandle>;
|