@cxyhhhhh/dsh-qqbot 0.1.0-dev.202608141525
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 +198 -0
- package/cordis.patch.yml +24 -0
- package/cordis.yml +69 -0
- package/dist/chunker.d.ts +14 -0
- package/dist/chunker.d.ts.map +1 -0
- package/dist/chunker.js +83 -0
- package/dist/chunker.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/inbound.d.ts +8 -0
- package/dist/inbound.d.ts.map +1 -0
- package/dist/inbound.js +71 -0
- package/dist/inbound.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/mention.d.ts +15 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +25 -0
- package/dist/mention.js.map +1 -0
- package/dist/outbound.d.ts +28 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +162 -0
- package/dist/outbound.js.map +1 -0
- package/dist/session-manager.d.ts +106 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +201 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/install.sh +108 -0
- package/package.json +70 -0
- package/pnpm-workspace.yaml +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @tencent-connect/dsh-qqbot
|
|
2
|
+
|
|
3
|
+
基于 [deepseek-harness](../deepseek-harness) (dsh) 的 QQ Bot IM 插件,将 QQ 消息平台作为 dsh agent 的前端协议驱动。
|
|
4
|
+
|
|
5
|
+
## 架构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
QQ 用户 → QQ WebSocket → dsh-im-qqbot → ctx.agents → dsh agent loop → LLM
|
|
9
|
+
↑ │
|
|
10
|
+
└── session/event ──────────┘
|
|
11
|
+
(assistant reply → QQ sendMarkdown)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
### 方式一:一键安装脚本
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 安装默认包最新版本
|
|
20
|
+
sh install.sh
|
|
21
|
+
|
|
22
|
+
# 指定版本
|
|
23
|
+
sh install.sh --version 0.2.0
|
|
24
|
+
|
|
25
|
+
# 指定包名
|
|
26
|
+
sh install.sh --pkg @myorg/my-qqbot-plugin
|
|
27
|
+
|
|
28
|
+
# 指定包名 + 版本
|
|
29
|
+
sh install.sh --pkg @myorg/my-qqbot-plugin --version 1.0.0
|
|
30
|
+
|
|
31
|
+
# 指定 npm registry
|
|
32
|
+
sh install.sh --registry https://registry.npmmirror.com
|
|
33
|
+
|
|
34
|
+
# 查看帮助
|
|
35
|
+
sh install.sh --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
脚本自动完成 dsh/pnpm 预检 + profile 初始化 + 插件注册。
|
|
39
|
+
|
|
40
|
+
### 方式二:手动执行
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 安装到 profile
|
|
44
|
+
dsh plugin --profile qqbot add @tencent-connect/dsh-qqbot
|
|
45
|
+
|
|
46
|
+
# 配置环境变量
|
|
47
|
+
export QQBOT_APPID="你的AppID"
|
|
48
|
+
export QQBOT_SECRET="你的AppSecret"
|
|
49
|
+
|
|
50
|
+
# 启动
|
|
51
|
+
dsh --profile qqbot
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 方式三:本地路径安装
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 构建
|
|
58
|
+
cd /path/to/dsh-qqbot
|
|
59
|
+
pnpm install && pnpm build
|
|
60
|
+
|
|
61
|
+
# 安装到 profile(本地路径)
|
|
62
|
+
dsh plugin --profile qqbot add /path/to/dsh-qqbot
|
|
63
|
+
|
|
64
|
+
# 启动
|
|
65
|
+
export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
|
|
66
|
+
dsh --profile qqbot
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 方式三:--patch 开发模式
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd /path/to/deepseek-harness
|
|
73
|
+
export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
|
|
74
|
+
pnpm dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 配置项
|
|
78
|
+
|
|
79
|
+
| 配置 | 类型 | 默认值 | 说明 |
|
|
80
|
+
|------|------|--------|------|
|
|
81
|
+
| `appId` | string | **必填** | QQ Bot AppID(或通过 `QQBOT_APPID` 环境变量) |
|
|
82
|
+
| `appSecret` | string | **必填** | QQ Bot AppSecret(或通过 `QQBOT_SECRET` 环境变量) |
|
|
83
|
+
| `provider` | string | `deepseek-official` | LLM 提供商名称 |
|
|
84
|
+
| `model` | string | `deepseek-chat` | 模型名称 |
|
|
85
|
+
| `preset` | string | - | Agent preset id |
|
|
86
|
+
| `cwd` | string | `process.cwd()` | Agent 工作目录 |
|
|
87
|
+
| `requireMention` | boolean | `true` | 群聊是否需要 @bot 才触发 |
|
|
88
|
+
| `groupPrompt` | string | - | 群聊额外 system prompt |
|
|
89
|
+
| `directPrompt` | string | - | 私聊额外 system prompt |
|
|
90
|
+
| `textChunkLimit` | number | `4500` | 单条消息最大字符数 |
|
|
91
|
+
| `sessionIdleTimeout` | number | `1800000` | 会话闲置超时(ms),默认 30 分钟 |
|
|
92
|
+
| `debug` | boolean | `false` | 调试模式 |
|
|
93
|
+
|
|
94
|
+
## 内置命令
|
|
95
|
+
|
|
96
|
+
| 命令 | 说明 |
|
|
97
|
+
|------|------|
|
|
98
|
+
| `/reset` | 重置当前会话(清除上下文) |
|
|
99
|
+
| `/clear` | 同 `/reset` |
|
|
100
|
+
|
|
101
|
+
## 核心模块
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
src/
|
|
105
|
+
├── index.ts # Cordis 插件入口(async apply)
|
|
106
|
+
├── config.ts # 配置 Schema
|
|
107
|
+
├── session-manager.ts # QQ peer → Agent 映射(get → resume → create)
|
|
108
|
+
├── inbound.ts # QQ 入站消息 → agent.followup()
|
|
109
|
+
├── outbound.ts # session/event → QQ sendMarkdown
|
|
110
|
+
├── mention.ts # @mention 检测/清理
|
|
111
|
+
├── chunker.ts # Markdown 文本切分(代码块/表格感知)
|
|
112
|
+
└── types.ts # 内部类型定义
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 会话路由
|
|
116
|
+
|
|
117
|
+
sessionKey 格式: `qqbot:${appId}:${kind}:${peerId}`
|
|
118
|
+
|
|
119
|
+
- c2c (私聊): `qqbot:102901613:c2c:B9CE1DEEC46B...`
|
|
120
|
+
- group (群聊): `qqbot:102901613:group:16FA861B01EE...`
|
|
121
|
+
|
|
122
|
+
SessionId 由 sessionKey 确定性派生(SHA-256),保证同一用户/群的消息始终路由到同一 agent,重启后可恢复会话。
|
|
123
|
+
|
|
124
|
+
会话解析策略(三级降级):
|
|
125
|
+
1. **进程内复用** — `agents.get(sessionId)`
|
|
126
|
+
2. **持久化恢复** — `agents.resume({ resumeSessionId, setup })`
|
|
127
|
+
3. **全新创建** — `agents.create({ sessionId, meta, setup })`
|
|
128
|
+
|
|
129
|
+
## 设计原则
|
|
130
|
+
|
|
131
|
+
- **纯 Cordis 插件** — 遵循 dsh "Plugins, not loop changes" 原则
|
|
132
|
+
- **声明式依赖** — `inject = ['agents']`,不直接耦合其他插件
|
|
133
|
+
- **会话隔离** — 每个 QQ 私聊用户/群聊各一个独立 Agent
|
|
134
|
+
- **Preset 支持** — 可通过 `agent-presets` 服务挂载预设(工具集、prompt 等)
|
|
135
|
+
- **闲置回收** — 超时自动 dispose Agent,防止内存泄漏
|
|
136
|
+
- **Markdown 输出** — 回复以 Markdown 格式发送,支持代码块/表格感知切分
|
|
137
|
+
|
|
138
|
+
## 本地开发
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# 安装依赖
|
|
142
|
+
pnpm install
|
|
143
|
+
|
|
144
|
+
# 构建
|
|
145
|
+
pnpm build
|
|
146
|
+
|
|
147
|
+
# 开发模式(watch)
|
|
148
|
+
pnpm dev
|
|
149
|
+
|
|
150
|
+
# 用 --patch 方式调试(在 dsh monorepo 中)
|
|
151
|
+
cd /path/to/deepseek-harness
|
|
152
|
+
export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
|
|
153
|
+
pnpm dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 断点调试
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
node --inspect ./node_modules/.bin/dsh --profile headless \
|
|
160
|
+
--patch /path/to/dsh-qqbot/cordis.dev.yml
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### VS Code launch.json
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"version": "0.2.0",
|
|
168
|
+
"configurations": [
|
|
169
|
+
{
|
|
170
|
+
"name": "dsh + qqbot (patch)",
|
|
171
|
+
"type": "node",
|
|
172
|
+
"request": "launch",
|
|
173
|
+
"cwd": "/path/to/deepseek-harness",
|
|
174
|
+
"runtimeExecutable": "pnpm",
|
|
175
|
+
"runtimeArgs": ["dsh", "web", "--patch", "/path/to/dsh-qqbot/cordis.dev.yml"],
|
|
176
|
+
"env": {
|
|
177
|
+
"QQBOT_APPID": "你的AppID",
|
|
178
|
+
"QQBOT_SECRET": "你的AppSecret",
|
|
179
|
+
"DEEPSEEK_API_KEY": "sk-xxx"
|
|
180
|
+
},
|
|
181
|
+
"console": "integratedTerminal"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 发布
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pnpm build
|
|
191
|
+
npm publish --access public
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
安装后用户通过以下命令添加到 dsh:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
dsh plugin --profile qqbot add @tencent-connect/dsh-qqbot
|
|
198
|
+
```
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# dsh-im-qqbot bundle patch
|
|
2
|
+
#
|
|
3
|
+
# 当本包通过 `dsh plugin --profile <name> add @tencent-connect/dsh-qqbot` 安装后,
|
|
4
|
+
# dsh 启动时自动加载此 patch。
|
|
5
|
+
#
|
|
6
|
+
# 环境变量:
|
|
7
|
+
# QQBOT_APPID - QQ Bot AppID(必填)
|
|
8
|
+
# QQBOT_SECRET - QQ Bot AppSecret(必填)
|
|
9
|
+
#
|
|
10
|
+
# 叠加顺序:dsh-base → 本 bundle patch → profile cordis.patch.yml → --patch
|
|
11
|
+
|
|
12
|
+
# ── 插入 im-qqbot 插件行 ──
|
|
13
|
+
- insert:
|
|
14
|
+
- id: im-qqbot
|
|
15
|
+
name: '@cxyhhhhh/dsh-qqbot'
|
|
16
|
+
config:
|
|
17
|
+
appId: __FROM_ENV__
|
|
18
|
+
appSecret: __FROM_ENV__
|
|
19
|
+
provider: deepseek-official
|
|
20
|
+
model: deepseek-chat
|
|
21
|
+
requireMention: true
|
|
22
|
+
textChunkLimit: 4500
|
|
23
|
+
sessionIdleTimeout: 1800000
|
|
24
|
+
debug: false
|
package/cordis.yml
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# dsh-im-qqbot 组合配置示例
|
|
2
|
+
#
|
|
3
|
+
# 运行: pnpm dsh --config ./cordis.yml
|
|
4
|
+
#
|
|
5
|
+
# 环境变量:
|
|
6
|
+
# QQBOT_APPID - QQ Bot AppID
|
|
7
|
+
# QQBOT_SECRET - QQ Bot AppSecret
|
|
8
|
+
# DEEPSEEK_API_KEY - DeepSeek API Key
|
|
9
|
+
# DSH_CWD - Agent 工作目录(可选)
|
|
10
|
+
|
|
11
|
+
# ── 核心 spine ──
|
|
12
|
+
- id: sessions
|
|
13
|
+
name: '@deepseek-ai/dsh-session'
|
|
14
|
+
|
|
15
|
+
- id: agent
|
|
16
|
+
name: '@deepseek-ai/dsh-agent'
|
|
17
|
+
|
|
18
|
+
- id: agent-loop
|
|
19
|
+
name: '@deepseek-ai/dsh-agent-loop'
|
|
20
|
+
|
|
21
|
+
- id: system-prompt
|
|
22
|
+
name: '@deepseek-ai/dsh-system-prompt'
|
|
23
|
+
|
|
24
|
+
- id: tools
|
|
25
|
+
name: '@deepseek-ai/dsh-tools'
|
|
26
|
+
|
|
27
|
+
# ── LLM 适配器 ──
|
|
28
|
+
- id: llm-deepseek
|
|
29
|
+
name: '@deepseek-ai/dsh-llm-deepseek'
|
|
30
|
+
config:
|
|
31
|
+
apiKey: ${DEEPSEEK_API_KEY}
|
|
32
|
+
|
|
33
|
+
# ── 执行能力(按需启用) ──
|
|
34
|
+
- id: tool-bash
|
|
35
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
36
|
+
|
|
37
|
+
- id: tool-fs
|
|
38
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
39
|
+
|
|
40
|
+
- id: tool-web
|
|
41
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
42
|
+
|
|
43
|
+
# ── 上下文管理 ──
|
|
44
|
+
- id: compaction
|
|
45
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
46
|
+
|
|
47
|
+
- id: token-meter
|
|
48
|
+
name: '@deepseek-ai/dsh-token-meter'
|
|
49
|
+
|
|
50
|
+
# ── 持久化 ──
|
|
51
|
+
- id: session-persistence
|
|
52
|
+
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
53
|
+
config:
|
|
54
|
+
root: ./.dsh-sessions
|
|
55
|
+
|
|
56
|
+
# ── QQ Bot IM 驱动 ──
|
|
57
|
+
- id: im-qqbot
|
|
58
|
+
name: '@deepseek-ai/dsh-im-qqbot'
|
|
59
|
+
config:
|
|
60
|
+
appId: ${QQBOT_APPID}
|
|
61
|
+
appSecret: ${QQBOT_SECRET}
|
|
62
|
+
transport: websocket
|
|
63
|
+
provider: deepseek
|
|
64
|
+
model: deepseek-chat
|
|
65
|
+
cwd: ${DSH_CWD:-/tmp/dsh-qqbot}
|
|
66
|
+
requireMention: false
|
|
67
|
+
textChunkLimit: 4500
|
|
68
|
+
sessionIdleTimeout: 1800000
|
|
69
|
+
debug: false
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown 文本切分器
|
|
3
|
+
*
|
|
4
|
+
* QQ 单条消息有字符数限制(约 5000),需要在合适边界切分,
|
|
5
|
+
* 并保持 GFM 表格、代码块的完整性。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 按换行边界切分 Markdown 文本
|
|
9
|
+
* - 不在代码块中间断开
|
|
10
|
+
* - 不在 GFM 表格中间断开
|
|
11
|
+
* - 优先在空行处断开
|
|
12
|
+
*/
|
|
13
|
+
export declare function chunkMarkdownText(text: string, limit: number): string[];
|
|
14
|
+
//# sourceMappingURL=chunker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.d.ts","sourceRoot":"","sources":["../src/chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAyEvE"}
|
package/dist/chunker.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown 文本切分器
|
|
3
|
+
*
|
|
4
|
+
* QQ 单条消息有字符数限制(约 5000),需要在合适边界切分,
|
|
5
|
+
* 并保持 GFM 表格、代码块的完整性。
|
|
6
|
+
*/
|
|
7
|
+
const CODE_FENCE_OPEN = /^```/;
|
|
8
|
+
const GFM_TABLE_LINE = /^\|.+\|$/;
|
|
9
|
+
/**
|
|
10
|
+
* 按换行边界切分 Markdown 文本
|
|
11
|
+
* - 不在代码块中间断开
|
|
12
|
+
* - 不在 GFM 表格中间断开
|
|
13
|
+
* - 优先在空行处断开
|
|
14
|
+
*/
|
|
15
|
+
export function chunkMarkdownText(text, limit) {
|
|
16
|
+
if (text.length <= limit)
|
|
17
|
+
return [text];
|
|
18
|
+
const lines = text.split('\n');
|
|
19
|
+
const chunks = [];
|
|
20
|
+
let current = '';
|
|
21
|
+
let inCodeBlock = false;
|
|
22
|
+
let tableBuffer = [];
|
|
23
|
+
const flushTable = () => {
|
|
24
|
+
if (tableBuffer.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
const block = tableBuffer.join('\n');
|
|
27
|
+
tableBuffer = [];
|
|
28
|
+
if (!current) {
|
|
29
|
+
current = block;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const candidate = `${current}\n${block}`;
|
|
33
|
+
if (candidate.length > limit) {
|
|
34
|
+
chunks.push(current);
|
|
35
|
+
current = block;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
current = candidate;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const appendLine = (line) => {
|
|
42
|
+
if (!current) {
|
|
43
|
+
current = line;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const candidate = `${current}\n${line}`;
|
|
47
|
+
if (candidate.length > limit) {
|
|
48
|
+
chunks.push(current);
|
|
49
|
+
current = line;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
current = candidate;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
// 追踪代码块状态
|
|
57
|
+
if (CODE_FENCE_OPEN.test(line)) {
|
|
58
|
+
flushTable();
|
|
59
|
+
inCodeBlock = !inCodeBlock;
|
|
60
|
+
appendLine(line);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// 代码块内部不切分
|
|
64
|
+
if (inCodeBlock) {
|
|
65
|
+
appendLine(line);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// GFM 表格行收集
|
|
69
|
+
if (GFM_TABLE_LINE.test(line)) {
|
|
70
|
+
tableBuffer.push(line);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// 非表格行,先 flush 积累的表格
|
|
74
|
+
flushTable();
|
|
75
|
+
appendLine(line);
|
|
76
|
+
}
|
|
77
|
+
// 处理剩余
|
|
78
|
+
flushTable();
|
|
79
|
+
if (current)
|
|
80
|
+
chunks.push(current);
|
|
81
|
+
return chunks.length > 0 ? chunks : [text];
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=chunker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.js","sourceRoot":"","sources":["../src/chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,WAAW,GAAa,EAAE,CAAC;IAE/B,MAAM,UAAU,GAAG,GAAS,EAAE;QAC5B,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACrC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,WAAW,GAAG,EAAE,CAAC;QAEjB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,KAAK,CAAC;YAChB,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,OAAO,KAAK,KAAK,EAAE,CAAC;QACzC,IAAI,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAQ,EAAE;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,IAAI,CAAC;YACf,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,OAAO,KAAK,IAAI,EAAE,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,UAAU;QACV,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,UAAU,EAAE,CAAC;YACb,WAAW,GAAG,CAAC,WAAW,CAAC;YAC3B,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QAED,WAAW;QACX,IAAI,WAAW,EAAE,CAAC;YAChB,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QAED,YAAY;QACZ,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QAED,qBAAqB;QACrB,UAAU,EAAE,CAAC;QACb,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,OAAO;IACP,UAAU,EAAE,CAAC;IACb,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-im-qqbot 插件配置 Schema
|
|
3
|
+
*/
|
|
4
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
5
|
+
export interface ImQQBotConfig {
|
|
6
|
+
/** QQ Bot AppID */
|
|
7
|
+
appId: string;
|
|
8
|
+
/** QQ Bot AppSecret */
|
|
9
|
+
appSecret: string;
|
|
10
|
+
/** dsh LLM 提供商名称 */
|
|
11
|
+
provider?: string;
|
|
12
|
+
/** 模型名称 */
|
|
13
|
+
model?: string;
|
|
14
|
+
/** Agent preset id */
|
|
15
|
+
preset?: string;
|
|
16
|
+
/** Agent 工作目录 */
|
|
17
|
+
cwd: string;
|
|
18
|
+
/** 是否启用群消息 @mention 门控 */
|
|
19
|
+
requireMention: boolean;
|
|
20
|
+
/** 群聊额外 system prompt */
|
|
21
|
+
groupPrompt?: string;
|
|
22
|
+
/** 私聊额外 system prompt */
|
|
23
|
+
directPrompt?: string;
|
|
24
|
+
/** 单条消息最大长度(QQ 限制约 5000 字符) */
|
|
25
|
+
textChunkLimit: number;
|
|
26
|
+
/** 每会话最大闲置时长(ms),超时自动回收 */
|
|
27
|
+
sessionIdleTimeout: number;
|
|
28
|
+
/** 调试模式 */
|
|
29
|
+
debug: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare const ConfigSchema: Schema<ImQQBotConfig>;
|
|
32
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW;IACX,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,CAa7C,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-im-qqbot 插件配置 Schema
|
|
3
|
+
*/
|
|
4
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
5
|
+
export const ConfigSchema = Schema.object({
|
|
6
|
+
appId: Schema.string().default('').description('QQ Bot AppID'),
|
|
7
|
+
appSecret: Schema.string().default('').description('QQ Bot AppSecret'),
|
|
8
|
+
provider: Schema.string().description('LLM provider name'),
|
|
9
|
+
model: Schema.string().description('Model name'),
|
|
10
|
+
preset: Schema.string().description('Agent preset id'),
|
|
11
|
+
cwd: Schema.string().default('/').description('Agent working directory'),
|
|
12
|
+
requireMention: Schema.boolean().default(true).description('群聊是否需要@bot触发'),
|
|
13
|
+
groupPrompt: Schema.string().description('群聊额外system prompt'),
|
|
14
|
+
directPrompt: Schema.string().description('私聊额外system prompt'),
|
|
15
|
+
textChunkLimit: Schema.number().default(4500).description('单条消息最大字符数'),
|
|
16
|
+
sessionIdleTimeout: Schema.number().default(30 * 60 * 1000).description('会话闲置超时(ms)'),
|
|
17
|
+
debug: Schema.boolean().default(false),
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AA6B9C,MAAM,CAAC,MAAM,YAAY,GAA0B,MAAM,CAAC,MAAM,CAAC;IAC/D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,kBAAkB,CAAC;IACtE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,mBAAmB,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;IAChD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,iBAAiB,CAAC;IACtD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,yBAAyB,CAAC;IACxE,cAAc,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC;IAC1E,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,mBAAmB,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,mBAAmB,CAAC;IAC9D,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC;IACtE,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC;IACrF,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACvC,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionManager } from './session-manager.js';
|
|
2
|
+
import type { ImQQBotConfig } from './config.js';
|
|
3
|
+
import type { Logger } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* 处理 QQ 入站消息
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleInbound(rawMsg: unknown, manager: SessionManager, config: ImQQBotConfig, logger: Logger): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAa,MAAM,EAAe,MAAM,YAAY,CAAC;AAkBjE;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAuDf"}
|
package/dist/inbound.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { detectMention, stripMention } from './mention.js';
|
|
3
|
+
/**
|
|
4
|
+
* 处理 QQ 入站消息
|
|
5
|
+
*/
|
|
6
|
+
export async function handleInbound(rawMsg, manager, config, logger) {
|
|
7
|
+
const msg = rawMsg;
|
|
8
|
+
// 从 SDK 消息中提取关键字段
|
|
9
|
+
const scope = msg.kind === 'group' ? 'group' : 'c2c';
|
|
10
|
+
const peerId = scope === 'group' ? (msg.groupOpenid ?? msg.senderId) : msg.senderId;
|
|
11
|
+
// 构造 replyTarget(用于出站回复)
|
|
12
|
+
const replyTarget = {
|
|
13
|
+
scope,
|
|
14
|
+
targetId: peerId,
|
|
15
|
+
msgId: msg.messageId,
|
|
16
|
+
};
|
|
17
|
+
console.log(`[im-qqbot] Processing: scope=${scope} peerId=${peerId} content="${msg.content}"`);
|
|
18
|
+
// ── 群消息 @mention 门控 ──
|
|
19
|
+
if (scope === 'group' && config.requireMention) {
|
|
20
|
+
if (!detectMention(msg.content)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ── 提取并清理文本 ──
|
|
25
|
+
let text = msg.content?.trim() ?? '';
|
|
26
|
+
if (scope === 'group') {
|
|
27
|
+
text = stripMention(text);
|
|
28
|
+
}
|
|
29
|
+
if (!text)
|
|
30
|
+
return;
|
|
31
|
+
// ── 命令处理 ──
|
|
32
|
+
if (text.startsWith('/')) {
|
|
33
|
+
const handled = handleCommand(text, scope, peerId, manager, logger);
|
|
34
|
+
if (handled)
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// ── 获取或创建会话 ──
|
|
38
|
+
let record;
|
|
39
|
+
try {
|
|
40
|
+
record = await manager.getOrCreate(scope, peerId, msg.senderId, replyTarget);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(`[im-qqbot] ERROR creating session: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// ── 构建 UserMessage → followup ──
|
|
47
|
+
const content = [{ type: 'text', text }];
|
|
48
|
+
const message = createUserMessage({
|
|
49
|
+
content,
|
|
50
|
+
source: { kind: 'user' },
|
|
51
|
+
});
|
|
52
|
+
record.agent.followup(message);
|
|
53
|
+
console.log(`[im-qqbot] → followup sent: key=${scope}:${peerId} text="${text}"`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 处理内置命令
|
|
57
|
+
*/
|
|
58
|
+
function handleCommand(text, scope, peerId, manager, _logger) {
|
|
59
|
+
const cmd = text.split(/\s+/)[0]?.toLowerCase();
|
|
60
|
+
switch (cmd) {
|
|
61
|
+
case '/reset':
|
|
62
|
+
case '/clear': {
|
|
63
|
+
void manager.remove(scope, peerId);
|
|
64
|
+
console.log(`[im-qqbot] Session reset: key=${scope}:${peerId}`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-im-qqbot — QQ Bot IM channel plugin for deepseek-harness
|
|
3
|
+
*
|
|
4
|
+
* Cordis 插件入口。将 QQ 消息平台作为 dsh 的前端协议驱动。
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { type ImQQBotConfig } from './config.js';
|
|
8
|
+
export declare const name = "im-qqbot";
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
export declare const Config: import("@deepseek-ai/schemastery").default<ImQQBotConfig>;
|
|
11
|
+
export type { ImQQBotConfig } from './config.js';
|
|
12
|
+
export declare function apply(ctx: Context, config: ImQQBotConfig): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAa/D,eAAO,MAAM,IAAI,aAAa,CAAC;AAC/B,eAAO,MAAM,MAAM,UAAa,CAAC;AACjC,eAAO,MAAM,MAAM,2DAAe,CAAC;AAEnC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { ConfigSchema } from './config.js';
|
|
4
|
+
import { SessionManager } from './session-manager.js';
|
|
5
|
+
import { handleInbound } from './inbound.js';
|
|
6
|
+
import { createOutboundHandler } from './outbound.js';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const PLUGIN_ROOT = resolve(__dirname, '..');
|
|
10
|
+
console.log('[im-qqbot] module loaded, plugin root:', PLUGIN_ROOT);
|
|
11
|
+
// ── Cordis 插件元数据 ──
|
|
12
|
+
export const name = 'im-qqbot';
|
|
13
|
+
export const inject = ['agents'];
|
|
14
|
+
export const Config = ConfigSchema;
|
|
15
|
+
// ── 插件主体 ──
|
|
16
|
+
export async function apply(ctx, config) {
|
|
17
|
+
const agents = ctx.agents;
|
|
18
|
+
const logger = ctx.logger ?? console;
|
|
19
|
+
console.log('[im-qqbot] apply() called');
|
|
20
|
+
const appId = resolveEnv(config.appId, 'QQBOT_APPID');
|
|
21
|
+
const appSecret = resolveEnv(config.appSecret, 'QQBOT_SECRET');
|
|
22
|
+
if (!appId || !appSecret) {
|
|
23
|
+
console.error('[im-qqbot] ERROR: Missing QQBOT_APPID or QQBOT_SECRET');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const resolvedConfig = { ...config, appId, appSecret };
|
|
27
|
+
await bootstrap(ctx, agents, resolvedConfig, logger);
|
|
28
|
+
}
|
|
29
|
+
async function bootstrap(ctx, agents, config, logger) {
|
|
30
|
+
const manager = new SessionManager(ctx, agents, config, logger);
|
|
31
|
+
// ── 动态 import QQ Bot SDK(纯 ESM 包,必须 import() 不能 require) ──
|
|
32
|
+
let bot;
|
|
33
|
+
try {
|
|
34
|
+
// 用绝对路径 import 插件自身 node_modules 中的包
|
|
35
|
+
const sdkPath = resolve(PLUGIN_ROOT, 'node_modules/@tencent-connect/qqbot-nodejs/dist/index.js');
|
|
36
|
+
const mod = await import(sdkPath);
|
|
37
|
+
const QQBot = mod.QQBot ?? mod.default?.QQBot ?? mod.default;
|
|
38
|
+
bot = new QQBot({
|
|
39
|
+
appId: config.appId,
|
|
40
|
+
appSecret: config.appSecret,
|
|
41
|
+
transport: 'websocket',
|
|
42
|
+
logger,
|
|
43
|
+
});
|
|
44
|
+
console.log('[im-qqbot] QQBot SDK loaded');
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48
|
+
console.error(`[im-qqbot] ERROR: Cannot load QQBot SDK: ${msg}`);
|
|
49
|
+
console.error(`[im-qqbot] TIP: cd ${PLUGIN_ROOT} && pnpm install && pnpm -C node_modules/@tencent-connect/qqbot-nodejs build`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// ── 入站 ──
|
|
53
|
+
bot.on('message', async (_mqCtx, msg) => {
|
|
54
|
+
console.log('[im-qqbot] ← message received:', JSON.stringify(msg, null, 2).slice(0, 500));
|
|
55
|
+
await handleInbound(msg, manager, config, logger);
|
|
56
|
+
});
|
|
57
|
+
// ── 出站 ──
|
|
58
|
+
const outboundHandler = createOutboundHandler(manager, bot, config, logger);
|
|
59
|
+
ctx
|
|
60
|
+
.on('session/event', outboundHandler);
|
|
61
|
+
bot.on('error', (err) => {
|
|
62
|
+
logger.error(`im-qqbot: bot error: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
});
|
|
64
|
+
bot.on('ready', () => {
|
|
65
|
+
console.log(`[im-qqbot] Bot ready! appId=${config.appId}`);
|
|
66
|
+
});
|
|
67
|
+
// ── 生命周期 ──
|
|
68
|
+
ctx
|
|
69
|
+
.effect(() => {
|
|
70
|
+
console.log(`[im-qqbot] Starting bot (appId=${config.appId})`);
|
|
71
|
+
void bot.start();
|
|
72
|
+
return async () => {
|
|
73
|
+
console.log('[im-qqbot] Shutting down');
|
|
74
|
+
await manager.disposeAll();
|
|
75
|
+
bot.stop();
|
|
76
|
+
};
|
|
77
|
+
}, 'im-qqbot.lifecycle');
|
|
78
|
+
}
|
|
79
|
+
function resolveEnv(configValue, envKey) {
|
|
80
|
+
if (configValue && configValue !== '__FROM_ENV__' && !configValue.startsWith('process.env')) {
|
|
81
|
+
return configValue;
|
|
82
|
+
}
|
|
83
|
+
return process.env[envKey] ?? '';
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|