@wu529778790/open-im 1.7.0-beta.2 → 1.7.0-beta.4

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 (44) hide show
  1. package/README.md +50 -3
  2. package/README.zh-CN.md +55 -2
  3. package/dist/commands/handler.d.ts +0 -6
  4. package/dist/commands/handler.js +0 -62
  5. package/dist/config-web.js +26 -18
  6. package/dist/config.d.ts +0 -10
  7. package/dist/config.js +1 -54
  8. package/dist/dingtalk/event-handler.js +2 -4
  9. package/dist/dingtalk/message-sender.d.ts +0 -2
  10. package/dist/dingtalk/message-sender.js +1 -10
  11. package/dist/feishu/event-handler.js +4 -157
  12. package/dist/feishu/message-sender.d.ts +0 -20
  13. package/dist/feishu/message-sender.js +0 -155
  14. package/dist/index.js +7 -16
  15. package/dist/manager.js +5 -2
  16. package/dist/qq/event-handler.js +2 -4
  17. package/dist/qq/event-handler.test.js +0 -1
  18. package/dist/qq/message-sender.d.ts +0 -1
  19. package/dist/qq/message-sender.js +1 -6
  20. package/dist/session/session-manager.d.ts +5 -1
  21. package/dist/session/session-manager.js +11 -3
  22. package/dist/setup.js +3 -7
  23. package/dist/shared/ai-task.js +10 -26
  24. package/dist/shared/system-messages.d.ts +0 -2
  25. package/dist/shared/system-messages.js +0 -32
  26. package/dist/shared/system-messages.test.js +1 -8
  27. package/dist/telegram/event-handler.js +2 -24
  28. package/dist/telegram/message-sender.d.ts +0 -1
  29. package/dist/telegram/message-sender.js +0 -14
  30. package/dist/wechat/event-handler.js +2 -28
  31. package/dist/wechat/message-sender.d.ts +0 -2
  32. package/dist/wechat/message-sender.js +0 -31
  33. package/dist/wework/event-handler.js +2 -4
  34. package/dist/wework/message-sender.d.ts +0 -2
  35. package/dist/wework/message-sender.js +1 -23
  36. package/package.json +1 -1
  37. package/dist/hook/permission-server.d.ts +0 -38
  38. package/dist/hook/permission-server.js +0 -301
  39. package/dist/hook/permission-server.test.d.ts +0 -1
  40. package/dist/hook/permission-server.test.js +0 -12
  41. package/dist/permission-mode/session-mode.d.ts +0 -7
  42. package/dist/permission-mode/session-mode.js +0 -59
  43. package/dist/permission-mode/types.d.ts +0 -11
  44. package/dist/permission-mode/types.js +0 -29
package/README.md CHANGED
@@ -57,9 +57,11 @@ The config file is stored at `~/.open-im/config.json` by default.
57
57
  | `open-im stop` | Stop the background service |
58
58
  | `open-im dev` | Run in the foreground for development/debugging |
59
59
 
60
- ## Graphical Config Page
60
+ ## Server Deployment & Config Page
61
61
 
62
- Open the config page at **http://127.0.0.1:39282** (or the URL shown after `open-im start`). The page includes:
62
+ ### Local (with browser)
63
+
64
+ Open the config page at [`http://127.0.0.1:39282`](http://127.0.0.1:39282) (or the URL shown after `open-im start`). The page includes:
63
65
 
64
66
  - **Dashboard** – Configured / Enabled platform count and service status (Idle or Running)
65
67
  - **Platforms** – Enable and configure Telegram, Feishu, QQ, WeCom, and DingTalk (credentials, proxy, per-platform AI tool, allowed user IDs). Each platform has a “Test Configuration” button.
@@ -68,10 +70,55 @@ Open the config page at **http://127.0.0.1:39282** (or the URL shown after `open
68
70
 
69
71
  WeChat is not in the web UI; configure it in `~/.open-im/config.json` or via `open-im init` if needed.
70
72
 
71
- - `open-im start` serves the config page and the bridge.
73
+ - `open-im start` serves both the config page and the bridge.
72
74
  - `open-im dev` opens the page automatically only when setup is incomplete.
73
75
  - To open the page when config already exists, run `open-im start` and visit the URL above.
74
76
 
77
+ ### On a headless server (no GUI)
78
+
79
+ Many servers do not have a desktop environment or browser. In that case, trying to auto-launch a browser (`xdg-open`, `open`, `start`) is unnecessary and may even fail. Use this pattern instead:
80
+
81
+ - **1) Disable automatic browser launch**
82
+
83
+ On the server:
84
+
85
+ ```bash
86
+ export OPEN_IM_NO_BROWSER=1
87
+ open-im start
88
+ ```
89
+
90
+ This starts the bridge and the config web server in the background without attempting to open a browser.
91
+
92
+ - **2) Verify that the config page is listening on the server**
93
+
94
+ On the server:
95
+
96
+ ```bash
97
+ ss -lntp | grep 39282 # or: netstat -lntp | grep 39282
98
+ curl -v http://127.0.0.1:39282/
99
+ ```
100
+
101
+ If you see a `LISTEN` line for `127.0.0.1:39282` and `curl` returns HTML, the config UI is running.
102
+
103
+ - **3) Access the config UI from your local machine via SSH tunnel**
104
+
105
+ Instead of exposing port 39282 to the public internet, use SSH port forwarding:
106
+
107
+ ```bash
108
+ # On your local machine:
109
+ ssh -L 39282:127.0.0.1:39282 user@your-server-ip
110
+ ```
111
+
112
+ Then open in your local browser:
113
+
114
+ ```text
115
+ http://127.0.0.1:39282/
116
+ ```
117
+
118
+ This safely tunnels the config page from the server to your local browser.
119
+
120
+ > If you really want to expose the config UI directly, you can change the listener in `config-web.ts` from `server.listen(port, "127.0.0.1", ...)` to `0.0.0.0` and open port 39282 in your firewall / security group. For security reasons, SSH tunneling is strongly recommended instead.
121
+
75
122
  ## Session Behavior
76
123
 
77
124
  Session context is stored locally in `~/.open-im/data/sessions.json` and is separate from the IM chat history itself. Each user has an independent session directory and session metadata. Sending `/new` resets the current AI session.
package/README.zh-CN.md CHANGED
@@ -57,9 +57,17 @@ open-im start
57
57
  | `open-im stop` | 停止后台服务 |
58
58
  | `open-im dev` | 前台运行(调试模式) |
59
59
 
60
- ## 图形化配置页面
60
+ ## 服务器部署与图形化配置
61
61
 
62
- 在浏览器中打开 **http://127.0.0.1:39282**(或执行 `open-im start` 后提示的地址),页面结构如下:
62
+ ### 本机(带浏览器)使用
63
+
64
+ 在本机直接运行:
65
+
66
+ ```bash
67
+ open-im start
68
+ ```
69
+
70
+ 然后在浏览器中打开 [`http://127.0.0.1:39282`](http://127.0.0.1:39282)(或命令行里提示的地址),页面结构如下:
63
71
 
64
72
  - **概览** – 已配置/已启用平台数量、服务状态(未启动或运行中)
65
73
  - **平台配置** – 启用并填写 Telegram、飞书、QQ、企业微信、钉钉的凭证(Bot Token/App ID/Secret、代理、该平台使用的 AI 工具、白名单用户 ID)。每个平台提供「校验配置」按钮
@@ -72,6 +80,51 @@ open-im start
72
80
  - `open-im dev` 仅在未完成配置时自动打开页面
73
81
  - 已有配置但想手动打开时,执行 `open-im start` 后访问上述地址即可
74
82
 
83
+ ### 在服务器上部署(无图形界面)
84
+
85
+ 很多服务器没有桌面环境和浏览器,此时「自动打开浏览器」既没意义,还可能因为缺少 `xdg-open` 报错。推荐如下用法:
86
+
87
+ - **1)关闭自动打开浏览器**
88
+
89
+ 在服务器上设置环境变量,然后启动:
90
+
91
+ ```bash
92
+ export OPEN_IM_NO_BROWSER=1
93
+ open-im start
94
+ ```
95
+
96
+ 这样只会在后台启动服务与配置页面,不会尝试执行 `xdg-open` / `open` / `start`。
97
+
98
+ - **2)检查配置页面是否已在服务器本机监听**
99
+
100
+ 在服务器上执行:
101
+
102
+ ```bash
103
+ ss -lntp | grep 39282 # 或 netstat -lntp | grep 39282
104
+ curl -v http://127.0.0.1:39282/
105
+ ```
106
+
107
+ 若看到 `LISTEN 0 ... 127.0.0.1:39282` 且 `curl` 返回 HTML,则说明 Web 配置页已正常启动。
108
+
109
+ - **3)通过 SSH 隧道在本地浏览器访问**
110
+
111
+ 不建议直接对外开放 39282 端口,而是使用 SSH 端口转发:
112
+
113
+ ```bash
114
+ # 在本地电脑执行,将本地 39282 转发到服务器 127.0.0.1:39282
115
+ ssh -L 39282:127.0.0.1:39282 user@your-server-ip
116
+ ```
117
+
118
+ 然后在本地浏览器访问:
119
+
120
+ ```text
121
+ http://127.0.0.1:39282/
122
+ ```
123
+
124
+ 即可打开服务器上的配置页面。
125
+
126
+ > 如确有需要,也可以自行修改 `config-web.ts` 中的监听地址,将 `server.listen(port, "127.0.0.1", ...)` 调整为 `0.0.0.0`,并在防火墙/安全组放行 39282 端口。但出于安全考虑,官方推荐使用 SSH 隧道方式。
127
+
75
128
  ## 会话说明
76
129
 
77
130
  会话上下文保存在本地 `~/.open-im/data/sessions.json`,与 IM 聊天记录本身无关。每个用户有独立会话目录和 session 信息,发送 `/new` 会重置当前 AI 会话。
@@ -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
  },
@@ -472,18 +459,38 @@ function toFileConfig(payload, existing) {
472
459
  };
473
460
  }
474
461
  function openBrowser(url) {
462
+ // 显式关闭自动打开浏览器(服务器环境推荐设置)
475
463
  if (process.env.OPEN_IM_NO_BROWSER === "1") {
476
464
  return;
477
465
  }
466
+ // 在无 TTY 且无图形环境(常见于服务器)时直接跳过,避免无意义的 xdg-open 调用
467
+ if (!process.stdout.isTTY && !process.env.DISPLAY) {
468
+ log.info(`Skipping browser launch for URL ${url} (no TTY/DISPLAY detected).`);
469
+ return;
470
+ }
471
+ const safeSpawn = (command, args) => {
472
+ try {
473
+ const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: process.platform === "win32" });
474
+ // 防止 ENOENT 之类的错误变成未捕获异常
475
+ child.on("error", (error) => {
476
+ log.warn(`Failed to launch browser command "${command}": ${error.code ?? error.message}`);
477
+ });
478
+ child.unref();
479
+ }
480
+ catch (error) {
481
+ log.warn(`Failed to spawn browser command "${command}": ${error instanceof Error ? error.message : String(error)}`);
482
+ }
483
+ };
478
484
  if (process.platform === "win32") {
479
- spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore", windowsHide: true }).unref();
485
+ safeSpawn("cmd", ["/c", "start", "", url]);
480
486
  return;
481
487
  }
482
488
  if (process.platform === "darwin") {
483
- spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
489
+ safeSpawn("open", [url]);
484
490
  return;
485
491
  }
486
- spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
492
+ // linux / 其他 UNIX 平台:优先尝试 xdg-open,失败时仅记录日志,不抛出
493
+ safeSpawn("xdg-open", [url]);
487
494
  }
488
495
  export function getWebConfigPort() {
489
496
  const fromEnv = process.env.OPEN_IM_WEB_PORT ? parseInt(process.env.OPEN_IM_WEB_PORT, 10) : NaN;
@@ -623,8 +630,9 @@ export async function startWebConfigServer(options) {
623
630
  }
624
631
  if (request.method === "POST" && requestUrl.pathname === "/api/service/start") {
625
632
  try {
626
- loadConfig();
627
- const started = startBackgroundService(options.cwd);
633
+ const config = loadConfig();
634
+ const workDir = config.claudeWorkDir ?? options.cwd;
635
+ const started = startBackgroundService(workDir);
628
636
  json(response, 200, { message: `Bridge started with pid ${started.pid}.`, pid: started.pid });
629
637
  if (!options.persistent) {
630
638
  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;