@mhfire/dsh-im-bridge 0.1.7 → 0.3.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.en.md CHANGED
@@ -4,7 +4,34 @@
4
4
 
5
5
  WeCom AI Bot ⇄ DeepSeek Harness Agent bridge — a **DSH plugin**.
6
6
 
7
- Creates Agents **in-process** inside a dsh profile (no child-process spawn): per-sender durable sessions (the same WeCom user reuses one session with memory), sessions registered with the Web GUI (live view and continue-chat), plus a Settings → Plugins card (`allowFrom` / `agentTimeoutSec` / `startHint`, written to `settings.yaml` with hot reload).
7
+ Creates Agents **in-process** inside a dsh profile (no child-process spawn): one durable DSH session **per WeCom chat window** (1:1 = that user; everyone in the same group shares one session a person’s DM and group chats stay separate), sessions registered with the Web GUI (live view and continue-chat), plus a Settings → Plugins card (`botId` / `secret`, allow-list, timeouts, copy, and model overrides, written to `settings.yaml`).
8
+
9
+ The Host half registers the `im-bridge` namespace through `installSettingsSection`. The browser half contributes a card into `settings.plugin.item` under `key: im-bridge`. The card can set `botId` / `secret` on the same user layer as the profile patch. Live fields such as `startHint` apply on the next message; changing credentials still requires a process restart to open the WebSocket.
10
+
11
+ ## Session granularity
12
+
13
+ One DSH session maps to one WeCom chat window, not “every window of the same userid”:
14
+
15
+ - **1:1**: `single:<userid>` — one Agent for that user
16
+ - **Group**: `group:<chatid>` — all members share one Agent and one serial queue (two people speaking at once still cannot concurrent-`followup`)
17
+ - `allowFrom` still filters by **sender** userid; rejected senders are not enqueued
18
+ - Leading `@nickname` mentions are stripped on arrival, so both the model input and the title read `测试一下` instead of `@MediaAgent 测试一下`. Mentions later in the text stay, and a message that is nothing but mentions goes to the model unchanged
19
+ - Process restart continues the same durable session via a stable `wecom-` + short hash of the key: adopt a live Agent if the process already has one (for example the browser already opened that row), `resume` from persistence otherwise, and `create` only when neither exists. Resume cwd / preset follow the archive, not the current config; a cwd mismatch logs a warning and is not rewritten.
20
+ - The GUI title is `企微·私聊` / `企微·群` plus the first user prompt (the same automatic title as other sessions), not a userid / chatid; two windows of the same kind with similar first lines stay two sidebar rows
21
+ - Archiving a WeCom session in the GUI ends that context: the next message silently starts a new session at `wecom-<hash>-2` (`-3` after another archive), with no extra WeCom notice. DSH has no unarchive API, so the archived session is merely no longer written to — it neither becomes visible again nor is deleted
22
+ - Every window still shares the same `workspace` (files / rag). That is environment isolation, separate from chat-context windows
23
+
24
+ ## Compatible DeepSeek Harness versions
25
+
26
+ DeepSeek Harness is still a developer preview and makes **no semver compatibility promise** to out-of-tree plugins. Package 0.3.0 is aligned to published tags by the APIs it actually calls:
27
+
28
+ | DSH | This package |
29
+ |---|---|
30
+ | [0.1.0-rc.8](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.0-rc.8), [0.1.1-rc.1](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.1-rc.1), [0.1.1-rc.2](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.1-rc.2) | Compatible (developed against the 0.1.1-rc.2 line) |
31
+ | [0.1.0-rc.7](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.0-rc.7) and earlier | Not compatible. rc.7 already has the plugin settings-card slot and `dsh plugin add`, but the browser has no `settingsScope.describe()`, so the card and the credential “Configured” badges fail |
32
+ | Newer RCs / untagged HEAD | Not guaranteed. After upgrading dsh, re-check the Settings card and the WeCom connection |
33
+
34
+ Pin `dsh` to `0.1.0-rc.8` or later, for example `npx @deepseek-ai/dsh@0.1.1-rc.2 web`. Do not rely on a floating `latest`.
8
35
 
9
36
  ## Install
10
37
 
@@ -13,7 +40,7 @@ Creates Agents **in-process** inside a dsh profile (no child-process spawn): per
13
40
  ```powershell
14
41
  dsh plugin --profile web add @mhfire/dsh-im-bridge
15
42
  # Or pin a version:
16
- # dsh plugin --profile web add @mhfire/dsh-im-bridge@0.1.2
43
+ # dsh plugin --profile web add @mhfire/dsh-im-bridge@0.3.0
17
44
  ```
18
45
 
19
46
  In `$DSH_HOME/profiles/web/cordis.patch.yml` (or your profile), supply credentials only (other fields ship as bundle defaults and can be overridden):
@@ -36,7 +63,25 @@ Install from this repo’s `plugin/` directory or a `file:` path:
36
63
  dsh plugin --profile web add <package-path>
37
64
  ```
38
65
 
39
- If `botId` / `secret` are missing, the plugin still loads (does not block `dsh web`); logs warn that WeCom connect is skipped. You can fill credentials in Settings Plugins; **restart** is required to connect (this release does not hot-start the WebSocket).
66
+ If `botId` / `secret` are missing, the plugin still loads (does not block `dsh web`); logs warn that WeCom connect is skipped. You can also fill them on the Settings plugin card (see the next section).
67
+
68
+ ## Settings plugin card
69
+
70
+ After the plugin is installed and `dsh web` is running, open **Settings → Plugins → Plugin configuration** and expand **WeCom Bridge** (same card chrome as Shell / Agent loop / Web search). **Save** writes the user layer of `settings.yaml`, the same layer as the profile `cordis.patch.yml`. **Discard** drops unsaved drafts. Fields marked **Overridden** can be **Reset** to the bundle default.
71
+
72
+ Fields, top to bottom:
73
+
74
+ | Card control | Config key | After save |
75
+ |---|---|---|
76
+ | Bot ID / Secret | `botId` / `secret` | Badge becomes “Configured”; a **process restart** is required to open the WebSocket. Inputs are password fields; stored literals never ride the wire. A **blank save does not clear** a stored credential |
77
+ | Allowed sender userids | `allowFrom` | Applies on the next message; comma-separated, empty = allow everyone |
78
+ | Task timeout (seconds) | `agentTimeoutSec` | Applies on the next message |
79
+ | Placeholder while thinking | `startHint` | Applies on the next message |
80
+ | Denied-sender reply | `deniedMessage` | Applies on the next message |
81
+ | Welcome message | `welcomeMessage` | Applies on the next message |
82
+ | WeCom-only provider / model | `provider` / `model` | Applies only to **later new** WeCom-window sessions; both must be set to override, otherwise the GUI default model is used |
83
+
84
+ `workspace`, `agentPreset`, `persona` / `personaFile`, `thinking`, `maxReplyBytes`, and `reasoningEffort` are not on the card; set them in the profile patch or the table below. This release does not hot-reconnect credentials.
40
85
 
41
86
  ## Configuration
42
87
 
@@ -50,12 +95,23 @@ The bundle `cordis.patch.yml` supplies defaults for every field except `botId` /
50
95
  | `agentTimeoutSec` | Max seconds per task; also drives progress / ETA |
51
96
  | `startHint` | Placeholder text when processing starts |
52
97
  | `agentPreset` | Agent preset to mount (default `standard`) |
98
+ | `provider` / `model` | WeCom-only model; **both must be non-empty** to override, otherwise follow the GUI `agent-default-model`. Filling only one warns and falls back. Editable in Settings; applies to later new WeCom-window sessions only |
99
+ | `reasoningEffort` | Optional effort when the WeCom override is in effect; ignored otherwise |
53
100
  | `persona` / `personaFile` | Bot persona; precedence: `personaFile` → `persona` → packaged default (zh/en via Host `locale.preference`); overrides do not follow language; do not commit secrets |
54
101
  | `maxReplyBytes` | Reply size cap in bytes (default 20000) |
55
102
  | `deniedMessage` | Reply when the sender is not on `allowFrom` (editable in Settings) |
56
103
  | `welcomeMessage` | Welcome text on `enter_chat` (editable in Settings) |
57
104
  | `thinking` | Streaming animation. Precedence: tool activity (`toolLabels`) > model stream phase (`reasoningStatus` / `outputStatus` from `assistant/chunk`) > timed `phases` fallback; also `spin` / `reasoningSpin` / `outputSpin` / `eggs` |
58
105
 
106
+ To give WeCom a different model from the GUI, set both in the profile `cordis.patch.yml`:
107
+
108
+ ```yaml
109
+ - id: im-bridge
110
+ config:
111
+ provider: deepseek-official
112
+ model: deepseek-reasoner
113
+ ```
114
+
59
115
  Behavior:
60
116
 
61
117
  1. `reasoning-delta` → rotating “model thinking” copy + `reasoningSpin`
@@ -91,6 +147,23 @@ Precedence: `personaFile` → `persona` string → packaged default persona.
91
147
 
92
148
  Or copy [`persona.example.md`](./persona.example.md) as a fill-in template. Supports `{{model}}` / `{{cwd}}`. Do not commit secret-bearing persona files. Welcome / deny / thinking copy remain Chinese config strings and do not follow locale yet.
93
149
 
150
+ ## Sending PNGs to WeCom
151
+
152
+ If the agent’s final reply contains Markdown pointing at a **workspace** PNG, the bridge uploads it after the text stream finishes and sends it as a **separate image message** (`uploadMedia`, then `sendMediaMessage` with `media_id`).
153
+
154
+ Trigger (paths relative to `workspace`):
155
+
156
+ ```markdown
157
+ ![screenshot](main_screen.png)
158
+ [screenshot](out/frame.png)
159
+ ```
160
+
161
+ - Only `.png`; `http(s):` / `data:` are ignored; the file must stay inside `workspace`
162
+ - Must be a real PNG (signature), at most **10MB** each, at most **10** images (first-seen order, deduped)
163
+ - Writing a PNG without that Markdown does **not** send it
164
+ - Images follow the text bubble; they are not inlined into the stream
165
+ - Inbound image / voice / file messages are still ignored
166
+
94
167
  ## Security
95
168
 
96
169
  - Do not commit secret-bearing files such as `config.json` / `persona.md`
package/README.md CHANGED
@@ -4,7 +4,34 @@
4
4
 
5
5
  企业微信智能机器人 ⇄ DeepSeek Harness Agent 桥接 **DSH 插件**。
6
6
 
7
- 在 dsh profile 内**进程内**创建 Agent(不再 spawn 子进程):per-sender 持久会话(同一企业微信用户复用同一会话,有上下文记忆),会话与 Web GUI 同进程注册(实时可见、可续聊),并在 Settings → 插件配置页提供配置卡片(`allowFrom` / `agentTimeoutSec` / `startHint`,写入 `settings.yaml` 热生效)。
7
+ 在 dsh profile 内**进程内**创建 Agent(不再 spawn 子进程):按**企微窗口**拆分持久会话(单聊 = 该用户一条;同一群里所有人共用一条;同一个人的私聊和群聊互不串上下文),会话与 Web GUI 同进程注册(实时可见、可续聊),并在 Settings → 插件配置页提供配置卡片(`botId` / `secret`、白名单、超时、提示语、模型覆盖,写入 `settings.yaml`)。
8
+
9
+ Host 通过 `installSettingsSection` 注册 `im-bridge` 命名空间;浏览器半包以 `key: im-bridge` 挂进 `settings.plugin.item`。卡片可填 `botId` / `secret`,与 profile patch 写入同一用户层;改 `startHint` 等热字段后下一轮消息即生效,改凭证仍需重启进程才会连 WebSocket。
10
+
11
+ ## 会话粒度
12
+
13
+ 一条 DSH 会话对应一个企微聊天窗口,而不是「同一个 userid 的所有窗口」:
14
+
15
+ - **单聊**:`single:<userid>`,该用户一条 Agent
16
+ - **群聊**:`group:<chatid>`,群内所有人共用一条 Agent 和同一条串行队列(两人同时发也不会并发 `followup`)
17
+ - `allowFrom` 仍按**发送者** userid 拦截;被拒的人不进队
18
+ - 群里 @机器人 的消息,开头的 `@昵称` 在入站就去掉:模型看到的和标题用的都是「测试一下」而不是「@MediaAgent 测试一下」;正文中间的 @某人 保留,整条只有 @ 时按原文交给模型
19
+ - 进程重启后用稳定 id `wecom-` + key 的短 hash 续上同一条会话:进程里已有活 Agent 就直接采用(例如浏览器已打开该行),存档里有就 `resume`,都没有才 `create`。resume 的 cwd / preset 跟存档,不跟当前配置;cwd 不一致时打警告,不改写。
20
+ - GUI 标题为「企微·私聊/群」+ 第一句用户话(与其它会话一样由 DSH 生成),不再露出 userid / chatid;同类型窗口若第一句话相近,侧栏仍是两行
21
+ - 在 GUI 里归档某条企微会话 = 结束那段上下文:下一条消息用 `wecom-<hash>-2`(再归档就 `-3`)静默开一条新会话,企微侧不额外提示。DSH 没有取消归档的接口,所以旧会话只是不再被本插件写入,既不会恢复可见也不会被删除
22
+ - 所有窗口仍共用同一个 `workspace`(文件 / rag 环境),与聊天上下文分窗是两件事
23
+
24
+ ## 兼容的 DeepSeek Harness 版本
25
+
26
+ DeepSeek Harness 仍是 developer preview,对外置插件**没有 semver 兼容承诺**。本包 0.3.0 按实际调用的 API 对齐已发布 tag:
27
+
28
+ | DSH | 本包 |
29
+ |---|---|
30
+ | [0.1.0-rc.8](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.0-rc.8)、[0.1.1-rc.1](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.1-rc.1)、[0.1.1-rc.2](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.1-rc.2) | 可适配(对照开发的是 0.1.1-rc.2 一线) |
31
+ | [0.1.0-rc.7](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.0-rc.7) 及更早 | 不可适配。rc.7 已有插件配置卡槽位和 `dsh plugin add`,但浏览器没有 `settingsScope.describe()`,配置卡加载和凭证「已配置」徽章会失败 |
32
+ | 更新的 RC / 未打 tag 的 HEAD | 未保证。升级 dsh 后请再验 Settings 卡和企微连线 |
33
+
34
+ 建议把 `dsh` 钉在 `0.1.0-rc.8` 及以上,例如 `npx @deepseek-ai/dsh@0.1.1-rc.2 web`,不要只跑浮动的 `latest`。
8
35
 
9
36
  ## 安装
10
37
 
@@ -13,7 +40,7 @@
13
40
  ```powershell
14
41
  dsh plugin --profile web add @mhfire/dsh-im-bridge
15
42
  # 或钉版本:
16
- # dsh plugin --profile web add @mhfire/dsh-im-bridge@0.1.2
43
+ # dsh plugin --profile web add @mhfire/dsh-im-bridge@0.3.0
17
44
  ```
18
45
 
19
46
  在 `$DSH_HOME/profiles/web/cordis.patch.yml`(或对应 profile)中补密钥即可(其余项已有 bundle 默认,可按需覆盖):
@@ -36,7 +63,25 @@ dsh plugin --profile web add @mhfire/dsh-im-bridge
36
63
  dsh plugin --profile web add <本包路径>
37
64
  ```
38
65
 
39
- 未配置 `botId` / `secret` 时插件仍会加载(不阻塞 `dsh web`),日志会提示跳过企微连线;Settings → 插件配置页仍可填写,保存后**重启**进程才会连接(本版本不做热启连)。
66
+ 未配置 `botId` / `secret` 时插件仍会加载(不阻塞 `dsh web`),日志会提示跳过企微连线;也可在 Settings 插件配置卡填写(见下节)。
67
+
68
+ ## Settings 插件配置卡
69
+
70
+ 安装插件并启动 `dsh web` 后,打开 **设置 → 插件 → 插件配置**,展开 **企业微信桥接**(与「终端 / Agent 循环 / 网页搜索」同一组卡片)。改完点右下角 **保存** 写入 `settings.yaml` 用户层,与 profile `cordis.patch.yml` 同一层;**放弃** 丢掉未保存草稿。带「已覆盖」的字段可 **重置** 回 bundle 默认。
71
+
72
+ 卡片字段自上而下:
73
+
74
+ | 卡片项 | 对应配置 | 保存后 |
75
+ |---|---|---|
76
+ | Bot ID / Secret | `botId` / `secret` | 徽章变为「已配置」;**须重启进程** 才会连 WebSocket。输入框为密码框,线上看不到已存字面值;**留空再保存不会清空**已存凭证 |
77
+ | 允许的发送者 userid | `allowFrom` | 下一轮消息生效;逗号分隔,空 = 允许所有人 |
78
+ | 单任务超时(秒) | `agentTimeoutSec` | 下一轮消息生效 |
79
+ | 开始处理时的占位提示 | `startHint` | 下一轮消息生效 |
80
+ | 非白名单拒绝文案 | `deniedMessage` | 下一轮消息生效 |
81
+ | 进入会话欢迎语 | `welcomeMessage` | 下一轮消息生效 |
82
+ | 企微专用 provider / model | `provider` / `model` | 只影响之后**新建**的企微窗口会话;须两项都填才覆盖,否则跟随 GUI 默认模型 |
83
+
84
+ `workspace`、`agentPreset`、`persona` / `personaFile`、`thinking`、`maxReplyBytes`、`reasoningEffort` 不在卡片上,仍在 profile patch 或下表中配置。本版本不做凭证热重连。
40
85
 
41
86
  ## 配置项
42
87
 
@@ -50,12 +95,23 @@ bundle 的 `cordis.patch.yml` 已为除 `botId` / `secret` 外的字段提供默
50
95
  | `agentTimeoutSec` | 单任务最长执行时间(秒),动画进度条/剩余估算的基准 |
51
96
  | `startHint` | 开始处理时的占位提示语 |
52
97
  | `agentPreset` | Agent 加入的 preset(默认 `standard`) |
98
+ | `provider` / `model` | 企微专用模型;**两者都非空**才覆盖,否则跟随 GUI 的 `agent-default-model`;只填一项会告警并回退。Settings 可编,只影响之后新建的企微窗口会话 |
99
+ | `reasoningEffort` | 覆盖生效时可选的推理强度;未覆盖模型时忽略 |
53
100
  | `persona` / `personaFile` | 机器人「人设」;优先级:`personaFile` → `persona` → 包内默认(按 Host `locale.preference` 选中/英);覆盖不跟语言切换;含敏感信息请勿入库 |
54
101
  | `maxReplyBytes` | 回复上限(字节,默认 20000) |
55
102
  | `deniedMessage` | 非白名单用户的拒绝文案(Settings 可编) |
56
103
  | `welcomeMessage` | 进入会话欢迎语(Settings 可编) |
57
104
  | `thinking` | 流式动画。优先级:工具活动(`toolLabels`)> 模型流式阶段(`reasoningStatus` / `outputStatus`,来自 `assistant/chunk`)> 时间轴 `phases` 兜底;另有 `spin` / `reasoningSpin` / `outputSpin` / `eggs` 等 |
58
105
 
106
+ 企微与 GUI 使用不同模型时,在 profile `cordis.patch.yml` 同时填写:
107
+
108
+ ```yaml
109
+ - id: im-bridge
110
+ config:
111
+ provider: deepseek-official
112
+ model: deepseek-reasoner
113
+ ```
114
+
59
115
  `thinking` 行为:
60
116
 
61
117
  1. 收到 `reasoning-delta` → 「模型思考中」类文案轮换 + `reasoningSpin`
@@ -91,6 +147,23 @@ thinking:
91
147
 
92
148
  也可复制 [`persona.example.md`](./persona.example.md) 为模板后按环境填写。支持 `{{model}}` / `{{cwd}}` 占位符。含环境凭据的人设文件请勿提交。欢迎语 / 拒绝文案 / 思考动画文案目前仍为中文配置项,不随语言切换。
93
149
 
150
+ ## 把 PNG 发到企业微信
151
+
152
+ Agent 的最终回复若包含指向**工作区内** PNG 的 Markdown,桥会在文字流结束之后,把图作为**独立图片消息**发出(先 `uploadMedia`,再用 `media_id` 调用 `sendMediaMessage`)。
153
+
154
+ 触发写法(相对 `workspace`):
155
+
156
+ ```markdown
157
+ ![屏幕截图](main_screen.png)
158
+ [屏幕截图](out/frame.png)
159
+ ```
160
+
161
+ - 只认 `.png`;跳过 `http(s):` / `data:`;路径必须落在 `workspace` 内
162
+ - 文件须为真实 PNG(文件头),单张不超过 **10MB**,最多 **10** 张(按出现顺序去重)
163
+ - 只把 PNG 写到磁盘、回复里没有上述 Markdown,**不会**发图
164
+ - 图在文字气泡之后另发,不会嵌进同一条流式消息
165
+ - 入站图片/语音/文件消息仍忽略
166
+
94
167
  ## 安全
95
168
 
96
169
  - `config.json` / `persona.md` 等含密钥文件不入库;
package/cordis.patch.yml CHANGED
@@ -9,6 +9,10 @@
9
9
  startHint: '🧠 正在思考...'
10
10
  agentTimeoutSec: 600
11
11
  agentPreset: standard
12
+ # 空 = 跟随 GUI 的 agent-default-model; provider 与 model 同时填写才只影响企微
13
+ provider: ''
14
+ model: ''
15
+ reasoningEffort: ''
12
16
  persona: ''
13
17
  personaFile: ''
14
18
  maxReplyBytes: 20000