@wu529778790/open-im 0.4.0 → 1.0.1
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 +77 -103
- package/dist/access/access-control.js +9 -2
- package/dist/claude/cli-runner.js +80 -28
- package/dist/cli.js +102 -183
- package/dist/config.d.ts +11 -1
- package/dist/config.js +67 -9
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/feishu/client.d.ts +5 -0
- package/dist/feishu/client.js +69 -0
- package/dist/feishu/event-handler.d.ts +8 -0
- package/dist/feishu/event-handler.js +255 -0
- package/dist/feishu/message-sender.d.ts +7 -0
- package/dist/feishu/message-sender.js +253 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +85 -69
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +160 -71
- package/dist/telegram/client.d.ts +2 -2
- package/dist/telegram/client.js +11 -23
- package/dist/telegram/event-handler.d.ts +3 -3
- package/dist/telegram/event-handler.js +84 -71
- package/dist/telegram/message-sender.d.ts +1 -1
- package/dist/telegram/message-sender.js +72 -89
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,144 +1,114 @@
|
|
|
1
1
|
# open-im
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
多平台 IM 桥接,将 Telegram 和飞书 (Feishu/Lark) 连接到 AI CLI 工具(Claude Code、Codex、Cursor),实现移动端/远程访问 AI 编程助手。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 功能特性
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **多平台**:支持 Telegram 和飞书,可同时启用
|
|
8
|
+
- **多 AI 工具**:通过配置切换 Claude Code / Codex / Cursor
|
|
9
|
+
- **流式输出**:节流更新,实时展示 AI 回复
|
|
10
|
+
- **会话管理**:每用户独立 session,`/new` 重置会话
|
|
11
|
+
- **命令支持**:`/help` `/new` `/cd` `/pwd` `/status` `/allow` `/deny`
|
|
8
12
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
- **📱 移动友好** - 告别终端,用手机照样写代码
|
|
12
|
-
- **⚡ 实时流式输出** - AI 思考过程实时可见,像在终端一样流畅
|
|
13
|
-
- **🔒 安全可控** - 支持白名单,只有你能用
|
|
14
|
-
- **🔄 独立会话** - 每个人独立 session,互不干扰
|
|
15
|
-
- **🛠️ 多 AI 支持** - Claude / Codex / Cursor 随意切换
|
|
13
|
+
## 环境要求
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- **Node.js** >= 20
|
|
16
|
+
- **AI CLI**:已安装 Claude Code CLI(或 Codex/Cursor)并加入 PATH
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
## 安装
|
|
20
19
|
|
|
21
20
|
```bash
|
|
22
|
-
|
|
21
|
+
npm install @wu529778790/open-im -g
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## ✨ 为什么选择 open-im
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
|
|
29
|
-
open-im
|
|
27
|
+
# 使用 npx 快速体验(无需全局安装)
|
|
28
|
+
npx @wu529778790/open-im start # 后台运行
|
|
29
|
+
npx @wu529778790/open-im stop # 停止后台进程
|
|
30
|
+
npx @wu529778790/open-im dev # 前台运行(调试),Ctrl+C 停止
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
如果配置引导未出现,可以手动运行:
|
|
33
|
+
或全局安装后直接使用:
|
|
35
34
|
|
|
36
35
|
```bash
|
|
37
|
-
|
|
36
|
+
npm install @wu529778790/open-im -g
|
|
37
|
+
open-im start
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
首次运行会进入交互式配置向导,按提示输入 Token 后自动启动。配置保存到 `~/.open-im/config.json`。
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
配置文件示例:
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"telegramBotToken": "你的Bot Token(从 @BotFather 获取)",
|
|
49
|
-
"allowedUserIds": ["你的Telegram用户ID"],
|
|
50
|
-
"claudeWorkDir": "/path/to/your/work/dir",
|
|
51
|
-
"claudeSkipPermissions": true,
|
|
52
|
-
"aiCommand": "claude",
|
|
53
|
-
"platforms": {
|
|
54
|
-
"telegram": {
|
|
55
|
-
"proxy": "http://127.0.0.1:7890"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
42
|
+
## 运行方式
|
|
60
43
|
|
|
61
|
-
|
|
44
|
+
| 命令 | 说明 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| `open-im start` | 后台运行,适合长期使用 |
|
|
47
|
+
| `open-im stop` | 停止后台进程 |
|
|
48
|
+
| `open-im dev` 或 `open-im` | 前台运行(调试),Ctrl+C 停止 |
|
|
62
49
|
|
|
63
|
-
|
|
50
|
+
## 会话说明
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
**会话上下文存储在本地**(`~/.open-im/data/sessions.json`),与 IM 聊天记录无关。每用户在本地维护独立的 session 和 Claude 会话 ID,`/new` 可重置当前会话。
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"platforms": {
|
|
71
|
-
"telegram": {
|
|
72
|
-
"proxy": "http://127.0.0.1:7890"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
54
|
+
```bash
|
|
55
|
+
npm i @wu529778790/open-im -g
|
|
56
|
+
open-im run
|
|
76
57
|
```
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
- HTTP 代理:`http://127.0.0.1:7890`
|
|
80
|
-
- HTTPS 代理:`https://127.0.0.1:7890`
|
|
81
|
-
- SOCKS5 代理:`socks5://127.0.0.1:1080`
|
|
82
|
-
|
|
83
|
-
**注意:**
|
|
84
|
-
- 代理仅用于访问 Telegram API,不会影响 AI 工具(Claude/Codex/Cursor)的网络请求
|
|
85
|
-
- 飞书等其他国内 IM 平台无需配置代理
|
|
86
|
-
- 如果你的网络能直接访问 Telegram,则无需配置代理
|
|
59
|
+
### 环境变量
|
|
87
60
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
61
|
+
| 变量 | 说明 |
|
|
62
|
+
|------|------|
|
|
63
|
+
| `TELEGRAM_BOT_TOKEN` | Telegram Bot Token(从 @BotFather 获取) |
|
|
64
|
+
| `FEISHU_APP_ID` | 飞书应用 App ID |
|
|
65
|
+
| `FEISHU_APP_SECRET` | 飞书应用 App Secret |
|
|
66
|
+
| `ALLOWED_USER_IDS` | 白名单用户 ID(逗号分隔,空=所有人) |
|
|
67
|
+
| `AI_COMMAND` | `claude` \| `codex` \| `cursor`,默认 `claude` |
|
|
68
|
+
| `CLAUDE_CLI_PATH` | Claude CLI 路径,默认 `claude` |
|
|
69
|
+
| `CLAUDE_WORK_DIR` | 工作目录 |
|
|
70
|
+
| `CLAUDE_SKIP_PERMISSIONS` | 跳过权限确认,默认 `true` |
|
|
71
|
+
| `CLAUDE_TIMEOUT_MS` | Claude 超时(毫秒),默认 600000 |
|
|
72
|
+
| `CLAUDE_MODEL` | Claude 模型(可选) |
|
|
73
|
+
| `ALLOWED_BASE_DIRS` | 允许访问的目录(逗号分隔) |
|
|
74
|
+
| `LOG_DIR` | 日志目录,默认 `~/.open-im/logs` |
|
|
75
|
+
| `LOG_LEVEL` | 日志级别:INFO/DEBUG/WARN/ERROR |
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
1. 在 Telegram 中搜索 @userinfobot
|
|
96
|
-
2. 发送任意消息
|
|
97
|
-
3. 机器人会返回你的用户 ID
|
|
98
|
-
4. 如不设置,则所有人都可以使用你的机器人
|
|
77
|
+
### 配置文件
|
|
99
78
|
|
|
100
|
-
|
|
79
|
+
配置优先级:环境变量 > `~/.open-im/config.json` > 默认值。
|
|
101
80
|
|
|
102
|
-
|
|
103
|
-
|------|------|
|
|
104
|
-
| `open-im` / `open-im run` | 前台运行(首次使用会引导配置) |
|
|
105
|
-
| `open-im init` | 初始化配置(首次使用或重新配置) |
|
|
106
|
-
| `open-im start` | 后台启动服务 |
|
|
107
|
-
| `open-im stop` | 停止服务 |
|
|
108
|
-
| `open-im restart` | 重启服务 |
|
|
81
|
+
至少需配置 **Telegram** 或 **飞书** 其一:
|
|
109
82
|
|
|
110
|
-
|
|
83
|
+
- **Telegram**:`TELEGRAM_BOT_TOKEN` 或 `telegramBotToken`
|
|
84
|
+
- **飞书**:`FEISHU_APP_ID` + `FEISHU_APP_SECRET` 或 `feishuAppId` + `feishuAppSecret`
|
|
111
85
|
|
|
112
|
-
|
|
113
|
-
|------|------|
|
|
114
|
-
| `/help` | 查看帮助 |
|
|
115
|
-
| `/new` | 开启新会话 |
|
|
116
|
-
| `/cd <路径>` | 切换工作目录 |
|
|
117
|
-
| `/pwd` | 查看当前目录 |
|
|
118
|
-
| `/status` | 查看运行状态 |
|
|
86
|
+
### 飞书配置说明
|
|
119
87
|
|
|
120
|
-
|
|
88
|
+
1. 在 [飞书开放平台](https://open.feishu.cn/) 创建企业自建应用
|
|
89
|
+
2. 开启「机器人」能力
|
|
90
|
+
3. 配置事件订阅:启用 `im.message.receive_v1`,使用 **长连接** 模式(WebSocket)
|
|
91
|
+
4. 将机器人添加到目标群聊或发起私聊
|
|
121
92
|
|
|
122
|
-
|
|
123
|
-
- ☕ **咖啡厅** - 没带电脑也能快速调试
|
|
124
|
-
- 🛋️ **沙发模式** - 躺着看 AI 帮你写代码
|
|
125
|
-
- 🌙 **紧急修复** - 半夜收到报警,手机直接处理
|
|
126
|
-
|
|
127
|
-
## 📦 安装方式
|
|
93
|
+
## 开发
|
|
128
94
|
|
|
129
95
|
```bash
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
npm i @wu529778790/open-im -g
|
|
96
|
+
npm run build # 构建
|
|
97
|
+
npm run dev # 直接运行源码(tsx,无需 build)
|
|
98
|
+
npm run foreground # 前台运行已构建版本
|
|
99
|
+
```
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
yarn global add @wu529778790/open-im
|
|
101
|
+
## IM 内命令
|
|
138
102
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
103
|
+
| 命令 | 说明 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `/help` | 显示帮助 |
|
|
106
|
+
| `/new` | 开始新会话 |
|
|
107
|
+
| `/status` | 显示状态(AI 工具、工作目录、费用等) |
|
|
108
|
+
| `/cd <路径>` | 切换工作目录 |
|
|
109
|
+
| `/pwd` | 显示当前工作目录 |
|
|
110
|
+
| `/allow` `/y` | 允许权限请求 |
|
|
111
|
+
| `/deny` `/n` | 拒绝权限请求 |
|
|
142
112
|
|
|
143
113
|
## 📝 License
|
|
144
114
|
|
|
@@ -151,16 +121,19 @@ pnpm i @wu529778790/open-im -g
|
|
|
151
121
|
如果配置引导没有出现,尝试以下方法:
|
|
152
122
|
|
|
153
123
|
1. **手动运行配置命令:**
|
|
124
|
+
|
|
154
125
|
```bash
|
|
155
126
|
npx @wu529778790/open-im init
|
|
156
127
|
```
|
|
157
128
|
|
|
158
129
|
2. **检查是否已有配置文件:**
|
|
130
|
+
|
|
159
131
|
```bash
|
|
160
132
|
cat ~/.open-im/config.json
|
|
161
133
|
```
|
|
162
134
|
|
|
163
135
|
3. **手动创建配置文件:**
|
|
136
|
+
|
|
164
137
|
```bash
|
|
165
138
|
mkdir -p ~/.open-im
|
|
166
139
|
cat > ~/.open-im/config.json << 'EOF'
|
|
@@ -220,6 +193,7 @@ npx @wu529778790/open-im run
|
|
|
220
193
|
```
|
|
221
194
|
|
|
222
195
|
支持的代理格式:
|
|
196
|
+
|
|
223
197
|
- HTTP:`http://127.0.0.1:7890`
|
|
224
198
|
- HTTPS:`https://127.0.0.1:7890`
|
|
225
199
|
- SOCKS5:`socks5://127.0.0.1:1080`
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import { createLogger } from '../logger.js';
|
|
2
|
+
const log = createLogger('AccessControl');
|
|
1
3
|
export class AccessControl {
|
|
2
4
|
allowedUserIds;
|
|
3
5
|
constructor(allowedUserIds) {
|
|
4
6
|
this.allowedUserIds = new Set(allowedUserIds);
|
|
7
|
+
log.info(`AccessControl initialized with ${allowedUserIds.length} allowed users:`, allowedUserIds);
|
|
5
8
|
}
|
|
6
9
|
isAllowed(userId) {
|
|
7
|
-
if (this.allowedUserIds.size === 0)
|
|
10
|
+
if (this.allowedUserIds.size === 0) {
|
|
11
|
+
log.debug(`Allowing user ${userId} (no whitelist configured)`);
|
|
8
12
|
return true;
|
|
9
|
-
|
|
13
|
+
}
|
|
14
|
+
const allowed = this.allowedUserIds.has(userId);
|
|
15
|
+
log.info(`Checking user ${userId}: ${allowed ? 'ALLOWED' : 'DENIED'}`);
|
|
16
|
+
return allowed;
|
|
10
17
|
}
|
|
11
18
|
}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import { createInterface } from
|
|
3
|
-
import { parseStreamLine, extractTextDelta, extractThinkingDelta, extractResult } from
|
|
4
|
-
import { isStreamInit, isContentBlockStart, isContentBlockDelta, isContentBlockStop } from
|
|
5
|
-
import { createLogger } from
|
|
6
|
-
const log = createLogger(
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { parseStreamLine, extractTextDelta, extractThinkingDelta, extractResult, } from "./stream-parser.js";
|
|
4
|
+
import { isStreamInit, isContentBlockStart, isContentBlockDelta, isContentBlockStop, } from "./types.js";
|
|
5
|
+
import { createLogger } from "../logger.js";
|
|
6
|
+
const log = createLogger("CliRunner");
|
|
7
7
|
export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, options) {
|
|
8
|
-
const args = [
|
|
8
|
+
const args = [
|
|
9
|
+
"-p",
|
|
10
|
+
"--output-format",
|
|
11
|
+
"stream-json",
|
|
12
|
+
"--verbose",
|
|
13
|
+
"--include-partial-messages",
|
|
14
|
+
];
|
|
9
15
|
if (options?.skipPermissions)
|
|
10
|
-
args.push(
|
|
16
|
+
args.push("--dangerously-skip-permissions");
|
|
11
17
|
if (options?.model)
|
|
12
|
-
args.push(
|
|
18
|
+
args.push("--model", options.model);
|
|
13
19
|
if (sessionId)
|
|
14
|
-
args.push(
|
|
15
|
-
args.push(
|
|
20
|
+
args.push("--resume", sessionId);
|
|
21
|
+
args.push("--", prompt);
|
|
16
22
|
const env = {};
|
|
17
23
|
for (const [k, v] of Object.entries(process.env)) {
|
|
24
|
+
// Skip CLAUDECODE to prevent nested session detection
|
|
25
|
+
if (k === "CLAUDECODE")
|
|
26
|
+
continue;
|
|
18
27
|
if (v !== undefined)
|
|
19
28
|
env[k] = v;
|
|
20
29
|
}
|
|
@@ -22,29 +31,68 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
22
31
|
env.CC_IM_CHAT_ID = options.chatId;
|
|
23
32
|
if (options?.hookPort)
|
|
24
33
|
env.CC_IM_HOOK_PORT = String(options.hookPort);
|
|
25
|
-
|
|
26
|
-
log.
|
|
27
|
-
let
|
|
28
|
-
|
|
34
|
+
// Try different spawn strategies based on platform
|
|
35
|
+
log.info(`Spawning CLI: path=${cliPath}, platform=${process.platform}`);
|
|
36
|
+
let child;
|
|
37
|
+
if (process.platform === "win32") {
|
|
38
|
+
// Check if running in Git Bash (MINGW) or MSYS
|
|
39
|
+
const isGitBash = process.env.MSYSTEM ||
|
|
40
|
+
process.env.MINGW_PREFIX ||
|
|
41
|
+
process.env.SHELL?.includes("bash");
|
|
42
|
+
log.info(`Detected environment: Git Bash=${isGitBash ? "yes" : "no"}`);
|
|
43
|
+
if (isGitBash) {
|
|
44
|
+
// In Git Bash, use shell for proper path resolution
|
|
45
|
+
log.info(`Using shell spawn for Git Bash environment`);
|
|
46
|
+
child = spawn(cliPath, args, {
|
|
47
|
+
cwd: workDir,
|
|
48
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
+
env,
|
|
50
|
+
shell: true,
|
|
51
|
+
windowsHide: true,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// In pure cmd/PowerShell, direct spawn works best
|
|
56
|
+
log.info(`Using direct spawn for Windows cmd/PowerShell`);
|
|
57
|
+
child = spawn(cliPath, args, {
|
|
58
|
+
cwd: workDir,
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
60
|
+
env,
|
|
61
|
+
windowsHide: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
child = spawn(cliPath, args, {
|
|
67
|
+
cwd: workDir,
|
|
68
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
env,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
log.info(`Claude CLI: pid=${child.pid}, cwd=${workDir}, session=${sessionId ?? "new"}`);
|
|
73
|
+
let accumulated = "";
|
|
74
|
+
let accumulatedThinking = "";
|
|
29
75
|
let completed = false;
|
|
30
|
-
let model =
|
|
76
|
+
let model = "";
|
|
31
77
|
const toolStats = {};
|
|
32
78
|
const pendingToolInputs = new Map();
|
|
33
79
|
const MAX_TIMEOUT = 2_147_483_647;
|
|
34
|
-
const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
|
|
80
|
+
const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
|
|
81
|
+
? Math.min(options.timeoutMs, MAX_TIMEOUT)
|
|
82
|
+
: 0;
|
|
35
83
|
let timeoutHandle = null;
|
|
36
84
|
if (timeoutMs > 0) {
|
|
37
85
|
timeoutHandle = setTimeout(() => {
|
|
38
86
|
if (!completed && !child.killed) {
|
|
39
87
|
completed = true;
|
|
40
88
|
log.warn(`Claude CLI timeout after ${timeoutMs}ms, killing pid=${child.pid}`);
|
|
41
|
-
child.kill(
|
|
89
|
+
child.kill("SIGTERM");
|
|
42
90
|
callbacks.onError(`执行超时(${timeoutMs}ms),已终止进程`);
|
|
43
91
|
}
|
|
44
92
|
}, timeoutMs);
|
|
45
93
|
}
|
|
46
94
|
const rl = createInterface({ input: child.stdout });
|
|
47
|
-
rl.on(
|
|
95
|
+
rl.on("line", (line) => {
|
|
48
96
|
const event = parseStreamLine(line);
|
|
49
97
|
if (!event)
|
|
50
98
|
return;
|
|
@@ -64,16 +112,18 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
64
112
|
callbacks.onThinking?.(accumulatedThinking);
|
|
65
113
|
return;
|
|
66
114
|
}
|
|
67
|
-
if (isContentBlockStart(event) &&
|
|
115
|
+
if (isContentBlockStart(event) &&
|
|
116
|
+
event.event.content_block?.type === "tool_use") {
|
|
68
117
|
const name = event.event.content_block.name;
|
|
69
118
|
if (name)
|
|
70
|
-
pendingToolInputs.set(event.event.index, { name, json:
|
|
119
|
+
pendingToolInputs.set(event.event.index, { name, json: "" });
|
|
71
120
|
return;
|
|
72
121
|
}
|
|
73
|
-
if (isContentBlockDelta(event) &&
|
|
122
|
+
if (isContentBlockDelta(event) &&
|
|
123
|
+
event.event.delta?.type === "input_json_delta") {
|
|
74
124
|
const pending = pendingToolInputs.get(event.event.index);
|
|
75
125
|
if (pending)
|
|
76
|
-
pending.json += event.event.delta.partial_json ??
|
|
126
|
+
pending.json += event.event.delta.partial_json ?? "";
|
|
77
127
|
return;
|
|
78
128
|
}
|
|
79
129
|
if (isContentBlockStop(event)) {
|
|
@@ -134,17 +184,19 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
134
184
|
}
|
|
135
185
|
}
|
|
136
186
|
};
|
|
137
|
-
child.on(
|
|
187
|
+
child.on("close", (code) => {
|
|
188
|
+
log.info(`Claude CLI closed: exitCode=${code}, pid=${child.pid}`);
|
|
138
189
|
exitCode = code;
|
|
139
190
|
childClosed = true;
|
|
140
191
|
finalize();
|
|
141
192
|
});
|
|
142
|
-
rl.on(
|
|
193
|
+
rl.on("close", () => {
|
|
143
194
|
rlClosed = true;
|
|
144
195
|
finalize();
|
|
145
196
|
});
|
|
146
|
-
child.on(
|
|
147
|
-
|
|
197
|
+
child.on("error", (err) => {
|
|
198
|
+
const errorCode = err.code;
|
|
199
|
+
log.error(`Claude CLI spawn error: ${err.message}, code=${errorCode}, path=${cliPath}`);
|
|
148
200
|
if (timeoutHandle)
|
|
149
201
|
clearTimeout(timeoutHandle);
|
|
150
202
|
if (!completed) {
|
|
@@ -160,7 +212,7 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
160
212
|
clearTimeout(timeoutHandle);
|
|
161
213
|
rl.close();
|
|
162
214
|
if (!child.killed)
|
|
163
|
-
child.kill(
|
|
215
|
+
child.kill("SIGTERM");
|
|
164
216
|
},
|
|
165
217
|
};
|
|
166
218
|
}
|