@rezti/dsh-rez-suite 0.1.49 → 0.1.50
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 +8 -2
- package/README.md +3 -3
- package/README.zh.md +2 -2
- package/cordis.patch.yml +1 -1
- package/lib/client.d.ts +31 -3
- package/lib/client.js +329 -7
- package/lib/index.js +241 -15
- package/lib/style.css +7 -0
- package/package.json +4 -3
- package/src/boss/mount.ts +4 -3
- package/src/boss/register.ts +1 -1
- package/src/boss/seed.ts +29 -0
- package/src/changelog.ts +33 -0
- package/src/channel-board.ts +55 -0
- package/src/client/locales.ts +62 -6
- package/src/client/panel/BoardTab.tsx +136 -0
- package/src/client/panel/ConfigTab.tsx +2 -2
- package/src/client/panel/StatusTab.tsx +31 -0
- package/src/client/panel/panel.module.css +7 -0
- package/src/client/settings-card.tsx +4 -1
- package/src/index.ts +3 -2
- package/src/protocol.ts +14 -0
- package/src/routes.ts +7 -1
- package/src/tools.ts +49 -6
- package/src/wecom-cli.ts +125 -0
- package/templates/boss/ops/AGENTS.md +2 -0
- package/templates/shared/wecom-cli.SOURCE.md +5 -0
- package/templates/shared/wecom-office/SKILL.md +31 -0
- package/templates/shared/wecomcli-calendar/SKILL.md +303 -0
- package/templates/shared/wecomcli-contact/SKILL.md +58 -0
- package/templates/shared/wecomcli-disk/SKILL.md +389 -0
- package/templates/shared/wecomcli-doc/SKILL.md +137 -0
- package/templates/shared/wecomcli-doc-manage/SKILL.md +132 -0
- package/templates/shared/wecomcli-email/SKILL.md +218 -0
- package/templates/shared/wecomcli-media/SKILL.md +98 -0
- package/templates/shared/wecomcli-meeting/SKILL.md +373 -0
- package/templates/shared/wecomcli-message/SKILL.md +200 -0
- package/templates/shared/wecomcli-shared/SKILL.md +73 -0
- package/templates/shared/wecomcli-sheet/SKILL.md +172 -0
- package/templates/shared/wecomcli-smartpage/SKILL.md +170 -0
- package/templates/shared/wecomcli-smartsheet/SKILL.md +154 -0
- package/templates/shared/wecomcli-todo/SKILL.md +76 -0
- package/templates/staff/design/AGENTS.md +2 -1
- package/templates/staff/ecommerce/.agents/skills/ops-ecommerce/SKILL.md +6 -0
- package/templates/staff/ecommerce/AGENTS.md +48 -0
- package/templates/staff/ecommerce/BOOTSTRAP.md +23 -0
- package/templates/staff/ecommerce/IDENTITY.md +8 -0
- package/templates/staff/ecommerce/MEMORY.md +9 -0
- package/templates/staff/ecommerce/PRIORITIES.md +3 -0
- package/templates/staff/ecommerce/SOUL.md +5 -0
- package/templates/staff/ecommerce/USER.md +8 -0
- package/templates/staff/hr/.agents/skills/staff-onboard-keys/SKILL.md +1 -1
- package/templates/staff/publish/.agents/skills/ops-publish/SKILL.md +6 -0
- package/templates/staff/publish/AGENTS.md +56 -0
- package/templates/staff/publish/BOOTSTRAP.md +23 -0
- package/templates/staff/publish/IDENTITY.md +8 -0
- package/templates/staff/publish/MEMORY.md +9 -0
- package/templates/staff/publish/PRIORITIES.md +3 -0
- package/templates/staff/publish/SOUL.md +6 -0
- package/templates/staff/publish/USER.md +8 -0
package/src/client/locales.ts
CHANGED
|
@@ -9,13 +9,14 @@ export const zh = {
|
|
|
9
9
|
'settings.description': '公司 MCP(Odoo / Nextcloud / HA / TAPD / GSC)和企业微信智能机器人。个人微信在「微信」页签扫码。',
|
|
10
10
|
'panel.title': 'Rez Suite',
|
|
11
11
|
'tab.config': '配置',
|
|
12
|
+
'tab.board': '看板',
|
|
12
13
|
'tab.weixin': '微信',
|
|
13
14
|
'tab.status': '状态',
|
|
14
15
|
'tab.audit': '用量',
|
|
15
16
|
'tab.changelog': '更新日志',
|
|
16
17
|
// config
|
|
17
18
|
'config.role': '角色',
|
|
18
|
-
'config.roleHint': '
|
|
19
|
+
'config.roleHint': '选身份后左侧自动出现工作区。人事/财务/法务助理全员可见;设计、发布、电商房间仅「运营」;实验室仅「工程师」;搞钱/产品/运营仅「老板」。',
|
|
19
20
|
'role.all': '全部',
|
|
20
21
|
'role.boss': '老板',
|
|
21
22
|
'role.engineer': '工程师',
|
|
@@ -43,7 +44,7 @@ export const zh = {
|
|
|
43
44
|
'config.wecom.botId': 'Bot ID',
|
|
44
45
|
'config.wecom.secret': 'Secret',
|
|
45
46
|
'config.wecom.webhook': '群机器人 Webhook',
|
|
46
|
-
'config.wecom.hint': 'Accio 同款:一个房间绑一个智能机器人。同事在企微跟哪个 bot
|
|
47
|
+
'config.wecom.hint': 'Accio 同款:一个房间绑一个智能机器人。同事在企微跟哪个 bot 说话,就进网页左侧那个房间的同一条会话。密钥只存本机 ~/.dsh/wecom-channels.json,不要写进 compose。BotID / Secret 在企业微信管理后台 → 智能机器人。',
|
|
47
48
|
'config.wecom.room': '房间',
|
|
48
49
|
'config.wecom.add': '添加绑定',
|
|
49
50
|
'config.wecom.remove': '删除',
|
|
@@ -84,7 +85,7 @@ export const zh = {
|
|
|
84
85
|
'config.testOk': '成功({tools} 个工具, {latency}ms)',
|
|
85
86
|
'config.testFail': '失败:{error}',
|
|
86
87
|
// status
|
|
87
|
-
'status.hint': '绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client
|
|
88
|
+
'status.hint': '绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。企微办公能力看下方 wecom-cli,不是 MCP 表。企查查只在法务助理房间可用;其它房间不显示或显示「房间未启用」。',
|
|
88
89
|
'status.total': '当前角色 {role},共注册 {count} 个 MCP 工具',
|
|
89
90
|
'status.col.server': 'Server',
|
|
90
91
|
'status.col.state': '状态',
|
|
@@ -92,6 +93,12 @@ export const zh = {
|
|
|
92
93
|
'status.col.registered': '已注册',
|
|
93
94
|
'status.col.error': '错误',
|
|
94
95
|
'status.none': '暂无状态',
|
|
96
|
+
'status.wecomCli': '企业微信办公(官方 wecom-cli,不是 MCP)',
|
|
97
|
+
'status.wecomCli.installed': 'CLI',
|
|
98
|
+
'status.wecomCli.authorized': '授权',
|
|
99
|
+
'status.wecomCli.yes': '已就绪',
|
|
100
|
+
'status.wecomCli.no': '未就绪',
|
|
101
|
+
'status.wecomCli.modules': '品类',
|
|
95
102
|
// audit
|
|
96
103
|
'audit.totalCalls': '调用次数',
|
|
97
104
|
'audit.inputTokens': '输入 token',
|
|
@@ -114,6 +121,27 @@ export const zh = {
|
|
|
114
121
|
'audit.empty': '暂无记录',
|
|
115
122
|
'changelog.current': '当前 {version}',
|
|
116
123
|
'changelog.upgrade': '升级:',
|
|
124
|
+
'board.hint': '合同 Account 拆两室:媒体走「发布」,电商走「电商」(一期只阿里巴巴国际站;Amazon 等有号)。不是独立后台。密钥不在这里。',
|
|
125
|
+
'board.people': 'People / 权限',
|
|
126
|
+
'board.peopleHint': '继续 Odoo 员工帐号(公司/记录规则)+ 企业微信一房间一 bot。不新建权限库,不改 Odoo 权限组。',
|
|
127
|
+
'board.role': '当前 Harness 身份:{role}(只决定侧栏房间)',
|
|
128
|
+
'board.media': '媒体连接',
|
|
129
|
+
'board.mediaHint': '内容/社媒在「发布」工作间(Post editor)。不建图库。',
|
|
130
|
+
'board.ecommerce': '电商平台',
|
|
131
|
+
'board.ecommerceHint': 'Listing 在「电商」工作间。一期只有阿里巴巴国际站。渠道商品 ID 写回 Odoo,库存从 Odoo 推到阿里。Amazon 没有号,标后续。',
|
|
132
|
+
'board.rag': 'RAG',
|
|
133
|
+
'board.ragHint': '基于已有 Dify,知识库对接 Nextcloud 锁死路径(Standards + Marketing/Publish)。Harness 不做推理。',
|
|
134
|
+
'board.inbox': 'Message inbox',
|
|
135
|
+
'board.inboxHint': '一期只做 WhatsApp Business,会话落到 Odoo(浙江雷泽,Business 号已有)。不用官方收费模块,走社区 add-on(OCA mail_gateway_whatsapp)。禁止自写 Meta 客户端。生产安装另开窗口。',
|
|
136
|
+
'board.col.source': '来源',
|
|
137
|
+
'board.col.state': '状态',
|
|
138
|
+
'board.col.channel': '渠道',
|
|
139
|
+
'board.col.room': '房间',
|
|
140
|
+
'board.col.phase': '阶段',
|
|
141
|
+
'board.bound': '已绑 bot',
|
|
142
|
+
'board.unbound': '未绑定',
|
|
143
|
+
'board.phase1': '一期',
|
|
144
|
+
'board.phaseLater': '后续',
|
|
117
145
|
// common
|
|
118
146
|
'common.close': '关闭',
|
|
119
147
|
'common.loading': '加载中…',
|
|
@@ -151,12 +179,13 @@ export const en: Record<RezKey, string> = {
|
|
|
151
179
|
'settings.description': 'Company MCP (Odoo / Nextcloud / HA / TAPD / GSC) and WeCom AI bots. Personal WeChat is the WeChat tab.',
|
|
152
180
|
'panel.title': 'Rez Suite',
|
|
153
181
|
'tab.config': 'Config',
|
|
182
|
+
'tab.board': 'Board',
|
|
154
183
|
'tab.weixin': 'WeChat',
|
|
155
184
|
'tab.status': 'Status',
|
|
156
185
|
'tab.audit': 'Usage',
|
|
157
186
|
'tab.changelog': 'Changelog',
|
|
158
187
|
'config.role': 'Role',
|
|
159
|
-
'config.roleHint': 'Identity registers workspaces in the sidebar. Staff assistants for every role; 设计 for Operations only; 实验室 for Engineer only; 搞钱 / 产品 / 运营 for Boss only.',
|
|
188
|
+
'config.roleHint': 'Identity registers workspaces in the sidebar. Staff assistants for every role; 设计 / 发布 / 电商 for Operations only; 实验室 for Engineer only; 搞钱 / 产品 / 运营 for Boss only.',
|
|
160
189
|
'role.all': 'All',
|
|
161
190
|
'role.boss': 'Boss',
|
|
162
191
|
'role.engineer': 'Engineer',
|
|
@@ -184,7 +213,7 @@ export const en: Record<RezKey, string> = {
|
|
|
184
213
|
'config.wecom.botId': 'Bot ID',
|
|
185
214
|
'config.wecom.secret': 'Secret',
|
|
186
215
|
'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
|
|
216
|
+
'config.wecom.hint': 'Accio-style: one WeCom AI bot per Harness room. Whoever talks to that bot lands in the same sidebar session. Secrets stay in ~/.dsh/wecom-channels.json, not compose. BotID / Secret come from WeCom admin → AI bot.',
|
|
188
217
|
'config.wecom.room': 'Room',
|
|
189
218
|
'config.wecom.add': 'Add binding',
|
|
190
219
|
'config.wecom.remove': 'Remove',
|
|
@@ -224,7 +253,7 @@ export const en: Record<RezKey, string> = {
|
|
|
224
253
|
'config.testResult': '{server}: {status}',
|
|
225
254
|
'config.testOk': 'OK ({tools} tools, {latency}ms)',
|
|
226
255
|
'config.testFail': 'Failed: {error}',
|
|
227
|
-
'status.hint': 'Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet. Qichacha is only for the Legal assistant room; other rooms hide the row or show “room not enabled”.',
|
|
256
|
+
'status.hint': 'Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet. WeCom office APIs are the wecom-cli row below, not the MCP table. Qichacha is only for the Legal assistant room; other rooms hide the row or show “room not enabled”.',
|
|
228
257
|
'status.total': 'Role {role}, {count} MCP tools registered',
|
|
229
258
|
'status.col.server': 'Server',
|
|
230
259
|
'status.col.state': 'State',
|
|
@@ -232,6 +261,12 @@ export const en: Record<RezKey, string> = {
|
|
|
232
261
|
'status.col.registered': 'Registered',
|
|
233
262
|
'status.col.error': 'Error',
|
|
234
263
|
'status.none': 'No status yet',
|
|
264
|
+
'status.wecomCli': 'WeCom office APIs (official wecom-cli, not MCP)',
|
|
265
|
+
'status.wecomCli.installed': 'CLI',
|
|
266
|
+
'status.wecomCli.authorized': 'Auth',
|
|
267
|
+
'status.wecomCli.yes': 'Ready',
|
|
268
|
+
'status.wecomCli.no': 'Not ready',
|
|
269
|
+
'status.wecomCli.modules': 'Modules',
|
|
235
270
|
'audit.totalCalls': 'Calls',
|
|
236
271
|
'audit.inputTokens': 'Input tokens',
|
|
237
272
|
'audit.outputTokens': 'Output tokens',
|
|
@@ -253,6 +288,27 @@ export const en: Record<RezKey, string> = {
|
|
|
253
288
|
'audit.empty': 'No records',
|
|
254
289
|
'changelog.current': 'Now {version}',
|
|
255
290
|
'changelog.upgrade': 'Upgrade:',
|
|
291
|
+
'board.hint': 'Account connections split into two rooms: media in 发布, ecommerce in 电商 (phase 1: Alibaba.com only; Amazon waits for an account). Not a separate admin site. Secrets stay off this board.',
|
|
292
|
+
'board.people': 'People / permissions',
|
|
293
|
+
'board.peopleHint': 'Keep Odoo employee accounts (company / record rules) plus one WeCom AI bot per room. No new ACL database. Do not change Odoo groups.',
|
|
294
|
+
'board.role': 'Current Harness identity: {role} (sidebar rooms only)',
|
|
295
|
+
'board.media': 'Media connections',
|
|
296
|
+
'board.mediaHint': 'Content / social lives in the 发布 room (Post editor). No media library.',
|
|
297
|
+
'board.ecommerce': 'Ecommerce platforms',
|
|
298
|
+
'board.ecommerceHint': 'Listings live in the 电商 room. Phase 1 is Alibaba.com. Write channel product IDs back to Odoo and push stock Odoo → Alibaba. Amazon is later until we have an account.',
|
|
299
|
+
'board.rag': 'RAG',
|
|
300
|
+
'board.ragHint': 'Reuse Dify. Index Nextcloud locked paths (Standards + Marketing/Publish). Harness does not run embeddings.',
|
|
301
|
+
'board.inbox': 'Message inbox',
|
|
302
|
+
'board.inboxHint': 'Phase 1 is WhatsApp Business into Odoo (Zhejiang ReZ-TI; WABA already exists). Skip the paid official module; reuse OCA mail_gateway_whatsapp. No homemade Meta client. Production install needs a separate window.',
|
|
303
|
+
'board.col.source': 'Source',
|
|
304
|
+
'board.col.state': 'State',
|
|
305
|
+
'board.col.channel': 'Channel',
|
|
306
|
+
'board.col.room': 'Room',
|
|
307
|
+
'board.col.phase': 'Phase',
|
|
308
|
+
'board.bound': 'Bot bound',
|
|
309
|
+
'board.unbound': 'Unbound',
|
|
310
|
+
'board.phase1': 'Phase 1',
|
|
311
|
+
'board.phaseLater': 'Later',
|
|
256
312
|
'common.close': 'Close',
|
|
257
313
|
'common.loading': 'Loading…',
|
|
258
314
|
'common.error': 'Error: {error}',
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account board: media (发布) vs ecommerce (电商; phase 1 Alibaba, Amazon later).
|
|
3
|
+
* People = Odoo user + WeCom room bots. No new ACL store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useState } from 'react'
|
|
7
|
+
import type { RezApi } from '../api.ts'
|
|
8
|
+
import type { RezPublicConfig, RezWecomBotPublic } from '../../protocol.ts'
|
|
9
|
+
import { ECOMMERCE_ROOM, MEDIA_ROOM, channelsFor, wecomBound } from '../../channel-board.ts'
|
|
10
|
+
import { errorMessage, useT } from './helpers.ts'
|
|
11
|
+
import css from './panel.module.css'
|
|
12
|
+
|
|
13
|
+
export function BoardTab({ api }: { api: RezApi }) {
|
|
14
|
+
const t = useT()
|
|
15
|
+
const [config, setConfig] = useState<RezPublicConfig | null>(null)
|
|
16
|
+
const [bots, setBots] = useState<RezWecomBotPublic[]>([])
|
|
17
|
+
const [error, setError] = useState('')
|
|
18
|
+
const [loading, setLoading] = useState(true)
|
|
19
|
+
|
|
20
|
+
const load = async (): Promise<void> => {
|
|
21
|
+
setLoading(true)
|
|
22
|
+
setError('')
|
|
23
|
+
try {
|
|
24
|
+
const next = await api.getConfig()
|
|
25
|
+
setConfig(next)
|
|
26
|
+
try {
|
|
27
|
+
const wecom = await api.wecomChannels()
|
|
28
|
+
setBots(wecom.bots)
|
|
29
|
+
} catch {
|
|
30
|
+
setBots([])
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
setError(errorMessage(err))
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
useEffect(() => { void load() }, [])
|
|
40
|
+
|
|
41
|
+
if (loading) return <div className={css.message}>{t('common.loading')}</div>
|
|
42
|
+
if (config === null) return <div className={css.error}>{error}</div>
|
|
43
|
+
|
|
44
|
+
const odooOk = config.connections.odoo.secretConfigured
|
|
45
|
+
const lang = t('tab.config') === '配置' ? 'zh' : 'en'
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={css.tabBody}>
|
|
49
|
+
<div className={css.toolbar}>
|
|
50
|
+
<span className={css.message}>{t('board.hint')}</span>
|
|
51
|
+
<button type="button" className={css.button} onClick={() => { void load() }}>{t('common.refresh')}</button>
|
|
52
|
+
</div>
|
|
53
|
+
{error !== '' && <div className={css.error}>{error}</div>}
|
|
54
|
+
|
|
55
|
+
<h3 className={css.boardTitle}>{t('board.people')}</h3>
|
|
56
|
+
<p className={css.message}>{t('board.peopleHint')}</p>
|
|
57
|
+
<div className={css.tableWrap}>
|
|
58
|
+
<table className={css.table}>
|
|
59
|
+
<thead>
|
|
60
|
+
<tr>
|
|
61
|
+
<th>{t('board.col.source')}</th>
|
|
62
|
+
<th>{t('board.col.state')}</th>
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
<tr>
|
|
67
|
+
<td>Odoo</td>
|
|
68
|
+
<td>
|
|
69
|
+
<span className={css.badge + ' ' + (odooOk ? css.badgeOk : css.badgeFail)}>
|
|
70
|
+
{odooOk ? t('config.secretConfigured') : t('config.secretMissing')}
|
|
71
|
+
</span>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
<tr>
|
|
75
|
+
<td>WeCom · {MEDIA_ROOM}</td>
|
|
76
|
+
<td>
|
|
77
|
+
<span className={css.badge + ' ' + (wecomBound(MEDIA_ROOM, bots) ? css.badgeOk : css.badgeFail)}>
|
|
78
|
+
{wecomBound(MEDIA_ROOM, bots) ? t('board.bound') : t('board.unbound')}
|
|
79
|
+
</span>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
<tr>
|
|
83
|
+
<td>WeCom · {ECOMMERCE_ROOM}</td>
|
|
84
|
+
<td>
|
|
85
|
+
<span className={css.badge + ' ' + (wecomBound(ECOMMERCE_ROOM, bots) ? css.badgeOk : css.badgeFail)}>
|
|
86
|
+
{wecomBound(ECOMMERCE_ROOM, bots) ? t('board.bound') : t('board.unbound')}
|
|
87
|
+
</span>
|
|
88
|
+
</td>
|
|
89
|
+
</tr>
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
</div>
|
|
93
|
+
<p className={css.message}>{t('board.role', { role: config.role })}</p>
|
|
94
|
+
|
|
95
|
+
<h3 className={css.boardTitle}>{t('board.media')}</h3>
|
|
96
|
+
<p className={css.message}>{t('board.mediaHint')}</p>
|
|
97
|
+
<ChannelTable kind="media" lang={lang} />
|
|
98
|
+
|
|
99
|
+
<h3 className={css.boardTitle}>{t('board.ecommerce')}</h3>
|
|
100
|
+
<p className={css.message}>{t('board.ecommerceHint')}</p>
|
|
101
|
+
<ChannelTable kind="ecommerce" lang={lang} />
|
|
102
|
+
|
|
103
|
+
<h3 className={css.boardTitle}>{t('board.rag')}</h3>
|
|
104
|
+
<p className={css.message}>{t('board.ragHint')}</p>
|
|
105
|
+
<h3 className={css.boardTitle}>{t('board.inbox')}</h3>
|
|
106
|
+
<p className={css.message}>{t('board.inboxHint')}</p>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function ChannelTable({ kind, lang }: { kind: 'media' | 'ecommerce'; lang: 'zh' | 'en' }) {
|
|
112
|
+
const t = useT()
|
|
113
|
+
const rows = channelsFor(kind)
|
|
114
|
+
return (
|
|
115
|
+
<div className={css.tableWrap}>
|
|
116
|
+
<table className={css.table}>
|
|
117
|
+
<thead>
|
|
118
|
+
<tr>
|
|
119
|
+
<th>{t('board.col.channel')}</th>
|
|
120
|
+
<th>{t('board.col.room')}</th>
|
|
121
|
+
<th>{t('board.col.phase')}</th>
|
|
122
|
+
</tr>
|
|
123
|
+
</thead>
|
|
124
|
+
<tbody>
|
|
125
|
+
{rows.map((row) => (
|
|
126
|
+
<tr key={row.id}>
|
|
127
|
+
<td>{lang === 'zh' ? row.nameZh : row.nameEn}</td>
|
|
128
|
+
<td>{row.room}</td>
|
|
129
|
+
<td>{row.phase === '1' ? t('board.phase1') : t('board.phaseLater')}</td>
|
|
130
|
+
</tr>
|
|
131
|
+
))}
|
|
132
|
+
</tbody>
|
|
133
|
+
</table>
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
@@ -252,12 +252,12 @@ export function ConfigTab({ api }: { api: RezApi }) {
|
|
|
252
252
|
try {
|
|
253
253
|
const wecom = await api.wecomChannels()
|
|
254
254
|
setWecomRooms(wecom.rooms.length > 0 ? wecom.rooms : [
|
|
255
|
-
'搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '实验室',
|
|
255
|
+
'搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '发布', '电商', '实验室',
|
|
256
256
|
])
|
|
257
257
|
setWecomDrafts(toWecomDrafts(wecom.bots))
|
|
258
258
|
setWecomStatus(wecom.status)
|
|
259
259
|
} catch {
|
|
260
|
-
setWecomRooms(['搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '实验室'])
|
|
260
|
+
setWecomRooms(['搞钱', '产品', '运营', '人事助理', '财务助理', '法务助理', '设计', '发布', '电商', '实验室'])
|
|
261
261
|
}
|
|
262
262
|
} catch (err) {
|
|
263
263
|
setError(errorMessage(err))
|
|
@@ -63,6 +63,37 @@ export function StatusTab({ api }: { api: RezApi }) {
|
|
|
63
63
|
</tbody>
|
|
64
64
|
</table>
|
|
65
65
|
</div>
|
|
66
|
+
{status.wecomCli !== undefined && (
|
|
67
|
+
<div className={css.tableWrap}>
|
|
68
|
+
<p className={css.message}>{t('status.wecomCli')}</p>
|
|
69
|
+
<table className={css.table}>
|
|
70
|
+
<thead>
|
|
71
|
+
<tr>
|
|
72
|
+
<th>{t('status.wecomCli.installed')}</th>
|
|
73
|
+
<th>{t('status.wecomCli.authorized')}</th>
|
|
74
|
+
<th>{t('status.wecomCli.modules')}</th>
|
|
75
|
+
<th>{t('status.col.error')}</th>
|
|
76
|
+
</tr>
|
|
77
|
+
</thead>
|
|
78
|
+
<tbody>
|
|
79
|
+
<tr>
|
|
80
|
+
<td>
|
|
81
|
+
<span className={css.badge + ' ' + (status.wecomCli.installed ? css.badgeOk : css.badgeFail)}>
|
|
82
|
+
{status.wecomCli.installed ? t('status.wecomCli.yes') : t('status.wecomCli.no')}
|
|
83
|
+
</span>
|
|
84
|
+
</td>
|
|
85
|
+
<td>
|
|
86
|
+
<span className={css.badge + ' ' + (status.wecomCli.authorized ? css.badgeOk : css.badgeFail)}>
|
|
87
|
+
{status.wecomCli.authorized ? t('status.wecomCli.yes') : t('status.wecomCli.no')}
|
|
88
|
+
</span>
|
|
89
|
+
</td>
|
|
90
|
+
<td>{(status.wecomCli.modules ?? []).map(row => row.label).join('、')}</td>
|
|
91
|
+
<td>{status.wecomCli.hint}</td>
|
|
92
|
+
</tr>
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
66
97
|
</div>
|
|
67
98
|
)
|
|
68
99
|
}
|
|
@@ -10,19 +10,21 @@ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
|
|
10
10
|
import { RezApi } from './api.ts'
|
|
11
11
|
import { RezT } from './panel/helpers.ts'
|
|
12
12
|
import { AuditTab } from './panel/AuditTab.tsx'
|
|
13
|
+
import { BoardTab } from './panel/BoardTab.tsx'
|
|
13
14
|
import { ChangelogTab } from './panel/ChangelogTab.tsx'
|
|
14
15
|
import { ConfigTab } from './panel/ConfigTab.tsx'
|
|
15
16
|
import { StatusTab } from './panel/StatusTab.tsx'
|
|
16
17
|
import { WeixinTab } from './panel/WeixinTab.tsx'
|
|
17
18
|
import css from './panel/panel.module.css'
|
|
18
19
|
|
|
19
|
-
export type RezTab = 'config' | 'weixin' | 'status' | 'audit' | 'changelog'
|
|
20
|
+
export type RezTab = 'config' | 'board' | 'weixin' | 'status' | 'audit' | 'changelog'
|
|
20
21
|
|
|
21
22
|
export function RezSettingsPage({ t }: { t: TranslateNS<'dsh-rez-suite'> }) {
|
|
22
23
|
const api = useMemo(() => new RezApi(), [])
|
|
23
24
|
const [activeTab, setActiveTab] = useState<RezTab>('config')
|
|
24
25
|
const tabs: ReadonlyArray<{ id: RezTab; label: string }> = [
|
|
25
26
|
{ id: 'config', label: t('tab.config') },
|
|
27
|
+
{ id: 'board', label: t('tab.board') },
|
|
26
28
|
{ id: 'weixin', label: t('tab.weixin') },
|
|
27
29
|
{ id: 'status', label: t('tab.status') },
|
|
28
30
|
{ id: 'audit', label: t('tab.audit') },
|
|
@@ -47,6 +49,7 @@ export function RezSettingsPage({ t }: { t: TranslateNS<'dsh-rez-suite'> }) {
|
|
|
47
49
|
))}
|
|
48
50
|
</div>
|
|
49
51
|
{activeTab === 'config' && <ConfigTab api={api} />}
|
|
52
|
+
{activeTab === 'board' && <BoardTab api={api} />}
|
|
50
53
|
{activeTab === 'weixin' && <WeixinTab api={api} />}
|
|
51
54
|
{activeTab === 'status' && <StatusTab api={api} />}
|
|
52
55
|
{activeTab === 'audit' && <AuditTab api={api} />}
|
package/src/index.ts
CHANGED
|
@@ -106,8 +106,9 @@ export const Config: z<RezConfig> = z.object({
|
|
|
106
106
|
const SECTION_ORDER = 150
|
|
107
107
|
|
|
108
108
|
export const REZ_GUIDANCE_CORE = [
|
|
109
|
-
'本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud /
|
|
110
|
-
'工具名 mcp__odoo__*、mcp__nextcloud__*、
|
|
109
|
+
'本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / Home Assistant / TAPD / GSC。',
|
|
110
|
+
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__homeassistant__*、mcp__tapd__*、mcp__gsc__*。',
|
|
111
|
+
'企业微信办公能力不是 MCP:走官方 wecom-cli >=1.1(通讯录/在线文档/在线表格/智能表格/智能文档/待办/日程/会议/邮件/微盘/消息)。本企业已开通这些品类。看 rez_status 的 wecomCli.modules,按房间 wecomcli-* 技能执行。不要因为没有 mcp__wechat__* 就说没有企微能力,也不要说「超过 10 人只能用文档」。CLI 报某模块未授权才说该能力未开通。设置页 BotID+Secret 只是房间消息通道。旧群 webhook 兼容才会出现 mcp__wechat__*。',
|
|
111
112
|
'密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN、REZ_TAPD_TOKEN、REZ_GSC_KEY。',
|
|
112
113
|
'个人微信(QClaw/ClawBot):打开 设置 → 插件 → ReZ-TI → 微信 扫码,然后直接在微信里说话。消息进入网页左侧「微信」文件夹并延续同一会话,模型跟网页默认。网页归档了再从微信说话会自动恢复到侧栏。发「重启对话」会新开一条可见会话并把旧会话归档。网页端必须开着。不必再跑命令、也不要调用微信 MCP 工具。',
|
|
113
114
|
'意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。',
|
package/src/protocol.ts
CHANGED
|
@@ -123,11 +123,25 @@ export interface RezServerStatus {
|
|
|
123
123
|
startedAt?: number
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/** Official @wecom/cli office APIs. Not mcp__wechat__*. */
|
|
127
|
+
export interface RezWecomCliModule {
|
|
128
|
+
id: string
|
|
129
|
+
label: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface RezWecomCliStatus {
|
|
133
|
+
installed: boolean
|
|
134
|
+
authorized: boolean
|
|
135
|
+
modules: RezWecomCliModule[]
|
|
136
|
+
hint: string
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
/** Complete /api/dsh-rez-suite/status response. */
|
|
127
140
|
export interface RezStatusResponse {
|
|
128
141
|
role: RezRoleId
|
|
129
142
|
servers: RezServerStatus[]
|
|
130
143
|
totalRegisteredTools: number
|
|
144
|
+
wecomCli: RezWecomCliStatus
|
|
131
145
|
}
|
|
132
146
|
|
|
133
147
|
/** QClaw-style WeChat channel status (scan QR, then talk in WeChat). */
|
package/src/routes.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { isRezRoleId, REZ_API } from './protocol.ts'
|
|
|
18
18
|
import type { McpHost } from './mcp-host.ts'
|
|
19
19
|
import type { TokenManager } from './token-manager.ts'
|
|
20
20
|
import { publicConfig } from './store.ts'
|
|
21
|
+
import { probeWecomCli } from './wecom-cli.ts'
|
|
21
22
|
|
|
22
23
|
const MAX_JSON_BODY_BYTES = 256 * 1024
|
|
23
24
|
const SECRET_MASK = '********'
|
|
@@ -200,7 +201,12 @@ export function makeRoutes(deps: RezRoutesDeps): WebRoute[] {
|
|
|
200
201
|
if (!guard(req, res, 'GET')) return
|
|
201
202
|
const servers = host.status()
|
|
202
203
|
const totalRegisteredTools = servers.reduce((sum, server) => sum + server.registeredTools, 0)
|
|
203
|
-
writeJson(res, 200, {
|
|
204
|
+
writeJson(res, 200, {
|
|
205
|
+
role: getConfig().role,
|
|
206
|
+
servers,
|
|
207
|
+
totalRegisteredTools,
|
|
208
|
+
wecomCli: probeWecomCli(),
|
|
209
|
+
})
|
|
204
210
|
},
|
|
205
211
|
},
|
|
206
212
|
{
|
package/src/tools.ts
CHANGED
|
@@ -8,12 +8,13 @@ import { defineTool as defineToolOriginal } from '@deepseek-ai/dsh-tools'
|
|
|
8
8
|
/** Loosen the typed helper for the suite's small fixed tools. */
|
|
9
9
|
const defineTool = defineToolOriginal as unknown as (options: any) => any
|
|
10
10
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
11
|
-
import type { RezConfig, RezTestResult } from './protocol.ts'
|
|
11
|
+
import type { RezConfig, RezTestResult, RezWecomCliStatus } from './protocol.ts'
|
|
12
12
|
import type { McpHost } from './mcp-host.ts'
|
|
13
13
|
import type { TokenManager } from './token-manager.ts'
|
|
14
14
|
import { publicConfig } from './store.ts'
|
|
15
15
|
import { cwdFromExecution } from './rooms.ts'
|
|
16
16
|
import { compileEsphomeFirmware } from './esphome-lab.ts'
|
|
17
|
+
import { probeWecomCli } from './wecom-cli.ts'
|
|
17
18
|
|
|
18
19
|
function text(value: string): ContentBlock[] {
|
|
19
20
|
return [{ type: 'text', text: value }]
|
|
@@ -30,10 +31,25 @@ function renderTest(results: RezTestResult[]): string {
|
|
|
30
31
|
}).join('\n')
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
function renderWecomCli(cli: RezWecomCliStatus | undefined): string {
|
|
35
|
+
const row = cli ?? { installed: false, authorized: false, modules: [], hint: '' }
|
|
36
|
+
const modules = (row.modules ?? []).map((item) => `${item.label}(${item.id})`).join('、')
|
|
37
|
+
return [
|
|
38
|
+
'wecom-cli(企微办公能力,不是 MCP): installed=' + (row.installed ? 'yes' : 'no')
|
|
39
|
+
+ ' authorized=' + (row.authorized ? 'yes' : 'no'),
|
|
40
|
+
modules.length > 0 ? 'modules: ' + modules : 'modules: (官方目录见 wecomcli-* 技能)',
|
|
41
|
+
row.hint,
|
|
42
|
+
].join('\n')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function rezStatusTool(
|
|
46
|
+
host: McpHost,
|
|
47
|
+
config: () => RezConfig,
|
|
48
|
+
wecomCli: () => RezWecomCliStatus = probeWecomCli,
|
|
49
|
+
) {
|
|
34
50
|
return defineTool({
|
|
35
51
|
name: 'rez_status',
|
|
36
|
-
description: 'List
|
|
52
|
+
description: 'List MCP server statuses plus WeCom office APIs (official wecom-cli, not mcp__wechat__). Triggers: rez, MCP, 连接状态, 企业微信, 企微能力.',
|
|
37
53
|
parameters: {},
|
|
38
54
|
output: {
|
|
39
55
|
schema: {
|
|
@@ -57,10 +73,36 @@ export function rezStatusTool(host: McpHost, config: () => RezConfig) {
|
|
|
57
73
|
},
|
|
58
74
|
},
|
|
59
75
|
},
|
|
76
|
+
wecomCli: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
required: true,
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
properties: {
|
|
81
|
+
installed: { type: 'boolean', required: true },
|
|
82
|
+
authorized: { type: 'boolean', required: true },
|
|
83
|
+
hint: { type: 'string', required: true },
|
|
84
|
+
modules: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
required: true,
|
|
87
|
+
items: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
properties: {
|
|
91
|
+
id: { type: 'string', required: true },
|
|
92
|
+
label: { type: 'string', required: true },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
60
98
|
},
|
|
61
99
|
additionalProperties: false,
|
|
62
100
|
},
|
|
63
|
-
render: (_args: any, value: {
|
|
101
|
+
render: (_args: any, value: {
|
|
102
|
+
role?: string
|
|
103
|
+
servers?: Array<Record<string, unknown>>
|
|
104
|
+
wecomCli?: RezWecomCliStatus
|
|
105
|
+
}) => {
|
|
64
106
|
const rows = (value.servers ?? []).map(server => [
|
|
65
107
|
server.server,
|
|
66
108
|
server.state,
|
|
@@ -68,7 +110,8 @@ export function rezStatusTool(host: McpHost, config: () => RezConfig) {
|
|
|
68
110
|
server.registeredTools,
|
|
69
111
|
server.lastError ?? '',
|
|
70
112
|
].join(' | '))
|
|
71
|
-
|
|
113
|
+
const mcp = 'role: ' + (value.role ?? '') + '\n' + ['server | state | discovered | registered | error', '--- | --- | --- | --- | ---', ...rows].join('\n')
|
|
114
|
+
return text(mcp + '\n\n' + renderWecomCli(value.wecomCli))
|
|
72
115
|
},
|
|
73
116
|
},
|
|
74
117
|
async execute(_args: unknown, exec?: { agent?: unknown }) {
|
|
@@ -78,7 +121,7 @@ export function rezStatusTool(host: McpHost, config: () => RezConfig) {
|
|
|
78
121
|
if (clean.startedAt === undefined) delete clean.startedAt
|
|
79
122
|
return clean
|
|
80
123
|
})
|
|
81
|
-
return { role: config().role, servers }
|
|
124
|
+
return { role: config().role, servers, wecomCli: wecomCli() }
|
|
82
125
|
},
|
|
83
126
|
})
|
|
84
127
|
}
|