dsh-helper 0.16.8 → 0.17.1
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/CHANGELOG.md +80 -1
- package/lib/index.js +70 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,86 @@
|
|
|
8
8
|
> (`package-lock.json` 根节点跟齐)与本文件顶部段落,两者连同提交一起走;发布只补 annotated tag,
|
|
9
9
|
> 不再用 `npm version` 升版。
|
|
10
10
|
|
|
11
|
-
## [0.
|
|
11
|
+
## [0.17.1] — 未发布
|
|
12
|
+
|
|
13
|
+
**修复 `lib/client.js` 的构建污染**:仓库里那份产物被打进了错误的 zod。
|
|
14
|
+
|
|
15
|
+
### 问题
|
|
16
|
+
|
|
17
|
+
本地 `node_modules` 从未 `npm ci` 过(只有一个 `.cache` 目录),esbuild 解析 `zod`
|
|
18
|
+
时向上层目录搜,命中了 **`../dsh-expert/node_modules/zod`(4.6.2)**,于是产物变成
|
|
19
|
+
994300 字节、内嵌 4.6.2;而干净环境(npm ci 后有 `node_modules/zod` 4.6.5)应当产出
|
|
20
|
+
**994948 字节、内嵌 4.6.5**。表现为 `verify:ci` 报「产物与仓库里的 lib/client.js 不一致」,
|
|
21
|
+
但 `src/client` 一行都没改 —— 是构建环境的问题,不是代码的问题。
|
|
22
|
+
|
|
23
|
+
### 修复
|
|
24
|
+
|
|
25
|
+
`npm ci` 补齐依赖后 `npm run build` 重出产物:994948 字节,md5 `a68f5e62…`,
|
|
26
|
+
内嵌 `node_modules/zod` 4.6.5。typecheck / client smoke(81) / host smoke(118) 全绿。
|
|
27
|
+
|
|
28
|
+
> 提醒:以后在干净 clone 上改客户端代码前,先 `npm ci`,否则产物会被隔壁仓库的依赖污染。
|
|
29
|
+
|
|
30
|
+
## [0.17.0] — 2026-11-11
|
|
31
|
+
|
|
32
|
+
**`send_file_to_im` 默认跟随当前会话,不再要求每个人手配 botId / toUserId。**
|
|
33
|
+
|
|
34
|
+
### 为什么改
|
|
35
|
+
|
|
36
|
+
0.16.9 及以前,「发给谁」只能靠 `config.json` 里的 `botId` + `toUserId` 或者工具参数指定。
|
|
37
|
+
这对单人环境够用,但这个插件是公开装的:**每个用户都得先去微信机器人插件里把
|
|
38
|
+
账号 id 和收件人 id 抄一遍**,抄错就静默失败(跨机器人投递在 iLink 侧是「已受理但
|
|
39
|
+
对方收不到」)。而这两个值本来就在微信机器人插件的 `config.json` 里,且**当前会话是
|
|
40
|
+
谁发起的**这件事,会话本身就知道。
|
|
41
|
+
|
|
42
|
+
### 现在怎么做
|
|
43
|
+
|
|
44
|
+
新增会话推断:从最近的 `user/message` 里解析 IM 层注入的
|
|
45
|
+
`<dsh_im_source>{"senderId":"…"}</dsh_im_source>`,用 senderId 去 match 账号表的
|
|
46
|
+
`ownerUserId`,反查出该用哪个机器人、回给谁。
|
|
47
|
+
|
|
48
|
+
取值优先级(高 → 低):
|
|
49
|
+
|
|
50
|
+
| 层 | 账号 | 收件人 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| ① 工具参数 | `botId` | `toUserId` |
|
|
53
|
+
| ② 当前会话推断 | 匹配到的机器人 | 该 senderId |
|
|
54
|
+
| ③ config.json | `botId` | `toUserId` |
|
|
55
|
+
| ④ 兜底 | 第一个账号 | 该账号 `ownerUserId` |
|
|
56
|
+
|
|
57
|
+
`config.json` 从「必填」降级为「非 IM 会话时的默认值」——对话框、CLI、定时任务
|
|
58
|
+
这些没有 IM 来源的会话,仍然按它发。
|
|
59
|
+
|
|
60
|
+
### 实测
|
|
61
|
+
|
|
62
|
+
用真实会话事件验证(妻子账号的会话):
|
|
63
|
+
|
|
64
|
+
- 会话推断 → `{botId: wx_97dda581…, toUserId: o9cq8008sm…}` ✅
|
|
65
|
+
- 无参数无配置 → 跟随会话 ✅
|
|
66
|
+
- 配置指向 A、会话是 B → **按会话走 B** ✅
|
|
67
|
+
- 显式传参 → 压过一切 ✅
|
|
68
|
+
- 非 IM 会话 / 账号表查无此人 → 返回 null,正常回落到配置 ✅
|
|
69
|
+
|
|
70
|
+
## [0.16.9] — 2026-11-11
|
|
71
|
+
|
|
72
|
+
**`send_file_to_im` 支持指定用哪个微信机器人发送** —— 修掉「在 A 账号的会话里发文件,永远从第一个账号发出」的问题。
|
|
73
|
+
|
|
74
|
+
### 新增
|
|
75
|
+
|
|
76
|
+
- 工具参数 `botId`:显式指定用哪个微信机器人账号发送文件。
|
|
77
|
+
省略时仍按 config.json 的 `botId` → 首个账号的顺序回落,行为与旧版一致。
|
|
78
|
+
- 为什么必须有它:跨机器人投递在 iLink 侧是静默失败的——用 A 机器人的 token 给 B 机器人
|
|
79
|
+
的用户发文件,接口会回「受理」,但对方永远收不到(各 bot 的用户 id 体系互相不认)。
|
|
80
|
+
之前实现里 `account = accountStore.list()[0]`,与当前会话所属账号无关,于是
|
|
81
|
+
「在谁的会话说、就发给谁」这个直觉落空了。
|
|
82
|
+
|
|
83
|
+
### 用法
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
send_file_to_im({ path: '/绝对/路径.xlsx', botId: 'wx_97dda58120f44f44ffa50e8e' })
|
|
87
|
+
// 收件人省略时会自动用该账号的 ownerUserId,即「发给这个机器人绑定的人」
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## [0.16.8] — 2026-11-11
|
|
12
91
|
|
|
13
92
|
**桌面端的重启按钮不再骗人** —— 原先在 DSH Desktop 里它显示为可点,一点就报「重启失败:untrusted」。
|
|
14
93
|
|
package/lib/index.js
CHANGED
|
@@ -1085,6 +1085,50 @@ export function describeDeliveryFailure(error, { tokenAgeMinutes } = {}) {
|
|
|
1085
1085
|
return wrapped;
|
|
1086
1086
|
}
|
|
1087
1087
|
|
|
1088
|
+
/**
|
|
1089
|
+
* 从当前会话最近的入站消息里推断「该用哪个机器人、发给谁」。
|
|
1090
|
+
*
|
|
1091
|
+
* 入站消息带一段由 IM 层注入的 `<dsh_im_source>{"senderId":"…"}</dsh_im_source>`,
|
|
1092
|
+
* 其中的 senderId 就是这次对话的主人。拿它去 match 账号表的 ownerUserId,就能反查出
|
|
1093
|
+
* 该用哪个机器人回他——**不需要用户在插件配置里再抄一遍 botId / toUserId**。
|
|
1094
|
+
*
|
|
1095
|
+
* 推断失败(不是 IM 会话、或账号表里没有这个人)返回 null,调用方按老逻辑回落。
|
|
1096
|
+
*/
|
|
1097
|
+
function inferImTargetFromSession(session, accounts) {
|
|
1098
|
+
let events;
|
|
1099
|
+
try {
|
|
1100
|
+
events = typeof session?.snapshotEvents === 'function'
|
|
1101
|
+
? session.snapshotEvents()
|
|
1102
|
+
: session?.events;
|
|
1103
|
+
} catch {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
if (!Array.isArray(events) || events.length === 0) return null;
|
|
1107
|
+
|
|
1108
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
1109
|
+
const ev = events[i];
|
|
1110
|
+
if (ev?.type !== 'user/message') continue;
|
|
1111
|
+
const content = ev?.data?.content ?? ev?.data?.message?.content;
|
|
1112
|
+
const text = Array.isArray(content)
|
|
1113
|
+
? content.map((part) => (part?.type === 'text' && typeof part.text === 'string' ? part.text : '')).join('')
|
|
1114
|
+
: '';
|
|
1115
|
+
if (!text || !text.includes('dsh_im_source')) continue;
|
|
1116
|
+
|
|
1117
|
+
const matched = /<dsh_im_source>\s*(\{[\s\S]*?\})\s*<\/dsh_im_source>/.exec(text);
|
|
1118
|
+
if (!matched) continue;
|
|
1119
|
+
let senderId;
|
|
1120
|
+
try {
|
|
1121
|
+
senderId = JSON.parse(matched[1])?.senderId;
|
|
1122
|
+
} catch {
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (typeof senderId !== 'string' || !senderId.trim()) continue;
|
|
1126
|
+
const owner = accounts.find((a) => a?.ownerUserId === senderId.trim());
|
|
1127
|
+
if (owner?.botId) return { botId: owner.botId, toUserId: senderId.trim() };
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1088
1132
|
/**
|
|
1089
1133
|
* dsh-im 后端:把文件投递到微信。
|
|
1090
1134
|
*
|
|
@@ -1092,14 +1136,21 @@ export function describeDeliveryFailure(error, { tokenAgeMinutes } = {}) {
|
|
|
1092
1136
|
* 文件已由 deliverFile 读完并校验过大小时才进来。
|
|
1093
1137
|
*/
|
|
1094
1138
|
async function deliverViaDshIm({
|
|
1095
|
-
credentials, settings, absolutePath, bytes, signal, logger,
|
|
1139
|
+
credentials, settings, absolutePath, bytes, signal, logger, session,
|
|
1096
1140
|
}) {
|
|
1097
1141
|
const dshImDir = resolveDshImDir(settings.dshImDir);
|
|
1098
1142
|
const { api, state, config } = await loadWeixinModules(dshImDir);
|
|
1099
1143
|
|
|
1100
1144
|
const dir = weixinDir();
|
|
1101
1145
|
const accountStore = await new config.WeixinConfigStore(join(dir, 'config.json')).load();
|
|
1102
|
-
|
|
1146
|
+
// 优先级:显式参数 → 当前会话推断 → 配置默认值 → 第一个账号。
|
|
1147
|
+
// 会话推断排在配置之前,是因为「谁在跟我说话」比「设置里填过谁」更精确:
|
|
1148
|
+
// 在有 IM 来源的会话里,默认就该回给那个发消息的人。
|
|
1149
|
+
const inferred = inferImTargetFromSession(session, accountStore.list());
|
|
1150
|
+
const wantedBotId = (typeof settings.explicitBotId === 'string' ? settings.explicitBotId.trim() : '')
|
|
1151
|
+
|| inferred?.botId
|
|
1152
|
+
|| (typeof settings.botId === 'string' ? settings.botId.trim() : '')
|
|
1153
|
+
|| '';
|
|
1103
1154
|
const account = (wantedBotId ? accountStore.get(wantedBotId) : null) ?? accountStore.list()[0];
|
|
1104
1155
|
|
|
1105
1156
|
if (!account?.botId) {
|
|
@@ -1112,7 +1163,9 @@ async function deliverViaDshIm({
|
|
|
1112
1163
|
throw new Error('微信机器人凭据缺失,请在「设置 → IM机器人」里移除后重新接入。');
|
|
1113
1164
|
}
|
|
1114
1165
|
|
|
1115
|
-
const toUserId = (typeof settings.
|
|
1166
|
+
const toUserId = (typeof settings.explicitToUserId === 'string' && settings.explicitToUserId.trim())
|
|
1167
|
+
|| (inferred && inferred.botId === account.botId ? inferred.toUserId : '')
|
|
1168
|
+
|| (typeof settings.toUserId === 'string' && settings.toUserId.trim())
|
|
1116
1169
|
|| account.ownerUserId;
|
|
1117
1170
|
if (!toUserId) throw new Error('没有可用的收件人(账号里缺少 ownerUserId),请在配置里指定 toUserId。');
|
|
1118
1171
|
|
|
@@ -1239,7 +1292,7 @@ export function detectDeliveryBackend(settings = {}) {
|
|
|
1239
1292
|
|
|
1240
1293
|
/** 核心:把文件投递到 IM。后端选好后各自投递,本函数只负责「读文件 + 分派」。 */
|
|
1241
1294
|
async function deliverFile({
|
|
1242
|
-
credentials, settings, filePath, workspace, signal, logger,
|
|
1295
|
+
credentials, settings, filePath, workspace, signal, logger, session,
|
|
1243
1296
|
}) {
|
|
1244
1297
|
// 白名单在这里归一化(一次 realpath),再交给唯一的读文件收口点。
|
|
1245
1298
|
const { absolutePath, bytes } = await readDeliveryFile(
|
|
@@ -1253,7 +1306,7 @@ async function deliverFile({
|
|
|
1253
1306
|
});
|
|
1254
1307
|
}
|
|
1255
1308
|
return deliverViaDshIm({
|
|
1256
|
-
credentials, settings, absolutePath, bytes, signal, logger,
|
|
1309
|
+
credentials, settings, absolutePath, bytes, signal, logger, session,
|
|
1257
1310
|
});
|
|
1258
1311
|
}
|
|
1259
1312
|
|
|
@@ -1775,6 +1828,13 @@ export function apply(ctx) {
|
|
|
1775
1828
|
type: 'string',
|
|
1776
1829
|
description: '可选:收件人 id。省略时用配置里的默认收件人,再退回该账号绑定的用户。',
|
|
1777
1830
|
},
|
|
1831
|
+
botId: {
|
|
1832
|
+
type: 'string',
|
|
1833
|
+
description: '可选:用哪个微信机器人账号发送(如 wx_97dda58120f44f44ffa50e8e)。'
|
|
1834
|
+
+ '省略时用 config.json 里的 botId,再退回第一个账号。'
|
|
1835
|
+
+ '**要投递给另一个微信绑定(另一个机器人账号的用户)时必须指定**——'
|
|
1836
|
+
+ '否则会从第一个机器人发出,跨机器人投递会被平台静默丢弃。',
|
|
1837
|
+
},
|
|
1778
1838
|
},
|
|
1779
1839
|
required: ['path'],
|
|
1780
1840
|
},
|
|
@@ -1801,20 +1861,21 @@ export function apply(ctx) {
|
|
|
1801
1861
|
+ '在「插件列表 → 辅助补丁」的设置卡片里打开「允许把文件投递到 IM」即可恢复。');
|
|
1802
1862
|
}
|
|
1803
1863
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1864
|
+
// 显式参数单独带下去:它必须压过「会话推断」和「配置默认值」。
|
|
1865
|
+
const explicitToUserId = typeof args?.toUserId === 'string' ? args.toUserId.trim() : '';
|
|
1866
|
+
const explicitBotId = typeof args?.botId === 'string' ? args.botId.trim() : '';
|
|
1807
1867
|
|
|
1808
1868
|
const workspace = exec?.agent?.session?.header?.cwd;
|
|
1809
1869
|
return deliverFile({
|
|
1810
1870
|
credentials: imCtx.credentials,
|
|
1811
1871
|
// 后端目录(dshImDir / imConnectDir)已经随 ...settings 展开(来自 config.json),
|
|
1812
1872
|
// 不需要再从 apply 的第二参数里拼一次——那个参数以前根本没被 DSH 传进来。
|
|
1813
|
-
settings: { ...settings,
|
|
1873
|
+
settings: { ...settings, explicitToUserId, explicitBotId },
|
|
1814
1874
|
filePath: args?.path,
|
|
1815
1875
|
workspace: typeof workspace === 'string' && workspace ? workspace : undefined,
|
|
1816
1876
|
signal: exec?.signal,
|
|
1817
1877
|
logger: imCtx.logger,
|
|
1878
|
+
session: exec?.agent?.session,
|
|
1818
1879
|
});
|
|
1819
1880
|
},
|
|
1820
1881
|
});
|