chat-devops 26.1.1 → 26.2.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 +103 -57
- package/dist/cli.js +548 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,73 +1,104 @@
|
|
|
1
1
|
# chat-devops
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
飞书 / Lark 消息机器人:扫码创建应用,通过 WebSocket 接收消息,并桥接到本机 coding agent(Claude Code、Codex、Cursor、Pi、Alice)处理。
|
|
4
4
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
- `/help` — 显示命令帮助
|
|
7
|
+
- **应用初始化**:首次运行扫码创建 PersonalAgent 飞书应用(`@larksuite/channel` 的 `registerApp`),或传入已有 `app-id` / `app-secret`
|
|
8
|
+
- **Agent 桥接**:将用户消息交给本地 agent CLI,支持流式 markdown / 交互卡片回复
|
|
9
|
+
- **多 agent 切换**:运行时用 `/use claude|codex|cursor|pi|alice` 切换,无需重启
|
|
10
|
+
- **会话与工作目录**:按 chat(及话题群 thread)隔离 session 与 cwd,支持 `/new`、`/reset`、`/resume`
|
|
11
|
+
- **斜杠命令**:`/cmd`、`/cwd`、`/status`、`/stop`、`/send`、`/help` 等(详见下方)
|
|
12
|
+
- **后台 daemon**:`start` / `stop` / `restart` / `status`,支持 macOS launchd、Linux systemd、Windows 任务计划
|
|
13
|
+
- **无 agent 降级**:agent 未安装或不可用时,仍可使用 `/cmd`、`/status` 等命令;普通消息回退为固定文本(`ping` → `pong`,其余 → `defaultReply`)
|
|
15
14
|
|
|
16
15
|
## 环境要求
|
|
17
16
|
|
|
18
17
|
- Node.js >= 20.12
|
|
18
|
+
- 至少安装一种 agent CLI(按需):
|
|
19
|
+
- Claude Code(`claude`)
|
|
20
|
+
- OpenAI Codex CLI(`codex`)
|
|
21
|
+
- Cursor Agent(`agent`)
|
|
22
|
+
- Pi(`pi`)
|
|
23
|
+
- Alice(`alice`)
|
|
19
24
|
|
|
20
25
|
## 快速开始
|
|
21
26
|
|
|
22
27
|
```bash
|
|
23
|
-
#
|
|
28
|
+
# 安装依赖(开发)
|
|
24
29
|
npm install
|
|
25
30
|
|
|
26
|
-
#
|
|
31
|
+
# 前台运行(监听 src/ 变更自动重启)
|
|
27
32
|
npm run dev
|
|
28
|
-
|
|
33
|
+
|
|
34
|
+
# 或单次前台运行
|
|
29
35
|
npm start
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
全局安装后可直接使用 `chat-devops` 命令:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g chat-devops
|
|
42
|
+
chat-devops run
|
|
43
|
+
```
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
扫码完成后,配置保存到 `~/.chat-devops/config.json`。在飞书中找到应用,私聊或在群里 @ 机器人发消息即可。
|
|
46
|
+
|
|
47
|
+
生产/发布前打包:
|
|
35
48
|
|
|
36
49
|
```bash
|
|
37
50
|
npm run build
|
|
38
51
|
npm run start:dist
|
|
39
52
|
```
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
在飞书中找到刚创建的应用,发私聊消息或在群里 @ 机器人:
|
|
54
|
+
## CLI 命令
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
| 命令 | 说明 |
|
|
57
|
+
|------|------|
|
|
58
|
+
| `chat-devops run` | 前台运行 bot |
|
|
59
|
+
| `chat-devops start` | 安装并以后台 daemon 启动 |
|
|
60
|
+
| `chat-devops stop` | 停止 daemon |
|
|
61
|
+
| `chat-devops restart` | 重启 daemon |
|
|
62
|
+
| `chat-devops status` | 查看 daemon 状态 |
|
|
63
|
+
| `chat-devops update` | 全局升级到最新版 |
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
常用选项:
|
|
49
66
|
|
|
50
67
|
```bash
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
chat-devops run --agent cursor # 指定 agent 类型
|
|
69
|
+
chat-devops run --agent disabled # 禁用 agent,仅命令模式
|
|
70
|
+
chat-devops run --app-id cli_xxx --app-secret <secret> --tenant feishu
|
|
71
|
+
chat-devops run --debug # Cursor agent 调试输出
|
|
55
72
|
```
|
|
56
73
|
|
|
57
|
-
国际版 Lark
|
|
74
|
+
国际版 Lark 将 `--tenant` 设为 `lark`。
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
node bin/chat-devops.mjs run --app-id cli_xxx --app-secret <secret> --tenant lark
|
|
61
|
-
```
|
|
76
|
+
## 斜杠命令
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
| 命令 | 说明 |
|
|
79
|
+
|------|------|
|
|
80
|
+
| `/help` | 显示帮助卡片 |
|
|
81
|
+
| `/status` | 当前 scope、cwd、session、agent、队列状态 |
|
|
82
|
+
| `/new` / `/reset` | 清空当前 chat 的 agent 会话 |
|
|
83
|
+
| `/new chat [name]` | 新建群并拉你进群,继承 cwd |
|
|
84
|
+
| `/resume` | 查看可恢复的会话 |
|
|
85
|
+
| `/cwd <path>` | 设置工作目录(绝对路径或 `~/...`) |
|
|
86
|
+
| `/cwd view` | 查看工作目录 |
|
|
87
|
+
| `/use claude\|codex\|cursor\|pi\|alice` | 切换 agent |
|
|
88
|
+
| `/cmd <shell>` / `$ <shell>` | 本机执行 shell;需交互输入时直接发下一条消息 |
|
|
89
|
+
| `/stop` | 中断正在运行的 agent 任务或 shell 命令 |
|
|
90
|
+
| `/send <path>` | 发送本地文件到当前会话 |
|
|
64
91
|
|
|
65
|
-
|
|
92
|
+
以 `/` 开头的消息均为命令,不会交给 agent。群中默认需 @ 机器人才响应(私聊始终响应)。
|
|
93
|
+
|
|
94
|
+
## 配置
|
|
66
95
|
|
|
67
|
-
|
|
96
|
+
默认路径:`~/.chat-devops/config.json`(可通过环境变量 `FEISHU_DEVOPS_HOME` 修改数据目录)。
|
|
68
97
|
|
|
69
98
|
```json
|
|
70
99
|
{
|
|
100
|
+
"schemaVersion": 2,
|
|
101
|
+
"agentKind": "claude",
|
|
71
102
|
"accounts": {
|
|
72
103
|
"app": {
|
|
73
104
|
"id": "cli_xxxxxxxxxxxx",
|
|
@@ -77,42 +108,57 @@ node bin/chat-devops.mjs run --app-id cli_xxx --app-secret <secret> --tenant lar
|
|
|
77
108
|
},
|
|
78
109
|
"preferences": {
|
|
79
110
|
"requireMentionInGroup": true,
|
|
80
|
-
"defaultReply": "hello world"
|
|
111
|
+
"defaultReply": "hello world",
|
|
112
|
+
"cmdEnabled": true,
|
|
113
|
+
"cmdTimeoutSeconds": 300,
|
|
114
|
+
"cmdProgressIntervalSeconds": 5,
|
|
115
|
+
"messageReply": "markdown",
|
|
116
|
+
"showToolCalls": true,
|
|
117
|
+
"maxConcurrentRuns": 10,
|
|
118
|
+
"runIdleTimeoutMinutes": 0,
|
|
119
|
+
"isolateTopicThreads": true,
|
|
120
|
+
"agentStopGraceMs": 5000,
|
|
121
|
+
"access": {
|
|
122
|
+
"allowedUsers": [],
|
|
123
|
+
"allowedChats": [],
|
|
124
|
+
"admins": []
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"workspaces": {
|
|
128
|
+
"default": "/path/to/project"
|
|
81
129
|
}
|
|
82
130
|
}
|
|
83
131
|
```
|
|
84
132
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
133
|
+
主要配置项:
|
|
134
|
+
|
|
135
|
+
- `agentKind`:默认 agent(`claude` / `codex` / `cursor` / `pi` / `alice`)
|
|
136
|
+
- `requireMentionInGroup`:群中是否必须 @ 机器人才响应(默认 `true`)
|
|
137
|
+
- `defaultReply`:agent 禁用或不可用时的固定回复
|
|
138
|
+
- `messageReply`:agent 回复渲染模式 — `markdown`(流式卡片)、`card`(完整交互卡)、`text`(完成后一次性发送)
|
|
139
|
+
- `cmdEnabled` / `cmdTimeoutSeconds` / `cmdProgressIntervalSeconds`:`/cmd` 相关
|
|
140
|
+
- `maxConcurrentRuns`:全局最大并发 agent 任务数(默认 `10`)
|
|
141
|
+
- `isolateTopicThreads`:话题群是否按话题隔离 cwd / session(默认 `true`)
|
|
142
|
+
- `preferences.access`:允许的用户、群聊、管理员白名单(空数组表示不限制)
|
|
90
143
|
|
|
91
|
-
> **安全提示**:`/cmd` 会在运行 bot 的本机执行任意 shell
|
|
144
|
+
> **安全提示**:`/cmd` 会在运行 bot 的本机执行任意 shell 命令,agent 也可能读写本地文件系统。仅建议在可信环境使用。
|
|
92
145
|
|
|
93
146
|
## 项目结构
|
|
94
147
|
|
|
95
148
|
```
|
|
96
149
|
src/
|
|
97
|
-
├──
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
├──
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
├──
|
|
106
|
-
|
|
107
|
-
└── utils/
|
|
108
|
-
└── feishu-auth.ts # 凭证校验
|
|
150
|
+
├── agent/ # Claude / Codex / Cursor / Pi / Alice 适配器
|
|
151
|
+
├── bot/ # WebSocket 通道、命令处理、agent 运行
|
|
152
|
+
├── card/ # 飞书交互卡片与流式渲染
|
|
153
|
+
├── cli/ # CLI 入口、启动、daemon 服务
|
|
154
|
+
├── config/ # 配置读写与 schema
|
|
155
|
+
├── daemon/ # launchd / systemd / schtasks 集成
|
|
156
|
+
├── policy/ # 访问控制与工作目录策略
|
|
157
|
+
├── runtime/ # agent run 执行器
|
|
158
|
+
├── session/ # 会话持久化
|
|
159
|
+
└── workspace/ # 按 scope 的工作目录
|
|
109
160
|
```
|
|
110
161
|
|
|
111
|
-
##
|
|
162
|
+
## 参考
|
|
112
163
|
|
|
113
|
-
|
|
114
|
-
|-------------------------------------|--------|
|
|
115
|
-
| Claude/Codex agent 适配 | 无 LLM,固定文本回复 |
|
|
116
|
-
| 多 profile、加密 keystore | 单配置文件,明文 secret(0600 权限) |
|
|
117
|
-
| 流式卡片、slash 命令 | 简单 text 回复 |
|
|
118
|
-
| lark-cli 预检、后台 daemon | 仅前台 `run` 命令 |
|
|
164
|
+
- [lark-coding-agent-bridge](https://github.com/zarazhangrui/feishu-claude-code-bridge)
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Command } from "commander";
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "chat-devops",
|
|
7
|
-
version: "26.
|
|
7
|
+
version: "26.2.0",
|
|
8
8
|
description: "\u98DE\u4E66\u6D88\u606F bot\uFF1A\u626B\u7801\u521B\u5EFA\u5E94\u7528\uFF0C\u901A\u8FC7 Claude Code / Codex \u5904\u7406\u6D88\u606F",
|
|
9
9
|
type: "module",
|
|
10
10
|
bin: {
|
|
@@ -95,6 +95,9 @@ function getAgentStopGraceMs(cfg) {
|
|
|
95
95
|
if (typeof raw !== "number" || !Number.isFinite(raw)) return 5e3;
|
|
96
96
|
return Math.min(3e4, Math.max(100, Math.floor(raw)));
|
|
97
97
|
}
|
|
98
|
+
function getIsolateTopicThreads(cfg) {
|
|
99
|
+
return cfg.preferences?.isolateTopicThreads !== false;
|
|
100
|
+
}
|
|
98
101
|
function getRunIdleTimeoutMs(cfg) {
|
|
99
102
|
const raw = cfg.preferences?.runIdleTimeoutMinutes;
|
|
100
103
|
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return void 0;
|
|
@@ -2036,7 +2039,7 @@ function getAgentPreflightDiagnostic(err) {
|
|
|
2036
2039
|
function isAgentPreflightDiagnostic(input) {
|
|
2037
2040
|
if (!input || typeof input !== "object") return false;
|
|
2038
2041
|
const raw = input;
|
|
2039
|
-
return typeof raw.code === "string" && raw.code.startsWith("agent-") && (raw.agentId === "claude" || raw.agentId === "codex" || raw.agentId === "cursor" || raw.agentId === "pi") && typeof raw.agentName === "string" && typeof raw.command === "string";
|
|
2042
|
+
return typeof raw.code === "string" && raw.code.startsWith("agent-") && (raw.agentId === "claude" || raw.agentId === "codex" || raw.agentId === "cursor" || raw.agentId === "pi" || raw.agentId === "alice") && typeof raw.agentName === "string" && typeof raw.command === "string";
|
|
2040
2043
|
}
|
|
2041
2044
|
function codeForSpawnError(err) {
|
|
2042
2045
|
if (err.code === "ENOENT") return "agent-binary-not-found";
|
|
@@ -3427,6 +3430,354 @@ function isWindowsCommandNotFoundLine4(line) {
|
|
|
3427
3430
|
return process.platform === "win32" && /is not recognized as an internal or external command|operable program or batch file/i.test(line);
|
|
3428
3431
|
}
|
|
3429
3432
|
|
|
3433
|
+
// src/agent/alice/ag-ui.ts
|
|
3434
|
+
var AG_UI = {
|
|
3435
|
+
RUN_STARTED: "RUN_STARTED",
|
|
3436
|
+
RUN_FINISHED: "RUN_FINISHED",
|
|
3437
|
+
RUN_ERROR: "RUN_ERROR",
|
|
3438
|
+
TEXT_MESSAGE_CONTENT: "TEXT_MESSAGE_CONTENT",
|
|
3439
|
+
TEXT_MESSAGE_CHUNK: "TEXT_MESSAGE_CHUNK",
|
|
3440
|
+
REASONING_MESSAGE_CONTENT: "REASONING_MESSAGE_CONTENT",
|
|
3441
|
+
REASONING_MESSAGE_CHUNK: "REASONING_MESSAGE_CHUNK",
|
|
3442
|
+
THINKING_TEXT_MESSAGE_CONTENT: "THINKING_TEXT_MESSAGE_CONTENT",
|
|
3443
|
+
TOOL_CALL_START: "TOOL_CALL_START",
|
|
3444
|
+
TOOL_CALL_ARGS: "TOOL_CALL_ARGS",
|
|
3445
|
+
TOOL_CALL_END: "TOOL_CALL_END",
|
|
3446
|
+
TOOL_CALL_RESULT: "TOOL_CALL_RESULT"
|
|
3447
|
+
};
|
|
3448
|
+
function createAgUiStreamState() {
|
|
3449
|
+
return { doneEmitted: false, errored: false };
|
|
3450
|
+
}
|
|
3451
|
+
function* translateAgUiEvent(raw, state) {
|
|
3452
|
+
if (!raw || typeof raw !== "object") return;
|
|
3453
|
+
const evt = raw;
|
|
3454
|
+
const type = evt.type;
|
|
3455
|
+
if (!type) return;
|
|
3456
|
+
switch (type) {
|
|
3457
|
+
case AG_UI.RUN_STARTED: {
|
|
3458
|
+
const sessionId = evt.sessionId ?? evt.threadId;
|
|
3459
|
+
state.threadId = sessionId ?? evt.threadId;
|
|
3460
|
+
state.runId = evt.runId;
|
|
3461
|
+
if (sessionId) {
|
|
3462
|
+
yield {
|
|
3463
|
+
type: "system",
|
|
3464
|
+
threadId: evt.threadId ?? sessionId,
|
|
3465
|
+
sessionId
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
case AG_UI.TEXT_MESSAGE_CONTENT:
|
|
3471
|
+
case AG_UI.TEXT_MESSAGE_CHUNK: {
|
|
3472
|
+
if (typeof evt.delta === "string" && evt.delta) {
|
|
3473
|
+
yield { type: "text", delta: evt.delta };
|
|
3474
|
+
}
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
case AG_UI.REASONING_MESSAGE_CONTENT:
|
|
3478
|
+
case AG_UI.REASONING_MESSAGE_CHUNK:
|
|
3479
|
+
case AG_UI.THINKING_TEXT_MESSAGE_CONTENT: {
|
|
3480
|
+
if (typeof evt.delta === "string" && evt.delta) {
|
|
3481
|
+
yield { type: "thinking", delta: evt.delta };
|
|
3482
|
+
}
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
case AG_UI.TOOL_CALL_START: {
|
|
3486
|
+
if (evt.toolCallId && evt.toolCallName) {
|
|
3487
|
+
state.pendingToolCall = {
|
|
3488
|
+
id: evt.toolCallId,
|
|
3489
|
+
name: evt.toolCallName,
|
|
3490
|
+
argsDelta: ""
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
case AG_UI.TOOL_CALL_ARGS: {
|
|
3496
|
+
if (state.pendingToolCall && evt.toolCallId === state.pendingToolCall.id && evt.delta) {
|
|
3497
|
+
state.pendingToolCall.argsDelta += evt.delta;
|
|
3498
|
+
}
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
case AG_UI.TOOL_CALL_END: {
|
|
3502
|
+
yield* flushPendingToolCall(state);
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
case AG_UI.TOOL_CALL_RESULT: {
|
|
3506
|
+
if (evt.toolCallId) {
|
|
3507
|
+
yield {
|
|
3508
|
+
type: "tool_result",
|
|
3509
|
+
id: evt.toolCallId,
|
|
3510
|
+
output: typeof evt.content === "string" ? evt.content : JSON.stringify(evt.content ?? ""),
|
|
3511
|
+
isError: false
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
case AG_UI.RUN_ERROR: {
|
|
3517
|
+
state.errored = true;
|
|
3518
|
+
yield {
|
|
3519
|
+
type: "error",
|
|
3520
|
+
message: evt.message ?? "alice agent run failed",
|
|
3521
|
+
terminationReason: "failed"
|
|
3522
|
+
};
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
case AG_UI.RUN_FINISHED: {
|
|
3526
|
+
if (!state.doneEmitted && !state.errored) {
|
|
3527
|
+
state.doneEmitted = true;
|
|
3528
|
+
yield {
|
|
3529
|
+
type: "done",
|
|
3530
|
+
threadId: state.threadId,
|
|
3531
|
+
sessionId: state.threadId,
|
|
3532
|
+
terminationReason: "normal"
|
|
3533
|
+
};
|
|
3534
|
+
}
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
default:
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
function* flushPendingToolCall(state) {
|
|
3542
|
+
const pending = state.pendingToolCall;
|
|
3543
|
+
if (!pending) return;
|
|
3544
|
+
state.pendingToolCall = void 0;
|
|
3545
|
+
let input = pending.argsDelta;
|
|
3546
|
+
if (pending.argsDelta) {
|
|
3547
|
+
try {
|
|
3548
|
+
input = JSON.parse(pending.argsDelta);
|
|
3549
|
+
} catch {
|
|
3550
|
+
input = pending.argsDelta;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
yield { type: "tool_use", id: pending.id, name: pending.name, input };
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
// src/agent/alice/adapter.ts
|
|
3557
|
+
function isAliceDebugEnabled(explicit) {
|
|
3558
|
+
if (explicit === true) return true;
|
|
3559
|
+
if (explicit === false) return false;
|
|
3560
|
+
const raw = process.env.FEISHU_DEVOPS_ALICE_DEBUG ?? process.env.LARK_CHANNEL_ALICE_DEBUG;
|
|
3561
|
+
return raw === "1" || raw === "true";
|
|
3562
|
+
}
|
|
3563
|
+
var AliceAdapter = class {
|
|
3564
|
+
id = "alice";
|
|
3565
|
+
displayName = "Alice Agent";
|
|
3566
|
+
binary;
|
|
3567
|
+
larkChannel;
|
|
3568
|
+
debug;
|
|
3569
|
+
botIdentity;
|
|
3570
|
+
constructor(opts = {}) {
|
|
3571
|
+
this.binary = opts.binary ?? "alice";
|
|
3572
|
+
this.larkChannel = opts.larkChannel;
|
|
3573
|
+
this.debug = isAliceDebugEnabled(opts.debug);
|
|
3574
|
+
}
|
|
3575
|
+
setBotIdentity(identity) {
|
|
3576
|
+
this.botIdentity = identity;
|
|
3577
|
+
}
|
|
3578
|
+
async isAvailable() {
|
|
3579
|
+
return (await this.checkAvailability()).ok;
|
|
3580
|
+
}
|
|
3581
|
+
async checkAvailability() {
|
|
3582
|
+
return checkAgentAvailability({
|
|
3583
|
+
agentId: "alice",
|
|
3584
|
+
agentName: "Alice Agent",
|
|
3585
|
+
command: this.binary,
|
|
3586
|
+
binaryPath: this.binary,
|
|
3587
|
+
args: ["--version"]
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
run(opts) {
|
|
3591
|
+
if (!opts.cwd) {
|
|
3592
|
+
throw new Error("cwd is required for AliceAdapter.run");
|
|
3593
|
+
}
|
|
3594
|
+
const stdinPrompt = prefixBridgeSystemPrompt(opts.prompt, this.botIdentity);
|
|
3595
|
+
const args = ["--mode", "json", "-p"];
|
|
3596
|
+
if (opts.sessionId) args.push("--resume", opts.sessionId);
|
|
3597
|
+
if (this.debug) args.push("--debug");
|
|
3598
|
+
if (this.debug) {
|
|
3599
|
+
console.log("[alice debug] spawn", {
|
|
3600
|
+
binary: this.binary,
|
|
3601
|
+
cwd: opts.cwd,
|
|
3602
|
+
args,
|
|
3603
|
+
promptChars: stdinPrompt.length,
|
|
3604
|
+
hasSession: Boolean(opts.sessionId),
|
|
3605
|
+
via: "stdin"
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
const child = spawnProcess(this.binary, args, {
|
|
3609
|
+
cwd: opts.cwd,
|
|
3610
|
+
env: mergeProcessEnv(process.env, buildLarkChannelEnv(this.larkChannel)),
|
|
3611
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3612
|
+
});
|
|
3613
|
+
log.info("agent", "spawn", {
|
|
3614
|
+
pid: child.pid ?? null,
|
|
3615
|
+
binary: this.binary,
|
|
3616
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
3617
|
+
hasSession: Boolean(opts.sessionId),
|
|
3618
|
+
promptChars: stdinPrompt.length,
|
|
3619
|
+
via: "stdin",
|
|
3620
|
+
agent: "alice",
|
|
3621
|
+
args: formatSpawnArgsForLog(args)
|
|
3622
|
+
});
|
|
3623
|
+
log.info("agent", "spawn-env", { agent: "alice", ...summarizeAgentEnv(process.env) });
|
|
3624
|
+
const stderrChunks = [];
|
|
3625
|
+
let runtimeError = null;
|
|
3626
|
+
let stderrBuffer = "";
|
|
3627
|
+
child.stderr.on("data", (chunk) => {
|
|
3628
|
+
stderrChunks.push(chunk);
|
|
3629
|
+
stderrBuffer += chunk.toString("utf8");
|
|
3630
|
+
let nl = stderrBuffer.indexOf("\n");
|
|
3631
|
+
while (nl !== -1) {
|
|
3632
|
+
const line = stderrBuffer.slice(0, nl);
|
|
3633
|
+
stderrBuffer = stderrBuffer.slice(nl + 1);
|
|
3634
|
+
if (line.trim()) log.warn("agent", "stderr", { line, agent: "alice" });
|
|
3635
|
+
if (isWindowsCommandNotFoundLine5(line)) {
|
|
3636
|
+
runtimeError = new Error(`failed to spawn alice: ${line.trim()}`);
|
|
3637
|
+
child.stdout.destroy();
|
|
3638
|
+
child.kill();
|
|
3639
|
+
}
|
|
3640
|
+
nl = stderrBuffer.indexOf("\n");
|
|
3641
|
+
}
|
|
3642
|
+
});
|
|
3643
|
+
child.on("error", (err) => {
|
|
3644
|
+
runtimeError = err;
|
|
3645
|
+
});
|
|
3646
|
+
child.on("exit", (code, signal) => {
|
|
3647
|
+
log.info("agent", "exit", { pid: child.pid ?? null, code, signal, agent: "alice" });
|
|
3648
|
+
});
|
|
3649
|
+
child.stdin.on("error", (err) => {
|
|
3650
|
+
log.warn("agent", "stdin-error", { message: err.message, agent: "alice" });
|
|
3651
|
+
});
|
|
3652
|
+
child.stdin.end(stdinPrompt, "utf8");
|
|
3653
|
+
const stopGraceMs = opts.stopGraceMs ?? 5e3;
|
|
3654
|
+
return {
|
|
3655
|
+
runId: opts.runId,
|
|
3656
|
+
events: createEventStream5(child, stderrChunks, () => runtimeError, this.debug),
|
|
3657
|
+
async stop() {
|
|
3658
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
3659
|
+
log.info("agent", "stop-sigterm", {
|
|
3660
|
+
pid: child.pid ?? null,
|
|
3661
|
+
graceMs: stopGraceMs,
|
|
3662
|
+
agent: "alice"
|
|
3663
|
+
});
|
|
3664
|
+
child.kill("SIGTERM");
|
|
3665
|
+
await new Promise((resolve4) => {
|
|
3666
|
+
const timer = setTimeout(() => {
|
|
3667
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
3668
|
+
log.warn("agent", "stop-sigkill", {
|
|
3669
|
+
pid: child.pid ?? null,
|
|
3670
|
+
graceMs: stopGraceMs,
|
|
3671
|
+
reason: "grace-period-expired",
|
|
3672
|
+
agent: "alice"
|
|
3673
|
+
});
|
|
3674
|
+
child.kill("SIGKILL");
|
|
3675
|
+
}
|
|
3676
|
+
resolve4();
|
|
3677
|
+
}, stopGraceMs);
|
|
3678
|
+
child.once("exit", () => {
|
|
3679
|
+
clearTimeout(timer);
|
|
3680
|
+
resolve4();
|
|
3681
|
+
});
|
|
3682
|
+
});
|
|
3683
|
+
},
|
|
3684
|
+
waitForExit(timeoutMs) {
|
|
3685
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
3686
|
+
return Promise.resolve(true);
|
|
3687
|
+
}
|
|
3688
|
+
return new Promise((resolve4) => {
|
|
3689
|
+
const onExit = () => {
|
|
3690
|
+
clearTimeout(timer);
|
|
3691
|
+
resolve4(true);
|
|
3692
|
+
};
|
|
3693
|
+
const timer = setTimeout(() => {
|
|
3694
|
+
child.removeListener("exit", onExit);
|
|
3695
|
+
resolve4(false);
|
|
3696
|
+
}, timeoutMs);
|
|
3697
|
+
child.once("exit", onExit);
|
|
3698
|
+
});
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3703
|
+
async function* createEventStream5(child, stderrChunks, getError, debug) {
|
|
3704
|
+
if (!child.pid) {
|
|
3705
|
+
const err = getError();
|
|
3706
|
+
yield {
|
|
3707
|
+
type: "error",
|
|
3708
|
+
message: err ? `failed to spawn alice: ${err.message}` : "spawn returned no pid",
|
|
3709
|
+
terminationReason: "failed"
|
|
3710
|
+
};
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
const streamState = createAgUiStreamState();
|
|
3714
|
+
const stats = createStdoutJsonStats();
|
|
3715
|
+
for await (const parsed of readStdoutJsonLines(child.stdout, { agent: "alice", debug, stats })) {
|
|
3716
|
+
if (debug) {
|
|
3717
|
+
console.log("[alice debug] event", parsed);
|
|
3718
|
+
}
|
|
3719
|
+
yield* translateAgUiEvent(parsed, streamState);
|
|
3720
|
+
}
|
|
3721
|
+
log.info("agent", "stdout-summary", {
|
|
3722
|
+
agent: "alice",
|
|
3723
|
+
rawLines: stats.rawLines,
|
|
3724
|
+
parsedOk: stats.parsedOk,
|
|
3725
|
+
parseFailed: stats.parseFailed,
|
|
3726
|
+
exitCode: child.exitCode,
|
|
3727
|
+
signal: child.signalCode,
|
|
3728
|
+
...stats.parseFailed > 0 ? { failedSamples: stats.failedSamples } : {}
|
|
3729
|
+
});
|
|
3730
|
+
if (stats.rawLines === 0) {
|
|
3731
|
+
log.warn("agent", "stdout-empty", {
|
|
3732
|
+
agent: "alice",
|
|
3733
|
+
exitCode: child.exitCode,
|
|
3734
|
+
signal: child.signalCode
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
const earlyRuntimeError = getError();
|
|
3738
|
+
if (earlyRuntimeError && child.exitCode === null && child.signalCode === null) {
|
|
3739
|
+
yield {
|
|
3740
|
+
type: "error",
|
|
3741
|
+
message: `alice runtime error: ${earlyRuntimeError.message}`,
|
|
3742
|
+
terminationReason: "failed"
|
|
3743
|
+
};
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
const exitCode = await new Promise((resolve4) => {
|
|
3747
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
3748
|
+
resolve4(child.exitCode);
|
|
3749
|
+
} else {
|
|
3750
|
+
child.once("exit", (code) => resolve4(code));
|
|
3751
|
+
}
|
|
3752
|
+
});
|
|
3753
|
+
const runtimeError = getError();
|
|
3754
|
+
if (!streamState.doneEmitted && !streamState.errored && exitCode === 0) {
|
|
3755
|
+
yield {
|
|
3756
|
+
type: "done",
|
|
3757
|
+
threadId: streamState.threadId,
|
|
3758
|
+
sessionId: streamState.threadId,
|
|
3759
|
+
terminationReason: "normal"
|
|
3760
|
+
};
|
|
3761
|
+
} else if (exitCode !== 0 && exitCode !== null && !streamState.errored) {
|
|
3762
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
3763
|
+
const detail = stderr ? `: ${stderr.slice(0, 500)}` : "";
|
|
3764
|
+
yield {
|
|
3765
|
+
type: "error",
|
|
3766
|
+
message: `alice agent exited with code ${exitCode}${detail}`,
|
|
3767
|
+
terminationReason: "failed"
|
|
3768
|
+
};
|
|
3769
|
+
} else if (runtimeError && !streamState.errored) {
|
|
3770
|
+
yield {
|
|
3771
|
+
type: "error",
|
|
3772
|
+
message: `alice runtime error: ${runtimeError.message}`,
|
|
3773
|
+
terminationReason: "failed"
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
function isWindowsCommandNotFoundLine5(line) {
|
|
3778
|
+
return process.platform === "win32" && /is not recognized as an internal or external command|operable program or batch file/i.test(line);
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3430
3781
|
// src/config/permissions.ts
|
|
3431
3782
|
var ACCESS_ORDER = {
|
|
3432
3783
|
"read-only": 0,
|
|
@@ -3628,8 +3979,8 @@ function normalizeProfileConfig(input) {
|
|
|
3628
3979
|
if (raw.schemaVersion !== 2) {
|
|
3629
3980
|
throw new Error("profile schemaVersion must be 2");
|
|
3630
3981
|
}
|
|
3631
|
-
if (raw.agentKind !== "claude" && raw.agentKind !== "codex" && raw.agentKind !== "cursor" && raw.agentKind !== "pi") {
|
|
3632
|
-
throw new Error("agentKind must be claude, codex, cursor, or
|
|
3982
|
+
if (raw.agentKind !== "claude" && raw.agentKind !== "codex" && raw.agentKind !== "cursor" && raw.agentKind !== "pi" && raw.agentKind !== "alice") {
|
|
3983
|
+
throw new Error("agentKind must be claude, codex, cursor, pi, or alice");
|
|
3633
3984
|
}
|
|
3634
3985
|
const accounts = normalizeAccounts(raw.accounts);
|
|
3635
3986
|
if (raw.agentKind === "codex" && !raw.codex) {
|
|
@@ -3773,7 +4124,7 @@ function numberOr(value, fallback) {
|
|
|
3773
4124
|
|
|
3774
4125
|
// src/config/agent-config.ts
|
|
3775
4126
|
function toProfileConfig(cfg) {
|
|
3776
|
-
const agentKind = cfg.agentKind === "codex" ? "codex" : cfg.agentKind === "cursor" ? "cursor" : cfg.agentKind === "pi" ? "pi" : "claude";
|
|
4127
|
+
const agentKind = cfg.agentKind === "codex" ? "codex" : cfg.agentKind === "cursor" ? "cursor" : cfg.agentKind === "pi" ? "pi" : cfg.agentKind === "alice" ? "alice" : "claude";
|
|
3777
4128
|
const base = {
|
|
3778
4129
|
schemaVersion: 2,
|
|
3779
4130
|
agentKind,
|
|
@@ -3874,6 +4225,14 @@ function createRuntimeAgent(cfg, opts = {}) {
|
|
|
3874
4225
|
...opts.cursorDebug === true ? { debug: true } : {}
|
|
3875
4226
|
});
|
|
3876
4227
|
}
|
|
4228
|
+
if (profileConfig.agentKind === "alice") {
|
|
4229
|
+
const command = process.env.FEISHU_DEVOPS_ALICE_BIN ?? process.env.LARK_CHANNEL_ALICE_BIN ?? "alice";
|
|
4230
|
+
return new AliceAdapter({
|
|
4231
|
+
binary: command,
|
|
4232
|
+
larkChannel,
|
|
4233
|
+
...opts.cursorDebug === true ? { debug: true } : {}
|
|
4234
|
+
});
|
|
4235
|
+
}
|
|
3877
4236
|
return new ClaudeAdapter({ larkChannel });
|
|
3878
4237
|
}
|
|
3879
4238
|
async function checkRuntimeAgentAvailability(agent) {
|
|
@@ -3905,6 +4264,10 @@ async function resolvePiBinaryPath() {
|
|
|
3905
4264
|
const command = process.env.FEISHU_DEVOPS_PI_BIN ?? process.env.LARK_CHANNEL_PI_BIN ?? "pi";
|
|
3906
4265
|
return resolveExecutablePath(command);
|
|
3907
4266
|
}
|
|
4267
|
+
async function resolveAliceBinaryPath() {
|
|
4268
|
+
const command = process.env.FEISHU_DEVOPS_ALICE_BIN ?? process.env.LARK_CHANNEL_ALICE_BIN ?? "alice";
|
|
4269
|
+
return resolveExecutablePath(command);
|
|
4270
|
+
}
|
|
3908
4271
|
function applyAgentKindToConfig(cfg, agentKind, codexBinaryPath) {
|
|
3909
4272
|
const next = { ...cfg, agentKind };
|
|
3910
4273
|
if (agentKind === "codex" && codexBinaryPath) {
|
|
@@ -4458,7 +4821,7 @@ var SessionCatalog = class {
|
|
|
4458
4821
|
function normalizeEntry(input) {
|
|
4459
4822
|
if (!input || typeof input !== "object") return void 0;
|
|
4460
4823
|
const raw = input;
|
|
4461
|
-
if (typeof raw.key !== "string" || typeof raw.scopeId !== "string" || raw.agentId !== "claude" && raw.agentId !== "codex" && raw.agentId !== "cursor" && raw.agentId !== "pi" || typeof raw.cwdRealpath !== "string" || typeof raw.policyFingerprint !== "string" || raw.status !== "active" && raw.status !== "archived" || typeof raw.updatedAt !== "number") {
|
|
4824
|
+
if (typeof raw.key !== "string" || typeof raw.scopeId !== "string" || raw.agentId !== "claude" && raw.agentId !== "codex" && raw.agentId !== "cursor" && raw.agentId !== "pi" && raw.agentId !== "alice" || typeof raw.cwdRealpath !== "string" || typeof raw.policyFingerprint !== "string" || raw.status !== "active" && raw.status !== "archived" || typeof raw.updatedAt !== "number") {
|
|
4462
4825
|
return void 0;
|
|
4463
4826
|
}
|
|
4464
4827
|
return {
|
|
@@ -4478,15 +4841,15 @@ function matchesIdentity(entry, input) {
|
|
|
4478
4841
|
return entry.scopeId === input.scopeId && entry.agentId === input.agentId && entry.cwdRealpath === input.cwdRealpath && entry.policyFingerprint === input.policyFingerprint && entry.key === sessionCatalogKey(input);
|
|
4479
4842
|
}
|
|
4480
4843
|
function isValidAgentEntry(entry) {
|
|
4481
|
-
if (entry.agentId === "claude" || entry.agentId === "cursor" || entry.agentId === "pi") {
|
|
4844
|
+
if (entry.agentId === "claude" || entry.agentId === "cursor" || entry.agentId === "pi" || entry.agentId === "alice") {
|
|
4482
4845
|
return Boolean(entry.sessionId) && !entry.threadId;
|
|
4483
4846
|
}
|
|
4484
4847
|
return Boolean(entry.threadId) && !entry.sessionId;
|
|
4485
4848
|
}
|
|
4486
4849
|
function assertAgentIdentity(input) {
|
|
4487
|
-
if (input.agentId === "claude" || input.agentId === "cursor" || input.agentId === "pi") {
|
|
4850
|
+
if (input.agentId === "claude" || input.agentId === "cursor" || input.agentId === "pi" || input.agentId === "alice") {
|
|
4488
4851
|
if (!input.sessionId || input.threadId) {
|
|
4489
|
-
throw new Error("Claude/Cursor/Pi catalog entries require sessionId and must not include threadId");
|
|
4852
|
+
throw new Error("Claude/Cursor/Pi/Alice catalog entries require sessionId and must not include threadId");
|
|
4490
4853
|
}
|
|
4491
4854
|
return;
|
|
4492
4855
|
}
|
|
@@ -4651,10 +5014,12 @@ var WorkspaceStore = class {
|
|
|
4651
5014
|
// src/bot/active-cmd-sessions.ts
|
|
4652
5015
|
var ActiveCmdSessions = class {
|
|
4653
5016
|
sessions = /* @__PURE__ */ new Map();
|
|
5017
|
+
interruptedScopes = /* @__PURE__ */ new Set();
|
|
4654
5018
|
register(scope, session) {
|
|
4655
5019
|
if (this.sessions.has(scope)) {
|
|
4656
5020
|
throw new Error(`cmd session already active for scope: ${scope}`);
|
|
4657
5021
|
}
|
|
5022
|
+
this.interruptedScopes.delete(scope);
|
|
4658
5023
|
this.sessions.set(scope, session);
|
|
4659
5024
|
}
|
|
4660
5025
|
get(scope) {
|
|
@@ -4675,10 +5040,17 @@ var ActiveCmdSessions = class {
|
|
|
4675
5040
|
interrupt(scope) {
|
|
4676
5041
|
const session = this.sessions.get(scope);
|
|
4677
5042
|
if (!session) return false;
|
|
5043
|
+
this.interruptedScopes.add(scope);
|
|
4678
5044
|
this.sessions.delete(scope);
|
|
4679
5045
|
session.handle.kill();
|
|
4680
5046
|
return true;
|
|
4681
5047
|
}
|
|
5048
|
+
/** 读取并清除用户 /stop 终止标记(供最终输出文案使用)。 */
|
|
5049
|
+
consumeInterrupted(scope) {
|
|
5050
|
+
if (!this.interruptedScopes.has(scope)) return false;
|
|
5051
|
+
this.interruptedScopes.delete(scope);
|
|
5052
|
+
return true;
|
|
5053
|
+
}
|
|
4682
5054
|
stopAll() {
|
|
4683
5055
|
for (const scope of [...this.sessions.keys()]) {
|
|
4684
5056
|
this.interrupt(scope);
|
|
@@ -4738,6 +5110,23 @@ function piCapability(profile) {
|
|
|
4738
5110
|
}
|
|
4739
5111
|
};
|
|
4740
5112
|
}
|
|
5113
|
+
function aliceCapability(profile) {
|
|
5114
|
+
const maxAccess = profile?.permissions.maxAccess ?? "full";
|
|
5115
|
+
return {
|
|
5116
|
+
agentId: "alice",
|
|
5117
|
+
sessionKind: "alice-thread",
|
|
5118
|
+
promptInjection: "stdin-prefix",
|
|
5119
|
+
systemPrompt: BRIDGE_SYSTEM_PROMPT,
|
|
5120
|
+
supportsNativeHistory: true,
|
|
5121
|
+
callback: {
|
|
5122
|
+
marker: "__bridge_cb",
|
|
5123
|
+
legacyMarkers: []
|
|
5124
|
+
},
|
|
5125
|
+
permissions: {
|
|
5126
|
+
maxAccess
|
|
5127
|
+
}
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
4741
5130
|
function codexCapability(profile) {
|
|
4742
5131
|
const maxAccess = profile.permissions.maxAccess;
|
|
4743
5132
|
return {
|
|
@@ -5213,27 +5602,6 @@ function footerLine(status) {
|
|
|
5213
5602
|
return "_\u270D\uFE0F \u6B63\u5728\u8F93\u51FA\u2026_";
|
|
5214
5603
|
}
|
|
5215
5604
|
|
|
5216
|
-
// src/bot/cwd.ts
|
|
5217
|
-
import { homedir as homedir3 } from "os";
|
|
5218
|
-
import { isAbsolute as isAbsolute2 } from "path";
|
|
5219
|
-
function expandTilde(p) {
|
|
5220
|
-
if (p === "~") return homedir3();
|
|
5221
|
-
if (p.startsWith("~/")) return `${homedir3()}${p.slice(1)}`;
|
|
5222
|
-
return p;
|
|
5223
|
-
}
|
|
5224
|
-
function isAbsoluteOrTilde(p) {
|
|
5225
|
-
return isAbsolute2(p) || p === "~" || p.startsWith("~/");
|
|
5226
|
-
}
|
|
5227
|
-
function chatScope(msg) {
|
|
5228
|
-
return msg.chatId;
|
|
5229
|
-
}
|
|
5230
|
-
function storedCwd(workspaces, chatId) {
|
|
5231
|
-
return workspaces.cwdFor(chatId);
|
|
5232
|
-
}
|
|
5233
|
-
function effectiveCwd(workspaces, chatId) {
|
|
5234
|
-
return workspaces.cwdFor(chatId) ?? process.cwd();
|
|
5235
|
-
}
|
|
5236
|
-
|
|
5237
5605
|
// src/policy/fingerprint.ts
|
|
5238
5606
|
import { createHash } from "crypto";
|
|
5239
5607
|
|
|
@@ -5368,7 +5736,7 @@ function reject(code, userVisible) {
|
|
|
5368
5736
|
|
|
5369
5737
|
// src/policy/workspace.ts
|
|
5370
5738
|
import { realpath, stat as stat2 } from "fs/promises";
|
|
5371
|
-
import { homedir as
|
|
5739
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
5372
5740
|
import { basename as basename2, dirname as dirname7, resolve as resolve2 } from "path";
|
|
5373
5741
|
async function resolveWorkingDirectory(requestedCwd) {
|
|
5374
5742
|
const trimmed = requestedCwd.trim();
|
|
@@ -5401,7 +5769,7 @@ function classifyHighRiskWorkingDirectory(real, requestedCwd, tempRealpath) {
|
|
|
5401
5769
|
if (real === dirname7(real)) {
|
|
5402
5770
|
return reject2("filesystem-root", requestedCwd, "\u4E0D\u80FD\u628A\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u8BBE\u4E3A\u5DE5\u4F5C\u76EE\u5F55\u3002");
|
|
5403
5771
|
}
|
|
5404
|
-
const home = resolve2(
|
|
5772
|
+
const home = resolve2(homedir3());
|
|
5405
5773
|
if (real === dirname7(home)) {
|
|
5406
5774
|
return reject2("user-root", requestedCwd, "\u4E0D\u80FD\u628A\u7528\u6237\u76EE\u5F55\u6839\u8BBE\u4E3A\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u8BF7\u9009\u62E9\u66F4\u5177\u4F53\u7684\u5B50\u76EE\u5F55\u3002");
|
|
5407
5775
|
}
|
|
@@ -5476,7 +5844,7 @@ async function startRunFlow(input) {
|
|
|
5476
5844
|
cwdRealpath: workspace.cwdRealpath,
|
|
5477
5845
|
policyFingerprint: policy.policyFingerprint
|
|
5478
5846
|
});
|
|
5479
|
-
if (catalogEntry?.agentId === "claude" || catalogEntry?.agentId === "cursor" || catalogEntry?.agentId === "pi") {
|
|
5847
|
+
if (catalogEntry?.agentId === "claude" || catalogEntry?.agentId === "cursor" || catalogEntry?.agentId === "pi" || catalogEntry?.agentId === "alice") {
|
|
5480
5848
|
sessionId = catalogEntry.sessionId;
|
|
5481
5849
|
resumeFrom = sessionId;
|
|
5482
5850
|
} else if (catalogEntry?.agentId === "codex") {
|
|
@@ -5484,7 +5852,7 @@ async function startRunFlow(input) {
|
|
|
5484
5852
|
resumeFrom = threadId;
|
|
5485
5853
|
}
|
|
5486
5854
|
}
|
|
5487
|
-
if (!resumeFrom && (input.capability.agentId === "claude" || input.capability.agentId === "cursor" || input.capability.agentId === "pi")) {
|
|
5855
|
+
if (!resumeFrom && (input.capability.agentId === "claude" || input.capability.agentId === "cursor" || input.capability.agentId === "pi" || input.capability.agentId === "alice")) {
|
|
5488
5856
|
resumeFrom = input.sessions.resumeFor(input.scopeId, workspace.cwdRealpath);
|
|
5489
5857
|
sessionId = resumeFrom;
|
|
5490
5858
|
const stale = input.sessions.getRaw(input.scopeId);
|
|
@@ -5526,7 +5894,7 @@ async function startRunFlow(input) {
|
|
|
5526
5894
|
}
|
|
5527
5895
|
function recordRunSessionEvent(input) {
|
|
5528
5896
|
if (input.event.type !== "system") return;
|
|
5529
|
-
if ((input.capability.agentId === "claude" || input.capability.agentId === "cursor" || input.capability.agentId === "pi") && input.event.sessionId) {
|
|
5897
|
+
if ((input.capability.agentId === "claude" || input.capability.agentId === "cursor" || input.capability.agentId === "pi" || input.capability.agentId === "alice") && input.event.sessionId) {
|
|
5530
5898
|
const cwdRealpath = input.event.cwd ?? input.policy.cwdRealpath;
|
|
5531
5899
|
input.sessions.set(input.scopeId, input.event.sessionId, cwdRealpath);
|
|
5532
5900
|
input.sessionCatalog?.upsertActive({
|
|
@@ -5560,7 +5928,7 @@ var BRIDGE_AGENT_INSTRUCTIONS = [
|
|
|
5560
5928
|
function openAccess() {
|
|
5561
5929
|
return { ok: true, reason: "allowed-user" };
|
|
5562
5930
|
}
|
|
5563
|
-
async function runAgentMessage(deps, msg) {
|
|
5931
|
+
async function runAgentMessage(deps, msg, scope) {
|
|
5564
5932
|
const {
|
|
5565
5933
|
channel,
|
|
5566
5934
|
cfg,
|
|
@@ -5571,14 +5939,13 @@ async function runAgentMessage(deps, msg) {
|
|
|
5571
5939
|
workspaces,
|
|
5572
5940
|
activePolicyFingerprints
|
|
5573
5941
|
} = deps;
|
|
5574
|
-
const scope = chatScope(msg);
|
|
5575
5942
|
const prompt = buildPrompt(msg, channel.botIdentity);
|
|
5576
5943
|
log.info("prompt", "built", { promptChars: prompt.length });
|
|
5577
5944
|
const sendOpts = {
|
|
5578
5945
|
replyTo: msg.messageId,
|
|
5579
5946
|
...msg.threadId ? { replyInThread: true } : {}
|
|
5580
5947
|
};
|
|
5581
|
-
const capability = profileConfig.agentKind === "codex" ? codexCapability(profileConfig) : profileConfig.agentKind === "cursor" ? cursorCapability(profileConfig) : profileConfig.agentKind === "pi" ? piCapability(profileConfig) : claudeCapability(profileConfig);
|
|
5948
|
+
const capability = profileConfig.agentKind === "codex" ? codexCapability(profileConfig) : profileConfig.agentKind === "cursor" ? cursorCapability(profileConfig) : profileConfig.agentKind === "pi" ? piCapability(profileConfig) : profileConfig.agentKind === "alice" ? aliceCapability(profileConfig) : claudeCapability(profileConfig);
|
|
5582
5949
|
const scopeContext = {
|
|
5583
5950
|
source: "im",
|
|
5584
5951
|
chatId: msg.chatId,
|
|
@@ -5954,6 +6321,9 @@ async function buildConfigForKind(cfg, kind) {
|
|
|
5954
6321
|
if (kind === "pi") {
|
|
5955
6322
|
return applyAgentKindToConfig(cfg, "pi");
|
|
5956
6323
|
}
|
|
6324
|
+
if (kind === "alice") {
|
|
6325
|
+
return applyAgentKindToConfig(cfg, "alice");
|
|
6326
|
+
}
|
|
5957
6327
|
return applyAgentKindToConfig(cfg, "claude");
|
|
5958
6328
|
}
|
|
5959
6329
|
function clearAgentSessionState(sessions, sessionCatalog) {
|
|
@@ -5974,6 +6344,7 @@ function agentLabel(kind) {
|
|
|
5974
6344
|
if (kind === "codex") return "Codex CLI";
|
|
5975
6345
|
if (kind === "cursor") return "Cursor Agent";
|
|
5976
6346
|
if (kind === "pi") return "Pi Agent";
|
|
6347
|
+
if (kind === "alice") return "Alice Agent";
|
|
5977
6348
|
return "Claude Code";
|
|
5978
6349
|
}
|
|
5979
6350
|
|
|
@@ -6201,6 +6572,17 @@ function normalizeTerminalOutput(text) {
|
|
|
6201
6572
|
const lf = text.replace(/\r\n/g, "\n");
|
|
6202
6573
|
return normalizeCarriageReturns(stripAnsiCodes(lf));
|
|
6203
6574
|
}
|
|
6575
|
+
var TerminalOutputBuffer = class {
|
|
6576
|
+
raw = "";
|
|
6577
|
+
append(chunk) {
|
|
6578
|
+
this.raw += decodeShellOutput(chunk);
|
|
6579
|
+
return normalizeTerminalOutput(this.raw);
|
|
6580
|
+
}
|
|
6581
|
+
appendText(text) {
|
|
6582
|
+
this.raw += text;
|
|
6583
|
+
return normalizeTerminalOutput(this.raw);
|
|
6584
|
+
}
|
|
6585
|
+
};
|
|
6204
6586
|
var WINDOWS_CP_TO_ENCODING = {
|
|
6205
6587
|
"65001": "utf-8",
|
|
6206
6588
|
"936": "gb18030",
|
|
@@ -6252,11 +6634,13 @@ function writePtyLine(term, text) {
|
|
|
6252
6634
|
term.write(`${line}\r`);
|
|
6253
6635
|
}
|
|
6254
6636
|
function attachOutputPump(child, onChunk) {
|
|
6637
|
+
const stdoutBuf = new TerminalOutputBuffer();
|
|
6638
|
+
const stderrBuf = new TerminalOutputBuffer();
|
|
6255
6639
|
child.stdout?.on("data", (chunk) => {
|
|
6256
|
-
onChunk("stdout",
|
|
6640
|
+
onChunk("stdout", stdoutBuf.append(chunk));
|
|
6257
6641
|
});
|
|
6258
6642
|
child.stderr?.on("data", (chunk) => {
|
|
6259
|
-
onChunk("stderr",
|
|
6643
|
+
onChunk("stderr", stderrBuf.append(chunk));
|
|
6260
6644
|
});
|
|
6261
6645
|
}
|
|
6262
6646
|
function childSpawnHandle(child) {
|
|
@@ -6291,8 +6675,8 @@ function spawnPipeShellCommand(command, opts) {
|
|
|
6291
6675
|
setTimeout(() => child.kill("SIGKILL"), 500).unref();
|
|
6292
6676
|
}, timeoutMs);
|
|
6293
6677
|
attachOutputPump(child, (kind, text) => {
|
|
6294
|
-
if (kind === "stdout") stdout
|
|
6295
|
-
else stderr
|
|
6678
|
+
if (kind === "stdout") stdout = text;
|
|
6679
|
+
else stderr = text;
|
|
6296
6680
|
opts.onOutput?.(kind, text);
|
|
6297
6681
|
});
|
|
6298
6682
|
child.on("close", (exitCode) => {
|
|
@@ -6341,8 +6725,8 @@ function spawnExpectShellCommand(command, expectPath, opts) {
|
|
|
6341
6725
|
setTimeout(() => child.kill("SIGKILL"), 500).unref();
|
|
6342
6726
|
}, timeoutMs);
|
|
6343
6727
|
attachOutputPump(child, (kind, text) => {
|
|
6344
|
-
if (kind === "stdout") stdout
|
|
6345
|
-
else stderr
|
|
6728
|
+
if (kind === "stdout") stdout = text;
|
|
6729
|
+
else stderr = text;
|
|
6346
6730
|
opts.onOutput?.(kind, text);
|
|
6347
6731
|
});
|
|
6348
6732
|
child.on("close", (exitCode) => {
|
|
@@ -6392,6 +6776,7 @@ function spawnPtyShellCommand(command, opts) {
|
|
|
6392
6776
|
}
|
|
6393
6777
|
});
|
|
6394
6778
|
let stdout = "";
|
|
6779
|
+
const stdoutBuf = new TerminalOutputBuffer();
|
|
6395
6780
|
let timedOut = false;
|
|
6396
6781
|
const timer = setTimeout(() => {
|
|
6397
6782
|
timedOut = true;
|
|
@@ -6399,9 +6784,8 @@ function spawnPtyShellCommand(command, opts) {
|
|
|
6399
6784
|
setTimeout(() => term.kill("SIGKILL"), 500).unref();
|
|
6400
6785
|
}, timeoutMs);
|
|
6401
6786
|
term.onData((data) => {
|
|
6402
|
-
|
|
6403
|
-
stdout
|
|
6404
|
-
opts.onOutput?.("stdout", text);
|
|
6787
|
+
stdout = stdoutBuf.appendText(data);
|
|
6788
|
+
opts.onOutput?.("stdout", stdout);
|
|
6405
6789
|
});
|
|
6406
6790
|
term.onExit(({ exitCode }) => {
|
|
6407
6791
|
clearTimeout(timer);
|
|
@@ -6435,8 +6819,8 @@ async function spawnShellCommand(command, opts = {}) {
|
|
|
6435
6819
|
setTimeout(() => child.kill("SIGKILL"), 500).unref();
|
|
6436
6820
|
}, timeoutMs);
|
|
6437
6821
|
attachOutputPump(child, (kind, text) => {
|
|
6438
|
-
if (kind === "stdout") stdout
|
|
6439
|
-
else stderr
|
|
6822
|
+
if (kind === "stdout") stdout = text;
|
|
6823
|
+
else stderr = text;
|
|
6440
6824
|
opts.onOutput?.(kind, text);
|
|
6441
6825
|
});
|
|
6442
6826
|
child.on("close", (exitCode) => {
|
|
@@ -6492,7 +6876,7 @@ function truncateShellOutput(stdout, stderr) {
|
|
|
6492
6876
|
truncated: true
|
|
6493
6877
|
};
|
|
6494
6878
|
}
|
|
6495
|
-
function formatShellResult(command, result) {
|
|
6879
|
+
function formatShellResult(command, result, opts) {
|
|
6496
6880
|
const lines = [`$ ${command}`];
|
|
6497
6881
|
const stdout = result.stdout.trimEnd();
|
|
6498
6882
|
const stderr = result.stderr.trimEnd();
|
|
@@ -6507,10 +6891,12 @@ function formatShellResult(command, result) {
|
|
|
6507
6891
|
lines.push("", `\`\`\`bash
|
|
6508
6892
|
${blockLines.join("\n")}
|
|
6509
6893
|
\`\`\``);
|
|
6510
|
-
} else if (result.exitCode === 0 && !result.timedOut) {
|
|
6894
|
+
} else if (result.exitCode === 0 && !result.timedOut && !opts?.interrupted) {
|
|
6511
6895
|
lines.push("", "_(\u65E0\u8F93\u51FA)_");
|
|
6512
6896
|
}
|
|
6513
|
-
if (
|
|
6897
|
+
if (opts?.interrupted) {
|
|
6898
|
+
lines.push("", "_\uFF08\u7528\u6237\u7EC8\u6B62\uFF09_");
|
|
6899
|
+
} else if (result.timedOut) {
|
|
6514
6900
|
lines.push("", "_\uFF08\u547D\u4EE4\u8D85\u65F6\uFF0C\u5DF2\u7EC8\u6B62\uFF09_");
|
|
6515
6901
|
} else if (result.exitCode !== 0 && result.exitCode !== null) {
|
|
6516
6902
|
lines.push("", `_\uFF08exit ${result.exitCode}\uFF09_`);
|
|
@@ -6523,9 +6909,17 @@ ${blockLines.join("\n")}
|
|
|
6523
6909
|
|
|
6524
6910
|
// src/bot/cmd-runner.ts
|
|
6525
6911
|
var OUTPUT_FLUSH_INTERVAL_MS = 300;
|
|
6526
|
-
function
|
|
6912
|
+
function looksWaitingForInput(stdout, stderr) {
|
|
6913
|
+
const raw = stdout + stderr;
|
|
6914
|
+
const trimmed = raw.trimEnd();
|
|
6915
|
+
if (!trimmed) return false;
|
|
6916
|
+
if (/[:?>#$]\s*$/.test(trimmed)) return true;
|
|
6917
|
+
if (/\(\s*[yY]\s*\/\s*[nN]\s*\)\s*$/.test(trimmed)) return true;
|
|
6918
|
+
return false;
|
|
6919
|
+
}
|
|
6920
|
+
function formatRunningStatus(command, elapsedSec, partial, waitingForInput = false) {
|
|
6527
6921
|
const elapsed = elapsedSec > 0 ? ` (${elapsedSec}s)` : "";
|
|
6528
|
-
const lines = [
|
|
6922
|
+
const lines = [`$ ${command}`];
|
|
6529
6923
|
if (partial) {
|
|
6530
6924
|
const stdout = partial.stdout.trimEnd();
|
|
6531
6925
|
const stderr = partial.stderr.trimEnd();
|
|
@@ -6545,9 +6939,13 @@ ${blockLines.join("\n")}
|
|
|
6545
6939
|
lines.push("", "_\uFF08\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09_");
|
|
6546
6940
|
}
|
|
6547
6941
|
}
|
|
6548
|
-
if (
|
|
6549
|
-
lines.push("", "\u2328\uFE0F \
|
|
6942
|
+
if (waitingForInput) {
|
|
6943
|
+
lines.push("", "\u2328\uFE0F \u9700\u8981\u4F60\u8F93\u5165");
|
|
6550
6944
|
}
|
|
6945
|
+
lines.push(
|
|
6946
|
+
"",
|
|
6947
|
+
waitingForInput ? `\u23F3 \u7B49\u5F85\u8F93\u5165\u2026${elapsed}\uFF08/stop \u53D6\u6D88\uFF09` : `\u23F3 \u6B63\u5728\u6267\u884C\u2026${elapsed}\uFF08/stop \u53D6\u6D88\uFF09`
|
|
6948
|
+
);
|
|
6551
6949
|
return lines.join("\n");
|
|
6552
6950
|
}
|
|
6553
6951
|
async function runShellWithProgress(command, cfg, onProgress, cwd, activeCmdSessions, scope) {
|
|
@@ -6568,7 +6966,8 @@ async function runShellWithProgress(command, cfg, onProgress, cwd, activeCmdSess
|
|
|
6568
6966
|
const pushProgress = async (partial) => {
|
|
6569
6967
|
if (finished) return;
|
|
6570
6968
|
elapsedSec = Math.floor((Date.now() - started) / 1e3);
|
|
6571
|
-
|
|
6969
|
+
const waitingForInput = partial ? looksWaitingForInput(partial.stdout, partial.stderr) : false;
|
|
6970
|
+
await onProgress(formatRunningStatus(command, elapsedSec, partial, waitingForInput));
|
|
6572
6971
|
};
|
|
6573
6972
|
const scheduleOutputFlush = () => {
|
|
6574
6973
|
if (finished || outputFlushScheduled) return;
|
|
@@ -6597,8 +6996,8 @@ async function runShellWithProgress(command, cfg, onProgress, cwd, activeCmdSess
|
|
|
6597
6996
|
timeoutMs: getCmdTimeoutMs(cfg),
|
|
6598
6997
|
interactive,
|
|
6599
6998
|
onOutput: (kind, chunk) => {
|
|
6600
|
-
if (kind === "stdout") stdout
|
|
6601
|
-
else stderr
|
|
6999
|
+
if (kind === "stdout") stdout = chunk;
|
|
7000
|
+
else stderr = chunk;
|
|
6602
7001
|
scheduleOutputFlush();
|
|
6603
7002
|
},
|
|
6604
7003
|
onSpawn: (handle) => {
|
|
@@ -6606,7 +7005,8 @@ async function runShellWithProgress(command, cfg, onProgress, cwd, activeCmdSess
|
|
|
6606
7005
|
activeCmdSessions.register(scope, { command, handle });
|
|
6607
7006
|
}
|
|
6608
7007
|
});
|
|
6609
|
-
|
|
7008
|
+
const interrupted = scope ? activeCmdSessions?.consumeInterrupted(scope) ?? false : false;
|
|
7009
|
+
return formatShellResult(command, result, { interrupted });
|
|
6610
7010
|
} finally {
|
|
6611
7011
|
finished = true;
|
|
6612
7012
|
if (outputFlushTimer) clearTimeout(outputFlushTimer);
|
|
@@ -6617,6 +7017,57 @@ async function runShellWithProgress(command, cfg, onProgress, cwd, activeCmdSess
|
|
|
6617
7017
|
}
|
|
6618
7018
|
}
|
|
6619
7019
|
|
|
7020
|
+
// src/bot/cwd.ts
|
|
7021
|
+
import { homedir as homedir4 } from "os";
|
|
7022
|
+
import { isAbsolute as isAbsolute2 } from "path";
|
|
7023
|
+
function expandTilde(p) {
|
|
7024
|
+
if (p === "~") return homedir4();
|
|
7025
|
+
if (p.startsWith("~/")) return `${homedir4()}${p.slice(1)}`;
|
|
7026
|
+
return p;
|
|
7027
|
+
}
|
|
7028
|
+
function isAbsoluteOrTilde(p) {
|
|
7029
|
+
return isAbsolute2(p) || p === "~" || p.startsWith("~/");
|
|
7030
|
+
}
|
|
7031
|
+
function storedCwd(workspaces, chatId) {
|
|
7032
|
+
return workspaces.cwdFor(chatId);
|
|
7033
|
+
}
|
|
7034
|
+
function effectiveCwd(workspaces, chatId) {
|
|
7035
|
+
return workspaces.cwdFor(chatId) ?? process.cwd();
|
|
7036
|
+
}
|
|
7037
|
+
|
|
7038
|
+
// src/bot/scope.ts
|
|
7039
|
+
function scopeForMessage(msg, cfg) {
|
|
7040
|
+
if (!getIsolateTopicThreads(cfg) || !msg.threadId) {
|
|
7041
|
+
return msg.chatId;
|
|
7042
|
+
}
|
|
7043
|
+
if (msg.chatMode === "topic") {
|
|
7044
|
+
return `${msg.chatId}:${msg.threadId}`;
|
|
7045
|
+
}
|
|
7046
|
+
return msg.chatId;
|
|
7047
|
+
}
|
|
7048
|
+
async function scopeForCardAction(channel, chatId, messageId, cfg) {
|
|
7049
|
+
if (!getIsolateTopicThreads(cfg)) {
|
|
7050
|
+
return { scope: chatId };
|
|
7051
|
+
}
|
|
7052
|
+
const mode = await channel.getChatMode(chatId);
|
|
7053
|
+
if (mode !== "topic") {
|
|
7054
|
+
return { scope: chatId };
|
|
7055
|
+
}
|
|
7056
|
+
const threadId = await lookupMessageThreadId(channel, messageId);
|
|
7057
|
+
if (!threadId) {
|
|
7058
|
+
return { scope: chatId };
|
|
7059
|
+
}
|
|
7060
|
+
return { scope: `${chatId}:${threadId}`, threadId };
|
|
7061
|
+
}
|
|
7062
|
+
async function lookupMessageThreadId(channel, messageId) {
|
|
7063
|
+
try {
|
|
7064
|
+
const [parent] = await channel.fetchRawMessage(messageId);
|
|
7065
|
+
return parent?.thread_id;
|
|
7066
|
+
} catch {
|
|
7067
|
+
return void 0;
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
|
|
6620
7071
|
// src/bot/group.ts
|
|
6621
7072
|
async function createBoundChat(opts) {
|
|
6622
7073
|
const { channel, name, inviteOpenId, description } = opts;
|
|
@@ -6726,7 +7177,7 @@ async function tryHandleCmdInput(ctx) {
|
|
|
6726
7177
|
}
|
|
6727
7178
|
async function tryHandleCommand(ctx) {
|
|
6728
7179
|
const input = normalizeCommandInput(ctx.msg);
|
|
6729
|
-
const scope = ctx.scope ||
|
|
7180
|
+
const scope = ctx.scope || scopeForMessage(ctx.msg, ctx.cfg);
|
|
6730
7181
|
ctx.scope = scope;
|
|
6731
7182
|
if (input.startsWith("$")) {
|
|
6732
7183
|
await handleCmd(input.slice(1).trim(), ctx);
|
|
@@ -6916,7 +7367,7 @@ async function handleStatus(_args, ctx) {
|
|
|
6916
7367
|
activeRun: Boolean(ctx.activeRuns?.get(ctx.scope)),
|
|
6917
7368
|
queue: ctx.processPool?.snapshot(),
|
|
6918
7369
|
scope: ctx.scope,
|
|
6919
|
-
chatMode: ctx.msg.chatType === "p2p" ? "p2p" : "group"
|
|
7370
|
+
chatMode: ctx.msg.chatMode ?? (ctx.msg.chatType === "p2p" ? "p2p" : "group")
|
|
6920
7371
|
});
|
|
6921
7372
|
await sendCard(ctx, card);
|
|
6922
7373
|
}
|
|
@@ -6938,7 +7389,7 @@ async function handleResume(args, ctx) {
|
|
|
6938
7389
|
const entries = sessionId ? [
|
|
6939
7390
|
{
|
|
6940
7391
|
sessionId,
|
|
6941
|
-
preview: isCodex ? "\u5F53\u524D Codex thread" : profile?.agentKind === "cursor" ? "\u5F53\u524D Cursor \u4F1A\u8BDD" : profile?.agentKind === "pi" ? "\u5F53\u524D Pi \u4F1A\u8BDD" : "\u5F53\u524D Claude \u4F1A\u8BDD",
|
|
7392
|
+
preview: isCodex ? "\u5F53\u524D Codex thread" : profile?.agentKind === "cursor" ? "\u5F53\u524D Cursor \u4F1A\u8BDD" : profile?.agentKind === "pi" ? "\u5F53\u524D Pi \u4F1A\u8BDD" : profile?.agentKind === "alice" ? "\u5F53\u524D Alice \u4F1A\u8BDD" : "\u5F53\u524D Claude \u4F1A\u8BDD",
|
|
6942
7393
|
relTime: "\u5F53\u524D",
|
|
6943
7394
|
current: true
|
|
6944
7395
|
}
|
|
@@ -6966,7 +7417,7 @@ async function handleResumeUse(sessionId, ctx) {
|
|
|
6966
7417
|
return;
|
|
6967
7418
|
}
|
|
6968
7419
|
ctx.sessions?.set(ctx.scope, sessionId, cwd);
|
|
6969
|
-
const agentLabel2 = profile?.agentKind === "cursor" ? "Cursor" : profile?.agentKind === "pi" ? "Pi" : "Claude";
|
|
7420
|
+
const agentLabel2 = profile?.agentKind === "cursor" ? "Cursor" : profile?.agentKind === "pi" ? "Pi" : profile?.agentKind === "alice" ? "Alice" : "Claude";
|
|
6970
7421
|
await replyMarkdown(ctx, `\u2713 \u5DF2\u6062\u590D ${agentLabel2} \u4F1A\u8BDD\uFF0C\u8BF7\u7EE7\u7EED\u53D1\u9001\u6D88\u606F\u3002`);
|
|
6971
7422
|
}
|
|
6972
7423
|
async function handleStop(_args, ctx) {
|
|
@@ -7019,7 +7470,7 @@ async function handleSwitch(args, ctx) {
|
|
|
7019
7470
|
}
|
|
7020
7471
|
const kind = args.trim().toLowerCase();
|
|
7021
7472
|
if (!isSwitchableAgentKind(kind)) {
|
|
7022
|
-
await replyText(ctx, "\u7528\u6CD5: `/use claude` | `/use codex` | `/use cursor` | `/use pi`");
|
|
7473
|
+
await replyText(ctx, "\u7528\u6CD5: `/use claude` | `/use codex` | `/use cursor` | `/use pi` | `/use alice`");
|
|
7023
7474
|
return;
|
|
7024
7475
|
}
|
|
7025
7476
|
const current = ctx.runtime.fullCfg.agentKind ?? "claude";
|
|
@@ -7052,7 +7503,7 @@ async function handleSwitch(args, ctx) {
|
|
|
7052
7503
|
);
|
|
7053
7504
|
}
|
|
7054
7505
|
function isSwitchableAgentKind(value) {
|
|
7055
|
-
return value === "claude" || value === "codex" || value === "cursor" || value === "pi";
|
|
7506
|
+
return value === "claude" || value === "codex" || value === "cursor" || value === "pi" || value === "alice";
|
|
7056
7507
|
}
|
|
7057
7508
|
function resolveStatusAgentName(ctx) {
|
|
7058
7509
|
if (ctx.runtime?.agentUnavailable) {
|
|
@@ -7078,6 +7529,9 @@ function runtimeAccessStatus(profile) {
|
|
|
7078
7529
|
if (profile.agentKind === "pi") {
|
|
7079
7530
|
return { label: "mode", value: "json" };
|
|
7080
7531
|
}
|
|
7532
|
+
if (profile.agentKind === "alice") {
|
|
7533
|
+
return { label: "mode", value: "json" };
|
|
7534
|
+
}
|
|
7081
7535
|
return {
|
|
7082
7536
|
label: "sandbox",
|
|
7083
7537
|
value: `${profile.sandbox.defaultMode}/${profile.sandbox.maxMode}`
|
|
@@ -7137,15 +7591,21 @@ async function handleCardAction(deps) {
|
|
|
7137
7591
|
const payload = value;
|
|
7138
7592
|
const cmd = typeof payload.cmd === "string" ? payload.cmd : "";
|
|
7139
7593
|
if (!cmd) return;
|
|
7140
|
-
|
|
7141
|
-
|
|
7594
|
+
const { scope, threadId } = await scopeForCardAction(
|
|
7595
|
+
deps.channel,
|
|
7596
|
+
deps.evt.chatId,
|
|
7597
|
+
deps.evt.messageId,
|
|
7598
|
+
deps.runtime.fullCfg
|
|
7599
|
+
);
|
|
7600
|
+
log.info("cardAction", "cmd", { cmd, scope, chatId: deps.evt.chatId });
|
|
7601
|
+
const msg = makeFakeMsg(deps.evt, threadId);
|
|
7142
7602
|
const ctx = {
|
|
7143
7603
|
channel: deps.channel,
|
|
7144
7604
|
cfg: deps.runtime.fullCfg,
|
|
7145
7605
|
fullCfg: deps.runtime.fullCfg,
|
|
7146
7606
|
profileConfig: deps.runtime.profileConfig,
|
|
7147
7607
|
msg,
|
|
7148
|
-
scope
|
|
7608
|
+
scope,
|
|
7149
7609
|
workspaces: deps.workspaces,
|
|
7150
7610
|
activeRuns: deps.activeRuns,
|
|
7151
7611
|
activeCmdSessions: deps.activeCmdSessions,
|
|
@@ -7176,11 +7636,12 @@ function composeArgs(sub, payload) {
|
|
|
7176
7636
|
const arg = typeof payload.arg === "string" && payload.arg || typeof payload.name === "string" && payload.name || "";
|
|
7177
7637
|
return arg ? `${sub} ${arg}` : sub;
|
|
7178
7638
|
}
|
|
7179
|
-
function makeFakeMsg(evt) {
|
|
7639
|
+
function makeFakeMsg(evt, threadId) {
|
|
7180
7640
|
return {
|
|
7181
7641
|
messageId: evt.messageId,
|
|
7182
7642
|
chatId: evt.chatId,
|
|
7183
7643
|
chatType: "p2p",
|
|
7644
|
+
...threadId ? { threadId, chatMode: "topic" } : {},
|
|
7184
7645
|
senderId: evt.operator.openId,
|
|
7185
7646
|
senderName: evt.operator.name,
|
|
7186
7647
|
content: "",
|
|
@@ -7252,7 +7713,8 @@ async function startChannel(cfg, startOpts = {}) {
|
|
|
7252
7713
|
handshakeTimeoutMs: 8e3,
|
|
7253
7714
|
httpTimeoutMs: 3e4,
|
|
7254
7715
|
respectProxyEnv: true,
|
|
7255
|
-
includeRawEvent: true
|
|
7716
|
+
includeRawEvent: true,
|
|
7717
|
+
resolveChatMode: true
|
|
7256
7718
|
};
|
|
7257
7719
|
const channel = createLarkChannel(opts);
|
|
7258
7720
|
let consecutiveReconnects = 0;
|
|
@@ -7363,17 +7825,12 @@ async function startChannel(cfg, startOpts = {}) {
|
|
|
7363
7825
|
}
|
|
7364
7826
|
async function handleMessage(channel, cfg, msg, workspaces, ctx) {
|
|
7365
7827
|
const preview = msg.content.length > 80 ? `${msg.content.slice(0, 80)}\u2026` : msg.content;
|
|
7366
|
-
log2("info", "message-received",
|
|
7367
|
-
chatType: msg.chatType,
|
|
7368
|
-
sender: msg.senderId,
|
|
7369
|
-
preview,
|
|
7370
|
-
mentionedBot: msg.mentionedBot
|
|
7371
|
-
});
|
|
7828
|
+
log2("info", "message-received", msg);
|
|
7372
7829
|
if (msg.chatType !== "p2p" && getRequireMentionInGroup(cfg) && !msg.mentionedBot) {
|
|
7373
7830
|
log2("info", "skip-no-mention", { chatId: msg.chatId });
|
|
7374
7831
|
return;
|
|
7375
7832
|
}
|
|
7376
|
-
const scope =
|
|
7833
|
+
const scope = scopeForMessage(msg, cfg);
|
|
7377
7834
|
const cmdInputCtx = {
|
|
7378
7835
|
channel,
|
|
7379
7836
|
cfg,
|
|
@@ -7412,7 +7869,8 @@ async function handleMessage(channel, cfg, msg, workspaces, ctx) {
|
|
|
7412
7869
|
workspaces,
|
|
7413
7870
|
activePolicyFingerprints: ctx.activePolicyFingerprints
|
|
7414
7871
|
},
|
|
7415
|
-
msg
|
|
7872
|
+
msg,
|
|
7873
|
+
scope
|
|
7416
7874
|
);
|
|
7417
7875
|
return;
|
|
7418
7876
|
}
|
|
@@ -7459,6 +7917,8 @@ async function runStart(opts) {
|
|
|
7459
7917
|
fullCfg = applyAgentKindToConfig(fullCfg, "cursor");
|
|
7460
7918
|
} else if (agentKind === "pi") {
|
|
7461
7919
|
fullCfg = applyAgentKindToConfig(fullCfg, "pi");
|
|
7920
|
+
} else if (agentKind === "alice") {
|
|
7921
|
+
fullCfg = applyAgentKindToConfig(fullCfg, "alice");
|
|
7462
7922
|
} else {
|
|
7463
7923
|
fullCfg = applyAgentKindToConfig(fullCfg, "claude");
|
|
7464
7924
|
}
|
|
@@ -7502,6 +7962,13 @@ async function runStart(opts) {
|
|
|
7502
7962
|
} catch {
|
|
7503
7963
|
console.log(`\u2713 Pi Agent: pi${opts.debug ? " (debug)" : ""}`);
|
|
7504
7964
|
}
|
|
7965
|
+
} else if (agentKind === "alice") {
|
|
7966
|
+
try {
|
|
7967
|
+
const alicePath = await resolveAliceBinaryPath();
|
|
7968
|
+
console.log(`\u2713 Alice Agent: ${alicePath}${opts.debug ? " (debug)" : ""}`);
|
|
7969
|
+
} catch {
|
|
7970
|
+
console.log(`\u2713 Alice Agent: alice${opts.debug ? " (debug)" : ""}`);
|
|
7971
|
+
}
|
|
7505
7972
|
} else {
|
|
7506
7973
|
console.log(`\u2713 Codex CLI: ${fullCfg.codex?.binaryPath ?? "codex"}`);
|
|
7507
7974
|
}
|
|
@@ -7593,11 +8060,11 @@ var runOptions = [
|
|
|
7593
8060
|
["--app-id <id>", "\u4F7F\u7528\u5DF2\u6709\u98DE\u4E66\u5E94\u7528\uFF08\u8DF3\u8FC7\u626B\u7801\u521B\u5EFA\uFF09"],
|
|
7594
8061
|
["--app-secret <secret>", "App Secret\uFF08\u914D\u5408 --app-id\uFF09"],
|
|
7595
8062
|
["--tenant <tenant>", "\u79DF\u6237\u57DF\u540D\uFF1Afeishu \u6216 lark\uFF08\u9ED8\u8BA4 feishu\uFF09"],
|
|
7596
|
-
["--agent <kind>", "agent \u7C7B\u578B\uFF1Aclaude / codex / cursor / pi / disabled\uFF08\u9ED8\u8BA4 claude\uFF09"],
|
|
8063
|
+
["--agent <kind>", "agent \u7C7B\u578B\uFF1Aclaude / codex / cursor / pi / alice / disabled\uFF08\u9ED8\u8BA4 claude\uFF09"],
|
|
7597
8064
|
["--debug", "Cursor agent \u8C03\u8BD5\uFF1A\u6253\u5370 spawn args \u4E0E\u539F\u751F stream-json \u4E8B\u4EF6"]
|
|
7598
8065
|
];
|
|
7599
8066
|
function parseRunOpts(opts) {
|
|
7600
|
-
const agent = opts.agent === "claude" || opts.agent === "codex" || opts.agent === "cursor" || opts.agent === "pi" || opts.agent === "disabled" ? opts.agent : void 0;
|
|
8067
|
+
const agent = opts.agent === "claude" || opts.agent === "codex" || opts.agent === "cursor" || opts.agent === "pi" || opts.agent === "alice" || opts.agent === "disabled" ? opts.agent : void 0;
|
|
7601
8068
|
return { ...opts, agent, debug: opts.debug === true };
|
|
7602
8069
|
}
|
|
7603
8070
|
program.command("run").description("\u524D\u53F0\u8FD0\u884C bot\uFF0C\u63A5\u6536\u6D88\u606F\u5E76\u901A\u8FC7 Claude Code / Codex / Cursor \u56DE\u590D").option(...runOptions[0]).option(...runOptions[1]).option(...runOptions[2]).option(...runOptions[3]).option(...runOptions[4]).option(...runOptions[5]).action(async (opts) => {
|