@qzsy/dsh-bridge 0.0.0-stage → 2.18.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +1107 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +488 -0
  4. package/README.md +509 -2
  5. package/client/build.mjs +43 -0
  6. package/client/client.js +9864 -0
  7. package/client/host-families.js +442 -0
  8. package/client/index.js +6883 -0
  9. package/client/locales.mjs +242 -0
  10. package/client/mobile-styles.js +1299 -0
  11. package/client/mobile-yield.js +219 -0
  12. package/client/panel-locales.mjs +777 -0
  13. package/client/picker-yield.js +77 -0
  14. package/client/resource-url-compat.js +90 -0
  15. package/client/settings-nav-icon.js +105 -0
  16. package/client/theme-tokens.js +49 -0
  17. package/client/tunnel-guard.js +46 -0
  18. package/client/unlock-manager.js +144 -0
  19. package/cordis.patch.yml +4 -0
  20. package/lib/auth/dsh-native-cookie.js +148 -0
  21. package/lib/auth/login-template.js +516 -0
  22. package/lib/auth/manager.js +596 -0
  23. package/lib/bridge-rpc-constants.js +66 -0
  24. package/lib/bridge-rpc.js +722 -0
  25. package/lib/cloudflared-manager.mjs +1277 -0
  26. package/lib/cloudflared-network-args.js +99 -0
  27. package/lib/cloudflared-process-scan.js +153 -0
  28. package/lib/compat.js +172 -0
  29. package/lib/connection-compat.js +115 -0
  30. package/lib/feishu/comment.js +50 -0
  31. package/lib/feishu/gateway.js +644 -0
  32. package/lib/feishu/index.js +432 -0
  33. package/lib/feishu/lark-bundled.mjs +131929 -0
  34. package/lib/feishu/markdown.js +148 -0
  35. package/lib/feishu/node.js +1152 -0
  36. package/lib/feishu/slash-commands.js +255 -0
  37. package/lib/index.js +3554 -0
  38. package/lib/platform/account-ops.js +248 -0
  39. package/lib/platform/activity.js +169 -0
  40. package/lib/platform/ask-user.js +293 -0
  41. package/lib/platform/base.js +147 -0
  42. package/lib/platform/changes-digest.js +93 -0
  43. package/lib/platform/commands.js +267 -0
  44. package/lib/platform/config-restore.js +78 -0
  45. package/lib/platform/conversation-bridge.js +1544 -0
  46. package/lib/platform/dsh-storage.js +222 -0
  47. package/lib/platform/index.js +10 -0
  48. package/lib/platform/instances.js +458 -0
  49. package/lib/platform/manager.js +130 -0
  50. package/lib/platform/message-split.js +229 -0
  51. package/lib/platform/session-catalog.js +405 -0
  52. package/lib/platform/session-config.js +52 -0
  53. package/lib/platform/stream-slices.js +21 -0
  54. package/lib/qq/gateway.js +1107 -0
  55. package/lib/qq/index.js +561 -0
  56. package/lib/qq/node.js +1153 -0
  57. package/lib/qq/qqbot-bundled.mjs +8273 -0
  58. package/lib/restart-helper.mjs +133 -0
  59. package/lib/saas/device-auth.mjs +366 -0
  60. package/lib/saas/tunnel-client.mjs +695 -0
  61. package/lib/security/log-sanitize.js +50 -0
  62. package/lib/security/path-validator.js +195 -0
  63. package/lib/security/rate-limiter.js +93 -0
  64. package/lib/session-strip.js +100 -0
  65. package/lib/sse-keepalive.js +115 -0
  66. package/lib/telegram/gateway.js +643 -0
  67. package/lib/telegram/index.js +255 -0
  68. package/lib/telegram/node.js +410 -0
  69. package/lib/tunnel-client.mjs +537 -0
  70. package/lib/wechat/gateway.js +1000 -0
  71. package/lib/wechat/index.js +282 -0
  72. package/lib/wechat/media.js +302 -0
  73. package/lib/wechat/node.js +360 -0
  74. package/llms.txt +36 -0
  75. package/locale/en.json +6 -0
  76. package/locale/zh.json +6 -0
  77. package/package.json +135 -4
@@ -0,0 +1,255 @@
1
+ // dsh-bridge 飞书 Slash Command(斜杠指令面板)同步
2
+ //
3
+ // 官方文档:docs/dev/lark-sdk/official-slash-command.md("飞书智能体支持 Slash Command")
4
+ //
5
+ // 该能力未包含在任何官方 Node SDK 中(@larksuite/channel / @larksuiteoapi/node-sdk 均无对应方法),
6
+ // 因此这里按官方文档直连 HTTP:管理接口为 application v7 的复数路径
7
+ // POST /open-apis/application/v7/app_slash_commands 创建(需 application:app_slash_command:write)
8
+ // GET /open-apis/application/v7/app_slash_commands 列表(需 ...:read)
9
+ // PATCH /open-apis/application/v7/app_slash_commands/:id 更新
10
+ // DELETE /open-apis/application/v7/app_slash_commands/:id 删除
11
+ //
12
+ // 已知约束(文档要点):客户端需 PC ≥7.70 / 移动端 ≥7.71 才显示面板;每应用上限 100 条;
13
+ // command 名称唯一(重名创建返回 code 40000000 + "command already exists");删除后同名可重建;
14
+ // 生效延迟为服务端约 5 分钟 + 客户端缓存约 3 分钟。
15
+
16
+ export const SLASH_COMMAND_API_PATH = '/open-apis/application/v7/app_slash_commands'
17
+
18
+ /**
19
+ * 图标统一使用官方默认值:实测(2026-10 生产验证)创建/更新时传入文档清单内的其他 icon_key
20
+ * 都会被服务端静默忽略——PATCH 返回 code 0,但 GET 列表里始终是 `skill_outlined`。
21
+ * 因此这里不写自定义图标,避免"每次同步都判定为需要更新"的无效 PATCH。
22
+ */
23
+ const DEFAULT_ICON = 'skill_outlined'
24
+
25
+ /**
26
+ * 期望注册的斜杠指令:与 lib/platform/commands.js 的文本指令一一对应(command 字段不带前导斜杠)。
27
+ */
28
+ export const DESIRED_SLASH_COMMANDS = [
29
+ { command: 'help', icon: DEFAULT_ICON, zh: '查看可用指令与用法', en: 'Show available commands' },
30
+ { command: 'new', icon: DEFAULT_ICON, zh: '新建会话并开始任务(可跟提示词)', en: 'Start a new session' },
31
+ { command: 'sessions', icon: DEFAULT_ICON, zh: '列出历史会话', en: 'List sessions' },
32
+ { command: 'use', icon: DEFAULT_ICON, zh: '切换到指定编号的会话', en: 'Switch session by index' },
33
+ { command: 'rename', icon: DEFAULT_ICON, zh: '重命名当前会话', en: 'Rename current session' },
34
+ { command: 'workspaces', icon: DEFAULT_ICON, zh: '查看已注册工作区', en: 'List workspaces' },
35
+ { command: 'addworkspace', icon: DEFAULT_ICON, zh: '添加工作区目录', en: 'Add a workspace directory' },
36
+ { command: 'status', icon: DEFAULT_ICON, zh: '查看 Agent 运行状态', en: 'Show agent status' },
37
+ { command: 'stop', icon: DEFAULT_ICON, zh: '停止当前正在执行的任务', en: 'Stop the running task' },
38
+ { command: 'end', icon: DEFAULT_ICON, zh: '结束当前会话', en: 'End the current session' },
39
+ { command: 'diff', icon: 'update-ai_outlined', zh: '导出本轮改动的 .patch 补丁', en: "Export this turn's changes as a .patch" },
40
+ { command: 'ask', icon: DEFAULT_ICON, zh: '回答 Agent 的提问(/ask 1 选第 1 项)', en: 'Answer the Agent\'s question' },
41
+ ]
42
+
43
+ /** 权限类错误码(文档 "典型错误码"):视为"应用没开通斜杠指令权限",不再重试 */
44
+ const PERMISSION_ERROR_CODES = new Set([99991640, 99991661, 99991663])
45
+
46
+ function isBizError(body) {
47
+ return Boolean(body) && typeof body.code === 'number' && body.code !== 0
48
+ }
49
+
50
+ function errorInfo(body, command) {
51
+ return {
52
+ code: typeof body?.code === 'number' ? body.code : undefined,
53
+ message: String(body?.msg ?? body?.message ?? body ?? 'unknown error'),
54
+ command: command ?? undefined,
55
+ }
56
+ }
57
+
58
+ function isAlreadyExists(body) {
59
+ return /already exists/i.test(String(body?.msg ?? ''))
60
+ }
61
+
62
+ function isNotFound(body) {
63
+ return /not found/i.test(String(body?.msg ?? ''))
64
+ }
65
+
66
+ /**
67
+ * 归一化已记录的指令映射:字符串视为 { id, ours: false }(保守:不认为是自己创建的,永不删除)。
68
+ */
69
+ export function normalizeManagedIds(managedIds = {}) {
70
+ const out = {}
71
+ for (const [command, value] of Object.entries(managedIds ?? {})) {
72
+ if (!value) continue
73
+ if (typeof value === 'string') out[command] = { id: value, ours: false }
74
+ else if (typeof value === 'object' && value.id) out[command] = { id: String(value.id), ours: value.ours === true }
75
+ }
76
+ return out
77
+ }
78
+
79
+ export function isPermissionErrorCode(code) {
80
+ return PERMISSION_ERROR_CODES.has(Number(code))
81
+ }
82
+
83
+ /** 创建/更新的请求体(icon 按文档放在 description 内) */
84
+ export function buildSlashCommandPayload(spec) {
85
+ return {
86
+ command: spec.command,
87
+ description: {
88
+ default_value: spec.zh,
89
+ i18n: { zh_cn: spec.zh, en_us: spec.en },
90
+ icon: { icon_key: spec.icon },
91
+ },
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 列表项与期望是否一致(列表响应把 icon 放在顶层,创建请求放在 description 内,两处都读)。
97
+ * 图标也参与比较:服务端当前只回默认图标,传自定义图标会被忽略——若不比较,就会每次同步都白 PATCH。
98
+ */
99
+ export function slashCommandMatches(existing, spec) {
100
+ const description = existing?.description ?? {}
101
+ const i18n = description.i18n ?? {}
102
+ const iconKey = existing?.icon?.icon_key ?? description.icon?.icon_key ?? ''
103
+ return String(description.default_value ?? '') === spec.zh
104
+ && String(i18n.zh_cn ?? '') === spec.zh
105
+ && String(i18n.en_us ?? '') === spec.en
106
+ && iconKey === spec.icon
107
+ }
108
+
109
+ /** 统一读取业务响应体:SDK 的默认 HTTP 实例已由拦截器剥离到业务体,自定义实例可能包一层 data */
110
+ function readBody(res) {
111
+ if (isBizError(res)) return res
112
+ if (res?.data && typeof res.data.code === 'number') return res.data
113
+ return res
114
+ }
115
+
116
+ async function callApi(client, token, { method, path, data }) {
117
+ const headers = { Authorization: `Bearer ${token}` }
118
+ if (data !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'
119
+ const url = `${client.domain}${path}`
120
+ try {
121
+ const res = await client.httpInstance.request({ url, method, headers, data })
122
+ return readBody(res)
123
+ } catch (err) {
124
+ // 非 2xx(如 400 缺 Token / Token 无效)由 axios 抛出,业务体在 err.response.data
125
+ const body = err?.response?.data
126
+ if (body) return readBody(body)
127
+ throw err
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 同步斜杠指令到期望清单(幂等):
133
+ * - 缺失 → 创建(记为 ours: true)
134
+ * - 存在但描述/图标不一致 → 更新(保留原归属:同名同名指令若本非本插件创建,记为 ours: false)
135
+ * - 存在且一致 → 采用(不写)
136
+ * - 仅【本插件创建过(ours: true)】且已不在清单内 → 删除
137
+ *
138
+ * managedIds 形如 { [command]: { id, ours } };字符串形式按 { id, ours: false } 保守处理(只读不删)。
139
+ *
140
+ * @param {object} opts
141
+ * @param {object} opts.client 底层 node-sdk Client(channel.rawClient)
142
+ * @param {() => Promise<string>} opts.getToken 取 tenant_access_token(调用方负责缓存)
143
+ * @param {object} [opts.logger]
144
+ * @param {Array} [opts.desired] 期望清单
145
+ * @param {object} [opts.managedIds] 之前记录的 { command: { id, ours } }
146
+ * @returns {Promise<{ok:boolean, ids:object, created:string[], updated:string[], adopted:string[], deleted:string[], error?:object}>}
147
+ */
148
+ export async function syncSlashCommands({
149
+ client,
150
+ getToken,
151
+ logger,
152
+ desired = DESIRED_SLASH_COMMANDS,
153
+ managedIds = {},
154
+ } = {}) {
155
+ const result = { ok: false, ids: normalizeManagedIds(managedIds), created: [], updated: [], adopted: [], deleted: [], error: null }
156
+ const ids = normalizeManagedIds(managedIds)
157
+ const desiredNames = new Set(desired.map((item) => item.command))
158
+
159
+ let token
160
+ try {
161
+ token = await getToken()
162
+ } catch (err) {
163
+ result.error = errorInfo(err)
164
+ return result
165
+ }
166
+
167
+ try {
168
+ return await syncWithToken({ client, token, desired, managedIds, result, ids, desiredNames })
169
+ } catch (err) {
170
+ // 网络异常等非业务错误:保留已完成进度,交由调用方决定是否重试
171
+ result.ids = ids
172
+ result.error = errorInfo(err)
173
+ return result
174
+ }
175
+ }
176
+
177
+ async function syncWithToken({ client, token, desired, managedIds, result, ids, desiredNames }) {
178
+ const listRes = await callApi(client, token, { method: 'GET', path: SLASH_COMMAND_API_PATH })
179
+ if (isBizError(listRes)) {
180
+ result.error = errorInfo(listRes)
181
+ return result
182
+ }
183
+ const items = Array.isArray(listRes?.data?.items) ? listRes.data.items : []
184
+ const byName = new Map(items.filter((item) => item?.command).map((item) => [item.command, item]))
185
+
186
+ for (const spec of desired) {
187
+ const found = byName.get(spec.command)
188
+ if (found?.command_id) {
189
+ // 归属沿用既有记录(此前创建过 → ours 保持 true;否则视为他人/手工创建,ours: false)
190
+ ids[spec.command] = { id: found.command_id, ours: ids[spec.command]?.ours === true }
191
+ if (slashCommandMatches(found, spec)) {
192
+ result.adopted.push(spec.command)
193
+ } else {
194
+ const res = await callApi(client, token, {
195
+ method: 'PATCH',
196
+ path: `${SLASH_COMMAND_API_PATH}/${found.command_id}`,
197
+ data: buildSlashCommandPayload(spec),
198
+ })
199
+ if (isBizError(res)) {
200
+ result.ids = ids
201
+ result.error = errorInfo(res, spec.command)
202
+ return result
203
+ }
204
+ result.updated.push(spec.command)
205
+ }
206
+ continue
207
+ }
208
+
209
+ const res = await callApi(client, token, {
210
+ method: 'POST',
211
+ path: SLASH_COMMAND_API_PATH,
212
+ data: buildSlashCommandPayload(spec),
213
+ })
214
+ if (isBizError(res)) {
215
+ // 并发或历史残留:服务端对 command 有唯一约束 → 重新拉列表认领其 command_id
216
+ if (isAlreadyExists(res)) {
217
+ const relist = await callApi(client, token, { method: 'GET', path: SLASH_COMMAND_API_PATH })
218
+ const item = (relist?.data?.items ?? []).find((it) => it?.command === spec.command)
219
+ if (item?.command_id) {
220
+ ids[spec.command] = { id: item.command_id, ours: false }
221
+ result.adopted.push(spec.command)
222
+ continue
223
+ }
224
+ }
225
+ result.ids = ids
226
+ result.error = errorInfo(res, spec.command)
227
+ return result
228
+ }
229
+ const created = res?.data?.command_id
230
+ if (created) ids[spec.command] = { id: created, ours: true }
231
+ result.created.push(spec.command)
232
+ }
233
+
234
+ // 清理:仅删除【本插件创建过(ours: true)】且已不在期望清单中的指令
235
+ for (const [command, entry] of Object.entries(normalizeManagedIds(managedIds))) {
236
+ if (desiredNames.has(command)) continue
237
+ if (!entry.ours) {
238
+ // 非本插件创建(同名冲突/手工创建):保留 id 以便后续更新,但绝不删除
239
+ ids[command] = entry
240
+ continue
241
+ }
242
+ const res = await callApi(client, token, { method: 'DELETE', path: `${SLASH_COMMAND_API_PATH}/${entry.id}` })
243
+ if (isBizError(res) && !isNotFound(res)) {
244
+ result.ids = ids
245
+ result.error = errorInfo(res, command)
246
+ return result
247
+ }
248
+ delete ids[command]
249
+ result.deleted.push(command)
250
+ }
251
+
252
+ result.ids = ids
253
+ result.ok = true
254
+ return result
255
+ }