Rebrand as dsh-im-ops fork of xmanrui/dsh-im@4.9.1.

Keep all IM channels; rename package/cordis id for local profile install without colliding with the community plugin. Document fork intent in FORK.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 15:02:54 +08:00
parent 965e839457
commit d53daf7d26
18 changed files with 70 additions and 28 deletions

43
FORK.md Normal file
View file

@ -0,0 +1,43 @@
# dsh-im-ops(运维 fork)
本仓库是 [`@xmanrui/dsh-im@4.9.1`](https://github.com/xmanrui/dsh-im) 的 **完整 fork**(九渠道 + AI Office 均保留),用于 Netx / 运维场景下自控:
- 访问控制(入站白名单 / 群策略)
- 会话策略(例如群聊拆个人 Session)
- 通告 / 投递默认与文案
上游 remote 名为 `upstream`。不要与社区包 `@xmanrui/dsh-im` 同时装进同一 profile。
## 与已删除的薄壳 `ops-im` 的关系
曾尝试只做 `ops_im__send` 适配层;因 **入站权限/会话仍在 dsh-im**,无法满足所有权诉求,已删除该薄仓库,改为本 fork。
## 安装(替换社区 im)
```powershell
# 若已安装社区包,先从 profile 移除 @xmanrui/dsh-im
cd D:\project\chatgpt\dsh-im-ops
npm install
npm run build
dsh plugin --profile web add -w "D:\project\chatgpt\dsh-im-ops"
# 重启 dsh web
```
包名:`dsh-im-ops@4.9.1-ops.0`(cordis id:`dsh-im-ops`)。
扫码态一般仍在 `~/.dsh/integrations/…`;换包后若异常,在 IM 设置里重新关联设备。
## 第一刀改动方向(尚未改业务逻辑)
1. WhatsApp / 通用 access mode:运维友好的默认与设置文案
2. 群 Session key:对齐 oclaw `user_in_chat`(可配置)
3. 投递默认目标 / 与 netxops 协作说明
业务改动前先 `npm run build` 冒烟扫码与私聊。
## 同步上游
```powershell
git fetch upstream --tags
git merge v4.9.x # 或 cherry-pick;冲突自行解决后再 build
```

View file

@ -1,4 +1,4 @@
<p align="center"> > **Fork:** dsh-im-ops — ops-owned fork of [@xmanrui/dsh-im@4.9.1](https://github.com/xmanrui/dsh-im). See [FORK.md](FORK.md). Do not install alongside the community package.\n\n<p align="center">
<img src="assets/logo-dsh-im-connecting-readme-3x2.png" alt="DSH-IM — Connecting DeepSeek Harness" width="420" height="280" align="middle">&nbsp;&nbsp; <img src="assets/logo-dsh-im-connecting-readme-3x2.png" alt="DSH-IM — Connecting DeepSeek Harness" width="420" height="280" align="middle">&nbsp;&nbsp;
<img src="assets/logo-plugin-phone.png" alt="DSH-IM phone logo" width="280" height="280" align="middle"> <img src="assets/logo-plugin-phone.png" alt="DSH-IM phone logo" width="280" height="280" align="middle">
</p> </p>

View file

@ -1,3 +1,3 @@
- insert: - insert:
- id: xmanrui-dsh-im - id: dsh-im-ops
name: '@xmanrui/dsh-im' name: dsh-im-ops

View file

@ -1,7 +1,7 @@
{ {
"name": "@xmanrui/dsh-im", "name": "dsh-im-ops",
"version": "4.9.1", "version": "4.9.1-ops.0",
"description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.", "description": "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 — access/session policies owned here.",
"keywords": [ "keywords": [
"deepseek-harness", "deepseek-harness",
"dsh", "dsh",
@ -20,7 +20,9 @@
"telegram", "telegram",
"discord", "discord",
"whatsapp", "whatsapp",
"ai-office" "ai-office",
"ops",
"fork"
], ],
"author": "xmanrui (https://github.com/xmanrui)", "author": "xmanrui (https://github.com/xmanrui)",
"contributors": [ "contributors": [
@ -41,14 +43,11 @@
"type": "module", "type": "module",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/xmanrui/dsh-im.git" "url": "git+https://github.com/hansjone/dsh-im-ops.git"
}, },
"homepage": "https://github.com/xmanrui/dsh-im#readme", "homepage": "https://github.com/hansjone/dsh-im-ops#readme",
"bugs": { "bugs": {
"url": "https://github.com/xmanrui/dsh-im/issues" "url": "https://github.com/hansjone/dsh-im-ops/issues"
},
"publishConfig": {
"access": "public"
}, },
"bin": { "bin": {
"dsh-im": "bin/dsh-im.mjs" "dsh-im": "bin/dsh-im.mjs"

View file

@ -7,7 +7,7 @@ import { build } from 'esbuild';
const sourceDirectory = dirname(fileURLToPath(import.meta.url)); const sourceDirectory = dirname(fileURLToPath(import.meta.url));
const packageRoot = resolve(sourceDirectory, '../..'); const packageRoot = resolve(sourceDirectory, '../..');
const outputPath = resolve(packageRoot, 'lib/client.js'); const outputPath = resolve(packageRoot, 'lib/client.js');
const loaderId = process.env.DSH_IM_CLIENT_ID ?? '@xmanrui/dsh-im'; const loaderId = process.env.DSH_IM_CLIENT_ID ?? 'dsh-im-ops';
const result = await build({ const result = await build({
entryPoints: [resolve(sourceDirectory, 'index.js')], entryPoints: [resolve(sourceDirectory, 'index.js')],

View file

@ -128,7 +128,7 @@ export function installDingtalkStyles() {
const existing = document.querySelector(`style[data-plugin-css="${DINGTALK_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${DINGTALK_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = DINGTALK_STYLE_ID; style.dataset.pluginCss = DINGTALK_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -11,7 +11,7 @@ export function installDiscordStyles() {
const existing = document.querySelector(`style[data-plugin-css="${DISCORD_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${DISCORD_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = DISCORD_STYLE_ID; style.dataset.pluginCss = DISCORD_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -562,7 +562,7 @@ export function installFeishuStyles() {
} }
const style = document.createElement("style"); const style = document.createElement("style");
style.dataset.plugin = "@xmanrui/dsh-im"; style.dataset.plugin = "dsh-im-ops";
style.dataset.pluginCss = FEISHU_STYLE_ID; style.dataset.pluginCss = FEISHU_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -11,7 +11,7 @@ export function installQqStyles() {
const existing = document.querySelector(`style[data-plugin-css="${QQ_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${QQ_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = QQ_STYLE_ID; style.dataset.pluginCss = QQ_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -26,7 +26,7 @@ export function installSlackStyles() {
const existing = document.querySelector(`style[data-plugin-css="${SLACK_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${SLACK_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = SLACK_STYLE_ID; style.dataset.pluginCss = SLACK_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -11,7 +11,7 @@ export function installTelegramStyles() {
const existing = document.querySelector(`style[data-plugin-css="${TELEGRAM_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${TELEGRAM_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = TELEGRAM_STYLE_ID; style.dataset.pluginCss = TELEGRAM_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -11,7 +11,7 @@ export function installWecomStyles() {
const existing = document.querySelector(`style[data-plugin-css="${WECOM_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${WECOM_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = WECOM_STYLE_ID; style.dataset.pluginCss = WECOM_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -107,7 +107,7 @@ export function installWeixinStyles() {
const existing = document.querySelector(`style[data-plugin-css="${WEIXIN_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${WEIXIN_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = WEIXIN_STYLE_ID; style.dataset.pluginCss = WEIXIN_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -11,7 +11,7 @@ export function installWhatsappStyles() {
const existing = document.querySelector(`style[data-plugin-css="${WHATSAPP_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${WHATSAPP_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = WHATSAPP_STYLE_ID; style.dataset.pluginCss = WHATSAPP_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -543,7 +543,7 @@ export function installImStyles() {
const existing = document.querySelector(`style[data-plugin-css="${IM_STYLE_ID}"]`); const existing = document.querySelector(`style[data-plugin-css="${IM_STYLE_ID}"]`);
if (existing) return () => {}; if (existing) return () => {};
const style = document.createElement('style'); const style = document.createElement('style');
style.dataset.plugin = '@xmanrui/dsh-im'; style.dataset.plugin = 'dsh-im-ops';
style.dataset.pluginCss = IM_STYLE_ID; style.dataset.pluginCss = IM_STYLE_ID;
style.textContent = CSS; style.textContent = CSS;
document.head.appendChild(style); document.head.appendChild(style);

View file

@ -109,7 +109,7 @@ function manualUpdateCommand(snapshot) {
? [...targets, snapshot.runningVersion, snapshot.installedVersion] ? [...targets, snapshot.runningVersion, snapshot.installedVersion]
.filter(validVersion).sort(compareVersionsDescending)[0] .filter(validVersion).sort(compareVersionsDescending)[0]
: 'latest'; : 'latest';
return `dsh plugin --profile ${profileArgument} add -w @xmanrui/dsh-im@${version}`; return `dsh plugin --profile ${profileArgument} add -w dsh-im-ops@${version}`;
} }
function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) { function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
@ -167,7 +167,7 @@ function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
: h(React.Fragment, null, : h(React.Fragment, null,
h('rect', { x: 7, y: 7, width: 10, height: 11, rx: 1.5 }), h('rect', { x: 7, y: 7, width: 10, height: 11, rx: 1.5 }),
h('path', { d: 'M5 13H3.5A1.5 1.5 0 0 1 2 11.5v-8A1.5 1.5 0 0 1 3.5 2h8A1.5 1.5 0 0 1 13 3.5V5' }))))), h('path', { d: 'M5 13H3.5A1.5 1.5 0 0 1 2 11.5v-8A1.5 1.5 0 0 1 3.5 2h8A1.5 1.5 0 0 1 13 3.5V5' }))))),
command.endsWith('@xmanrui/dsh-im@latest') ? h('p', { className: 'dim-updateManualHint' }, command.endsWith('dsh-im-ops@latest') ? h('p', { className: 'dim-updateManualHint' },
'尚未确认目标版本,此命令安装执行时 npm 的 latest 版本。') : null, '尚未确认目标版本,此命令安装执行时 npm 的 latest 版本。') : null,
h('p', { className: 'dim-updateManualHint' }, desktop h('p', { className: 'dim-updateManualHint' }, desktop
? '请在当前 Desktop 的内置终端执行,完成后手动重启。' ? '请在当前 Desktop 的内置终端执行,完成后手动重启。'

View file

@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
import semver from 'semver'; import semver from 'semver';
export const PACKAGE_NAME = '@xmanrui/dsh-im'; export const PACKAGE_NAME = 'dsh-im-ops';
export const NPM_REGISTRY = 'https://registry.npmjs.org/'; export const NPM_REGISTRY = 'https://registry.npmjs.org/';
const INSTALL_TIMEOUT_MS = 15 * 60_000; const INSTALL_TIMEOUT_MS = 15 * 60_000;

View file

@ -51,7 +51,7 @@ export async function fetchNpmRelease(fetchImpl = globalThis.fetch, timeoutMs =
const integrity = value.dist?.integrity; const integrity = value.dist?.integrity;
if (tarball.origin !== new URL(NPM_REGISTRY).origin || tarball.username || tarball.password if (tarball.origin !== new URL(NPM_REGISTRY).origin || tarball.username || tarball.password
|| tarball.search || tarball.hash || tarball.search || tarball.hash
|| tarball.pathname !== `/@xmanrui/dsh-im/-/dsh-im-${version}.tgz` || tarball.pathname !== `/dsh-im-ops/-/dsh-im-${version}.tgz`
|| typeof integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity)) { || typeof integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity)) {
throw updateError('invalid-release'); throw updateError('invalid-release');
} }