@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 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
+ ```
@@ -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"}
@@ -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"}
@@ -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"}
@@ -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":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIzD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAiB3D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAe,EACf,OAAuB,EACvB,MAAqB,EACrB,MAAc;IAEd,MAAM,GAAG,GAAG,MAAoB,CAAC;IAEjC,kBAAkB;IAClB,MAAM,KAAK,GAAc,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,MAAM,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;IAEpF,yBAAyB;IACzB,MAAM,WAAW,GAAgB;QAC/B,KAAK;QACL,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,GAAG,CAAC,SAAS;KACrB,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,gCAAgC,KAAK,WAAW,MAAM,aAAa,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;IAE/F,wBAAwB;IACxB,IAAI,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC/C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,IAAI,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,aAAa;IACb,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACpE,IAAI,OAAO;YAAE,OAAO;IACtB,CAAC;IAED,gBAAgB;IAChB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,sCAAsC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxG,OAAO;IACT,CAAC;IAED,kCAAkC;IAClC,MAAM,OAAO,GAAmB,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,iBAAiB,CAAC;QAChC,OAAO;QACP,MAAM,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE;KAClC,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,mCAAmC,KAAK,IAAI,MAAM,UAAU,IAAI,GAAG,CAAC,CAAC;AACnF,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CACpB,IAAY,EACZ,KAAgB,EAChB,MAAc,EACd,OAAuB,EACvB,OAAe;IAEf,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAEhD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,iCAAiC,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;QACD;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -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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"}