@rezti/dsh-rez-suite 0.1.48 → 0.1.49
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 +6 -2
- package/README.md +2 -2
- package/README.zh.md +1 -1
- package/cordis.patch.yml +1 -1
- package/lib/client.d.ts +7 -1
- package/lib/client.js +207 -4
- package/lib/index.js +6 -4
- package/lib/style.css +11 -0
- package/package.json +3 -3
- package/src/changelog.ts +15 -0
- package/src/client/api.ts +15 -0
- package/src/client/locales.ts +14 -2
- package/src/client/panel/ConfigTab.tsx +137 -2
- package/src/client/panel/panel.module.css +11 -0
- package/src/esphome-lab.ts +4 -3
- package/src/observe-mcp.ts +1 -1
- package/src/protocol.ts +23 -0
- package/templates/staff/hr/.agents/skills/staff-onboard-keys/SKILL.md +8 -6
- package/templates/staff/lab/AGENTS.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
升级:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
8
|
+
dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.49
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
不要用裸 `plugin update`,不要写 `latest`(新 profile 有 24 小时发布冷却,当天可能装不上)。不要装 **0.1.46**(GitHub 子依赖会被 DSH 拦住)。
|
|
12
12
|
|
|
13
|
-
发版顺序:`
|
|
13
|
+
发版顺序:`wechat@0.1.15` → `suite@0.1.49`。
|
|
14
|
+
|
|
15
|
+
## 0.1.49 — 2026-08-19
|
|
16
|
+
|
|
17
|
+
- **企业微信智能机器人**:设置页按房间填 BotID + Secret。同事在企微跟哪个机器人说话,消息进网页左侧那个房间。密钥只存本机 `~/.dsh/wecom-channels.json`,不进 compose。旧群 Webhook 仅在一个 bot 都没绑时兼容。
|
|
14
18
|
|
|
15
19
|
## 0.1.48 — 2026-08-19
|
|
16
20
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
雷泽插件的**唯一安装入口**。Odoo / 企微 / HA / TAPD / GSC / 企查查(仅法务助理房间) / 天眼查(仅老板) / 工作区(老板三室 + 全员助理)都由这一包拉齐,不要再逐个 `plugin add`,**不要**再装 `@rezti/dsh-rez-boss-desk`。
|
|
4
4
|
|
|
5
|
-
发版顺序(必做):先发 `@rezti/dsh-rez-
|
|
5
|
+
发版顺序(必做):先发 `@rezti/dsh-rez-wechat@0.1.15` → `@rezti/dsh-rez-suite@0.1.49`。员工机安装包仍钉已发布的 suite,不要顺手改审批镜像 `SUITE_VERSION`。
|
|
6
6
|
|
|
7
7
|
设置里选身份后,左侧自己出现对应房间(全员三助理;工程师另加实验室;运营另加设计;老板另加搞钱/产品/运营),不必手动添加工作区。实验室:大模型写 YAML、调 MCP、给下载链接。
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# 第一次 / 升级(钉死版本,避开 24h 发布冷却)
|
|
15
|
-
dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
15
|
+
dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.49
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
本版改了什么:见 [CHANGELOG.md](./CHANGELOG.md)。
|
package/README.zh.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-rez-suite
|
|
2
2
|
|
|
3
|
-
**用户只装这一包。** 升级只记:`dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
3
|
+
**用户只装这一包。** 升级只记:`dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.49`(不要用裸 `update`;不要 latest;不要 0.1.46 / 0.1.47)。本版改了什么见 [CHANGELOG.md](./CHANGELOG.md)。拆分包给开发改代码;不要对同一 profile 再单独 add 子包。
|
|
4
4
|
|
|
5
5
|
说明书 / 规格书用输入栏**回形针**上传原 PDF(官方对话只收图,不要拖文件;不要用微信发 PDF)。先入 `Marketing/Inbox` + TAPD 测试中,凯文跟 Harness 说审批了哪份后,再写入 `Marketing/Publish/…`。
|
|
6
6
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# 用户只装这一包:`dsh plugin --profile web add @rezti/dsh-rez-suite`
|
|
2
|
-
# 升级只记这一条:`dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
2
|
+
# 升级只记这一条:`dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.49`
|
|
3
3
|
# 不要再单独 add 子包,会重复注册。
|
|
4
4
|
#
|
|
5
5
|
# MCP rows are started by the domain plugins after rezSso resolves secrets.
|
package/lib/client.d.ts
CHANGED
|
@@ -7255,7 +7255,7 @@ declare const zh: {
|
|
|
7255
7255
|
readonly 'entry.label': "Rez";
|
|
7256
7256
|
readonly 'entry.tooltip': "Rez Suite 配置与审计面板";
|
|
7257
7257
|
readonly 'settings.title': "ReZ-TI";
|
|
7258
|
-
readonly 'settings.description': "公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC
|
|
7258
|
+
readonly 'settings.description': "公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC)和企业微信智能机器人。个人微信在「微信」页签扫码。";
|
|
7259
7259
|
readonly 'panel.title': "Rez Suite";
|
|
7260
7260
|
readonly 'tab.config': "配置";
|
|
7261
7261
|
readonly 'tab.weixin': "微信";
|
|
@@ -7291,6 +7291,12 @@ declare const zh: {
|
|
|
7291
7291
|
readonly 'config.wecom.botId': "Bot ID";
|
|
7292
7292
|
readonly 'config.wecom.secret': "Secret";
|
|
7293
7293
|
readonly 'config.wecom.webhook': "群机器人 Webhook";
|
|
7294
|
+
readonly 'config.wecom.hint': "Accio 同款:一个房间绑一个智能机器人。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机 ~/.dsh/wecom-channels.json,不要写进 compose。BotID / Secret 在企业微信管理后台 → 智能机器人。";
|
|
7295
|
+
readonly 'config.wecom.room': "房间";
|
|
7296
|
+
readonly 'config.wecom.add': "添加绑定";
|
|
7297
|
+
readonly 'config.wecom.remove': "删除";
|
|
7298
|
+
readonly 'config.wecom.empty': "还没有绑定。给人事/财务/法务等房间各建一个企微智能机器人,就能变成不同角色。";
|
|
7299
|
+
readonly 'config.wecom.status': "通道";
|
|
7294
7300
|
readonly 'config.ha': "Home Assistant";
|
|
7295
7301
|
readonly 'config.ha.url': "HA MCP URL";
|
|
7296
7302
|
readonly 'config.ha.token': "长期访问令牌";
|
package/lib/client.js
CHANGED
|
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
(function(){var id="dsh-rez-suite-css";if(typeof document==="undefined"||document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent=".r9dlUa_wordmark {\n min-width: 0;\n color: inherit;\n align-items: center;\n gap: 8px;\n display: inline-flex;\n overflow: hidden;\n}\n\n.r9dlUa_mark {\n object-fit: contain;\n flex: none;\n width: 28px;\n height: 28px;\n display: block;\n}\n\n.r9dlUa_markOnly {\n object-fit: contain;\n width: 100%;\n max-width: 28px;\n height: 100%;\n max-height: 28px;\n display: block;\n}\n\n.r9dlUa_name {\n letter-spacing: .02em;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: 15px;\n font-weight: 650;\n overflow: hidden;\n}\n\n.r9dlUa_badge {\n letter-spacing: .06em;\n opacity: .72;\n white-space: nowrap;\n border: 1px solid;\n border-radius: 4px;\n flex: none;\n padding: 2px 6px;\n font-size: 10px;\n font-weight: 700;\n line-height: 1.2;\n}\n.LOjPTW_shell {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n display: flex;\n}\n\n.LOjPTW_card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n\n.LOjPTW_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.LOjPTW_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.LOjPTW_header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n\n.LOjPTW_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.LOjPTW_headText {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n\n.LOjPTW_name {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.LOjPTW_description {\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n\n.LOjPTW_chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n}\n\n.LOjPTW_chevronOpen {\n transform: rotate(180deg);\n}\n\n.LOjPTW_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px 12px;\n padding-top: 8px;\n}\n\n.LOjPTW_tabBar {\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n flex: none;\n gap: 2px;\n display: flex;\n}\n\n.LOjPTW_tab {\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n background: none;\n border: none;\n border-bottom: 2px solid #0000;\n border-radius: 6px 6px 0 0;\n padding: 7px 14px;\n font-size: 13px;\n}\n\n.LOjPTW_tab:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_tab[data-active] {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: var(--dsw-alias-state-business-primary);\n font-weight: 600;\n}\n\n.LOjPTW_tabBody {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n padding: 12px 0 4px;\n display: flex;\n}\n\n.LOjPTW_section {\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base));\n border-radius: 10px;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n display: flex;\n}\n\n.LOjPTW_sectionTitle {\n color: var(--dsw-alias-label-primary);\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.LOjPTW_field {\n flex-direction: column;\n align-items: stretch;\n gap: 4px;\n display: flex;\n}\n\n.LOjPTW_label {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n}\n\n.LOjPTW_input, .LOjPTW_select {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n padding: 6px 8px;\n font-size: 13px;\n}\n\n.LOjPTW_checkbox {\n justify-self: start;\n width: 15px;\n height: 15px;\n}\n\n.LOjPTW_toolbar {\n flex-wrap: wrap;\n flex: none;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.LOjPTW_button {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n border-radius: 8px;\n padding: 7px 12px;\n font-size: 13px;\n}\n\n.LOjPTW_button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_button:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.LOjPTW_primaryButton {\n background: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.LOjPTW_dangerButton {\n background: var(--dsw-alias-state-danger, #c62828);\n border-color: var(--dsw-alias-state-danger, #c62828);\n color: #fff;\n}\n\n.LOjPTW_qr {\n background: #fff;\n border-radius: 8px;\n width: 200px;\n height: 200px;\n margin: 8px 0;\n display: block;\n}\n\n.LOjPTW_message {\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n font-size: 12.5px;\n line-height: 1.5;\n}\n\n.LOjPTW_error {\n color: var(--dsw-alias-state-danger, #c62828);\n}\n\n.LOjPTW_tableWrap {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n overflow: auto;\n}\n\n.LOjPTW_table {\n border-collapse: collapse;\n width: 100%;\n font-size: 12.5px;\n}\n\n.LOjPTW_table th {\n z-index: 1;\n text-align: left;\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n padding: 8px 10px;\n position: sticky;\n top: 0;\n}\n\n.LOjPTW_table td {\n border-top: 1px solid var(--dsw-alias-border-l1);\n vertical-align: top;\n padding: 8px 10px;\n}\n\n.LOjPTW_badge {\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n display: inline-block;\n}\n\n.LOjPTW_badgeOk {\n color: #229a55;\n background: #229a5524;\n}\n\n.LOjPTW_badgeFail {\n color: #c62828;\n background: #c6282824;\n}\n\n.LOjPTW_cards {\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 8px;\n display: grid;\n}\n\n.LOjPTW_stat {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n padding: 10px;\n}\n\n.LOjPTW_cardLabel {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n.LOjPTW_cardValue {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n}\n\n.LOjPTW_upgradeLayer {\n flex: none;\n align-items: center;\n width: 100%;\n height: 42px;\n margin: 8px 0 0;\n display: flex;\n position: relative;\n}\n\n.LOjPTW_upgradeLayerRail {\n width: 36px;\n height: 36px;\n margin: 0;\n}\n\n.LOjPTW_upgradeButton {\n width: calc(100% + 4px);\n height: 42px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n background: none;\n border: none;\n border-radius: 12px;\n align-items: center;\n gap: 8px;\n margin: 0 -2px;\n padding: 0 10px 0 8px;\n font-family: inherit;\n font-size: 14px;\n display: inline-flex;\n overflow: hidden;\n}\n\n.LOjPTW_upgradeButton:hover, .LOjPTW_upgradeButton[data-outdated] {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_upgradeButton:disabled {\n cursor: progress;\n opacity: .72;\n}\n\n.LOjPTW_upgradeButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.LOjPTW_upgradeLayerRail .LOjPTW_upgradeButton {\n border-radius: 50%;\n justify-content: center;\n gap: 0;\n width: 36px;\n height: 36px;\n padding: 0;\n}\n\n.LOjPTW_upgradeLabel {\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n overflow: hidden;\n}\n\n.LOjPTW_upgradeMeta {\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n flex: none;\n margin-left: auto;\n font-size: 12px;\n line-height: 16px;\n}\n\n.LOjPTW_upgradeDot {\n background: var(--dsw-alias-brand-primary);\n border-radius: 50%;\n flex: none;\n width: 7px;\n height: 7px;\n}\n\n.LOjPTW_changelogDate {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n margin-left: 8px;\n font-weight: 500;\n}\n\n.LOjPTW_changelogBlock {\n flex-direction: column;\n gap: 6px;\n display: flex;\n}\n\n.LOjPTW_changelogHeading {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.LOjPTW_changelogList {\n color: var(--dsw-alias-label-primary);\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding-left: 18px;\n font-size: 13px;\n line-height: 1.55;\n display: flex;\n}\n\n.LOjPTW_changelogList li + li {\n margin: 0;\n}\n\n.LOjPTW_changelogCode {\n background: var(--dsw-alias-bg-layer-3);\n border: 1px solid var(--dsw-alias-border-l2);\n word-break: break-all;\n border-radius: 6px;\n padding: 1px 6px;\n font-size: 12px;\n}\n";document.head.appendChild(s);})();
|
|
7
|
+
(function(){var id="dsh-rez-suite-css";if(typeof document==="undefined"||document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent=".r9dlUa_wordmark {\n min-width: 0;\n color: inherit;\n align-items: center;\n gap: 8px;\n display: inline-flex;\n overflow: hidden;\n}\n\n.r9dlUa_mark {\n object-fit: contain;\n flex: none;\n width: 28px;\n height: 28px;\n display: block;\n}\n\n.r9dlUa_markOnly {\n object-fit: contain;\n width: 100%;\n max-width: 28px;\n height: 100%;\n max-height: 28px;\n display: block;\n}\n\n.r9dlUa_name {\n letter-spacing: .02em;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: 15px;\n font-weight: 650;\n overflow: hidden;\n}\n\n.r9dlUa_badge {\n letter-spacing: .06em;\n opacity: .72;\n white-space: nowrap;\n border: 1px solid;\n border-radius: 4px;\n flex: none;\n padding: 2px 6px;\n font-size: 10px;\n font-weight: 700;\n line-height: 1.2;\n}\n.LOjPTW_shell {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n display: flex;\n}\n\n.LOjPTW_card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n\n.LOjPTW_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.LOjPTW_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.LOjPTW_header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n\n.LOjPTW_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.LOjPTW_headText {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n\n.LOjPTW_name {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.LOjPTW_description {\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n\n.LOjPTW_chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n}\n\n.LOjPTW_chevronOpen {\n transform: rotate(180deg);\n}\n\n.LOjPTW_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px 12px;\n padding-top: 8px;\n}\n\n.LOjPTW_tabBar {\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n flex: none;\n gap: 2px;\n display: flex;\n}\n\n.LOjPTW_tab {\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n background: none;\n border: none;\n border-bottom: 2px solid #0000;\n border-radius: 6px 6px 0 0;\n padding: 7px 14px;\n font-size: 13px;\n}\n\n.LOjPTW_tab:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_tab[data-active] {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: var(--dsw-alias-state-business-primary);\n font-weight: 600;\n}\n\n.LOjPTW_tabBody {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n padding: 12px 0 4px;\n display: flex;\n}\n\n.LOjPTW_section {\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base));\n border-radius: 10px;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n display: flex;\n}\n\n.LOjPTW_sectionTitle {\n color: var(--dsw-alias-label-primary);\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.LOjPTW_field {\n flex-direction: column;\n align-items: stretch;\n gap: 4px;\n display: flex;\n}\n\n.LOjPTW_label {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n}\n\n.LOjPTW_input, .LOjPTW_select {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n padding: 6px 8px;\n font-size: 13px;\n}\n\n.LOjPTW_checkbox {\n justify-self: start;\n width: 15px;\n height: 15px;\n}\n\n.LOjPTW_wecomRow {\n grid-template-columns: minmax(7rem, .8fr) minmax(8rem, 1fr) minmax(8rem, 1fr) auto;\n align-items: end;\n gap: 8px;\n display: grid;\n}\n\n.LOjPTW_wecomRow .LOjPTW_message {\n grid-column: 1 / -1;\n}\n\n.LOjPTW_toolbar {\n flex-wrap: wrap;\n flex: none;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.LOjPTW_button {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n border-radius: 8px;\n padding: 7px 12px;\n font-size: 13px;\n}\n\n.LOjPTW_button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_button:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.LOjPTW_primaryButton {\n background: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.LOjPTW_dangerButton {\n background: var(--dsw-alias-state-danger, #c62828);\n border-color: var(--dsw-alias-state-danger, #c62828);\n color: #fff;\n}\n\n.LOjPTW_qr {\n background: #fff;\n border-radius: 8px;\n width: 200px;\n height: 200px;\n margin: 8px 0;\n display: block;\n}\n\n.LOjPTW_message {\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n font-size: 12.5px;\n line-height: 1.5;\n}\n\n.LOjPTW_error {\n color: var(--dsw-alias-state-danger, #c62828);\n}\n\n.LOjPTW_tableWrap {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n overflow: auto;\n}\n\n.LOjPTW_table {\n border-collapse: collapse;\n width: 100%;\n font-size: 12.5px;\n}\n\n.LOjPTW_table th {\n z-index: 1;\n text-align: left;\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n padding: 8px 10px;\n position: sticky;\n top: 0;\n}\n\n.LOjPTW_table td {\n border-top: 1px solid var(--dsw-alias-border-l1);\n vertical-align: top;\n padding: 8px 10px;\n}\n\n.LOjPTW_badge {\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n display: inline-block;\n}\n\n.LOjPTW_badgeOk {\n color: #229a55;\n background: #229a5524;\n}\n\n.LOjPTW_badgeFail {\n color: #c62828;\n background: #c6282824;\n}\n\n.LOjPTW_cards {\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 8px;\n display: grid;\n}\n\n.LOjPTW_stat {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n padding: 10px;\n}\n\n.LOjPTW_cardLabel {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n.LOjPTW_cardValue {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n}\n\n.LOjPTW_upgradeLayer {\n flex: none;\n align-items: center;\n width: 100%;\n height: 42px;\n margin: 8px 0 0;\n display: flex;\n position: relative;\n}\n\n.LOjPTW_upgradeLayerRail {\n width: 36px;\n height: 36px;\n margin: 0;\n}\n\n.LOjPTW_upgradeButton {\n width: calc(100% + 4px);\n height: 42px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n background: none;\n border: none;\n border-radius: 12px;\n align-items: center;\n gap: 8px;\n margin: 0 -2px;\n padding: 0 10px 0 8px;\n font-family: inherit;\n font-size: 14px;\n display: inline-flex;\n overflow: hidden;\n}\n\n.LOjPTW_upgradeButton:hover, .LOjPTW_upgradeButton[data-outdated] {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.LOjPTW_upgradeButton:disabled {\n cursor: progress;\n opacity: .72;\n}\n\n.LOjPTW_upgradeButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.LOjPTW_upgradeLayerRail .LOjPTW_upgradeButton {\n border-radius: 50%;\n justify-content: center;\n gap: 0;\n width: 36px;\n height: 36px;\n padding: 0;\n}\n\n.LOjPTW_upgradeLabel {\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n overflow: hidden;\n}\n\n.LOjPTW_upgradeMeta {\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n flex: none;\n margin-left: auto;\n font-size: 12px;\n line-height: 16px;\n}\n\n.LOjPTW_upgradeDot {\n background: var(--dsw-alias-brand-primary);\n border-radius: 50%;\n flex: none;\n width: 7px;\n height: 7px;\n}\n\n.LOjPTW_changelogDate {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n margin-left: 8px;\n font-weight: 500;\n}\n\n.LOjPTW_changelogBlock {\n flex-direction: column;\n gap: 6px;\n display: flex;\n}\n\n.LOjPTW_changelogHeading {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.LOjPTW_changelogList {\n color: var(--dsw-alias-label-primary);\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding-left: 18px;\n font-size: 13px;\n line-height: 1.55;\n display: flex;\n}\n\n.LOjPTW_changelogList li + li {\n margin: 0;\n}\n\n.LOjPTW_changelogCode {\n background: var(--dsw-alias-bg-layer-3);\n border: 1px solid var(--dsw-alias-border-l2);\n word-break: break-all;\n border-radius: 6px;\n padding: 1px 6px;\n font-size: 12px;\n}\n";document.head.appendChild(s);})();
|
|
8
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
9
|
let react = require("react");
|
|
10
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -139,7 +139,7 @@ const zh = {
|
|
|
139
139
|
"entry.label": "Rez",
|
|
140
140
|
"entry.tooltip": "Rez Suite 配置与审计面板",
|
|
141
141
|
"settings.title": "ReZ-TI",
|
|
142
|
-
"settings.description": "公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC
|
|
142
|
+
"settings.description": "公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC)和企业微信智能机器人。个人微信在「微信」页签扫码。",
|
|
143
143
|
"panel.title": "Rez Suite",
|
|
144
144
|
"tab.config": "配置",
|
|
145
145
|
"tab.weixin": "微信",
|
|
@@ -175,6 +175,12 @@ const zh = {
|
|
|
175
175
|
"config.wecom.botId": "Bot ID",
|
|
176
176
|
"config.wecom.secret": "Secret",
|
|
177
177
|
"config.wecom.webhook": "群机器人 Webhook",
|
|
178
|
+
"config.wecom.hint": "Accio 同款:一个房间绑一个智能机器人。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机 ~/.dsh/wecom-channels.json,不要写进 compose。BotID / Secret 在企业微信管理后台 → 智能机器人。",
|
|
179
|
+
"config.wecom.room": "房间",
|
|
180
|
+
"config.wecom.add": "添加绑定",
|
|
181
|
+
"config.wecom.remove": "删除",
|
|
182
|
+
"config.wecom.empty": "还没有绑定。给人事/财务/法务等房间各建一个企微智能机器人,就能变成不同角色。",
|
|
183
|
+
"config.wecom.status": "通道",
|
|
178
184
|
"config.ha": "Home Assistant",
|
|
179
185
|
"config.ha.url": "HA MCP URL",
|
|
180
186
|
"config.ha.token": "长期访问令牌",
|
|
@@ -268,7 +274,7 @@ const en = {
|
|
|
268
274
|
"entry.label": "Rez",
|
|
269
275
|
"entry.tooltip": "Rez Suite configuration and audit panel",
|
|
270
276
|
"settings.title": "ReZ-TI",
|
|
271
|
-
"settings.description": "Company MCP (Odoo / Nextcloud / HA / TAPD / GSC)
|
|
277
|
+
"settings.description": "Company MCP (Odoo / Nextcloud / HA / TAPD / GSC) and WeCom AI bots. Personal WeChat is the WeChat tab.",
|
|
272
278
|
"panel.title": "Rez Suite",
|
|
273
279
|
"tab.config": "Config",
|
|
274
280
|
"tab.weixin": "WeChat",
|
|
@@ -304,6 +310,12 @@ const en = {
|
|
|
304
310
|
"config.wecom.botId": "Bot ID",
|
|
305
311
|
"config.wecom.secret": "Secret",
|
|
306
312
|
"config.wecom.webhook": "Group bot webhook",
|
|
313
|
+
"config.wecom.hint": "Accio-style: one WeCom AI bot per Harness room. Whoever talks to that bot lands in that sidebar folder. Secrets stay in ~/.dsh/wecom-channels.json, not compose. BotID / Secret come from WeCom admin → AI bot.",
|
|
314
|
+
"config.wecom.room": "Room",
|
|
315
|
+
"config.wecom.add": "Add binding",
|
|
316
|
+
"config.wecom.remove": "Remove",
|
|
317
|
+
"config.wecom.empty": "No bots yet. Bind a WeCom AI bot to HR / finance / legal / … so each room is a different role.",
|
|
318
|
+
"config.wecom.status": "Channel",
|
|
307
319
|
"config.ha": "Home Assistant",
|
|
308
320
|
"config.ha.url": "HA MCP URL",
|
|
309
321
|
"config.ha.token": "Long-lived access token",
|
|
@@ -409,6 +421,7 @@ const REZ_API = {
|
|
|
409
421
|
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
410
422
|
weixin: "/api/dsh-rez-suite/weixin",
|
|
411
423
|
weixinVerify: "/api/dsh-rez-suite/weixin/verify",
|
|
424
|
+
wecomChannels: "/api/dsh-rez-suite/wecom/channels",
|
|
412
425
|
update: "/api/dsh-rez-suite/update"
|
|
413
426
|
};
|
|
414
427
|
//#endregion
|
|
@@ -471,6 +484,16 @@ var RezApi = class {
|
|
|
471
484
|
async weixinLogout() {
|
|
472
485
|
return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
|
|
473
486
|
}
|
|
487
|
+
async wecomChannels() {
|
|
488
|
+
return readJson(await fetch(REZ_API.wecomChannels));
|
|
489
|
+
}
|
|
490
|
+
async saveWecomChannels(bots) {
|
|
491
|
+
return readJson(await fetch(REZ_API.wecomChannels, {
|
|
492
|
+
method: "PUT",
|
|
493
|
+
headers: { "content-type": "application/json" },
|
|
494
|
+
body: JSON.stringify({ bots })
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
474
497
|
async updateStatus() {
|
|
475
498
|
return readJson(await fetch(REZ_API.update));
|
|
476
499
|
}
|
|
@@ -550,7 +573,8 @@ var panel_module_default = {
|
|
|
550
573
|
"upgradeLabel": "LOjPTW_upgradeLabel",
|
|
551
574
|
"upgradeLayer": "LOjPTW_upgradeLayer",
|
|
552
575
|
"upgradeLayerRail": "LOjPTW_upgradeLayerRail",
|
|
553
|
-
"upgradeMeta": "LOjPTW_upgradeMeta"
|
|
576
|
+
"upgradeMeta": "LOjPTW_upgradeMeta",
|
|
577
|
+
"wecomRow": "LOjPTW_wecomRow"
|
|
554
578
|
};
|
|
555
579
|
//#endregion
|
|
556
580
|
//#region src/client/panel/AuditTab.tsx
|
|
@@ -719,6 +743,20 @@ function AuditTab({ api }) {
|
|
|
719
743
|
//#endregion
|
|
720
744
|
//#region src/changelog.ts
|
|
721
745
|
const SUITE_CHANGELOG = [
|
|
746
|
+
{
|
|
747
|
+
version: "0.1.49",
|
|
748
|
+
date: "2026-08-19",
|
|
749
|
+
sections: [{
|
|
750
|
+
title: {
|
|
751
|
+
zh: "企业微信智能机器人",
|
|
752
|
+
en: "WeCom AI bots"
|
|
753
|
+
},
|
|
754
|
+
items: [{
|
|
755
|
+
zh: "设置页按房间绑定企微智能机器人(BotID + Secret)。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机,不进 compose。",
|
|
756
|
+
en: "Bind a WeCom AI bot (BotID + Secret) per Harness room. Talk to that bot in WeCom and the thread lands in that sidebar folder. Secrets stay on disk, not in compose."
|
|
757
|
+
}]
|
|
758
|
+
}]
|
|
759
|
+
},
|
|
722
760
|
{
|
|
723
761
|
version: "0.1.48",
|
|
724
762
|
date: "2026-08-19",
|
|
@@ -910,6 +948,128 @@ const EMPTY_SECRETS = {
|
|
|
910
948
|
tianyancha: "",
|
|
911
949
|
esphome: ""
|
|
912
950
|
};
|
|
951
|
+
const WECOM_SECRET_MASK = "********";
|
|
952
|
+
function WecomSection({ rooms, drafts, status, onChange }) {
|
|
953
|
+
const t = useT();
|
|
954
|
+
const used = new Set(drafts.map((row) => row.room));
|
|
955
|
+
const unused = rooms.filter((room) => !used.has(room));
|
|
956
|
+
const hintFor = (room) => status.find((row) => row.room === room)?.hint ?? "";
|
|
957
|
+
const add = () => {
|
|
958
|
+
const room = unused[0];
|
|
959
|
+
if (room === void 0) return;
|
|
960
|
+
onChange([...drafts, {
|
|
961
|
+
key: `new-${room}-${Date.now()}`,
|
|
962
|
+
room,
|
|
963
|
+
botId: "",
|
|
964
|
+
secret: "",
|
|
965
|
+
secretConfigured: false
|
|
966
|
+
}]);
|
|
967
|
+
};
|
|
968
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
969
|
+
title: t("config.wecom"),
|
|
970
|
+
children: [
|
|
971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
972
|
+
className: panel_module_default.message,
|
|
973
|
+
children: t("config.wecom.hint")
|
|
974
|
+
}),
|
|
975
|
+
drafts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
976
|
+
className: panel_module_default.message,
|
|
977
|
+
children: t("config.wecom.empty")
|
|
978
|
+
}),
|
|
979
|
+
drafts.map((row, index) => {
|
|
980
|
+
const options = rooms.filter((room) => room === row.room || !used.has(room));
|
|
981
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
982
|
+
className: panel_module_default.wecomRow,
|
|
983
|
+
children: [
|
|
984
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
985
|
+
label: t("config.wecom.room"),
|
|
986
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
987
|
+
className: panel_module_default.select,
|
|
988
|
+
value: row.room,
|
|
989
|
+
onChange: (event) => {
|
|
990
|
+
const next = drafts.slice();
|
|
991
|
+
next[index] = {
|
|
992
|
+
...row,
|
|
993
|
+
room: event.target.value
|
|
994
|
+
};
|
|
995
|
+
onChange(next);
|
|
996
|
+
},
|
|
997
|
+
children: options.map((room) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
998
|
+
value: room,
|
|
999
|
+
children: room
|
|
1000
|
+
}, room))
|
|
1001
|
+
})
|
|
1002
|
+
}),
|
|
1003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1004
|
+
label: t("config.wecom.botId"),
|
|
1005
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1006
|
+
className: panel_module_default.input,
|
|
1007
|
+
autoComplete: "off",
|
|
1008
|
+
value: row.botId,
|
|
1009
|
+
onChange: (event) => {
|
|
1010
|
+
const next = drafts.slice();
|
|
1011
|
+
next[index] = {
|
|
1012
|
+
...row,
|
|
1013
|
+
botId: event.target.value
|
|
1014
|
+
};
|
|
1015
|
+
onChange(next);
|
|
1016
|
+
}
|
|
1017
|
+
})
|
|
1018
|
+
}),
|
|
1019
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1020
|
+
label: t("config.wecom.secret"),
|
|
1021
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1022
|
+
className: panel_module_default.input,
|
|
1023
|
+
type: "password",
|
|
1024
|
+
autoComplete: "off",
|
|
1025
|
+
value: row.secret,
|
|
1026
|
+
placeholder: row.secretConfigured ? t("config.secretConfigured") : t("config.secretMissing"),
|
|
1027
|
+
onChange: (event) => {
|
|
1028
|
+
const next = drafts.slice();
|
|
1029
|
+
next[index] = {
|
|
1030
|
+
...row,
|
|
1031
|
+
secret: event.target.value
|
|
1032
|
+
};
|
|
1033
|
+
onChange(next);
|
|
1034
|
+
}
|
|
1035
|
+
})
|
|
1036
|
+
}),
|
|
1037
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1038
|
+
type: "button",
|
|
1039
|
+
className: panel_module_default.button,
|
|
1040
|
+
onClick: () => {
|
|
1041
|
+
onChange(drafts.filter((_, i) => i !== index));
|
|
1042
|
+
},
|
|
1043
|
+
children: t("config.wecom.remove")
|
|
1044
|
+
}),
|
|
1045
|
+
hintFor(row.room) !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1046
|
+
className: panel_module_default.message,
|
|
1047
|
+
children: hintFor(row.room)
|
|
1048
|
+
})
|
|
1049
|
+
]
|
|
1050
|
+
}, row.key);
|
|
1051
|
+
}),
|
|
1052
|
+
unused.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1053
|
+
className: panel_module_default.toolbar,
|
|
1054
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1055
|
+
type: "button",
|
|
1056
|
+
className: panel_module_default.button,
|
|
1057
|
+
onClick: add,
|
|
1058
|
+
children: t("config.wecom.add")
|
|
1059
|
+
})
|
|
1060
|
+
})
|
|
1061
|
+
]
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function toWecomDrafts(bots) {
|
|
1065
|
+
return bots.map((bot, index) => ({
|
|
1066
|
+
key: `${bot.room}-${index}`,
|
|
1067
|
+
room: bot.room,
|
|
1068
|
+
botId: bot.botId,
|
|
1069
|
+
secret: "",
|
|
1070
|
+
secretConfigured: bot.secretConfigured
|
|
1071
|
+
}));
|
|
1072
|
+
}
|
|
913
1073
|
function Field({ label, children }) {
|
|
914
1074
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
915
1075
|
className: panel_module_default.field,
|
|
@@ -1003,9 +1163,38 @@ function ConfigTab({ api }) {
|
|
|
1003
1163
|
const [message, setMessage] = (0, react.useState)("");
|
|
1004
1164
|
const [error, setError] = (0, react.useState)("");
|
|
1005
1165
|
const [results, setResults] = (0, react.useState)([]);
|
|
1166
|
+
const [wecomRooms, setWecomRooms] = (0, react.useState)([]);
|
|
1167
|
+
const [wecomDrafts, setWecomDrafts] = (0, react.useState)([]);
|
|
1168
|
+
const [wecomStatus, setWecomStatus] = (0, react.useState)([]);
|
|
1006
1169
|
const load = async () => {
|
|
1007
1170
|
try {
|
|
1008
1171
|
setConfig(await api.getConfig());
|
|
1172
|
+
try {
|
|
1173
|
+
const wecom = await api.wecomChannels();
|
|
1174
|
+
setWecomRooms(wecom.rooms.length > 0 ? wecom.rooms : [
|
|
1175
|
+
"搞钱",
|
|
1176
|
+
"产品",
|
|
1177
|
+
"运营",
|
|
1178
|
+
"人事助理",
|
|
1179
|
+
"财务助理",
|
|
1180
|
+
"法务助理",
|
|
1181
|
+
"设计",
|
|
1182
|
+
"实验室"
|
|
1183
|
+
]);
|
|
1184
|
+
setWecomDrafts(toWecomDrafts(wecom.bots));
|
|
1185
|
+
setWecomStatus(wecom.status);
|
|
1186
|
+
} catch {
|
|
1187
|
+
setWecomRooms([
|
|
1188
|
+
"搞钱",
|
|
1189
|
+
"产品",
|
|
1190
|
+
"运营",
|
|
1191
|
+
"人事助理",
|
|
1192
|
+
"财务助理",
|
|
1193
|
+
"法务助理",
|
|
1194
|
+
"设计",
|
|
1195
|
+
"实验室"
|
|
1196
|
+
]);
|
|
1197
|
+
}
|
|
1009
1198
|
} catch (err) {
|
|
1010
1199
|
setError(errorMessage(err));
|
|
1011
1200
|
} finally {
|
|
@@ -1092,6 +1281,14 @@ function ConfigTab({ api }) {
|
|
|
1092
1281
|
const saved = await api.saveConfig(payload);
|
|
1093
1282
|
setConfig(saved);
|
|
1094
1283
|
setSecrets(EMPTY_SECRETS);
|
|
1284
|
+
const wecomSaved = await api.saveWecomChannels(wecomDrafts.map((row) => ({
|
|
1285
|
+
room: row.room,
|
|
1286
|
+
botId: row.botId.trim(),
|
|
1287
|
+
secret: row.secret.trim().length > 0 ? row.secret.trim() : row.secretConfigured ? WECOM_SECRET_MASK : ""
|
|
1288
|
+
})));
|
|
1289
|
+
setWecomRooms(wecomSaved.rooms);
|
|
1290
|
+
setWecomDrafts(toWecomDrafts(wecomSaved.bots));
|
|
1291
|
+
setWecomStatus(wecomSaved.status);
|
|
1095
1292
|
setMessage(t("config.saved"));
|
|
1096
1293
|
} catch (err) {
|
|
1097
1294
|
setError(errorMessage(err));
|
|
@@ -1205,6 +1402,12 @@ function ConfigTab({ api }) {
|
|
|
1205
1402
|
usernameLabel: t("config.nextcloud.username"),
|
|
1206
1403
|
secretLabel: t("config.nextcloud.appPassword")
|
|
1207
1404
|
}),
|
|
1405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WecomSection, {
|
|
1406
|
+
rooms: wecomRooms,
|
|
1407
|
+
drafts: wecomDrafts,
|
|
1408
|
+
status: wecomStatus,
|
|
1409
|
+
onChange: setWecomDrafts
|
|
1410
|
+
}),
|
|
1208
1411
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
|
|
1209
1412
|
title: t("config.ha"),
|
|
1210
1413
|
row: config.connections.homeassistant,
|
package/lib/index.js
CHANGED
|
@@ -2935,7 +2935,7 @@ const COMPOSED_SERVERS = [
|
|
|
2935
2935
|
"tianyancha",
|
|
2936
2936
|
"esphome"
|
|
2937
2937
|
];
|
|
2938
|
-
/** Front panel hides
|
|
2938
|
+
/** Front panel hides webhook MCP wechat status; AI bots live in the Config WeCom bindings. TAPD 1154488930001001231. */
|
|
2939
2939
|
const FRONT_HIDDEN_SERVERS = /* @__PURE__ */ new Set(["wechat"]);
|
|
2940
2940
|
function mcpToolPrefix(server) {
|
|
2941
2941
|
if (server === "qcc") return "mcp__qcc-";
|
|
@@ -3062,6 +3062,7 @@ const REZ_API = {
|
|
|
3062
3062
|
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
3063
3063
|
weixin: "/api/dsh-rez-suite/weixin",
|
|
3064
3064
|
weixinVerify: "/api/dsh-rez-suite/weixin/verify",
|
|
3065
|
+
wecomChannels: "/api/dsh-rez-suite/wecom/channels",
|
|
3065
3066
|
update: "/api/dsh-rez-suite/update"
|
|
3066
3067
|
};
|
|
3067
3068
|
//#endregion
|
|
@@ -7719,9 +7720,10 @@ function toolAbortedBeforeDispatchResult(prior) {
|
|
|
7719
7720
|
//#endregion
|
|
7720
7721
|
//#region src/esphome-lab.ts
|
|
7721
7722
|
/**
|
|
7722
|
-
* ESPHome lab: official dsh-mcp-client → lab MCP
|
|
7723
|
-
*
|
|
7724
|
-
*
|
|
7723
|
+
* ESPHome lab client in Harness: official dsh-mcp-client → rezti-lab MCP.
|
|
7724
|
+
* The compiler lives in Mac Mini Docker (`deploy/lab`, compose name rezti-lab).
|
|
7725
|
+
* Missing lab / missing token must not block `dsh web` (failOnStartupError: false).
|
|
7726
|
+
* Never puts firmware bytes into the model context; the tool returns a click-later download link.
|
|
7725
7727
|
*/
|
|
7726
7728
|
const ESPHOME_MCP_URL = "http://127.0.0.1:8100/mcp";
|
|
7727
7729
|
/** POST /v1/compile 只交 YAML、立刻回链接。真机 `esphome compile` 在后台,最长见实验室 `ESPHOME_COMPILE_TIMEOUT`(默认 20 分钟)。 */
|
package/lib/style.css
CHANGED
|
@@ -209,6 +209,17 @@
|
|
|
209
209
|
height: 15px;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
.LOjPTW_wecomRow {
|
|
213
|
+
grid-template-columns: minmax(7rem, .8fr) minmax(8rem, 1fr) minmax(8rem, 1fr) auto;
|
|
214
|
+
align-items: end;
|
|
215
|
+
gap: 8px;
|
|
216
|
+
display: grid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.LOjPTW_wecomRow .LOjPTW_message {
|
|
220
|
+
grid-column: 1 / -1;
|
|
221
|
+
}
|
|
222
|
+
|
|
212
223
|
.LOjPTW_toolbar {
|
|
213
224
|
flex-wrap: wrap;
|
|
214
225
|
flex: none;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-suite",
|
|
3
|
-
"description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.49",
|
|
4
|
+
"version": "0.1.49",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@rezti/dsh-rez-sso": "^0.1.14",
|
|
46
46
|
"@rezti/dsh-rez-tapd": "^0.1.1",
|
|
47
47
|
"@rezti/dsh-rez-token-manager": "^0.1.4",
|
|
48
|
-
"@rezti/dsh-rez-wechat": "^0.1.
|
|
48
|
+
"@rezti/dsh-rez-wechat": "^0.1.15",
|
|
49
49
|
"@rezti/dsh-web-file-uploader": "0.2.3",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
51
51
|
"zod": "^4.4.3"
|
package/src/changelog.ts
CHANGED
|
@@ -20,6 +20,21 @@ export interface ChangelogRelease {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const SUITE_CHANGELOG: readonly ChangelogRelease[] = [
|
|
23
|
+
{
|
|
24
|
+
version: '0.1.49',
|
|
25
|
+
date: '2026-08-19',
|
|
26
|
+
sections: [
|
|
27
|
+
{
|
|
28
|
+
title: { zh: '企业微信智能机器人', en: 'WeCom AI bots' },
|
|
29
|
+
items: [
|
|
30
|
+
{
|
|
31
|
+
zh: '设置页按房间绑定企微智能机器人(BotID + Secret)。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机,不进 compose。',
|
|
32
|
+
en: 'Bind a WeCom AI bot (BotID + Secret) per Harness room. Talk to that bot in WeCom and the thread lands in that sidebar folder. Secrets stay on disk, not in compose.',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
23
38
|
{
|
|
24
39
|
version: '0.1.48',
|
|
25
40
|
date: '2026-08-19',
|
package/src/client/api.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type RezUpdateApplyResult,
|
|
13
13
|
type RezUpdateStatus,
|
|
14
14
|
type RezWeixinStatus,
|
|
15
|
+
type RezWecomChannelsResponse,
|
|
15
16
|
} from '../protocol.ts'
|
|
16
17
|
|
|
17
18
|
export class RezApiError extends Error {
|
|
@@ -100,6 +101,20 @@ export class RezApi {
|
|
|
100
101
|
return readJson<RezWeixinStatus>(response)
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
async wecomChannels(): Promise<RezWecomChannelsResponse> {
|
|
105
|
+
const response = await fetch(REZ_API.wecomChannels)
|
|
106
|
+
return readJson<RezWecomChannelsResponse>(response)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async saveWecomChannels(bots: Array<{ room: string; botId: string; secret?: string }>): Promise<RezWecomChannelsResponse> {
|
|
110
|
+
const response = await fetch(REZ_API.wecomChannels, {
|
|
111
|
+
method: 'PUT',
|
|
112
|
+
headers: { 'content-type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({ bots }),
|
|
114
|
+
})
|
|
115
|
+
return readJson<RezWecomChannelsResponse>(response)
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
async updateStatus(): Promise<RezUpdateStatus> {
|
|
104
119
|
const response = await fetch(REZ_API.update)
|
|
105
120
|
return readJson<RezUpdateStatus>(response)
|
package/src/client/locales.ts
CHANGED
|
@@ -6,7 +6,7 @@ export const zh = {
|
|
|
6
6
|
'entry.label': 'Rez',
|
|
7
7
|
'entry.tooltip': 'Rez Suite 配置与审计面板',
|
|
8
8
|
'settings.title': 'ReZ-TI',
|
|
9
|
-
'settings.description': '公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC
|
|
9
|
+
'settings.description': '公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC)和企业微信智能机器人。个人微信在「微信」页签扫码。',
|
|
10
10
|
'panel.title': 'Rez Suite',
|
|
11
11
|
'tab.config': '配置',
|
|
12
12
|
'tab.weixin': '微信',
|
|
@@ -43,6 +43,12 @@ export const zh = {
|
|
|
43
43
|
'config.wecom.botId': 'Bot ID',
|
|
44
44
|
'config.wecom.secret': 'Secret',
|
|
45
45
|
'config.wecom.webhook': '群机器人 Webhook',
|
|
46
|
+
'config.wecom.hint': 'Accio 同款:一个房间绑一个智能机器人。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机 ~/.dsh/wecom-channels.json,不要写进 compose。BotID / Secret 在企业微信管理后台 → 智能机器人。',
|
|
47
|
+
'config.wecom.room': '房间',
|
|
48
|
+
'config.wecom.add': '添加绑定',
|
|
49
|
+
'config.wecom.remove': '删除',
|
|
50
|
+
'config.wecom.empty': '还没有绑定。给人事/财务/法务等房间各建一个企微智能机器人,就能变成不同角色。',
|
|
51
|
+
'config.wecom.status': '通道',
|
|
46
52
|
'config.ha': 'Home Assistant',
|
|
47
53
|
'config.ha.url': 'HA MCP URL',
|
|
48
54
|
'config.ha.token': '长期访问令牌',
|
|
@@ -142,7 +148,7 @@ export const en: Record<RezKey, string> = {
|
|
|
142
148
|
'entry.label': 'Rez',
|
|
143
149
|
'entry.tooltip': 'Rez Suite configuration and audit panel',
|
|
144
150
|
'settings.title': 'ReZ-TI',
|
|
145
|
-
'settings.description': 'Company MCP (Odoo / Nextcloud / HA / TAPD / GSC)
|
|
151
|
+
'settings.description': 'Company MCP (Odoo / Nextcloud / HA / TAPD / GSC) and WeCom AI bots. Personal WeChat is the WeChat tab.',
|
|
146
152
|
'panel.title': 'Rez Suite',
|
|
147
153
|
'tab.config': 'Config',
|
|
148
154
|
'tab.weixin': 'WeChat',
|
|
@@ -178,6 +184,12 @@ export const en: Record<RezKey, string> = {
|
|
|
178
184
|
'config.wecom.botId': 'Bot ID',
|
|
179
185
|
'config.wecom.secret': 'Secret',
|
|
180
186
|
'config.wecom.webhook': 'Group bot webhook',
|
|
187
|
+
'config.wecom.hint': 'Accio-style: one WeCom AI bot per Harness room. Whoever talks to that bot lands in that sidebar folder. Secrets stay in ~/.dsh/wecom-channels.json, not compose. BotID / Secret come from WeCom admin → AI bot.',
|
|
188
|
+
'config.wecom.room': 'Room',
|
|
189
|
+
'config.wecom.add': 'Add binding',
|
|
190
|
+
'config.wecom.remove': 'Remove',
|
|
191
|
+
'config.wecom.empty': 'No bots yet. Bind a WeCom AI bot to HR / finance / legal / … so each room is a different role.',
|
|
192
|
+
'config.wecom.status': 'Channel',
|
|
181
193
|
'config.ha': 'Home Assistant',
|
|
182
194
|
'config.ha.url': 'HA MCP URL',
|
|
183
195
|
'config.ha.token': 'Long-lived access token',
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useEffect, useState, type ReactNode } from 'react'
|
|
7
7
|
import type { RezApi } from '../api.ts'
|
|
8
|
-
import type { RezPublicConfig, RezPublicConnection, RezRoleId, RezTestResult } from '../../protocol.ts'
|
|
8
|
+
import type { RezPublicConfig, RezPublicConnection, RezRoleId, RezTestResult, RezWecomBindingStatus, RezWecomBotPublic } from '../../protocol.ts'
|
|
9
9
|
import { errorMessage, useT } from './helpers.ts'
|
|
10
10
|
import css from './panel.module.css'
|
|
11
11
|
|
|
@@ -31,6 +31,115 @@ type SecretDrafts = {
|
|
|
31
31
|
|
|
32
32
|
const EMPTY_SECRETS: SecretDrafts = { odoo: '', nextcloud: '', wechat: '', homeassistant: '', tapd: '', gsc: '', qcc: '', tianyancha: '', esphome: '' }
|
|
33
33
|
|
|
34
|
+
const WECOM_SECRET_MASK = '********'
|
|
35
|
+
|
|
36
|
+
type WecomDraft = {
|
|
37
|
+
key: string
|
|
38
|
+
room: string
|
|
39
|
+
botId: string
|
|
40
|
+
secret: string
|
|
41
|
+
secretConfigured: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function WecomSection({
|
|
45
|
+
rooms,
|
|
46
|
+
drafts,
|
|
47
|
+
status,
|
|
48
|
+
onChange,
|
|
49
|
+
}: {
|
|
50
|
+
rooms: string[]
|
|
51
|
+
drafts: WecomDraft[]
|
|
52
|
+
status: RezWecomBindingStatus[]
|
|
53
|
+
onChange: (next: WecomDraft[]) => void
|
|
54
|
+
}) {
|
|
55
|
+
const t = useT()
|
|
56
|
+
const used = new Set(drafts.map(row => row.room))
|
|
57
|
+
const unused = rooms.filter(room => !used.has(room))
|
|
58
|
+
const hintFor = (room: string): string => status.find(row => row.room === room)?.hint ?? ''
|
|
59
|
+
|
|
60
|
+
const add = (): void => {
|
|
61
|
+
const room = unused[0]
|
|
62
|
+
if (room === undefined) return
|
|
63
|
+
onChange([...drafts, { key: `new-${room}-${Date.now()}`, room, botId: '', secret: '', secretConfigured: false }])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Section title={t('config.wecom')}>
|
|
68
|
+
<p className={css.message}>{t('config.wecom.hint')}</p>
|
|
69
|
+
{drafts.length === 0 && <p className={css.message}>{t('config.wecom.empty')}</p>}
|
|
70
|
+
{drafts.map((row, index) => {
|
|
71
|
+
const options = rooms.filter(room => room === row.room || !used.has(room))
|
|
72
|
+
return (
|
|
73
|
+
<div key={row.key} className={css.wecomRow}>
|
|
74
|
+
<Field label={t('config.wecom.room')}>
|
|
75
|
+
<select
|
|
76
|
+
className={css.select}
|
|
77
|
+
value={row.room}
|
|
78
|
+
onChange={event => {
|
|
79
|
+
const next = drafts.slice()
|
|
80
|
+
next[index] = { ...row, room: event.target.value }
|
|
81
|
+
onChange(next)
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{options.map(room => <option key={room} value={room}>{room}</option>)}
|
|
85
|
+
</select>
|
|
86
|
+
</Field>
|
|
87
|
+
<Field label={t('config.wecom.botId')}>
|
|
88
|
+
<input
|
|
89
|
+
className={css.input}
|
|
90
|
+
autoComplete="off"
|
|
91
|
+
value={row.botId}
|
|
92
|
+
onChange={event => {
|
|
93
|
+
const next = drafts.slice()
|
|
94
|
+
next[index] = { ...row, botId: event.target.value }
|
|
95
|
+
onChange(next)
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
</Field>
|
|
99
|
+
<Field label={t('config.wecom.secret')}>
|
|
100
|
+
<input
|
|
101
|
+
className={css.input}
|
|
102
|
+
type="password"
|
|
103
|
+
autoComplete="off"
|
|
104
|
+
value={row.secret}
|
|
105
|
+
placeholder={row.secretConfigured ? t('config.secretConfigured') : t('config.secretMissing')}
|
|
106
|
+
onChange={event => {
|
|
107
|
+
const next = drafts.slice()
|
|
108
|
+
next[index] = { ...row, secret: event.target.value }
|
|
109
|
+
onChange(next)
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
</Field>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
className={css.button}
|
|
116
|
+
onClick={() => { onChange(drafts.filter((_, i) => i !== index)) }}
|
|
117
|
+
>
|
|
118
|
+
{t('config.wecom.remove')}
|
|
119
|
+
</button>
|
|
120
|
+
{hintFor(row.room) !== '' && <p className={css.message}>{hintFor(row.room)}</p>}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
})}
|
|
124
|
+
{unused.length > 0 && (
|
|
125
|
+
<div className={css.toolbar}>
|
|
126
|
+
<button type="button" className={css.button} onClick={add}>{t('config.wecom.add')}</button>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</Section>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function toWecomDrafts(bots: RezWecomBotPublic[]): WecomDraft[] {
|
|
134
|
+
return bots.map((bot, index) => ({
|
|
135
|
+
key: `${bot.room}-${index}`,
|
|
136
|
+
room: bot.room,
|
|
137
|
+
botId: bot.botId,
|
|
138
|
+
secret: '',
|
|
139
|
+
secretConfigured: bot.secretConfigured,
|
|
140
|
+
}))
|
|
141
|
+
}
|
|
142
|
+
|
|
34
143
|
function Field({ label, children }: { label: string; children: ReactNode }) {
|
|
35
144
|
return (
|
|
36
145
|
<div className={css.field}>
|
|
@@ -133,10 +242,23 @@ export function ConfigTab({ api }: { api: RezApi }) {
|
|
|
133
242
|
const [message, setMessage] = useState('')
|
|
134
243
|
const [error, setError] = useState('')
|
|
135
244
|
const [results, setResults] = useState<RezTestResult[]>([])
|
|
245
|
+
const [wecomRooms, setWecomRooms] = useState<string[]>([])
|
|
246
|
+
const [wecomDrafts, setWecomDrafts] = useState<WecomDraft[]>([])
|
|
247
|
+
const [wecomStatus, setWecomStatus] = useState<RezWecomBindingStatus[]>([])
|
|
136
248
|
|
|
137
249
|
const load = async (): Promise<void> => {
|
|
138
250
|
try {
|
|
139
251
|
setConfig(await api.getConfig())
|
|
252
|
+
try {
|
|
253
|
+
const wecom = await api.wecomChannels()
|
|
254
|
+
setWecomRooms(wecom.rooms.length > 0 ? wecom.rooms : [
|
|
255
|
+
'搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '实验室',
|
|
256
|
+
])
|
|
257
|
+
setWecomDrafts(toWecomDrafts(wecom.bots))
|
|
258
|
+
setWecomStatus(wecom.status)
|
|
259
|
+
} catch {
|
|
260
|
+
setWecomRooms(['搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '实验室'])
|
|
261
|
+
}
|
|
140
262
|
} catch (err) {
|
|
141
263
|
setError(errorMessage(err))
|
|
142
264
|
} finally {
|
|
@@ -184,6 +306,14 @@ export function ConfigTab({ api }: { api: RezApi }) {
|
|
|
184
306
|
const saved = await api.saveConfig(payload)
|
|
185
307
|
setConfig(saved)
|
|
186
308
|
setSecrets(EMPTY_SECRETS)
|
|
309
|
+
const wecomSaved = await api.saveWecomChannels(wecomDrafts.map(row => ({
|
|
310
|
+
room: row.room,
|
|
311
|
+
botId: row.botId.trim(),
|
|
312
|
+
secret: row.secret.trim().length > 0 ? row.secret.trim() : (row.secretConfigured ? WECOM_SECRET_MASK : ''),
|
|
313
|
+
})))
|
|
314
|
+
setWecomRooms(wecomSaved.rooms)
|
|
315
|
+
setWecomDrafts(toWecomDrafts(wecomSaved.bots))
|
|
316
|
+
setWecomStatus(wecomSaved.status)
|
|
187
317
|
setMessage(t('config.saved'))
|
|
188
318
|
} catch (err) {
|
|
189
319
|
setError(errorMessage(err))
|
|
@@ -251,7 +381,12 @@ export function ConfigTab({ api }: { api: RezApi }) {
|
|
|
251
381
|
usernameLabel={t('config.nextcloud.username')}
|
|
252
382
|
secretLabel={t('config.nextcloud.appPassword')}
|
|
253
383
|
/>
|
|
254
|
-
|
|
384
|
+
<WecomSection
|
|
385
|
+
rooms={wecomRooms}
|
|
386
|
+
drafts={wecomDrafts}
|
|
387
|
+
status={wecomStatus}
|
|
388
|
+
onChange={setWecomDrafts}
|
|
389
|
+
/>
|
|
255
390
|
<McpSection
|
|
256
391
|
title={t('config.ha')}
|
|
257
392
|
row={config.connections.homeassistant}
|
|
@@ -169,6 +169,17 @@
|
|
|
169
169
|
height: 15px;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
.wecomRow {
|
|
173
|
+
display: grid;
|
|
174
|
+
grid-template-columns: minmax(7rem, 0.8fr) minmax(8rem, 1fr) minmax(8rem, 1fr) auto;
|
|
175
|
+
gap: 8px;
|
|
176
|
+
align-items: end;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.wecomRow .message {
|
|
180
|
+
grid-column: 1 / -1;
|
|
181
|
+
}
|
|
182
|
+
|
|
172
183
|
.toolbar {
|
|
173
184
|
display: flex;
|
|
174
185
|
align-items: center;
|
package/src/esphome-lab.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ESPHome lab: official dsh-mcp-client → lab MCP
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* ESPHome lab client in Harness: official dsh-mcp-client → rezti-lab MCP.
|
|
3
|
+
* The compiler lives in Mac Mini Docker (`deploy/lab`, compose name rezti-lab).
|
|
4
|
+
* Missing lab / missing token must not block `dsh web` (failOnStartupError: false).
|
|
5
|
+
* Never puts firmware bytes into the model context; the tool returns a click-later download link.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { loadRezConnections, readManagedCredential, REZ_CREDENTIAL_REFS, startMcpWhenSecret } from '@rezti/dsh-rez-sso'
|
package/src/observe-mcp.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant',
|
|
|
14
14
|
|
|
15
15
|
export type ComposedServer = (typeof COMPOSED_SERVERS)[number]
|
|
16
16
|
|
|
17
|
-
/** Front panel hides
|
|
17
|
+
/** Front panel hides webhook MCP wechat status; AI bots live in the Config WeCom bindings. TAPD 1154488930001001231. */
|
|
18
18
|
export const FRONT_HIDDEN_SERVERS: ReadonlySet<ComposedServer> = new Set(['wechat'])
|
|
19
19
|
|
|
20
20
|
export function mcpToolPrefix(server: string): string {
|
package/src/protocol.ts
CHANGED
|
@@ -142,6 +142,28 @@ export interface RezWeixinStatus {
|
|
|
142
142
|
hint: string
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/** Accio-style WeCom AI bot: one BotID+Secret per Harness room. */
|
|
146
|
+
export type RezWecomBindingPhase = 'idle' | 'connecting' | 'authenticated' | 'error'
|
|
147
|
+
|
|
148
|
+
export interface RezWecomBotPublic {
|
|
149
|
+
room: string
|
|
150
|
+
botId: string
|
|
151
|
+
secretConfigured: boolean
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface RezWecomBindingStatus {
|
|
155
|
+
room: string
|
|
156
|
+
botId: string
|
|
157
|
+
phase: RezWecomBindingPhase
|
|
158
|
+
hint: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface RezWecomChannelsResponse {
|
|
162
|
+
rooms: string[]
|
|
163
|
+
bots: RezWecomBotPublic[]
|
|
164
|
+
status: RezWecomBindingStatus[]
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
/** One audit ledger row. */
|
|
146
168
|
export interface RezAuditRecord {
|
|
147
169
|
id: number
|
|
@@ -197,5 +219,6 @@ export const REZ_API = {
|
|
|
197
219
|
auditReset: REZ_API_BASE + '/audit/reset',
|
|
198
220
|
weixin: REZ_API_BASE + '/weixin',
|
|
199
221
|
weixinVerify: REZ_API_BASE + '/weixin/verify',
|
|
222
|
+
wecomChannels: REZ_API_BASE + '/wecom/channels',
|
|
200
223
|
update: REZ_API_BASE + '/update',
|
|
201
224
|
} as const
|
|
@@ -22,21 +22,23 @@ description: 人事助理。引导员工自己领取本机安装包、申请各
|
|
|
22
22
|
|
|
23
23
|
没有 GitHub 权限:找人事要包,不要下未知来源的安装程序。
|
|
24
24
|
|
|
25
|
-
**发包装时说清楚:** zip/exe 只是启动壳,第一次会从外网下 Node
|
|
25
|
+
**发包装时说清楚:** zip/exe 只是启动壳,第一次会从外网下 Node、**pnpm**、dsh,再装钉死的 suite(现在 `0.1.48`)。不是装完就能离线用。长期分发走 CI artifact,不要把仓里 `deploy/dist/` 当主渠道。
|
|
26
26
|
|
|
27
27
|
回形针上传走 npm `@rezti/dsh-web-file-uploader`,装进 suite 即可。不要再单独从 GitHub add。
|
|
28
28
|
|
|
29
29
|
### Mac
|
|
30
30
|
|
|
31
31
|
1. 解压 zip,把 **ReZ-TI Harness** 拖进「应用程序」。
|
|
32
|
-
2.
|
|
33
|
-
3.
|
|
32
|
+
2. 若提示无法打开 / 已损坏:让对方双击包里的 **首次打开.command**,或在终端执行 `xattr -cr "/Applications/ReZ-TI Harness.app"`,再**右键 → 打开**。
|
|
33
|
+
3. 第一次会弹出终端:下载 Node、安装 pnpm 和稳定 dsh、拉 **0.1.48** suite。不是装完就能离线用。
|
|
34
|
+
4. 浏览器打开 `http://127.0.0.1:3080/`。失败看 `~/Library/Application Support/ReZ-TI/harness/logs/harness.log`。
|
|
34
35
|
|
|
35
36
|
### Windows
|
|
36
37
|
|
|
37
38
|
1. 运行 exe(装到 `%LOCALAPPDATA%\ReZ-TI\Harness`,一般不需要管理员)。
|
|
38
|
-
2.
|
|
39
|
-
3.
|
|
39
|
+
2. 若提示「Windows 已保护你的电脑」:让对方点 **更多信息 → 仍要运行**。未签名,不是病毒。
|
|
40
|
+
3. 桌面 / 开始菜单点 **ReZ-TI Harness**。会弹出黑色命令窗口,不要立刻关掉。第一次拉 Node、**pnpm**、dsh、钉死 suite。
|
|
41
|
+
4. 打开 `http://127.0.0.1:3080/`。窗口一闪而过:看 `%LOCALAPPDATA%\ReZ-TI\harness\logs\harness.log`。ARM 电脑暂不支持。
|
|
40
42
|
|
|
41
43
|
密钥写在本机 `~/.dsh`(Windows `%USERPROFILE%\.dsh`)。不要删这份目录。
|
|
42
44
|
|
|
@@ -79,7 +81,7 @@ description: 人事助理。引导员工自己领取本机安装包、申请各
|
|
|
79
81
|
| 用途 | 面板 | 谁要 | 怎么拿 |
|
|
80
82
|
|------|------|------|--------|
|
|
81
83
|
| TAPD Access Token | TAPD → Access Token;项目 ID 默认 `54488930`;**昵称改成自己的 TAPD 显示名**(不要留「凯文智能家居」) | 跟需求/缺陷的岗 | TAPD 个人设置生成 token。昵称必须是 TAPD 里的中文名 |
|
|
82
|
-
|
|
|
84
|
+
| 企微智能机器人 BotID + Secret | 企业微信 → 智能机器人 | 要在企微里当某个房间角色说话的岗 | 设置页「企业微信」:选房间、填 BotID 和 Secret。一个房间一个 bot。密钥只存本机,不要发群、不要写进 compose。不要再申请群 Webhook |
|
|
83
85
|
| HA 长期访问令牌 | Home Assistant → 长期访问令牌 | 只调试 HA 的岗 | HA 个人资料 → 长期访问令牌。默认 URL `http://homeassistant.local:8123/api/mcp`,现场不同再改 |
|
|
84
86
|
| GSC 服务账号 JSON | GSC → 服务账号 JSON 或密钥文件路径 | 运营/SEO | 向凯文要公司服务账号文件路径,不要个人 Google 帐号硬接 |
|
|
85
87
|
| 企查查 API Key | 企查查(法务助理) | **仅法务助理房间** | 未开通就说法务房间未挂企查查。其他房间不要引导申请 |
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## 每次开场
|
|
8
8
|
|
|
9
9
|
1. YAML 来源:对方说板型/功能(ESP32-H2 / C3 等)你就写完整 YAML;或回形针 `.yaml` / `.yml`;或把全文贴进对话。缺 `esphome.name` 就补。密钥不要写进 MEMORY,也不要放到公开下载目录。
|
|
10
|
-
2. 调 `mcp__esphome__compile`。没有 MCP 再用 `rez_esphome_compile`。未配置实验室 URL / Token 就说「实验室编译未开通」,指向 `deploy/
|
|
10
|
+
2. 调 `mcp__esphome__compile`。没有 MCP 再用 `rez_esphome_compile`。未配置实验室 URL / Token 就说「实验室编译未开通」,指向 `deploy/lab/README.md`。不要自写编译器,不要自写 Device Builder WebSocket。实验室 Docker 没起时 Harness 仍可用,不要假装已经编完。
|
|
11
11
|
3. 工具立刻返回 **download 链接**。把链接发给对方去点。编译要几分钟:先点开是 `README.md`(编译中或失败日志);成功后再点/刷新是 `firmware.bin`。有 Nextcloud 时链接是公开目录。**不要把 bin 贴进对话。**
|
|
12
12
|
4. 默认不 OTA、不替对方刷机。对方明确要求刷机再另说,并先征得批准。
|
|
13
13
|
|