@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.
Files changed (40) hide show
  1. package/dist/commands/handler.d.ts +0 -6
  2. package/dist/commands/handler.js +0 -62
  3. package/dist/config-web.js +3 -15
  4. package/dist/config.d.ts +0 -10
  5. package/dist/config.js +1 -54
  6. package/dist/dingtalk/event-handler.js +2 -4
  7. package/dist/dingtalk/message-sender.d.ts +0 -2
  8. package/dist/dingtalk/message-sender.js +1 -10
  9. package/dist/feishu/event-handler.js +4 -157
  10. package/dist/feishu/message-sender.d.ts +0 -20
  11. package/dist/feishu/message-sender.js +0 -155
  12. package/dist/index.js +0 -13
  13. package/dist/manager.js +5 -2
  14. package/dist/qq/event-handler.js +2 -4
  15. package/dist/qq/event-handler.test.js +0 -1
  16. package/dist/qq/message-sender.d.ts +0 -1
  17. package/dist/qq/message-sender.js +1 -6
  18. package/dist/setup.js +3 -7
  19. package/dist/shared/ai-task.js +2 -25
  20. package/dist/shared/system-messages.d.ts +0 -2
  21. package/dist/shared/system-messages.js +0 -32
  22. package/dist/shared/system-messages.test.js +1 -8
  23. package/dist/telegram/event-handler.js +2 -24
  24. package/dist/telegram/message-sender.d.ts +0 -1
  25. package/dist/telegram/message-sender.js +0 -14
  26. package/dist/wechat/event-handler.js +2 -28
  27. package/dist/wechat/message-sender.d.ts +0 -2
  28. package/dist/wechat/message-sender.js +0 -31
  29. package/dist/wework/event-handler.js +2 -4
  30. package/dist/wework/message-sender.d.ts +0 -2
  31. package/dist/wework/message-sender.js +1 -23
  32. package/package.json +1 -1
  33. package/dist/hook/permission-server.d.ts +0 -38
  34. package/dist/hook/permission-server.js +0 -301
  35. package/dist/hook/permission-server.test.d.ts +0 -1
  36. package/dist/hook/permission-server.test.js +0 -12
  37. package/dist/permission-mode/session-mode.d.ts +0 -7
  38. package/dist/permission-mode/session-mode.js +0 -59
  39. package/dist/permission-mode/types.d.ts +0 -11
  40. 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
  /**
@@ -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 版本
@@ -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 started = startBackgroundService(options.cwd);
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', 'claudeSkipPermissions',
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, sendPermissionCard, sendModeCard, sendDirectorySelection, } from './message-sender.js';
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, sendModeCard, sendDirectorySelection },
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, buildModeMessage, buildPermissionRequestMessage, } from '../shared/system-messages.js';
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, sendTextReplyByOpenId, startTypingLoop, sendImageReply, createFeishuButtonCard, sendModeCard, createFeishuModeCardReadOnly, delayUpdateCard, sendThinkingCard, streamContentUpdate, sendFinalCards, sendErrorCard, } from './message-sender.js';
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, sendModeCard },
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
- // 处理 mode 按钮(兼容 value 为对象或 JSON 字符串)
269
- const modeAv = parseActionValue(actionValue);
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>;