@wu529778790/open-im 1.0.2-beta.2 → 1.0.2-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/config.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface Config {
16
16
  claudeSkipPermissions: boolean;
17
17
  claudeTimeoutMs: number;
18
18
  claudeModel?: string;
19
+ hookPort: number;
19
20
  logDir: string;
20
21
  logLevel: LogLevel;
21
22
  platforms: {
package/dist/config.js CHANGED
@@ -87,6 +87,9 @@ export function loadConfig() {
87
87
  const claudeTimeoutMs = process.env.CLAUDE_TIMEOUT_MS !== undefined
88
88
  ? parseInt(process.env.CLAUDE_TIMEOUT_MS, 10) || 600000
89
89
  : file.claudeTimeoutMs ?? 600000;
90
+ const hookPort = process.env.HOOK_PORT !== undefined
91
+ ? parseInt(process.env.HOOK_PORT, 10) || 35801
92
+ : file.hookPort ?? 35801;
90
93
  // 6. 校验 Claude CLI
91
94
  if (aiCommand === 'claude') {
92
95
  if (isAbsolute(claudeCliPath) || claudeCliPath.includes('/') || claudeCliPath.includes('\\')) {
@@ -166,6 +169,7 @@ export function loadConfig() {
166
169
  claudeSkipPermissions,
167
170
  claudeTimeoutMs,
168
171
  claudeModel: process.env.CLAUDE_MODEL ?? file.claudeModel,
172
+ hookPort,
169
173
  logDir,
170
174
  logLevel,
171
175
  platforms,
@@ -2,8 +2,8 @@ import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { AccessControl } from '../access/access-control.js';
4
4
  import { RequestQueue } from '../queue/request-queue.js';
5
- import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, startTypingLoop, sendImageReply, } from './message-sender.js';
6
- import { registerPermissionSender } from '../hook/permission-server.js';
5
+ import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, startTypingLoop, sendImageReply, createFeishuButtonCard, } from './message-sender.js';
6
+ import { registerPermissionSender, resolvePermissionById } from '../hook/permission-server.js';
7
7
  import { CommandHandler } from '../commands/handler.js';
8
8
  import { getAdapter } from '../adapters/registry.js';
9
9
  import { runAITask } from '../shared/ai-task.js';
@@ -56,6 +56,48 @@ async function downloadFeishuImage(client, imageKey) {
56
56
  await writeFile(imagePath, buffer);
57
57
  return imagePath;
58
58
  }
59
+ /**
60
+ * Send permission prompt card with interactive buttons
61
+ */
62
+ async function sendPermissionCard(chatId, requestId, toolName, toolInput) {
63
+ const { getClient } = await import('./client.js');
64
+ const client = getClient();
65
+ // Format tool input for display
66
+ let formattedInput;
67
+ if (toolInput.length > 300) {
68
+ formattedInput = toolInput.slice(0, 300) + '...';
69
+ }
70
+ else {
71
+ formattedInput = toolInput;
72
+ }
73
+ const content = `**工具:** \`${toolName}\`
74
+
75
+ **参数:**
76
+ \`\`\`
77
+ ${formattedInput}
78
+ \`\`\`
79
+
80
+ **请求 ID:** \`${requestId.slice(-8)}\``;
81
+ const cardContent = createFeishuButtonCard('权限请求', content, [
82
+ { label: '✅ 允许', value: `allow_${requestId}`, type: 'primary' },
83
+ { label: '❌ 拒绝', value: `deny_${requestId}`, type: 'default' },
84
+ ]);
85
+ try {
86
+ await client.im.message.create({
87
+ data: {
88
+ receive_id: chatId,
89
+ msg_type: 'interactive',
90
+ content: cardContent,
91
+ },
92
+ params: { receive_id_type: 'chat_id' },
93
+ });
94
+ log.info(`Permission card sent for request ${requestId}`);
95
+ }
96
+ catch (err) {
97
+ log.error('Failed to send permission card:', err);
98
+ throw err;
99
+ }
100
+ }
59
101
  export function setupFeishuHandlers(config, sessionManager) {
60
102
  const accessControl = new AccessControl(config.feishuAllowedUserIds);
61
103
  const requestQueue = new RequestQueue();
@@ -68,7 +110,7 @@ export function setupFeishuHandlers(config, sessionManager) {
68
110
  sender: { sendTextReply },
69
111
  getRunningTasksSize: () => runningTasks.size,
70
112
  });
71
- registerPermissionSender('feishu', {});
113
+ registerPermissionSender('feishu', { sendTextReply, sendPermissionCard });
72
114
  async function handleAIRequest(userId, chatId, prompt, workDir, convId, _threadCtx, replyToMessageId) {
73
115
  log.info(`[AI_REQUEST] userId=${userId}, chatId=${chatId}, promptLength=${prompt.length}`);
74
116
  log.info(`[AI_REQUEST] Full prompt: "${prompt}"`);
@@ -133,6 +175,10 @@ export function setupFeishuHandlers(config, sessionManager) {
133
175
  // "app_id": "...",
134
176
  // "message": { "chat_id": "...", "content": "...", ... },
135
177
  // "sender": { "sender_id": { "open_id": "..." } }
178
+ // "action": { // For card button clicks
179
+ // "action_id": "...",
180
+ // "value": { "action": "permission", "value": "allow_xxx" }
181
+ // }
136
182
  // }
137
183
  const event = data;
138
184
  const eventType = event?.event_type;
@@ -140,6 +186,40 @@ export function setupFeishuHandlers(config, sessionManager) {
140
186
  // Handle message received events
141
187
  if (eventType === 'im.message.receive_v1') {
142
188
  log.info('[handleEvent] Processing im.message.receive_v1 event');
189
+ // Check if this is a card button click event
190
+ // For interactive cards, the action is in a different location
191
+ if (event?.action) {
192
+ const action = event.action;
193
+ log.info('[handleEvent] Card action detected:', action);
194
+ if (action?.value) {
195
+ const actionValue = action.value;
196
+ if (actionValue.action === 'permission' && actionValue.value) {
197
+ const buttonValue = actionValue.value;
198
+ let decision = null;
199
+ let requestId = null;
200
+ if (buttonValue.startsWith('allow_')) {
201
+ decision = 'allow';
202
+ requestId = buttonValue.slice(6);
203
+ }
204
+ else if (buttonValue.startsWith('deny_')) {
205
+ decision = 'deny';
206
+ requestId = buttonValue.slice(5);
207
+ }
208
+ if (decision && requestId) {
209
+ log.info(`[handleEvent] Permission button clicked: ${decision} for ${requestId}`);
210
+ const resolved = resolvePermissionById(requestId, decision);
211
+ const chatId = event.message?.chat_id ?? '';
212
+ if (resolved) {
213
+ await sendTextReply(chatId, decision === 'allow' ? '✅ 权限已允许' : '❌ 权限已拒绝');
214
+ }
215
+ else {
216
+ await sendTextReply(chatId, '⚠️ 权限请求已过期或不存在');
217
+ }
218
+ return;
219
+ }
220
+ }
221
+ }
222
+ }
143
223
  const message = event?.message;
144
224
  if (!message) {
145
225
  log.warn('No message data in event');
@@ -1,4 +1,13 @@
1
1
  export type MessageStatus = 'thinking' | 'streaming' | 'done' | 'error';
2
+ /**
3
+ * Create Feishu card with action buttons
4
+ * Used for permission prompts and other interactive requests
5
+ */
6
+ export declare function createFeishuButtonCard(title: string, content: string, buttons: Array<{
7
+ label: string;
8
+ value: string;
9
+ type?: 'primary' | 'default';
10
+ }>): string;
2
11
  export declare function sendThinkingMessage(chatId: string, replyToMessageId: string | undefined, toolId?: string): Promise<string>;
3
12
  export declare function updateMessage(chatId: string, messageId: string, content: string, status: MessageStatus, note?: string, toolId?: string): Promise<void>;
4
13
  export declare function sendFinalMessages(chatId: string, messageId: string, fullContent: string, note: string, toolId?: string): Promise<void>;
@@ -61,6 +61,59 @@ function createFeishuCard(title, content, status, note) {
61
61
  };
62
62
  return JSON.stringify(card);
63
63
  }
64
+ /**
65
+ * Create Feishu card with action buttons
66
+ * Used for permission prompts and other interactive requests
67
+ */
68
+ export function createFeishuButtonCard(title, content, buttons) {
69
+ const elements = [];
70
+ // Main content
71
+ elements.push({
72
+ tag: 'div',
73
+ text: {
74
+ tag: 'lark_md',
75
+ content: content,
76
+ },
77
+ });
78
+ // Add separator
79
+ elements.push({ tag: 'hr' });
80
+ // Add action buttons
81
+ const actionGroups = [];
82
+ // Split buttons into rows (max 4 buttons per row in Feishu)
83
+ for (let i = 0; i < buttons.length; i += 4) {
84
+ const row = buttons.slice(i, i + 4).map((btn) => ({
85
+ tag: 'button',
86
+ text: {
87
+ tag: 'plain_text',
88
+ content: btn.label,
89
+ },
90
+ type: btn.type || 'default',
91
+ value: {
92
+ action: 'permission',
93
+ value: btn.value,
94
+ },
95
+ }));
96
+ actionGroups.push({
97
+ tag: 'action',
98
+ actions: row,
99
+ });
100
+ }
101
+ elements.push(...actionGroups);
102
+ const card = {
103
+ config: {
104
+ wide_screen_mode: true,
105
+ },
106
+ header: {
107
+ template: 'blue',
108
+ title: {
109
+ content: `🔐 ${title}`,
110
+ tag: 'plain_text',
111
+ },
112
+ },
113
+ elements,
114
+ };
115
+ return JSON.stringify(card);
116
+ }
64
117
  async function getTenantAccessToken() {
65
118
  const client = getClient();
66
119
  const resp = await client.auth.tenantAccessToken.internal({
@@ -1,4 +1,36 @@
1
- export declare function resolveLatestPermission(_chatId: string, _decision: 'allow' | 'deny'): string | null;
2
- export declare function getPendingCount(_chatId: string): number;
3
- export declare function resolvePermissionById(_requestId: string, _decision: 'allow' | 'deny'): boolean;
4
- export declare function registerPermissionSender(_platform: string, _sender: unknown): void;
1
+ /**
2
+ * Permission Server - Handles tool permission requests from Claude CLI
3
+ *
4
+ * When claudeSkipPermissions is false, Claude CLI will make HTTP requests
5
+ * to this server to ask for user approval before running tools.
6
+ */
7
+ interface MessageSender {
8
+ sendTextReply(chatId: string, text: string): Promise<void>;
9
+ sendPermissionCard?(chatId: string, requestId: string, toolName: string, toolInput: string): Promise<void>;
10
+ }
11
+ /**
12
+ * Start the permission HTTP server
13
+ */
14
+ export declare function startPermissionServer(port?: number): number;
15
+ /**
16
+ * Stop the permission HTTP server
17
+ */
18
+ export declare function stopPermissionServer(): void;
19
+ /**
20
+ * Register the message sender for sending permission prompts
21
+ */
22
+ export declare function registerPermissionSender(_platform: string, sender: MessageSender): void;
23
+ /**
24
+ * Get the number of pending permission requests for a chat
25
+ */
26
+ export declare function getPendingCount(chatId: string): number;
27
+ /**
28
+ * Resolve the latest pending permission request for a chat
29
+ * Returns the requestId if found, null otherwise
30
+ */
31
+ export declare function resolveLatestPermission(chatId: string, decision: 'allow' | 'deny'): string | null;
32
+ /**
33
+ * Resolve a specific permission request by ID
34
+ */
35
+ export declare function resolvePermissionById(requestId: string, decision: 'allow' | 'deny'): boolean;
36
+ export {};
@@ -1,12 +1,290 @@
1
- export function resolveLatestPermission(_chatId, _decision) {
2
- return null;
1
+ /**
2
+ * Permission Server - Handles tool permission requests from Claude CLI
3
+ *
4
+ * When claudeSkipPermissions is false, Claude CLI will make HTTP requests
5
+ * to this server to ask for user approval before running tools.
6
+ */
7
+ import { createServer } from 'node:http';
8
+ import { createLogger } from '../logger.js';
9
+ const log = createLogger('PermissionServer');
10
+ const PERMISSION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes default timeout
11
+ const DEFAULT_PORT = 35801;
12
+ // Global state
13
+ let server = null;
14
+ let serverPort = DEFAULT_PORT;
15
+ const pendingRequests = new Map();
16
+ const requestsByChat = new Map();
17
+ let messageSender = null;
18
+ /**
19
+ * Start the permission HTTP server
20
+ */
21
+ export function startPermissionServer(port = DEFAULT_PORT) {
22
+ if (server) {
23
+ log.warn('Permission server already running');
24
+ return serverPort;
25
+ }
26
+ serverPort = port;
27
+ server = createServer(handleRequest);
28
+ server.listen(port, () => {
29
+ log.info(`Permission server listening on port ${port}`);
30
+ });
31
+ server.on('error', (err) => {
32
+ log.error('Permission server error:', err);
33
+ });
34
+ // Cleanup expired permissions every minute
35
+ setInterval(() => {
36
+ cleanupExpiredPermissions();
37
+ }, 60 * 1000).unref();
38
+ return port;
3
39
  }
4
- export function getPendingCount(_chatId) {
5
- return 0;
40
+ /**
41
+ * Stop the permission HTTP server
42
+ */
43
+ export function stopPermissionServer() {
44
+ if (server) {
45
+ server.close(() => {
46
+ log.info('Permission server stopped');
47
+ });
48
+ server = null;
49
+ // Reject all pending requests
50
+ for (const req of pendingRequests.values()) {
51
+ clearTimeout(req.timeout);
52
+ req.resolve(false);
53
+ }
54
+ pendingRequests.clear();
55
+ requestsByChat.clear();
56
+ }
6
57
  }
7
- export function resolvePermissionById(_requestId, _decision) {
8
- return false;
58
+ /**
59
+ * Register the message sender for sending permission prompts
60
+ */
61
+ export function registerPermissionSender(_platform, sender) {
62
+ messageSender = sender;
63
+ log.info('Message sender registered for permission prompts');
9
64
  }
10
- export function registerPermissionSender(_platform, _sender) {
11
- /* stub */
65
+ /**
66
+ * Get the number of pending permission requests for a chat
67
+ */
68
+ export function getPendingCount(chatId) {
69
+ const requests = requestsByChat.get(chatId);
70
+ return requests ? requests.length : 0;
71
+ }
72
+ /**
73
+ * Resolve the latest pending permission request for a chat
74
+ * Returns the requestId if found, null otherwise
75
+ */
76
+ export function resolveLatestPermission(chatId, decision) {
77
+ const requests = requestsByChat.get(chatId);
78
+ if (!requests || requests.length === 0) {
79
+ return null;
80
+ }
81
+ // Get the oldest (first) pending request
82
+ const request = requests.shift();
83
+ if (requests.length === 0) {
84
+ requestsByChat.delete(chatId);
85
+ }
86
+ // Remove from global map
87
+ pendingRequests.delete(request.requestId);
88
+ // Clear timeout and resolve
89
+ clearTimeout(request.timeout);
90
+ request.resolve(decision === 'allow');
91
+ log.info(`Resolved permission ${request.requestId}: ${decision} for tool ${request.toolName}`);
92
+ return request.requestId;
93
+ }
94
+ /**
95
+ * Resolve a specific permission request by ID
96
+ */
97
+ export function resolvePermissionById(requestId, decision) {
98
+ const request = pendingRequests.get(requestId);
99
+ if (!request) {
100
+ log.warn(`Permission request not found: ${requestId}`);
101
+ return false;
102
+ }
103
+ // Remove from chat's list
104
+ const chatRequests = requestsByChat.get(request.chatId);
105
+ if (chatRequests) {
106
+ const index = chatRequests.findIndex(r => r.requestId === requestId);
107
+ if (index !== -1) {
108
+ chatRequests.splice(index, 1);
109
+ }
110
+ if (chatRequests.length === 0) {
111
+ requestsByChat.delete(request.chatId);
112
+ }
113
+ }
114
+ // Remove from global map
115
+ pendingRequests.delete(requestId);
116
+ // Clear timeout and resolve
117
+ clearTimeout(request.timeout);
118
+ request.resolve(decision === 'allow');
119
+ log.info(`Resolved permission ${requestId}: ${decision} for tool ${request.toolName}`);
120
+ return true;
121
+ }
122
+ /**
123
+ * Handle incoming HTTP requests from Claude CLI
124
+ */
125
+ function handleRequest(req, res) {
126
+ const url = new URL(req.url || '', `http://${req.headers.host}`);
127
+ log.debug(`Permission request: ${req.method} ${url.pathname}`);
128
+ if (url.pathname === '/permission' && req.method === 'POST') {
129
+ handlePermissionRequest(req, res);
130
+ }
131
+ else if (url.pathname === '/health' && req.method === 'GET') {
132
+ res.writeHead(200, { 'Content-Type': 'application/json' });
133
+ res.end(JSON.stringify({ status: 'ok', port: serverPort }));
134
+ }
135
+ else {
136
+ res.writeHead(404);
137
+ res.end('Not found');
138
+ }
139
+ }
140
+ /**
141
+ * Handle a permission request from Claude CLI
142
+ */
143
+ function handlePermissionRequest(req, res) {
144
+ let body = '';
145
+ req.on('data', (chunk) => {
146
+ body += chunk.toString();
147
+ });
148
+ req.on('end', async () => {
149
+ try {
150
+ const data = JSON.parse(body);
151
+ const { requestId, toolName, toolInput } = data;
152
+ if (!requestId || !toolName) {
153
+ res.writeHead(400, { 'Content-Type': 'application/json' });
154
+ res.end(JSON.stringify({ error: 'Missing required fields' }));
155
+ return;
156
+ }
157
+ // Get chatId from environment (set by CC_IM_CHAT_ID)
158
+ const chatId = process.env.CC_IM_CHAT_ID;
159
+ if (!chatId) {
160
+ log.warn('No chatId in environment, auto-allowing permission');
161
+ res.writeHead(200, { 'Content-Type': 'application/json' });
162
+ res.end(JSON.stringify({ allowed: true }));
163
+ return;
164
+ }
165
+ // Check if skip permissions is enabled
166
+ if (process.env.CC_SKIP_PERMISSIONS === 'true') {
167
+ log.info(`Skip permissions enabled, auto-allowing ${toolName}`);
168
+ res.writeHead(200, { 'Content-Type': 'application/json' });
169
+ res.end(JSON.stringify({ allowed: true }));
170
+ return;
171
+ }
172
+ // Check if already resolved (race condition)
173
+ if (pendingRequests.has(requestId)) {
174
+ log.warn(`Duplicate permission request: ${requestId}`);
175
+ res.writeHead(409, { 'Content-Type': 'application/json' });
176
+ res.end(JSON.stringify({ error: 'Duplicate request' }));
177
+ return;
178
+ }
179
+ // Send pending response
180
+ res.writeHead(202, { 'Content-Type': 'application/json' });
181
+ res.end(JSON.stringify({ status: 'pending' }));
182
+ // Create permission request
183
+ const permissionRequest = {
184
+ requestId,
185
+ chatId,
186
+ toolName,
187
+ toolInput: formatToolInput(toolInput),
188
+ timestamp: Date.now(),
189
+ resolve: null, // Will set below
190
+ timeout: null,
191
+ };
192
+ // Create promise for waiting for user response
193
+ const permissionPromise = new Promise((resolve) => {
194
+ permissionRequest.resolve = resolve;
195
+ });
196
+ // Set timeout
197
+ permissionRequest.timeout = setTimeout(() => {
198
+ log.info(`Permission request ${requestId} timed out`);
199
+ resolvePermissionById(requestId, 'deny');
200
+ }, PERMISSION_TIMEOUT_MS);
201
+ // Store request
202
+ pendingRequests.set(requestId, permissionRequest);
203
+ // Add to chat's list
204
+ let chatRequests = requestsByChat.get(chatId);
205
+ if (!chatRequests) {
206
+ chatRequests = [];
207
+ requestsByChat.set(chatId, chatRequests);
208
+ }
209
+ chatRequests.push(permissionRequest);
210
+ // Send permission prompt to user
211
+ await sendPermissionPrompt(permissionRequest);
212
+ // Wait for user response
213
+ const allowed = await permissionPromise;
214
+ log.info(`Permission ${requestId} for ${toolName}: ${allowed ? 'ALLOWED' : 'DENIED'}`);
215
+ }
216
+ catch (err) {
217
+ log.error('Error handling permission request:', err);
218
+ res.writeHead(500, { 'Content-Type': 'application/json' });
219
+ res.end(JSON.stringify({ error: 'Internal server error' }));
220
+ }
221
+ });
222
+ }
223
+ /**
224
+ * Send permission prompt to the user
225
+ */
226
+ async function sendPermissionPrompt(request) {
227
+ if (!messageSender) {
228
+ log.warn('No message sender registered, cannot send permission prompt');
229
+ return;
230
+ }
231
+ // Try to use interactive button card if available (Feishu)
232
+ if (messageSender.sendPermissionCard) {
233
+ try {
234
+ await messageSender.sendPermissionCard(request.chatId, request.requestId, request.toolName, request.toolInput);
235
+ return;
236
+ }
237
+ catch (err) {
238
+ log.debug('Failed to send permission card, falling back to text:', err);
239
+ }
240
+ }
241
+ // Fallback to text-based prompt
242
+ const prompt = `🔐 **权限请求**
243
+
244
+ 工具: \`${request.toolName}\`
245
+
246
+ 参数:
247
+ \`\`\`
248
+ ${request.toolInput}
249
+ \`\`\`
250
+
251
+ 回复 \`/allow\` 允许,\`/deny\` 拒绝
252
+
253
+ 请求 ID: ${request.requestId}`;
254
+ try {
255
+ await messageSender.sendTextReply(request.chatId, prompt);
256
+ }
257
+ catch (err) {
258
+ log.error('Failed to send permission prompt:', err);
259
+ }
260
+ }
261
+ /**
262
+ * Format tool input for display
263
+ */
264
+ function formatToolInput(toolInput) {
265
+ if (typeof toolInput === 'string') {
266
+ return toolInput;
267
+ }
268
+ if (typeof toolInput === 'object' && toolInput !== null) {
269
+ try {
270
+ const str = JSON.stringify(toolInput, null, 2);
271
+ return str.length > 500 ? str.slice(0, 500) + '...' : str;
272
+ }
273
+ catch {
274
+ return String(toolInput);
275
+ }
276
+ }
277
+ return String(toolInput);
278
+ }
279
+ /**
280
+ * Clean up expired permission requests
281
+ */
282
+ function cleanupExpiredPermissions() {
283
+ const now = Date.now();
284
+ for (const [requestId, request] of pendingRequests.entries()) {
285
+ if (now - request.timestamp > PERMISSION_TIMEOUT_MS) {
286
+ log.info(`Cleaning up expired permission: ${requestId}`);
287
+ resolvePermissionById(requestId, 'deny');
288
+ }
289
+ }
12
290
  }
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { SessionManager } from "./session/session-manager.js";
16
16
  import { loadActiveChats, getActiveChatId, flushActiveChats, } from "./shared/active-chats.js";
17
17
  import { initLogger, createLogger, closeLogger } from "./logger.js";
18
18
  import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
19
+ import { startPermissionServer, stopPermissionServer } from "./hook/permission-server.js";
19
20
  import { createRequire } from "node:module";
20
21
  const require = createRequire(import.meta.url);
21
22
  const { version: APP_VERSION } = require("../package.json");
@@ -46,6 +47,9 @@ export async function main() {
46
47
  initLogger(config.logDir, config.logLevel);
47
48
  loadActiveChats();
48
49
  initAdapters(config);
50
+ // Start permission server for tool approval
51
+ const actualPort = startPermissionServer(config.hookPort);
52
+ log.info(`Permission server listening on port ${actualPort}`);
49
53
  log.info("Starting open-im bridge...");
50
54
  log.info(`AI 工具: ${config.aiCommand}`);
51
55
  log.info(`工作目录: ${config.claudeWorkDir}`);
@@ -99,6 +103,7 @@ export async function main() {
99
103
  stopTelegram();
100
104
  feishuHandle?.stop();
101
105
  stopFeishu();
106
+ stopPermissionServer();
102
107
  sessionManager.destroy();
103
108
  cleanupAdapters();
104
109
  flushActiveChats();
@@ -153,6 +153,7 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
153
153
  timeoutMs: config.claudeTimeoutMs,
154
154
  model: sessionManager.getModel(ctx.userId, ctx.threadId) ?? config.claudeModel,
155
155
  chatId: ctx.chatId,
156
+ hookPort: config.hookPort,
156
157
  });
157
158
  taskState = { handle, latestContent: '', settle, startedAt: Date.now() };
158
159
  platformAdapter.onTaskReady(taskState);
@@ -77,7 +77,7 @@ export function setupTelegramHandlers(bot, config, sessionManager) {
77
77
  sender: { sendTextReply },
78
78
  getRunningTasksSize: () => runningTasks.size,
79
79
  });
80
- registerPermissionSender("telegram", {});
80
+ registerPermissionSender("telegram", { sendTextReply });
81
81
  async function handleAIRequest(userId, chatId, prompt, workDir, convId, _threadCtx, replyToMessageId) {
82
82
  // 在用户每次发送消息时就累加计数,确保提示能轮换显示
83
83
  const currentTurns = sessionManager.addTurns(userId, 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.0.2-beta.2",
3
+ "version": "1.0.2-beta.3",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",