@wu529778790/open-im 0.3.12 → 1.0.0
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 +117 -78
- package/dist/access/access-control.js +9 -2
- package/dist/claude/cli-runner.js +79 -28
- package/dist/cli.js +102 -183
- package/dist/config.d.ts +16 -2
- package/dist/config.js +70 -7
- 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 +164 -66
- package/dist/telegram/client.d.ts +2 -2
- package/dist/telegram/client.js +11 -22
- 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/.env.example +0 -16
package/README.md
CHANGED
|
@@ -1,121 +1,115 @@
|
|
|
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
|
-
}
|
|
54
|
-
```
|
|
42
|
+
## 运行方式
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
| 命令 | 说明 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| `open-im start` | 后台运行,适合长期使用 |
|
|
47
|
+
| `open-im stop` | 停止后台进程 |
|
|
48
|
+
| `open-im dev` 或 `open-im` | 前台运行(调试),Ctrl+C 停止 |
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
1. 在 Telegram 中搜索 @userinfobot
|
|
64
|
-
2. 发送任意消息
|
|
65
|
-
3. 机器人会返回你的用户 ID
|
|
66
|
-
4. 如不设置,则所有人都可以使用你的机器人
|
|
50
|
+
## 会话说明
|
|
67
51
|
|
|
68
|
-
|
|
52
|
+
**会话上下文存储在本地**(`~/.open-im/data/sessions.json`),与 IM 聊天记录无关。每用户在本地维护独立的 session 和 Claude 会话 ID,`/new` 可重置当前会话。
|
|
69
53
|
|
|
70
54
|
```bash
|
|
71
|
-
|
|
72
|
-
export ALLOWED_USER_IDS="用户ID1,用户ID2"
|
|
55
|
+
npm i @wu529778790/open-im -g
|
|
73
56
|
open-im run
|
|
74
57
|
```
|
|
75
58
|
|
|
76
|
-
|
|
59
|
+
### 环境变量
|
|
77
60
|
|
|
78
|
-
|
|
|
61
|
+
| 变量 | 说明 |
|
|
79
62
|
|------|------|
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
|
|
86
|
-
|
|
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 |
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|------|------|
|
|
90
|
-
| `/help` | 查看帮助 |
|
|
91
|
-
| `/new` | 开启新会话 |
|
|
92
|
-
| `/cd <路径>` | 切换工作目录 |
|
|
93
|
-
| `/pwd` | 查看当前目录 |
|
|
94
|
-
| `/status` | 查看运行状态 |
|
|
77
|
+
### 配置文件
|
|
95
78
|
|
|
96
|
-
|
|
79
|
+
配置优先级:环境变量 > `~/.open-im/config.json` > 默认值。
|
|
97
80
|
|
|
98
|
-
|
|
99
|
-
- ☕ **咖啡厅** - 没带电脑也能快速调试
|
|
100
|
-
- 🛋️ **沙发模式** - 躺着看 AI 帮你写代码
|
|
101
|
-
- 🌙 **紧急修复** - 半夜收到报警,手机直接处理
|
|
81
|
+
至少需配置 **Telegram** 或 **飞书** 其一:
|
|
102
82
|
|
|
103
|
-
|
|
83
|
+
- **Telegram**:`TELEGRAM_BOT_TOKEN` 或 `telegramBotToken`
|
|
84
|
+
- **飞书**:`FEISHU_APP_ID` + `FEISHU_APP_SECRET` 或 `feishuAppId` + `feishuAppSecret`
|
|
104
85
|
|
|
105
|
-
|
|
106
|
-
# npx(无需安装)
|
|
107
|
-
npx @wu529778790/open-im run
|
|
86
|
+
### 飞书配置说明
|
|
108
87
|
|
|
109
|
-
|
|
110
|
-
|
|
88
|
+
1. 在 [飞书开放平台](https://open.feishu.cn/) 创建企业自建应用
|
|
89
|
+
2. 开启「机器人」能力
|
|
90
|
+
3. 配置事件订阅:启用 `im.message.receive_v1`,使用 **长连接** 模式(WebSocket)
|
|
91
|
+
4. 将机器人添加到目标群聊或发起私聊
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
yarn global add @wu529778790/open-im
|
|
93
|
+
## 开发
|
|
114
94
|
|
|
115
|
-
|
|
116
|
-
|
|
95
|
+
```bash
|
|
96
|
+
npm run build # 构建
|
|
97
|
+
npm run dev # 直接运行源码(tsx,无需 build)
|
|
98
|
+
npm run foreground # 前台运行已构建版本
|
|
117
99
|
```
|
|
118
100
|
|
|
101
|
+
## IM 内命令
|
|
102
|
+
|
|
103
|
+
| 命令 | 说明 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `/help` | 显示帮助 |
|
|
106
|
+
| `/new` | 开始新会话 |
|
|
107
|
+
| `/status` | 显示状态(AI 工具、工作目录、费用等) |
|
|
108
|
+
| `/cd <路径>` | 切换工作目录 |
|
|
109
|
+
| `/pwd` | 显示当前工作目录 |
|
|
110
|
+
| `/allow` `/y` | 允许权限请求 |
|
|
111
|
+
| `/deny` `/n` | 拒绝权限请求 |
|
|
112
|
+
|
|
119
113
|
## 📝 License
|
|
120
114
|
|
|
121
115
|
[MIT](LICENSE)
|
|
@@ -127,22 +121,30 @@ pnpm i @wu529778790/open-im -g
|
|
|
127
121
|
如果配置引导没有出现,尝试以下方法:
|
|
128
122
|
|
|
129
123
|
1. **手动运行配置命令:**
|
|
124
|
+
|
|
130
125
|
```bash
|
|
131
126
|
npx @wu529778790/open-im init
|
|
132
127
|
```
|
|
133
128
|
|
|
134
129
|
2. **检查是否已有配置文件:**
|
|
130
|
+
|
|
135
131
|
```bash
|
|
136
132
|
cat ~/.open-im/config.json
|
|
137
133
|
```
|
|
138
134
|
|
|
139
135
|
3. **手动创建配置文件:**
|
|
136
|
+
|
|
140
137
|
```bash
|
|
141
138
|
mkdir -p ~/.open-im
|
|
142
139
|
cat > ~/.open-im/config.json << 'EOF'
|
|
143
140
|
{
|
|
144
141
|
"telegramBotToken": "你的Bot Token",
|
|
145
142
|
"allowedUserIds": ["你的Telegram用户ID"],
|
|
143
|
+
"platforms": {
|
|
144
|
+
"telegram": {
|
|
145
|
+
"proxy": "http://127.0.0.1:7890"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
146
148
|
"claudeWorkDir": "$(pwd)",
|
|
147
149
|
"claudeSkipPermissions": true,
|
|
148
150
|
"aiCommand": "claude"
|
|
@@ -175,3 +177,40 @@ npx @wu529778790/open-im run
|
|
|
175
177
|
1. 在 Telegram 中搜索 @userinfobot
|
|
176
178
|
2. 点击"START"或发送任意消息
|
|
177
179
|
3. 机器人会返回你的用户 ID(数字)
|
|
180
|
+
|
|
181
|
+
### Q: 如何配置代理?
|
|
182
|
+
|
|
183
|
+
如果你的网络环境无法直接访问 Telegram,需要在配置文件中添加代理设置:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"platforms": {
|
|
188
|
+
"telegram": {
|
|
189
|
+
"proxy": "http://127.0.0.1:7890"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
支持的代理格式:
|
|
196
|
+
|
|
197
|
+
- HTTP:`http://127.0.0.1:7890`
|
|
198
|
+
- HTTPS:`https://127.0.0.1:7890`
|
|
199
|
+
- SOCKS5:`socks5://127.0.0.1:1080`
|
|
200
|
+
|
|
201
|
+
注意:代理仅用于访问 Telegram API,不影响 AI 工具的网络请求。
|
|
202
|
+
|
|
203
|
+
### Q: Telegram 机器人无响应?
|
|
204
|
+
|
|
205
|
+
可能原因及解决方法:
|
|
206
|
+
|
|
207
|
+
1. **网络问题 - Telegram 被阻断**
|
|
208
|
+
- 配置代理(见上方"如何配置代理")
|
|
209
|
+
- 测试代理是否可用:`curl -x http://127.0.0.1:7890 https://api.telegram.org`
|
|
210
|
+
|
|
211
|
+
2. **Token 错误**
|
|
212
|
+
- 重新获取 Token:在 @BotFather 中使用 `/revoke` 命令
|
|
213
|
+
|
|
214
|
+
3. **用户 ID 白名单问题**
|
|
215
|
+
- 检查配置文件中的 `allowedUserIds` 是否包含你的用户 ID
|
|
216
|
+
- 或留空允许所有人访问(仅开发环境建议)
|
|
@@ -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,67 @@ 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
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// In pure cmd/PowerShell, direct spawn works best
|
|
55
|
+
log.info(`Using direct spawn for Windows cmd/PowerShell`);
|
|
56
|
+
child = spawn(cliPath, args, {
|
|
57
|
+
cwd: workDir,
|
|
58
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
59
|
+
env,
|
|
60
|
+
windowsHide: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
child = spawn(cliPath, args, {
|
|
66
|
+
cwd: workDir,
|
|
67
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
68
|
+
env,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
log.info(`Claude CLI: pid=${child.pid}, cwd=${workDir}, session=${sessionId ?? "new"}`);
|
|
72
|
+
let accumulated = "";
|
|
73
|
+
let accumulatedThinking = "";
|
|
29
74
|
let completed = false;
|
|
30
|
-
let model =
|
|
75
|
+
let model = "";
|
|
31
76
|
const toolStats = {};
|
|
32
77
|
const pendingToolInputs = new Map();
|
|
33
78
|
const MAX_TIMEOUT = 2_147_483_647;
|
|
34
|
-
const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
|
|
79
|
+
const timeoutMs = options?.timeoutMs && options.timeoutMs > 0
|
|
80
|
+
? Math.min(options.timeoutMs, MAX_TIMEOUT)
|
|
81
|
+
: 0;
|
|
35
82
|
let timeoutHandle = null;
|
|
36
83
|
if (timeoutMs > 0) {
|
|
37
84
|
timeoutHandle = setTimeout(() => {
|
|
38
85
|
if (!completed && !child.killed) {
|
|
39
86
|
completed = true;
|
|
40
87
|
log.warn(`Claude CLI timeout after ${timeoutMs}ms, killing pid=${child.pid}`);
|
|
41
|
-
child.kill(
|
|
88
|
+
child.kill("SIGTERM");
|
|
42
89
|
callbacks.onError(`执行超时(${timeoutMs}ms),已终止进程`);
|
|
43
90
|
}
|
|
44
91
|
}, timeoutMs);
|
|
45
92
|
}
|
|
46
93
|
const rl = createInterface({ input: child.stdout });
|
|
47
|
-
rl.on(
|
|
94
|
+
rl.on("line", (line) => {
|
|
48
95
|
const event = parseStreamLine(line);
|
|
49
96
|
if (!event)
|
|
50
97
|
return;
|
|
@@ -64,16 +111,18 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
64
111
|
callbacks.onThinking?.(accumulatedThinking);
|
|
65
112
|
return;
|
|
66
113
|
}
|
|
67
|
-
if (isContentBlockStart(event) &&
|
|
114
|
+
if (isContentBlockStart(event) &&
|
|
115
|
+
event.event.content_block?.type === "tool_use") {
|
|
68
116
|
const name = event.event.content_block.name;
|
|
69
117
|
if (name)
|
|
70
|
-
pendingToolInputs.set(event.event.index, { name, json:
|
|
118
|
+
pendingToolInputs.set(event.event.index, { name, json: "" });
|
|
71
119
|
return;
|
|
72
120
|
}
|
|
73
|
-
if (isContentBlockDelta(event) &&
|
|
121
|
+
if (isContentBlockDelta(event) &&
|
|
122
|
+
event.event.delta?.type === "input_json_delta") {
|
|
74
123
|
const pending = pendingToolInputs.get(event.event.index);
|
|
75
124
|
if (pending)
|
|
76
|
-
pending.json += event.event.delta.partial_json ??
|
|
125
|
+
pending.json += event.event.delta.partial_json ?? "";
|
|
77
126
|
return;
|
|
78
127
|
}
|
|
79
128
|
if (isContentBlockStop(event)) {
|
|
@@ -134,17 +183,19 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
134
183
|
}
|
|
135
184
|
}
|
|
136
185
|
};
|
|
137
|
-
child.on(
|
|
186
|
+
child.on("close", (code) => {
|
|
187
|
+
log.info(`Claude CLI closed: exitCode=${code}, pid=${child.pid}`);
|
|
138
188
|
exitCode = code;
|
|
139
189
|
childClosed = true;
|
|
140
190
|
finalize();
|
|
141
191
|
});
|
|
142
|
-
rl.on(
|
|
192
|
+
rl.on("close", () => {
|
|
143
193
|
rlClosed = true;
|
|
144
194
|
finalize();
|
|
145
195
|
});
|
|
146
|
-
child.on(
|
|
147
|
-
|
|
196
|
+
child.on("error", (err) => {
|
|
197
|
+
const errorCode = err.code;
|
|
198
|
+
log.error(`Claude CLI spawn error: ${err.message}, code=${errorCode}, path=${cliPath}`);
|
|
148
199
|
if (timeoutHandle)
|
|
149
200
|
clearTimeout(timeoutHandle);
|
|
150
201
|
if (!completed) {
|
|
@@ -160,7 +211,7 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
|
|
|
160
211
|
clearTimeout(timeoutHandle);
|
|
161
212
|
rl.close();
|
|
162
213
|
if (!child.killed)
|
|
163
|
-
child.kill(
|
|
214
|
+
child.kill("SIGTERM");
|
|
164
215
|
},
|
|
165
216
|
};
|
|
166
217
|
}
|