@yvhitxcel/opencode-remote 0.15.0 → 0.16.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 +95 -8
- package/dist/core/auth.js +41 -108
- package/dist/core/router.js +152 -60
- package/dist/feishu/commands.js +2 -35
- package/dist/feishu/handler.js +17 -26
- package/dist/opencode/client.js +47 -78
- package/dist/telegram/adapter.js +55 -0
- package/dist/telegram/bot.js +48 -207
- package/dist/weixin/bot.js +12 -2
- package/dist/weixin/commands.js +2 -42
- package/dist/weixin/handler.js +80 -107
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
通过微信、Telegram、飞书等平台随时随地控制 OpenCode。
|
|
4
4
|
|
|
5
|
+
## 为什么这么爽?
|
|
6
|
+
|
|
7
|
+
**输入一个字母 `z`,14 位 AI 专家帮你审项目。** 架构师、安全研究员、测试工程师、运维……每人给出犀利点评,技术经理汇总 P0-P2 任务清单。你在手机上躺着看就行。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 安装
|
|
11
|
+
npm install -g @yvhitxcel/opencode-remote
|
|
12
|
+
|
|
13
|
+
# 开干
|
|
14
|
+
opencode-remote telegram
|
|
15
|
+
# → 输入 /z 叫专家团队
|
|
16
|
+
# → 输入 z 让专家分析项目
|
|
17
|
+
# → AI 自动干活,你喝茶
|
|
18
|
+
```
|
|
19
|
+
|
|
5
20
|
## 功能特性
|
|
6
21
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
22
|
+
- **🤖 专家评审系统** — `/z` 一键召唤 14 位 AI 专家,自动分析、投票、出方案
|
|
23
|
+
- **📱 多平台支持** — 微信、飞书、Telegram
|
|
24
|
+
- **🧠 多 AI Agent** — OpenCode、Claude Code、Codex、GitHub Copilot
|
|
25
|
+
- **🔄 循环任务** — `/loop` 让 AI 持续干活
|
|
26
|
+
- **🔍 一键诊断** — `/diagnose` 检查各组件状态
|
|
11
27
|
|
|
12
28
|
## 安装
|
|
13
29
|
|
|
@@ -17,15 +33,42 @@ npm install -g opencode-remote
|
|
|
17
33
|
|
|
18
34
|
## 快速开始
|
|
19
35
|
|
|
36
|
+
推荐路径:**Telegram(5分钟)→ 微信(10分钟)→ 飞书(30分钟)**
|
|
37
|
+
|
|
20
38
|
```bash
|
|
21
|
-
#
|
|
39
|
+
# 1. 安装
|
|
40
|
+
npm install -g @yvhitxcel/opencode-remote
|
|
41
|
+
|
|
42
|
+
# 2. 启动 Telegram(最快上手,无需配置)
|
|
43
|
+
opencode-remote telegram
|
|
44
|
+
# 在 Telegram 里搜索你的 bot,发送 /start
|
|
45
|
+
|
|
46
|
+
# 3. 微信(需要 iLink 平台账号)
|
|
22
47
|
opencode-remote weixin
|
|
48
|
+
# 扫码登录后即可使用
|
|
23
49
|
|
|
24
|
-
#
|
|
50
|
+
# 4. 飞书(需要企业版账号)
|
|
25
51
|
opencode-remote feishu
|
|
52
|
+
```
|
|
26
53
|
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
## 首次使用
|
|
55
|
+
|
|
56
|
+
1. 安装后运行 `opencode-remote telegram`
|
|
57
|
+
2. 在 Telegram 里找到你的 bot,发送 `/start`
|
|
58
|
+
3. 发送 `/help` 查看所有命令
|
|
59
|
+
4. 发送一条消息给 AI,比如"你好"
|
|
60
|
+
5. **发送 `/z` 启动专家模式,然后发送 `z` —— 14 位 AI 专家开始分析你的项目**
|
|
61
|
+
|
|
62
|
+
> 💡 所有核心功能(对话、会话管理、AI 模型切换、专家评审)无需任何配置。只有 `/upload` 上传才需要七牛云。
|
|
63
|
+
|
|
64
|
+
## 手机开发工作流
|
|
65
|
+
|
|
66
|
+
把 `weixin.bat` 复制到项目根目录,双击运行,扫码登录后即可在手机上通过微信开发该项目。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 或者手动指定目录
|
|
70
|
+
cd 你的项目
|
|
71
|
+
opencode-remote
|
|
29
72
|
```
|
|
30
73
|
|
|
31
74
|
## 平台兼容性
|
|
@@ -56,6 +99,8 @@ opencode-remote telegram
|
|
|
56
99
|
|
|
57
100
|
> ✅ 可用 ❌ 未实现
|
|
58
101
|
|
|
102
|
+
> 💡 七牛云仅用于 `/upload` `/delete` 文件上传功能,**所有核心命令(对话、会话管理、AI 模型切换等)无需任何配置即可使用**。安装后直接 `opencode-remote telegram` 即可体验。
|
|
103
|
+
|
|
59
104
|
## 快速使用
|
|
60
105
|
|
|
61
106
|
```bash
|
|
@@ -69,14 +114,56 @@ opencode-remote weixin
|
|
|
69
114
|
opencode-remote feishu
|
|
70
115
|
```
|
|
71
116
|
|
|
117
|
+
## 手机开发工作流
|
|
118
|
+
|
|
119
|
+
1. 把 `weixin.bat` 复制到你的项目根目录
|
|
120
|
+
2. 双击运行(或在终端执行),扫码登录微信
|
|
121
|
+
3. 之后在手机上发消息给 bot,AI 会直接操作这个项目目录
|
|
122
|
+
4. 查看状态、修改代码、git 提交,全部在微信里完成
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 或者手动指定项目目录
|
|
126
|
+
cd 你的项目
|
|
127
|
+
opencode-remote
|
|
128
|
+
```
|
|
129
|
+
|
|
72
130
|
## 配置说明
|
|
73
131
|
|
|
74
132
|
首次运行会在 `~/.opencode-remote/` 目录生成配置。详见 `.env.example`。
|
|
75
133
|
|
|
134
|
+
**七牛云是可选的**,不配也能用全部核心功能。只有上传构建产物才需要配置。
|
|
135
|
+
|
|
136
|
+
## 常见问题
|
|
137
|
+
|
|
138
|
+
**Q: 为什么有些命令不能用?**
|
|
139
|
+
A: 先运行 `/diagnose` 检查各组件状态。Telegram 功能最全,微信和飞书部分命令需要额外配置。
|
|
140
|
+
|
|
141
|
+
**Q: 微信怎么登录?**
|
|
142
|
+
A: 运行 `opencode-remote weixin`,终端会显示二维码,用微信扫码即可。
|
|
143
|
+
|
|
144
|
+
**Q: 专家评审怎么用?**
|
|
145
|
+
A: 发 `z` 或 `叫全部专家`,AI 自动扫项目、组队评审、出 P0/P1 修复方案,**然后自动修代码**。评审过程包括三级质量保障:脑内路径追踪、服务端模拟验证、对抗评审。
|
|
146
|
+
|
|
147
|
+
**Q: `/z` 什么时候用?**
|
|
148
|
+
A: 任何时候。发一个 `z` 让 AI 评审当前项目,发 `z 帮我看看这个bug` 聚焦具体问题。先 `/z` 设置自定义 prompt 再发问题也行。
|
|
149
|
+
|
|
150
|
+
**Q: `/z` 自动修代码会改坏吗?**
|
|
151
|
+
A: **强烈建议在 git 仓库中使用。** 如果改坏了可以 `git checkout .` 回滚。没有 git 的项目,AI 改完不可逆。不确定的话先发 `/z off` 关闭自动执行,只看报告不改代码。
|
|
152
|
+
|
|
153
|
+
**Q: 需要自己的服务器吗?**
|
|
154
|
+
A: 需要一台电脑运行 bot,手机上通过 IM 控制。OpenCode 也运行在这台电脑上。
|
|
155
|
+
|
|
156
|
+
**Q: 如何更新?**
|
|
157
|
+
A: `npm update -g @yvhitxcel/opencode-remote`
|
|
158
|
+
|
|
76
159
|
## 系统要求
|
|
77
160
|
|
|
78
161
|
- Node.js >= 18.0.0
|
|
79
162
|
|
|
163
|
+
## 致谢
|
|
164
|
+
|
|
165
|
+
本项目基于 [opencode-remote-control](https://github.com/ceociocto/opencode-remote-control) 开发。
|
|
166
|
+
|
|
80
167
|
## 许可证
|
|
81
168
|
|
|
82
169
|
MIT License
|
package/dist/core/auth.js
CHANGED
|
@@ -1,119 +1,52 @@
|
|
|
1
|
-
// Authorization management for OpenCode Remote Control
|
|
2
|
-
// First user to send /start becomes the owner automatically
|
|
3
|
-
const authState = {
|
|
4
|
-
telegramOwner: null,
|
|
5
|
-
feishuOwner: null,
|
|
6
|
-
weixinOwner: null,
|
|
7
|
-
};
|
|
8
|
-
// Auth file path for persistence
|
|
9
1
|
import { homedir } from 'os';
|
|
10
2
|
import { join } from 'path';
|
|
11
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
writeFileSync(AUTH_FILE, JSON.stringify(authState, null, 2));
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
console.error('Failed to save auth state:', error);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Initialize on module load
|
|
42
|
-
loadAuth();
|
|
4
|
+
|
|
5
|
+
const OWNER_KEY = { telegram: 'telegramOwner', feishu: 'feishuOwner', weixin: 'weixinOwner' };
|
|
6
|
+
const AUTH_FILE = join(homedir(), '.opencode-remote', 'auth.json');
|
|
7
|
+
|
|
8
|
+
const state = { telegramOwner: null, feishuOwner: null, weixinOwner: null };
|
|
9
|
+
|
|
10
|
+
function load() {
|
|
11
|
+
try {
|
|
12
|
+
if (existsSync(AUTH_FILE)) {
|
|
13
|
+
const d = JSON.parse(readFileSync(AUTH_FILE, 'utf-8'));
|
|
14
|
+
for (const k of Object.keys(OWNER_KEY)) state[OWNER_KEY[k]] = d[OWNER_KEY[k]] || null;
|
|
15
|
+
}
|
|
16
|
+
} catch (e) { console.warn('[auth] load failed:', e.message); }
|
|
17
|
+
}
|
|
18
|
+
function save() {
|
|
19
|
+
try {
|
|
20
|
+
const dir = join(homedir(), '.opencode-remote');
|
|
21
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
22
|
+
writeFileSync(AUTH_FILE, JSON.stringify(state, null, 2));
|
|
23
|
+
} catch (e) { console.error('[auth] save failed:', e.message); }
|
|
24
|
+
}
|
|
25
|
+
load();
|
|
26
|
+
|
|
43
27
|
export function isAuthorized(platform, userId) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
else if (platform === 'feishu') {
|
|
48
|
-
return authState.feishuOwner === userId;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
return authState.weixinOwner === userId;
|
|
52
|
-
}
|
|
28
|
+
const key = OWNER_KEY[platform];
|
|
29
|
+
return key ? state[key] === userId : false;
|
|
53
30
|
}
|
|
54
31
|
export function hasOwner(platform) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
else if (platform === 'feishu') {
|
|
59
|
-
return authState.feishuOwner !== null;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
return authState.weixinOwner !== null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
export function claimOwnership(platform, userId) {
|
|
66
|
-
if (platform === 'telegram') {
|
|
67
|
-
if (authState.telegramOwner) {
|
|
68
|
-
if (authState.telegramOwner === userId) {
|
|
69
|
-
return { success: true, message: 'already_owner' };
|
|
70
|
-
}
|
|
71
|
-
return { success: false, message: 'already_claimed' };
|
|
72
|
-
}
|
|
73
|
-
authState.telegramOwner = userId;
|
|
74
|
-
saveAuth();
|
|
75
|
-
return { success: true, message: 'claimed' };
|
|
76
|
-
}
|
|
77
|
-
else if (platform === 'feishu') {
|
|
78
|
-
if (authState.feishuOwner) {
|
|
79
|
-
if (authState.feishuOwner === userId) {
|
|
80
|
-
return { success: true, message: 'already_owner' };
|
|
81
|
-
}
|
|
82
|
-
return { success: false, message: 'already_claimed' };
|
|
83
|
-
}
|
|
84
|
-
authState.feishuOwner = userId;
|
|
85
|
-
saveAuth();
|
|
86
|
-
return { success: true, message: 'claimed' };
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
// weixin
|
|
90
|
-
if (authState.weixinOwner) {
|
|
91
|
-
if (authState.weixinOwner === userId) {
|
|
92
|
-
return { success: true, message: 'already_owner' };
|
|
93
|
-
}
|
|
94
|
-
return { success: false, message: 'already_claimed' };
|
|
95
|
-
}
|
|
96
|
-
authState.weixinOwner = userId;
|
|
97
|
-
saveAuth();
|
|
98
|
-
return { success: true, message: 'claimed' };
|
|
99
|
-
}
|
|
32
|
+
const key = OWNER_KEY[platform];
|
|
33
|
+
return key ? state[key] !== null : false;
|
|
100
34
|
}
|
|
101
35
|
export function getOwner(platform) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
36
|
+
const key = OWNER_KEY[platform];
|
|
37
|
+
return key ? state[key] : null;
|
|
38
|
+
}
|
|
39
|
+
export function claimOwnership(platform, userId) {
|
|
40
|
+
const key = OWNER_KEY[platform];
|
|
41
|
+
if (!key) return { success: false, message: 'unknown_platform' };
|
|
42
|
+
if (state[key]) {
|
|
43
|
+
if (state[key] === userId) return { success: true, message: 'already_owner' };
|
|
44
|
+
return { success: false, message: 'already_claimed' };
|
|
45
|
+
}
|
|
46
|
+
state[key] = userId;
|
|
47
|
+
save();
|
|
48
|
+
return { success: true, message: 'claimed' };
|
|
111
49
|
}
|
|
112
|
-
// For debugging/display
|
|
113
50
|
export function getAuthStatus() {
|
|
114
|
-
|
|
115
|
-
telegram: authState.telegramOwner !== null,
|
|
116
|
-
feishu: authState.feishuOwner !== null,
|
|
117
|
-
weixin: authState.weixinOwner !== null,
|
|
118
|
-
};
|
|
51
|
+
return { telegram: !!state.telegramOwner, feishu: !!state.feishuOwner, weixin: !!state.weixinOwner };
|
|
119
52
|
}
|
package/dist/core/router.js
CHANGED
|
@@ -7,7 +7,6 @@ export const COMMAND_ALIASES = {
|
|
|
7
7
|
help: ['help', 'h', '?'],
|
|
8
8
|
status: ['status'],
|
|
9
9
|
reset: ['reset'],
|
|
10
|
-
stop: ['stop'],
|
|
11
10
|
restart: ['restart'],
|
|
12
11
|
sessions: ['sessions', 'sw'],
|
|
13
12
|
delsessions: ['delsessions', 'del'],
|
|
@@ -25,7 +24,116 @@ export const COMMAND_ALIASES = {
|
|
|
25
24
|
copilot: ['copilot'],
|
|
26
25
|
agents: ['agents'],
|
|
27
26
|
model: ['model'],
|
|
28
|
-
expert: ['expert', 'z', 'review'],
|
|
27
|
+
expert: ['expert', 'z', 'Z', 'review'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const EXPERT_SYSTEM_PROMPT = `你是一个专家评审系统。用户消息含触发词(z/c/叫全部专家/专家点评)时启动评审,前后可带具体问题则聚焦该问题。
|
|
31
|
+
|
|
32
|
+
## 角色(14 位)
|
|
33
|
+
1. 架构师 — 代码架构、模块划分、依赖管理
|
|
34
|
+
2. 后端工程师 — 稳定性、错误处理、性能
|
|
35
|
+
3. 测试工程师 — 测试覆盖、可测性
|
|
36
|
+
4. VC / 投资人 — 值不值得投
|
|
37
|
+
5. 开源社区经理 — 新人能不能上车
|
|
38
|
+
6. Flutter 开发者 — 移动端好不好用
|
|
39
|
+
7. SRE / 运维 — 能不能上线
|
|
40
|
+
8. 安全研究员 — 有没有洞
|
|
41
|
+
9. AI 研究员 — agent loop 质量
|
|
42
|
+
10. 用户支持 — 用户卡在哪
|
|
43
|
+
11. 技术写作者 — 文档好不好写
|
|
44
|
+
12. 竞品分析师 — 市场定位
|
|
45
|
+
13. Git 专家 — commit 质量、分支管理、历史整洁度、回滚安全
|
|
46
|
+
14. **技术经理(最后出场)** — 汇总以上 13 位意见,给出 P0-P2 分级的可执行任务清单
|
|
47
|
+
|
|
48
|
+
## 执行流程
|
|
49
|
+
|
|
50
|
+
### 第 1 步:1-13 号专家点评
|
|
51
|
+
每人最多 200 字。**言辞必须苛刻、犀利、严谨。不讨好,不委婉。不投票。**
|
|
52
|
+
- 如果某条意见在上轮已提过但未修复,必须指出"上轮已提过,未落实"
|
|
53
|
+
- 格式: \`[意见] 内容 / [上轮已提: N]\`
|
|
54
|
+
|
|
55
|
+
### 第 2 步:技术经理提出 15 条问题
|
|
56
|
+
基于以上 13 位意见,输出 1) 2) ... 15) 清单。
|
|
57
|
+
- **1-5 为 P0(红色🔴)**,6-10 为 P1(黄色🟡),11-15 为 P2(蓝色🔵)
|
|
58
|
+
- 每条包含一句话描述(做什么),不展开
|
|
59
|
+
- 小 bug 合并同类项,一条 = 一个可执行动作
|
|
60
|
+
- **每条必须标注对应的专家意见编号**(格式: \`1) xxx [来源: 3,7,11]\`)
|
|
61
|
+
- **不含投票结果。不含投票结果。不含投票结果。**
|
|
62
|
+
|
|
63
|
+
### 第 3 步:1-13 号专家投票
|
|
64
|
+
每人从 15 条中选 3 条最关键的。格式:\`-> 三票:1, 7, 12\`
|
|
65
|
+
- **投票时必须检查自己上轮的意见是否已落实,如未落实则优先投票给相关条目**
|
|
66
|
+
|
|
67
|
+
### 第 4 步:技术经理公布结果
|
|
68
|
+
得票统计,汇入 P0-P2,标记得票数。
|
|
69
|
+
- **P0 🔴 1) xxx ( 票)[来源: 3,7] — 说明**
|
|
70
|
+
- **P1 🟡 7) xxx ( 票)[来源: 1,5] — 说明**
|
|
71
|
+
|
|
72
|
+
### 第 5 步:自动执行
|
|
73
|
+
对 P0(票数 ≥ 3)按得票从高到低逐个自动执行修复。
|
|
74
|
+
|
|
75
|
+
## 三级质量保障(嵌入评审全程)
|
|
76
|
+
|
|
77
|
+
### 1. 脑内执行路径追踪
|
|
78
|
+
在评审和修复代码时,**在脑中逐条执行关键路径**:变量怎么赋值、条件怎么分支、循环怎么迭代、异常怎么传播。不只看代码静态结构,要模拟运行时行为。发现逻辑断点、边界遗漏、状态覆盖不全立即提出。
|
|
79
|
+
|
|
80
|
+
### 2. 服务端模拟验证
|
|
81
|
+
对评审中发现的 P0/P1 问题,在修复后**自动执行模拟验证**:
|
|
82
|
+
- 跑 \`npm run lint\` 检查语法和模块图
|
|
83
|
+
- 跑 \`node --test\` 验证单元测试
|
|
84
|
+
- 对修改的文件做 \`node --check\` 语法校验
|
|
85
|
+
- 如果项目有 CI 脚本,触发本地等效检查
|
|
86
|
+
- 验证结果写入执行总结
|
|
87
|
+
|
|
88
|
+
### 3. LLM 对抗评审
|
|
89
|
+
修复完成后,**以对抗视角重新审视自己的修改**:"这段修改有没有引入新 bug?有没有遗漏边界情况?变更是否最小?会不会破坏现有功能?"
|
|
90
|
+
- 如果发现自己的修改有问题→回退重做
|
|
91
|
+
- 如果确认无误→在总结中标注"已通过对抗评审"
|
|
92
|
+
|
|
93
|
+
## 核心三要素
|
|
94
|
+
1. **吃透代码再动手** — 不靠猜测修复 bug
|
|
95
|
+
2. **追到根因** — 用户反馈的现象要追到代码根因
|
|
96
|
+
3. **改完必须验证** — 先跑测试跑 lint 再交付
|
|
97
|
+
|
|
98
|
+
## 四项基本原则(硬约束)
|
|
99
|
+
|
|
100
|
+
1. **一次性做好,不重复返工** — 同一个模块的同类问题最多修两轮。第三轮还提同类问题说明方案不对,技术经理必须输出"换方案"而非"继续修"。
|
|
101
|
+
|
|
102
|
+
2. **以用户价值为导向** — P0 排序:用户能不能跑起来 > 会不会崩 > 好不好用。内部代码质量默认不进 P0。
|
|
103
|
+
|
|
104
|
+
3. **对用户友好** — 每次评审必须包含"首次使用视角"。P0 必须至少有一条直接回应首次使用视角。**若没有,整轮评审无效。**
|
|
105
|
+
|
|
106
|
+
4. **快速迭代,先上线** — R4 开始技术经理必须回答"是否可以发布?最短路径是什么?" 是则只保留阻塞发布的问题。
|
|
107
|
+
|
|
108
|
+
## 规则
|
|
109
|
+
- 言辞苛刻犀利,不讨好,不委婉
|
|
110
|
+
- 节约 token:摘要不超过 400 字,不贴源码
|
|
111
|
+
- 上轮已提过且已修的问题本轮不得再提(除非验收不合格)
|
|
112
|
+
- 技术经理的 15 条必须标注来源,**没有对应来源的问题不得出现**`;
|
|
113
|
+
|
|
114
|
+
const COMMAND_HELP = {
|
|
115
|
+
start: '认领所有权',
|
|
116
|
+
help: '显示帮助',
|
|
117
|
+
status: '连接状态',
|
|
118
|
+
reset: '重置会话',
|
|
119
|
+
restart: '重启 Bot',
|
|
120
|
+
sessions: '浏览会话',
|
|
121
|
+
delsessions: '删除会话',
|
|
122
|
+
loop: '循环任务',
|
|
123
|
+
edit: '编辑消息',
|
|
124
|
+
diagnose: '系统诊断',
|
|
125
|
+
refresh: '刷新上下文',
|
|
126
|
+
copy: '复制回复',
|
|
127
|
+
revert: '撤销消息',
|
|
128
|
+
upload: '上传文件',
|
|
129
|
+
delete: '删除上传文件',
|
|
130
|
+
oc: '使用 OpenCode',
|
|
131
|
+
cc: '使用 Claude Code',
|
|
132
|
+
cx: '使用 Codex',
|
|
133
|
+
copilot: '使用 Copilot',
|
|
134
|
+
agents: '查看 Agent',
|
|
135
|
+
model: '切换模型',
|
|
136
|
+
expert: '专家评审(z/叫全部专家)',
|
|
29
137
|
};
|
|
30
138
|
|
|
31
139
|
const COMMAND_MAP = {};
|
|
@@ -35,6 +143,46 @@ for (const [cmd, aliases] of Object.entries(COMMAND_ALIASES)) {
|
|
|
35
143
|
}
|
|
36
144
|
}
|
|
37
145
|
|
|
146
|
+
/**
|
|
147
|
+
* 保持 typing 指示器常亮。每 8 秒刷新一次,30 秒无活动自熄。
|
|
148
|
+
* 调用 poke 刷新计时,done 手动关闭。
|
|
149
|
+
* 放在 core 层避免被各平台 handler 误删。
|
|
150
|
+
*/
|
|
151
|
+
export function startTypingPing(adapter, threadId) {
|
|
152
|
+
let lastActivity = Date.now();
|
|
153
|
+
const timer = setInterval(() => {
|
|
154
|
+
adapter.sendTypingIndicator(threadId).catch(() => {});
|
|
155
|
+
if (Date.now() - lastActivity > 30000) clearInterval(timer);
|
|
156
|
+
}, 3000);
|
|
157
|
+
return {
|
|
158
|
+
poke: () => { lastActivity = Date.now(); },
|
|
159
|
+
done: () => { clearInterval(timer); },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getHelpText() {
|
|
164
|
+
const lines = ['📖 指令\n'];
|
|
165
|
+
const groups = [
|
|
166
|
+
['🟢 常用', ['start', 'help', 'status', 'reset', 'copy', 'revert', 'diagnose']],
|
|
167
|
+
['🔄 任务', ['loop', 'refresh', 'restart']],
|
|
168
|
+
['📂 会话', ['sessions', 'delsessions']],
|
|
169
|
+
['🤖 AI', ['model', 'agents', 'oc', 'cc']],
|
|
170
|
+
['⬆️ 文件', ['upload', 'delete']],
|
|
171
|
+
['🧠 专家', ['expert']],
|
|
172
|
+
];
|
|
173
|
+
for (const [title, cmds] of groups) {
|
|
174
|
+
lines.push(title);
|
|
175
|
+
for (const cmd of cmds) {
|
|
176
|
+
const aliases = COMMAND_ALIASES[cmd];
|
|
177
|
+
const aliasStr = aliases.length > 1 ? ` (${aliases.slice(1).join(', ')})` : '';
|
|
178
|
+
lines.push(` /${cmd}${aliasStr} — ${COMMAND_HELP[cmd] || cmd}`);
|
|
179
|
+
}
|
|
180
|
+
lines.push('');
|
|
181
|
+
}
|
|
182
|
+
lines.push('💬 直接发消息给 AI!');
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
38
186
|
export function detectCommand(text) {
|
|
39
187
|
const trimmed = text.trim();
|
|
40
188
|
if (trimmed === 'h' || trimmed === '?') {
|
|
@@ -106,33 +254,7 @@ export async function routeMessage(parsed, ctx) {
|
|
|
106
254
|
case 'command': {
|
|
107
255
|
switch (parsed.command) {
|
|
108
256
|
case 'help':
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
🟢 常用:
|
|
112
|
-
/start — 首次认证
|
|
113
|
-
/help — 帮助
|
|
114
|
-
/status — 连接状态
|
|
115
|
-
/reset — 清空会话
|
|
116
|
-
/copy — 复制回复
|
|
117
|
-
/revert — 撤销消息
|
|
118
|
-
|
|
119
|
-
🔄 任务:
|
|
120
|
-
/loop — 循环执行
|
|
121
|
-
/refresh — 刷新上下文
|
|
122
|
-
/restart — 重启 bot
|
|
123
|
-
/stop — 停止 bot
|
|
124
|
-
|
|
125
|
-
📂 会话:
|
|
126
|
-
/sessions — 浏览会话
|
|
127
|
-
/delsessions — 删除会话
|
|
128
|
-
|
|
129
|
-
🤖 AI 模型:
|
|
130
|
-
/model — 切换模型
|
|
131
|
-
/agents — 查看可用 Agent
|
|
132
|
-
/oc — 使用 OpenCode
|
|
133
|
-
/cc — 使用 Claude Code
|
|
134
|
-
|
|
135
|
-
💬 直接发消息给 AI!`;
|
|
257
|
+
return getHelpText();
|
|
136
258
|
|
|
137
259
|
case 'agents': {
|
|
138
260
|
const agents = registry.listAgents();
|
|
@@ -165,9 +287,6 @@ export async function routeMessage(parsed, ctx) {
|
|
|
165
287
|
case 'restart':
|
|
166
288
|
return '🔄 重启信号已发送,bot 即将重启...';
|
|
167
289
|
|
|
168
|
-
case 'stop':
|
|
169
|
-
return '🛑 停止信号已发送';
|
|
170
|
-
|
|
171
290
|
case 'sessions': {
|
|
172
291
|
const sessions = await getSessionsList();
|
|
173
292
|
if (!sessions || sessions.length === 0) return '📭 暂无会话';
|
|
@@ -288,38 +407,11 @@ export async function routeMessage(parsed, ctx) {
|
|
|
288
407
|
return ctx.opencodeSessionId ? '✏️ 用法: /edit <消息编号>' : '❌ 没有活跃的会话';
|
|
289
408
|
|
|
290
409
|
case 'expert': {
|
|
291
|
-
const { execSync } = await import('child_process');
|
|
292
|
-
const { existsSync, readFileSync } = await import('fs');
|
|
293
|
-
const { homedir } = await import('os');
|
|
294
|
-
const { join } = await import('path');
|
|
295
|
-
const projectRoot = process.cwd();
|
|
296
|
-
let gitStatus = '', recentCommits = '', dirTree = '';
|
|
297
|
-
try { gitStatus = execSync('git status --short', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { gitStatus = '(not a git repo)'; }
|
|
298
|
-
try { recentCommits = execSync('git log --oneline -5', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { recentCommits = '(no commits)'; }
|
|
299
|
-
try { dirTree = execSync('cmd /c "tree /F /A"', { cwd: projectRoot, timeout: 5000, encoding: 'utf-8' }); } catch { dirTree = '(failed to get tree)'; }
|
|
300
|
-
const customPromptPath = join(homedir(), '.opencode-remote', 'expert-prompt.md');
|
|
301
|
-
let promptTemplate = '';
|
|
302
|
-
if (existsSync(customPromptPath)) {
|
|
303
|
-
promptTemplate = readFileSync(customPromptPath, 'utf-8');
|
|
304
|
-
} else {
|
|
305
|
-
promptTemplate = `你是一个软件工程专家团队。请按以下流程执行:
|
|
306
|
-
|
|
307
|
-
## 项目上下文
|
|
308
|
-
{git_status}
|
|
309
|
-
{recent_commits}
|
|
310
|
-
{directory_tree}
|
|
311
|
-
|
|
312
|
-
## 要求
|
|
313
|
-
1. 分析项目当前状态
|
|
314
|
-
2. 找出问题
|
|
315
|
-
3. 给出改进建议`;
|
|
316
|
-
}
|
|
317
|
-
const prompt = promptTemplate.replace('{git_status}', gitStatus.trim()).replace('{recent_commits}', recentCommits.trim()).replace('{directory_tree}', dirTree.trim());
|
|
318
410
|
const agent = registry.findAgent('opencode');
|
|
319
411
|
if (!agent) return '❌ OpenCode agent not found';
|
|
320
412
|
const available = await agent.isAvailable().catch(() => false);
|
|
321
413
|
if (!available) return '❌ OpenCode 不可用';
|
|
322
|
-
const response = await agent.sendPrompt(ctx.threadId || 'expert-review',
|
|
414
|
+
const response = await agent.sendPrompt(ctx.threadId || 'expert-review', EXPERT_SYSTEM_PROMPT + '\n\n用户问题:' + (parsed.arg || '请评审当前项目'), []);
|
|
323
415
|
return response || '无响应';
|
|
324
416
|
}
|
|
325
417
|
|
package/dist/feishu/commands.js
CHANGED
|
@@ -3,7 +3,7 @@ import { splitMessage } from '../core/notifications.js';
|
|
|
3
3
|
import { EMOJI } from '../core/types.js';
|
|
4
4
|
import { initOpenCode, createSession, sendMessage, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession, listProviders, updateGlobalModel } from '../opencode/client.js';
|
|
5
5
|
import { claimOwnership } from '../core/auth.js';
|
|
6
|
-
import { COMMAND_ALIASES, detectCommand } from '../core/router.js';
|
|
6
|
+
import { COMMAND_ALIASES, detectCommand, getHelpText } from '../core/router.js';
|
|
7
7
|
import { registry } from '../core/registry.js';
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
9
|
import { join, basename } from 'path';
|
|
@@ -76,40 +76,7 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
76
76
|
return true;
|
|
77
77
|
}
|
|
78
78
|
case 'help':
|
|
79
|
-
await adapter.reply(ctx.threadId,
|
|
80
|
-
|
|
81
|
-
/start — 首次认证
|
|
82
|
-
/help h ? — 帮助
|
|
83
|
-
/status — 连接状态
|
|
84
|
-
/reset — 清空会话
|
|
85
|
-
/restart — 重启 bot
|
|
86
|
-
/stop — 停止 bot
|
|
87
|
-
/retry — 重试连接
|
|
88
|
-
/approve .a .y .1 — 同意变更
|
|
89
|
-
/reject .r .n .0 — 拒绝变更
|
|
90
|
-
/diff — 查看变更
|
|
91
|
-
/files — 已修改文件
|
|
92
|
-
/sessions — 浏览会话
|
|
93
|
-
/delsessions — 删除会话
|
|
94
|
-
/loop — 循环任务
|
|
95
|
-
/summary — 会话摘要
|
|
96
|
-
/compact — 压缩会话上下文
|
|
97
|
-
/copy — 复制最新 AI 回复
|
|
98
|
-
/revert — 撤销 AI 回复
|
|
99
|
-
/switchdir — 切换项目目录
|
|
100
|
-
/scope — 设置上下文范围
|
|
101
|
-
/analyze — 分析后执行
|
|
102
|
-
/commit — 生成提交信息
|
|
103
|
-
/review — 代码审查
|
|
104
|
-
/flush — 刷新记忆
|
|
105
|
-
|
|
106
|
-
🤖 AI 模型:
|
|
107
|
-
/model — 切换模型
|
|
108
|
-
/agents — 查看可用 Agent
|
|
109
|
-
/oc — 使用 OpenCode
|
|
110
|
-
/cc — 使用 Claude Code
|
|
111
|
-
|
|
112
|
-
💬 直接发消息给 AI!`);
|
|
79
|
+
await adapter.reply(ctx.threadId, getHelpText());
|
|
113
80
|
return true;
|
|
114
81
|
case 'agents': {
|
|
115
82
|
const agents = registry.listAgents();
|