@wu529778790/open-im 1.0.2-beta.2 → 1.0.2
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/README.md +34 -2
- package/dist/adapters/claude-adapter.js +1 -0
- package/dist/adapters/tool-adapter.interface.d.ts +2 -0
- package/dist/claude/cli-runner.d.ts +1 -0
- package/dist/claude/cli-runner.js +5 -1
- package/dist/claude/process-pool.d.ts +1 -0
- package/dist/claude/process-pool.js +5 -1
- package/dist/commands/handler.d.ts +5 -0
- package/dist/commands/handler.js +46 -8
- package/dist/config.d.ts +2 -0
- package/dist/config.js +6 -0
- package/dist/feishu/client.d.ts +1 -1
- package/dist/feishu/client.js +13 -0
- package/dist/feishu/event-handler.d.ts +1 -1
- package/dist/feishu/event-handler.js +253 -47
- package/dist/feishu/message-sender.d.ts +22 -0
- package/dist/feishu/message-sender.js +174 -2
- package/dist/hook/permission-server.d.ts +37 -4
- package/dist/hook/permission-server.js +288 -8
- package/dist/index.js +11 -0
- package/dist/permission-mode/session-mode.d.ts +7 -0
- package/dist/permission-mode/session-mode.js +59 -0
- package/dist/permission-mode/types.d.ts +11 -0
- package/dist/permission-mode/types.js +29 -0
- package/dist/shared/ai-task.js +17 -1
- package/dist/shared/chat-user-map.d.ts +2 -0
- package/dist/shared/chat-user-map.js +11 -0
- package/dist/telegram/event-handler.js +18 -3
- package/dist/telegram/message-sender.d.ts +1 -0
- package/dist/telegram/message-sender.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,8 +87,16 @@ open-im run
|
|
|
87
87
|
|
|
88
88
|
1. 在 [飞书开放平台](https://open.feishu.cn/) 创建企业自建应用
|
|
89
89
|
2. 开启「机器人」能力
|
|
90
|
-
3. 配置事件订阅:启用 `im.message.receive_v1
|
|
91
|
-
4.
|
|
90
|
+
3. 配置事件订阅:启用 `im.message.receive_v1`(接收消息),使用 **长连接** 模式(WebSocket)
|
|
91
|
+
4. **卡片按钮(/mode、权限允许/拒绝)需额外配置回调**:
|
|
92
|
+
- 打开 [开放平台](https://open.feishu.cn/app) → 进入你的应用 → **「事件与回调」**
|
|
93
|
+
- 注意:页面有 **「事件」** 和 **「回调」** 两个 Tab,卡片在 **「回调」** 里,不在「事件」里
|
|
94
|
+
- 切换到 **「回调」** Tab → 选择 **「使用长连接接收回调」**
|
|
95
|
+
- 点击 **「添加回调」**(或类似按钮)→ 在列表中找到 **「卡片回传交互」**(`card.action.trigger`)
|
|
96
|
+
- 若列表里搜不到,可尝试:切换分类、搜「action」或「trigger」、或直接浏览「消息与群组」相关分类
|
|
97
|
+
5. 将机器人添加到目标群聊或发起私聊
|
|
98
|
+
|
|
99
|
+
**若点击 /mode 卡片按钮报错**:说明未配置卡片回调。配置较复杂时,可直接用 `/mode ask`、`/mode yolo` 等命令切换模式,无需卡片。
|
|
92
100
|
|
|
93
101
|
## 开发
|
|
94
102
|
|
|
@@ -103,6 +111,8 @@ npm run foreground # 前台运行已构建版本
|
|
|
103
111
|
| 命令 | 说明 |
|
|
104
112
|
|------|------|
|
|
105
113
|
| `/help` | 显示帮助 |
|
|
114
|
+
| `/mode` | 切换权限模式(卡片/按钮选择) |
|
|
115
|
+
| `/mode <模式>` | 直接切换:ask / accept-edits / plan / yolo |
|
|
106
116
|
| `/new` | 开始新会话 |
|
|
107
117
|
| `/status` | 显示状态(AI 工具、工作目录、费用等) |
|
|
108
118
|
| `/cd <路径>` | 切换工作目录 |
|
|
@@ -110,6 +120,17 @@ npm run foreground # 前台运行已构建版本
|
|
|
110
120
|
| `/allow` `/y` | 允许权限请求 |
|
|
111
121
|
| `/deny` `/n` | 拒绝权限请求 |
|
|
112
122
|
|
|
123
|
+
### 权限模式
|
|
124
|
+
|
|
125
|
+
与 Claude Code 官方命名一致,见 [permissions](https://code.claude.com/docs/en/permissions):
|
|
126
|
+
|
|
127
|
+
| 模式 | Claude 名 | 说明 |
|
|
128
|
+
|------|-----------|------|
|
|
129
|
+
| ask | default | 首次使用每个工具时提示确认 |
|
|
130
|
+
| accept-edits | acceptEdits | 编辑权限自动通过 |
|
|
131
|
+
| plan | plan | 仅分析,不修改文件不执行命令 |
|
|
132
|
+
| yolo | bypassPermissions | 跳过所有权限确认 |
|
|
133
|
+
|
|
113
134
|
## 📝 License
|
|
114
135
|
|
|
115
136
|
[MIT](LICENSE)
|
|
@@ -214,3 +235,14 @@ npx @wu529778790/open-im run
|
|
|
214
235
|
3. **用户 ID 白名单问题**
|
|
215
236
|
- 检查配置文件中的 `allowedUserIds` 是否包含你的用户 ID
|
|
216
237
|
- 或留空允许所有人访问(仅开发环境建议)
|
|
238
|
+
|
|
239
|
+
### Q: 飞书 /mode 卡片点击报错(如 200340)?
|
|
240
|
+
|
|
241
|
+
说明未配置**卡片回调**。注意:卡片在 **「回调」** Tab,不在「事件」Tab。
|
|
242
|
+
|
|
243
|
+
1. 打开 [飞书开放平台](https://open.feishu.cn/app) → 进入你的应用 → **「事件与回调」**
|
|
244
|
+
2. 切换到 **「回调」** Tab(不是「事件」)
|
|
245
|
+
3. 选择 **「使用长连接接收回调」**
|
|
246
|
+
4. 添加 **「卡片回传交互」**(`card.action.trigger`)— 若搜不到,可尝试搜「action」「trigger」或浏览分类
|
|
247
|
+
|
|
248
|
+
**更简单**:直接用 `/mode ask`、`/mode yolo` 等命令切换模式,无需配置卡片。
|
|
@@ -17,6 +17,7 @@ export class ClaudeAdapter {
|
|
|
17
17
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
18
18
|
const opts = {
|
|
19
19
|
skipPermissions: options?.skipPermissions,
|
|
20
|
+
permissionMode: options?.permissionMode,
|
|
20
21
|
timeoutMs: options?.timeoutMs,
|
|
21
22
|
model: options?.model,
|
|
22
23
|
chatId: options?.chatId,
|
|
@@ -21,6 +21,8 @@ export interface RunCallbacks {
|
|
|
21
21
|
}
|
|
22
22
|
export interface RunOptions {
|
|
23
23
|
skipPermissions?: boolean;
|
|
24
|
+
/** Claude --permission-mode: default | acceptEdits | plan(yolo 时用 skipPermissions) */
|
|
25
|
+
permissionMode?: 'default' | 'acceptEdits' | 'plan';
|
|
24
26
|
timeoutMs?: number;
|
|
25
27
|
model?: string;
|
|
26
28
|
chatId?: string;
|
|
@@ -12,8 +12,12 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
12
12
|
"--verbose",
|
|
13
13
|
"--include-partial-messages",
|
|
14
14
|
];
|
|
15
|
-
if (options?.skipPermissions)
|
|
15
|
+
if (options?.skipPermissions) {
|
|
16
16
|
args.push("--dangerously-skip-permissions");
|
|
17
|
+
}
|
|
18
|
+
else if (options?.permissionMode) {
|
|
19
|
+
args.push("--permission-mode", options.permissionMode);
|
|
20
|
+
}
|
|
17
21
|
if (options?.model)
|
|
18
22
|
args.push("--model", options.model);
|
|
19
23
|
if (sessionId)
|
|
@@ -62,8 +62,12 @@ export class ClaudeProcessPool {
|
|
|
62
62
|
"--verbose",
|
|
63
63
|
"--include-partial-messages",
|
|
64
64
|
];
|
|
65
|
-
if (options.skipPermissions)
|
|
65
|
+
if (options.skipPermissions) {
|
|
66
66
|
args.push("--dangerously-skip-permissions");
|
|
67
|
+
}
|
|
68
|
+
else if (options.permissionMode) {
|
|
69
|
+
args.push("--permission-mode", options.permissionMode);
|
|
70
|
+
}
|
|
67
71
|
if (options.model)
|
|
68
72
|
args.push("--model", options.model);
|
|
69
73
|
if (sessionId)
|
|
@@ -1,11 +1,14 @@
|
|
|
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';
|
|
4
5
|
import type { ThreadContext } from '../shared/types.js';
|
|
5
6
|
export type { ThreadContext };
|
|
6
7
|
export interface MessageSender {
|
|
7
8
|
sendTextReply(chatId: string, text: string, threadCtx?: ThreadContext): Promise<void>;
|
|
8
9
|
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>;
|
|
9
12
|
}
|
|
10
13
|
export interface CommandHandlerDeps {
|
|
11
14
|
config: Config;
|
|
@@ -19,6 +22,8 @@ export declare class CommandHandler {
|
|
|
19
22
|
private deps;
|
|
20
23
|
constructor(deps: CommandHandlerDeps);
|
|
21
24
|
dispatch(text: string, chatId: string, userId: string, platform: 'feishu' | 'telegram', handleClaudeRequest: ClaudeRequestHandler): Promise<boolean>;
|
|
25
|
+
private handleMode;
|
|
26
|
+
private getClearHistoryHint;
|
|
22
27
|
private handleHelp;
|
|
23
28
|
private handleNew;
|
|
24
29
|
private handlePwd;
|
package/dist/commands/handler.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { resolveLatestPermission, getPendingCount } from '../hook/permission-server.js';
|
|
2
|
+
import { getPermissionMode, setPermissionMode } from '../permission-mode/session-mode.js';
|
|
3
|
+
import { MODE_LABELS, MODE_DESCRIPTIONS, parsePermissionMode } from '../permission-mode/types.js';
|
|
2
4
|
import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
|
|
3
5
|
import { execFile } from 'node:child_process';
|
|
4
6
|
import { readdirSync } from 'node:fs';
|
|
@@ -16,8 +18,10 @@ export class CommandHandler {
|
|
|
16
18
|
}
|
|
17
19
|
if (t === '/help')
|
|
18
20
|
return this.handleHelp(chatId, platform);
|
|
21
|
+
if (t === '/mode' || t.startsWith('/mode '))
|
|
22
|
+
return this.handleMode(chatId, userId, platform, t.slice(6).trim());
|
|
19
23
|
if (t === '/new')
|
|
20
|
-
return this.handleNew(chatId, userId);
|
|
24
|
+
return this.handleNew(chatId, userId, platform);
|
|
21
25
|
if (t === '/pwd')
|
|
22
26
|
return this.handlePwd(chatId, userId);
|
|
23
27
|
if (t === '/status')
|
|
@@ -27,7 +31,7 @@ export class CommandHandler {
|
|
|
27
31
|
if (t === '/deny' || t === '/n')
|
|
28
32
|
return this.handleDeny(chatId);
|
|
29
33
|
if (t === '/cd' || t.startsWith('/cd ')) {
|
|
30
|
-
return this.handleCd(chatId, userId, t.slice(3).trim());
|
|
34
|
+
return this.handleCd(chatId, userId, t.slice(3).trim(), platform);
|
|
31
35
|
}
|
|
32
36
|
const cmd = t.split(/\s+/)[0];
|
|
33
37
|
if (TERMINAL_ONLY_COMMANDS.has(cmd)) {
|
|
@@ -36,11 +40,46 @@ export class CommandHandler {
|
|
|
36
40
|
}
|
|
37
41
|
return false;
|
|
38
42
|
}
|
|
43
|
+
async handleMode(chatId, userId, platform, arg) {
|
|
44
|
+
const defaultMode = this.deps.config.defaultPermissionMode;
|
|
45
|
+
const currentMode = getPermissionMode(userId, defaultMode);
|
|
46
|
+
if (arg) {
|
|
47
|
+
const parsed = parsePermissionMode(arg);
|
|
48
|
+
if (parsed) {
|
|
49
|
+
setPermissionMode(userId, parsed);
|
|
50
|
+
await this.deps.sender.sendTextReply(chatId, `✅ 权限模式已切换为 **${MODE_LABELS[parsed]}**\n${MODE_DESCRIPTIONS[parsed]}`);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
await this.deps.sender.sendTextReply(chatId, `无效模式: ${arg}\n可用: ask, accept-edits, plan, yolo`);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (platform === 'feishu' && this.deps.sender.sendModeCard) {
|
|
57
|
+
await this.deps.sender.sendModeCard(chatId, userId, currentMode);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (platform === 'telegram' && this.deps.sender.sendModeKeyboard) {
|
|
61
|
+
await this.deps.sender.sendModeKeyboard(chatId, userId, currentMode);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const lines = [
|
|
65
|
+
`🔐 **权限模式** (当前: ${MODE_LABELS[currentMode]})`,
|
|
66
|
+
'',
|
|
67
|
+
...['ask', 'accept-edits', 'plan', 'yolo'].map((m) => `• \`/mode ${m}\` - ${MODE_LABELS[m]}: ${MODE_DESCRIPTIONS[m]}`),
|
|
68
|
+
];
|
|
69
|
+
await this.deps.sender.sendTextReply(chatId, lines.join('\n'));
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
getClearHistoryHint(platform) {
|
|
73
|
+
return platform === 'feishu'
|
|
74
|
+
? '💡 提示:如需清除本对话的历史消息,请点击飞书聊天右上角「...」→ 清除聊天记录'
|
|
75
|
+
: '💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史';
|
|
76
|
+
}
|
|
39
77
|
async handleHelp(chatId, platform) {
|
|
40
78
|
const help = [
|
|
41
79
|
'📋 可用命令:',
|
|
42
80
|
'',
|
|
43
81
|
'/help - 显示帮助',
|
|
82
|
+
'/mode - 切换权限模式(安全/编辑放行/只读/YOLO)',
|
|
44
83
|
'/new - 开始新会话(AI 上下文重置)',
|
|
45
84
|
'/status - 显示状态',
|
|
46
85
|
'/cd <路径> - 切换工作目录',
|
|
@@ -48,16 +87,15 @@ export class CommandHandler {
|
|
|
48
87
|
'/allow (/y) - 允许权限请求',
|
|
49
88
|
'/deny (/n) - 拒绝权限请求',
|
|
50
89
|
'',
|
|
51
|
-
|
|
90
|
+
this.getClearHistoryHint(platform),
|
|
52
91
|
].join('\n');
|
|
53
92
|
await this.deps.sender.sendTextReply(chatId, help);
|
|
54
93
|
return true;
|
|
55
94
|
}
|
|
56
|
-
async handleNew(chatId, userId) {
|
|
95
|
+
async handleNew(chatId, userId, platform) {
|
|
57
96
|
const ok = this.deps.sessionManager.newSession(userId);
|
|
58
97
|
await this.deps.sender.sendTextReply(chatId, ok
|
|
59
|
-
?
|
|
60
|
-
'💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史'
|
|
98
|
+
? `✅ AI 会话已重置,下一条消息将使用全新上下文。\n\n${this.getClearHistoryHint(platform)}`
|
|
61
99
|
: '当前没有活动会话。');
|
|
62
100
|
return true;
|
|
63
101
|
}
|
|
@@ -82,7 +120,7 @@ export class CommandHandler {
|
|
|
82
120
|
await this.deps.sender.sendTextReply(chatId, lines.join('\n'));
|
|
83
121
|
return true;
|
|
84
122
|
}
|
|
85
|
-
async handleCd(chatId, userId, dir) {
|
|
123
|
+
async handleCd(chatId, userId, dir, platform) {
|
|
86
124
|
// 如果 dir 为空,显示目录选择界面
|
|
87
125
|
if (!dir) {
|
|
88
126
|
const currentDir = this.deps.sessionManager.getWorkDir(userId);
|
|
@@ -98,7 +136,7 @@ export class CommandHandler {
|
|
|
98
136
|
const resolved = await this.deps.sessionManager.setWorkDir(userId, dir);
|
|
99
137
|
await this.deps.sender.sendTextReply(chatId, `📁 工作目录已切换到: ${resolved}\n\n` +
|
|
100
138
|
`🔄 AI 会话已重置,下一条消息将使用全新上下文。\n` +
|
|
101
|
-
|
|
139
|
+
this.getClearHistoryHint(platform));
|
|
102
140
|
}
|
|
103
141
|
catch (err) {
|
|
104
142
|
await this.deps.sender.sendTextReply(chatId, err instanceof Error ? err.message : String(err));
|
package/dist/config.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export interface Config {
|
|
|
14
14
|
claudeWorkDir: string;
|
|
15
15
|
allowedBaseDirs: string[];
|
|
16
16
|
claudeSkipPermissions: boolean;
|
|
17
|
+
defaultPermissionMode: 'ask' | 'accept-edits' | 'plan' | 'yolo';
|
|
17
18
|
claudeTimeoutMs: number;
|
|
18
19
|
claudeModel?: string;
|
|
20
|
+
hookPort: number;
|
|
19
21
|
logDir: string;
|
|
20
22
|
logLevel: LogLevel;
|
|
21
23
|
platforms: {
|
package/dist/config.js
CHANGED
|
@@ -84,9 +84,13 @@ export function loadConfig() {
|
|
|
84
84
|
const claudeSkipPermissions = process.env.CLAUDE_SKIP_PERMISSIONS !== undefined
|
|
85
85
|
? process.env.CLAUDE_SKIP_PERMISSIONS === 'true'
|
|
86
86
|
: file.claudeSkipPermissions ?? true;
|
|
87
|
+
const defaultPermissionMode = (file.defaultPermissionMode ?? 'ask');
|
|
87
88
|
const claudeTimeoutMs = process.env.CLAUDE_TIMEOUT_MS !== undefined
|
|
88
89
|
? parseInt(process.env.CLAUDE_TIMEOUT_MS, 10) || 600000
|
|
89
90
|
: file.claudeTimeoutMs ?? 600000;
|
|
91
|
+
const hookPort = process.env.HOOK_PORT !== undefined
|
|
92
|
+
? parseInt(process.env.HOOK_PORT, 10) || 35801
|
|
93
|
+
: file.hookPort ?? 35801;
|
|
90
94
|
// 6. 校验 Claude CLI
|
|
91
95
|
if (aiCommand === 'claude') {
|
|
92
96
|
if (isAbsolute(claudeCliPath) || claudeCliPath.includes('/') || claudeCliPath.includes('\\')) {
|
|
@@ -164,8 +168,10 @@ export function loadConfig() {
|
|
|
164
168
|
claudeWorkDir,
|
|
165
169
|
allowedBaseDirs,
|
|
166
170
|
claudeSkipPermissions,
|
|
171
|
+
defaultPermissionMode,
|
|
167
172
|
claudeTimeoutMs,
|
|
168
173
|
claudeModel: process.env.CLAUDE_MODEL ?? file.claudeModel,
|
|
174
|
+
hookPort,
|
|
169
175
|
logDir,
|
|
170
176
|
logLevel,
|
|
171
177
|
platforms,
|
package/dist/feishu/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client } from '@larksuiteoapi/node-sdk';
|
|
2
2
|
import type { Config } from '../config.js';
|
|
3
3
|
export declare function getClient(): Client;
|
|
4
|
-
export declare function initFeishu(config: Config, eventHandler: (data: unknown) => Promise<void
|
|
4
|
+
export declare function initFeishu(config: Config, eventHandler: (data: unknown) => Promise<void | Record<string, unknown>>): Promise<void>;
|
|
5
5
|
export declare function stopFeishu(): void;
|
package/dist/feishu/client.js
CHANGED
|
@@ -34,6 +34,19 @@ export async function initFeishu(config, eventHandler) {
|
|
|
34
34
|
log.error('[EVENT] Error calling event handler:', err);
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
+
// 卡片按钮点击回调(权限允许/拒绝等)
|
|
38
|
+
'card.action.trigger': async (data) => {
|
|
39
|
+
log.info('[EVENT] Received Feishu card action event');
|
|
40
|
+
log.info('[EVENT] Card action data:', JSON.stringify(data).slice(0, 800));
|
|
41
|
+
try {
|
|
42
|
+
const result = await eventHandler(data);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
log.error('[EVENT] Error handling card action:', err);
|
|
47
|
+
return { toast: { type: 'error', content: '处理失败' } };
|
|
48
|
+
}
|
|
49
|
+
},
|
|
37
50
|
});
|
|
38
51
|
// Register catch-all handler using wildcard
|
|
39
52
|
eventDispatcher.register({
|
|
@@ -3,6 +3,6 @@ import type { SessionManager } from '../session/session-manager.js';
|
|
|
3
3
|
export interface FeishuEventHandlerHandle {
|
|
4
4
|
stop: () => void;
|
|
5
5
|
getRunningTaskCount: () => number;
|
|
6
|
-
handleEvent: (data: unknown) => Promise<void
|
|
6
|
+
handleEvent: (data: unknown) => Promise<void | Record<string, unknown>>;
|
|
7
7
|
}
|
|
8
8
|
export declare function setupFeishuHandlers(config: Config, sessionManager: SessionManager): FeishuEventHandlerHandle;
|