@rezti/dsh-rez-suite 0.1.18 → 0.1.20
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/README.md +2 -2
- package/lib/client.d.ts +14 -0
- package/lib/client.js +142 -5
- package/lib/index.d.ts +3 -2
- package/lib/index.js +246 -20
- package/lib/style.css +81 -0
- package/package.json +8 -7
- package/src/boss/mount.ts +46 -13
- package/src/boss/register.ts +38 -2
- package/src/client/api.ts +12 -0
- package/src/client/index.ts +14 -0
- package/src/client/locales.ts +14 -0
- package/src/client/panel/panel.module.css +82 -0
- package/src/client/upgrade-button.tsx +115 -0
- package/src/index.ts +16 -6
- package/src/protocol.ts +13 -0
- package/src/routes.ts +30 -2
- package/src/self-update.ts +155 -0
package/lib/style.css
CHANGED
|
@@ -287,3 +287,84 @@
|
|
|
287
287
|
font-size: 15px;
|
|
288
288
|
font-weight: 600;
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
.LOjPTW_upgradeLayer {
|
|
292
|
+
flex: none;
|
|
293
|
+
align-items: center;
|
|
294
|
+
width: 100%;
|
|
295
|
+
height: 42px;
|
|
296
|
+
margin: 8px 0 0;
|
|
297
|
+
display: flex;
|
|
298
|
+
position: relative;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.LOjPTW_upgradeLayerRail {
|
|
302
|
+
width: 36px;
|
|
303
|
+
height: 36px;
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.LOjPTW_upgradeButton {
|
|
308
|
+
width: calc(100% + 4px);
|
|
309
|
+
height: 42px;
|
|
310
|
+
color: var(--dsw-alias-label-primary);
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
background: none;
|
|
313
|
+
border: none;
|
|
314
|
+
border-radius: 12px;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
margin: 0 -2px;
|
|
318
|
+
padding: 0 10px 0 8px;
|
|
319
|
+
font-family: inherit;
|
|
320
|
+
font-size: 14px;
|
|
321
|
+
display: inline-flex;
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.LOjPTW_upgradeButton:hover, .LOjPTW_upgradeButton[data-outdated] {
|
|
326
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.LOjPTW_upgradeButton:disabled {
|
|
330
|
+
cursor: progress;
|
|
331
|
+
opacity: .72;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.LOjPTW_upgradeButton:focus-visible {
|
|
335
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
336
|
+
outline-offset: -2px;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.LOjPTW_upgradeLayerRail .LOjPTW_upgradeButton {
|
|
340
|
+
border-radius: 50%;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
gap: 0;
|
|
343
|
+
width: 36px;
|
|
344
|
+
height: 36px;
|
|
345
|
+
padding: 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.LOjPTW_upgradeLabel {
|
|
349
|
+
text-overflow: ellipsis;
|
|
350
|
+
white-space: nowrap;
|
|
351
|
+
min-width: 0;
|
|
352
|
+
overflow: hidden;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.LOjPTW_upgradeMeta {
|
|
356
|
+
color: var(--dsw-alias-label-tertiary);
|
|
357
|
+
font-variant-numeric: tabular-nums;
|
|
358
|
+
flex: none;
|
|
359
|
+
margin-left: auto;
|
|
360
|
+
font-size: 12px;
|
|
361
|
+
line-height: 16px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.LOjPTW_upgradeDot {
|
|
365
|
+
background: var(--dsw-alias-brand-primary);
|
|
366
|
+
border-radius: 50%;
|
|
367
|
+
flex: none;
|
|
368
|
+
width: 7px;
|
|
369
|
+
height: 7px;
|
|
370
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-suite",
|
|
3
3
|
"description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web update @rezti/dsh-rez-suite",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"@deepseek-ai/dsh-client-connection",
|
|
31
31
|
"@deepseek-ai/dsh-client-locale",
|
|
32
32
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
33
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
34
35
|
],
|
|
35
36
|
"platform": "web"
|
|
36
37
|
}
|
|
@@ -38,13 +39,13 @@
|
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
40
41
|
"zod": "^4.4.3",
|
|
41
|
-
"@rezti/dsh-rez-ha-bridge": "0.1.7",
|
|
42
|
-
"@rezti/dsh-rez-intent": "0.1.4",
|
|
43
|
-
"@rezti/dsh-rez-nextcloud": "0.1.7",
|
|
44
42
|
"@rezti/dsh-rez-token-manager": "0.1.4",
|
|
43
|
+
"@rezti/dsh-rez-sso": "0.1.7",
|
|
44
|
+
"@rezti/dsh-rez-intent": "0.1.4",
|
|
45
45
|
"@rezti/dsh-rez-wechat": "0.1.11",
|
|
46
|
-
"@rezti/dsh-rez-
|
|
47
|
-
"@rezti/dsh-rez-
|
|
46
|
+
"@rezti/dsh-rez-ha-bridge": "0.1.7",
|
|
47
|
+
"@rezti/dsh-rez-nextcloud": "0.1.7",
|
|
48
|
+
"@rezti/dsh-rez-odoo": "0.1.7"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"react": "^18.2.0",
|
package/src/boss/mount.ts
CHANGED
|
@@ -1,31 +1,64 @@
|
|
|
1
1
|
import { homedir } from 'node:os'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import type { Context } from '@deepseek-ai/cordis'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import type { RezRoleId } from '../protocol.ts'
|
|
5
|
+
import {
|
|
6
|
+
registerBossWorkspaces,
|
|
7
|
+
unregisterBossWorkspaces,
|
|
8
|
+
type BossRoomRef,
|
|
9
|
+
type WorkspaceRegistrar,
|
|
10
|
+
} from './register.ts'
|
|
11
|
+
import { BOSS_ROOMS, seedBossWorkspaces } from './seed.ts'
|
|
12
|
+
|
|
13
|
+
export const BOSS_ROLE_HINT = '设置 → 插件 → ReZ-TI → 角色选「老板」后,左侧会出现搞钱 / 产品 / 运营三室。不必手动「添加工作区」。'
|
|
6
14
|
|
|
7
15
|
export const BOSS_GUIDANCE = [
|
|
8
|
-
'
|
|
9
|
-
'
|
|
16
|
+
'当前角色是老板。侧栏已登记三室:~/.dsh/workspaces/搞钱、产品、运营。不必手动「添加工作区」。',
|
|
17
|
+
'每个文件夹是独立作战室,官方 dsh 会注入该目录 AGENTS.md。',
|
|
10
18
|
'若该室仍有 BOOTSTRAP.md:本会话先做短问三拍,沉淀到 PRIORITIES.md / MEMORY.md,然后删除 BOOTSTRAP.md。',
|
|
11
19
|
'已初始化后:读 PRIORITIES.md + MEMORY.md + 今日/昨日 memory/;回答固定四行:结论 → 数字(哪家公司、哪段时间)→ 拍板 1~3 件事 → 未执行任何过账/付款/对外发送。',
|
|
12
20
|
'微信文件夹仍是收件箱,不要在那边查账或砍 SKU。',
|
|
13
21
|
].join('')
|
|
14
22
|
|
|
23
|
+
export function bossGuidanceFor(role: RezRoleId): string {
|
|
24
|
+
return role === 'boss' ? BOSS_GUIDANCE : BOSS_ROLE_HINT
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
function destRoot(env: NodeJS.ProcessEnv = process.env): string {
|
|
16
28
|
if (env.REZ_BOSS_WORKSPACES !== undefined && env.REZ_BOSS_WORKSPACES !== '') return env.REZ_BOSS_WORKSPACES
|
|
17
29
|
const home = env.DSH_HOME !== undefined && env.DSH_HOME !== '' ? env.DSH_HOME : join(homedir(), '.dsh')
|
|
18
30
|
return join(home, 'workspaces')
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
export function expectedBossRooms(env: NodeJS.ProcessEnv = process.env): BossRoomRef[] {
|
|
34
|
+
const root = destRoot(env)
|
|
35
|
+
return BOSS_ROOMS.map((room) => ({ path: join(root, room.folder), folder: room.folder }))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function syncBossWorkspaces(
|
|
39
|
+
registry: WorkspaceRegistrar,
|
|
40
|
+
role: RezRoleId,
|
|
41
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
if (role === 'boss') {
|
|
44
|
+
const seeded = seedBossWorkspaces({ destRoot: destRoot(env) })
|
|
45
|
+
await registerBossWorkspaces(registry, seeded.rooms)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
await unregisterBossWorkspaces(registry, expectedBossRooms(env))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Seed/register 三室 only for role=boss; drop sidebar rows otherwise. Returns a re-sync function. */
|
|
52
|
+
export function mountBossDesk(ctx: Context, getRole: () => RezRoleId): () => void {
|
|
53
|
+
const run = (): void => {
|
|
54
|
+
ctx.inject(['workspaceRegistry'], (inner) => {
|
|
55
|
+
const registry = inner.get('workspaceRegistry') as WorkspaceRegistrar
|
|
56
|
+
void syncBossWorkspaces(registry, getRole()).catch((error: unknown) => {
|
|
57
|
+
const why = error instanceof Error ? error.message : String(error)
|
|
58
|
+
console.error('[dsh-rez-suite] boss workspace sync failed:', why)
|
|
59
|
+
})
|
|
29
60
|
})
|
|
30
|
-
}
|
|
61
|
+
}
|
|
62
|
+
run()
|
|
63
|
+
return run
|
|
31
64
|
}
|
package/src/boss/register.ts
CHANGED
|
@@ -1,18 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Register
|
|
2
|
+
* Register or drop the three boss rooms in the official workspace sidebar.
|
|
3
3
|
* `workspaceRegistry.create` is idempotent: same canonical path returns the
|
|
4
4
|
* existing record. New records are prepended, so we register in reverse to
|
|
5
5
|
* keep 搞钱 → 产品 → 运营 at the top of the sidebar.
|
|
6
|
+
* `delete` only drops the registry row — directories and session logs stay.
|
|
6
7
|
*/
|
|
8
|
+
import { realpathSync } from 'node:fs'
|
|
9
|
+
|
|
10
|
+
export interface WorkspaceRecord {
|
|
11
|
+
id: string
|
|
12
|
+
path: string
|
|
13
|
+
title: string
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export interface WorkspaceRegistrar {
|
|
8
17
|
create(path: string, title?: string): Promise<unknown>
|
|
18
|
+
list(): WorkspaceRecord[]
|
|
19
|
+
delete(id: string): Promise<boolean>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type BossRoomRef = { path: string; folder: string }
|
|
23
|
+
|
|
24
|
+
function canon(path: string): string {
|
|
25
|
+
try {
|
|
26
|
+
return realpathSync(path)
|
|
27
|
+
} catch {
|
|
28
|
+
return path
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isRegisteredBossRoom(workspace: WorkspaceRecord, rooms: ReadonlyArray<BossRoomRef>): boolean {
|
|
33
|
+
const wanted = new Set(rooms.map((room) => canon(room.path)))
|
|
34
|
+
return wanted.has(canon(workspace.path))
|
|
9
35
|
}
|
|
10
36
|
|
|
11
37
|
export async function registerBossWorkspaces(
|
|
12
38
|
registry: WorkspaceRegistrar,
|
|
13
|
-
rooms: ReadonlyArray<
|
|
39
|
+
rooms: ReadonlyArray<BossRoomRef>,
|
|
14
40
|
): Promise<void> {
|
|
15
41
|
for (const room of [...rooms].reverse()) {
|
|
16
42
|
await registry.create(room.path, room.folder)
|
|
17
43
|
}
|
|
18
44
|
}
|
|
45
|
+
|
|
46
|
+
export async function unregisterBossWorkspaces(
|
|
47
|
+
registry: WorkspaceRegistrar,
|
|
48
|
+
rooms: ReadonlyArray<BossRoomRef>,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
for (const workspace of registry.list()) {
|
|
51
|
+
if (!isRegisteredBossRoom(workspace, rooms)) continue
|
|
52
|
+
await registry.delete(workspace.id)
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/client/api.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
type RezServerStatus,
|
|
10
10
|
type RezStatusResponse,
|
|
11
11
|
type RezTestResult,
|
|
12
|
+
type RezUpdateApplyResult,
|
|
13
|
+
type RezUpdateStatus,
|
|
12
14
|
type RezWeixinStatus,
|
|
13
15
|
} from '../protocol.ts'
|
|
14
16
|
|
|
@@ -97,4 +99,14 @@ export class RezApi {
|
|
|
97
99
|
const response = await fetch(REZ_API.weixin, { method: 'DELETE' })
|
|
98
100
|
return readJson<RezWeixinStatus>(response)
|
|
99
101
|
}
|
|
102
|
+
|
|
103
|
+
async updateStatus(): Promise<RezUpdateStatus> {
|
|
104
|
+
const response = await fetch(REZ_API.update)
|
|
105
|
+
return readJson<RezUpdateStatus>(response)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async applyUpdate(): Promise<RezUpdateApplyResult> {
|
|
109
|
+
const response = await fetch(REZ_API.update, { method: 'POST' })
|
|
110
|
+
return readJson<RezUpdateApplyResult>(response)
|
|
111
|
+
}
|
|
100
112
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
|
11
11
|
import type {} from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
12
|
import { en, zh, type RezKey } from './locales.ts'
|
|
13
13
|
import { RezPluginCard, RezSettingsPage } from './settings-card.tsx'
|
|
14
|
+
import { UpgradeButton } from './upgrade-button.tsx'
|
|
14
15
|
|
|
15
16
|
const NS = 'dsh-rez-suite'
|
|
16
17
|
|
|
@@ -29,6 +30,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
29
30
|
scope: 'root'
|
|
30
31
|
owner: { children?: never }
|
|
31
32
|
}
|
|
33
|
+
'sidebar.footer.action': {
|
|
34
|
+
kind: 'list'
|
|
35
|
+
scope: 'root'
|
|
36
|
+
owner: { wide: boolean }
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -53,4 +59,12 @@ export function apply(ctx: ClientContext): void {
|
|
|
53
59
|
label: () => t('settings.title'),
|
|
54
60
|
locale: NS,
|
|
55
61
|
}, RezSettingsPage))
|
|
62
|
+
|
|
63
|
+
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
|
|
64
|
+
name: 'sidebar.footer.action',
|
|
65
|
+
id: 'rez-upgrade',
|
|
66
|
+
order: 10,
|
|
67
|
+
label: () => t('upgrade.label'),
|
|
68
|
+
locale: NS,
|
|
69
|
+
}, UpgradeButton))
|
|
56
70
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -104,6 +104,13 @@ export const zh = {
|
|
|
104
104
|
'weixin.listening': '通道在听:微信 → 网页「微信」会话(同一条,不另开窗口)→ 回微信。',
|
|
105
105
|
'weixin.loggedIn': '已登录,通道未在听。点「重新连接」即可拉起,不必再扫码。',
|
|
106
106
|
'weixin.loggedOut': '尚未登录。',
|
|
107
|
+
'upgrade.label': '升级',
|
|
108
|
+
'upgrade.current': '已最新',
|
|
109
|
+
'upgrade.aria': '升级 ReZ-TI 插件',
|
|
110
|
+
'upgrade.running': '升级中…',
|
|
111
|
+
'upgrade.reloading': '正在重载',
|
|
112
|
+
'upgrade.already': '已是 {version}',
|
|
113
|
+
'upgrade.reloadTimeout': '升级完成,但网页还没重新连上。请刷新这一页。',
|
|
107
114
|
} as const
|
|
108
115
|
|
|
109
116
|
export type RezKey = keyof typeof zh
|
|
@@ -206,6 +213,13 @@ export const en: Record<RezKey, string> = {
|
|
|
206
213
|
'weixin.listening': 'Channel is live: WeChat → the WeChat folder session on the left → WeChat.',
|
|
207
214
|
'weixin.loggedIn': 'Logged in, listener idle. Click Reconnect — no need to scan again.',
|
|
208
215
|
'weixin.loggedOut': 'Not logged in.',
|
|
216
|
+
'upgrade.label': 'Upgrade',
|
|
217
|
+
'upgrade.current': 'Up to date',
|
|
218
|
+
'upgrade.aria': 'Upgrade the ReZ-TI plugin',
|
|
219
|
+
'upgrade.running': 'Updating…',
|
|
220
|
+
'upgrade.reloading': 'Reloading',
|
|
221
|
+
'upgrade.already': 'Already {version}',
|
|
222
|
+
'upgrade.reloadTimeout': 'Update finished, but the page did not reconnect. Refresh this tab.',
|
|
209
223
|
}
|
|
210
224
|
|
|
211
225
|
/** Minimal {name} interpolator shared by the panel helpers. */
|
|
@@ -294,3 +294,85 @@
|
|
|
294
294
|
font-weight: 600;
|
|
295
295
|
color: var(--dsw-alias-label-primary);
|
|
296
296
|
}
|
|
297
|
+
|
|
298
|
+
.upgradeLayer {
|
|
299
|
+
flex: none;
|
|
300
|
+
align-items: center;
|
|
301
|
+
width: 100%;
|
|
302
|
+
height: 42px;
|
|
303
|
+
margin: 8px 0 0;
|
|
304
|
+
display: flex;
|
|
305
|
+
position: relative;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.upgradeLayerRail {
|
|
309
|
+
width: 36px;
|
|
310
|
+
height: 36px;
|
|
311
|
+
margin: 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.upgradeButton {
|
|
315
|
+
width: calc(100% + 4px);
|
|
316
|
+
height: 42px;
|
|
317
|
+
color: var(--dsw-alias-label-primary);
|
|
318
|
+
cursor: pointer;
|
|
319
|
+
background: transparent;
|
|
320
|
+
border: none;
|
|
321
|
+
border-radius: 12px;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 8px;
|
|
324
|
+
margin: 0 -2px;
|
|
325
|
+
padding: 0 10px 0 8px;
|
|
326
|
+
font-family: inherit;
|
|
327
|
+
font-size: 14px;
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
overflow: hidden;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.upgradeButton:hover,
|
|
333
|
+
.upgradeButton[data-outdated] {
|
|
334
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.upgradeButton:disabled {
|
|
338
|
+
cursor: progress;
|
|
339
|
+
opacity: 0.72;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.upgradeButton:focus-visible {
|
|
343
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
344
|
+
outline-offset: -2px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.upgradeLayerRail .upgradeButton {
|
|
348
|
+
border-radius: 50%;
|
|
349
|
+
justify-content: center;
|
|
350
|
+
gap: 0;
|
|
351
|
+
width: 36px;
|
|
352
|
+
height: 36px;
|
|
353
|
+
padding: 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.upgradeLabel {
|
|
357
|
+
text-overflow: ellipsis;
|
|
358
|
+
white-space: nowrap;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
overflow: hidden;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.upgradeMeta {
|
|
364
|
+
color: var(--dsw-alias-label-tertiary);
|
|
365
|
+
font-variant-numeric: tabular-nums;
|
|
366
|
+
flex: none;
|
|
367
|
+
margin-left: auto;
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
line-height: 16px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.upgradeDot {
|
|
373
|
+
width: 7px;
|
|
374
|
+
height: 7px;
|
|
375
|
+
border-radius: 50%;
|
|
376
|
+
background: var(--dsw-alias-brand-primary);
|
|
377
|
+
flex: none;
|
|
378
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar footer action: upgrade @rezti/dsh-rez-suite without a terminal.
|
|
3
|
+
* Host code still needs a process respawn; the button does that after pnpm add.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import { RezApi } from './api.ts'
|
|
8
|
+
import { tt } from './panel/helpers.ts'
|
|
9
|
+
import css from './panel/panel.module.css'
|
|
10
|
+
import type { RezUpdateStatus } from '../protocol.ts'
|
|
11
|
+
|
|
12
|
+
function UpgradeIcon() {
|
|
13
|
+
return (
|
|
14
|
+
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
|
|
15
|
+
<path
|
|
16
|
+
d="M8 12.5V4.8M8 4.8 4.8 8M8 4.8 11.2 8M3 13.2h10"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="1.4"
|
|
20
|
+
strokeLinecap="round"
|
|
21
|
+
strokeLinejoin="round"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function waitUntilHostBack(api: RezApi, timeoutMs = 60_000): Promise<void> {
|
|
28
|
+
const deadline = Date.now() + timeoutMs
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 800))
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
try {
|
|
32
|
+
await api.updateStatus()
|
|
33
|
+
return
|
|
34
|
+
} catch {
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error(tt('upgrade.reloadTimeout'))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function UpgradeButton({ wide }: { wide: boolean }) {
|
|
42
|
+
const api = useMemo(() => new RezApi(), [])
|
|
43
|
+
const [info, setInfo] = useState<RezUpdateStatus>()
|
|
44
|
+
const [busy, setBusy] = useState(false)
|
|
45
|
+
const [note, setNote] = useState('')
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let cancelled = false
|
|
49
|
+
void api.updateStatus().then((value) => {
|
|
50
|
+
if (!cancelled) setInfo(value)
|
|
51
|
+
}).catch((error: unknown) => {
|
|
52
|
+
if (!cancelled) setNote(error instanceof Error ? error.message : String(error))
|
|
53
|
+
})
|
|
54
|
+
return () => { cancelled = true }
|
|
55
|
+
}, [api])
|
|
56
|
+
|
|
57
|
+
const label = busy
|
|
58
|
+
? tt('upgrade.running')
|
|
59
|
+
: info?.outdated === true
|
|
60
|
+
? tt('upgrade.label')
|
|
61
|
+
: tt('upgrade.current')
|
|
62
|
+
|
|
63
|
+
const meta = note !== ''
|
|
64
|
+
? note
|
|
65
|
+
: info === undefined
|
|
66
|
+
? ''
|
|
67
|
+
: info.outdated
|
|
68
|
+
? info.latest
|
|
69
|
+
: info.installed
|
|
70
|
+
|
|
71
|
+
async function onClick(): Promise<void> {
|
|
72
|
+
if (busy) return
|
|
73
|
+
setBusy(true)
|
|
74
|
+
setNote('')
|
|
75
|
+
try {
|
|
76
|
+
const snapshot = info ?? await api.updateStatus()
|
|
77
|
+
setInfo(snapshot)
|
|
78
|
+
if (!snapshot.outdated) {
|
|
79
|
+
setNote(tt('upgrade.already', { version: snapshot.installed }))
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
const result = await api.applyUpdate()
|
|
83
|
+
setInfo(result)
|
|
84
|
+
if (result.restarting) {
|
|
85
|
+
setNote(tt('upgrade.reloading'))
|
|
86
|
+
await waitUntilHostBack(api)
|
|
87
|
+
window.location.reload()
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
setNote(tt('upgrade.already', { version: result.installed }))
|
|
91
|
+
} catch (error) {
|
|
92
|
+
setNote(error instanceof Error ? error.message : String(error))
|
|
93
|
+
} finally {
|
|
94
|
+
setBusy(false)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className={wide ? css.upgradeLayer : `${css.upgradeLayer} ${css.upgradeLayerRail}`}>
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
className={css.upgradeButton}
|
|
103
|
+
data-outdated={info?.outdated === true ? '' : undefined}
|
|
104
|
+
aria-label={tt('upgrade.aria')}
|
|
105
|
+
disabled={busy}
|
|
106
|
+
onClick={() => { void onClick() }}
|
|
107
|
+
>
|
|
108
|
+
<UpgradeIcon />
|
|
109
|
+
{wide && <span className={css.upgradeLabel}>{label}</span>}
|
|
110
|
+
{wide && meta !== '' && <span className={css.upgradeMeta}>{meta}</span>}
|
|
111
|
+
{!wide && info?.outdated === true && <span className={css.upgradeDot} />}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -27,7 +27,8 @@ import { defaultConfig, mergeConfig, saveConfig } from './store.ts'
|
|
|
27
27
|
import { TokenManager } from './token-manager.ts'
|
|
28
28
|
import { rezConfigTool, rezStatusTool } from './tools.ts'
|
|
29
29
|
import type { RezConfig } from './protocol.ts'
|
|
30
|
-
import {
|
|
30
|
+
import { bossGuidanceFor, mountBossDesk } from './boss/mount.ts'
|
|
31
|
+
import { liveSelfUpdate } from './self-update.ts'
|
|
31
32
|
|
|
32
33
|
export const name = 'rez-suite'
|
|
33
34
|
|
|
@@ -79,16 +80,19 @@ export const Config: z<RezConfig> = z.object({
|
|
|
79
80
|
|
|
80
81
|
const SECTION_ORDER = 150
|
|
81
82
|
|
|
82
|
-
export const
|
|
83
|
+
export const REZ_GUIDANCE_CORE = [
|
|
83
84
|
'本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。',
|
|
84
85
|
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。',
|
|
85
86
|
'密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN。',
|
|
86
87
|
'个人微信(QClaw/ClawBot):打开 设置 → 插件 → ReZ-TI → 微信 扫码,然后直接在微信里说话。消息进入网页左侧「微信」文件夹并延续同一会话,模型跟网页默认。网页归档了再从微信说话会自动恢复到侧栏。发「重启对话」会新开一条可见会话并把旧会话归档。网页端必须开着。不必再跑命令、也不要调用微信 MCP 工具。',
|
|
87
88
|
'意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。',
|
|
88
89
|
'涉及资金、对外发送、设备控制、生产写入时先征得用户批准。',
|
|
89
|
-
BOSS_GUIDANCE,
|
|
90
90
|
].join('')
|
|
91
91
|
|
|
92
|
+
export function rezGuidanceFor(role: RezConfig['role']): string {
|
|
93
|
+
return REZ_GUIDANCE_CORE + bossGuidanceFor(role)
|
|
94
|
+
}
|
|
95
|
+
|
|
92
96
|
function normalize(input: Partial<RezConfig> | undefined): RezConfig {
|
|
93
97
|
return mergeConfig(defaultConfig(), input ?? {})
|
|
94
98
|
}
|
|
@@ -110,8 +114,8 @@ function delegatedHost(ctx: Context, getConfig: () => RezConfig): McpHost {
|
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export function apply(ctx: Context, config?: RezConfig): void {
|
|
113
|
-
mountBossDesk(ctx)
|
|
114
117
|
let current: () => RezConfig = () => normalize(config)
|
|
118
|
+
const syncBossDesk = mountBossDesk(ctx, () => current().role)
|
|
115
119
|
|
|
116
120
|
const tokens = new TokenManager(joinTokenDb())
|
|
117
121
|
const host = delegatedHost(ctx, () => current())
|
|
@@ -128,6 +132,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
128
132
|
const value = normalize(next as Partial<RezConfig>)
|
|
129
133
|
saveConfig(value)
|
|
130
134
|
current = () => value
|
|
135
|
+
syncBossDesk()
|
|
131
136
|
try {
|
|
132
137
|
const settings = ctx.get('settings') as { update?: (namespace: unknown, value: unknown) => Promise<void> } | undefined
|
|
133
138
|
if (settings?.update !== undefined) await settings.update(REZ_SETTINGS_NAMESPACE, value)
|
|
@@ -142,6 +147,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
142
147
|
const emit = ctx.emit as unknown as (event: string, value: string) => void
|
|
143
148
|
emit('credentials/updated', ref)
|
|
144
149
|
},
|
|
150
|
+
selfUpdate: liveSelfUpdate(),
|
|
145
151
|
})
|
|
146
152
|
|
|
147
153
|
let disposeRoutes: (() => void) | undefined
|
|
@@ -160,7 +166,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
160
166
|
disposeSection = ctx.systemPrompt.section({
|
|
161
167
|
name: 'plugin:dsh-rez-suite',
|
|
162
168
|
order: SECTION_ORDER,
|
|
163
|
-
text:
|
|
169
|
+
text: rezGuidanceFor(value.role),
|
|
164
170
|
})
|
|
165
171
|
}
|
|
166
172
|
|
|
@@ -179,8 +185,12 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
179
185
|
setSource: (source) => {
|
|
180
186
|
current = source
|
|
181
187
|
sync()
|
|
188
|
+
syncBossDesk()
|
|
189
|
+
},
|
|
190
|
+
onChange: () => {
|
|
191
|
+
sync()
|
|
192
|
+
syncBossDesk()
|
|
182
193
|
},
|
|
183
|
-
onChange: sync,
|
|
184
194
|
})
|
|
185
195
|
|
|
186
196
|
sync()
|
package/src/protocol.ts
CHANGED
|
@@ -161,6 +161,18 @@ export interface ApiErrorBody {
|
|
|
161
161
|
error: string
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** Installed vs npm latest for the suite package. */
|
|
165
|
+
export interface RezUpdateStatus {
|
|
166
|
+
package: string
|
|
167
|
+
installed: string
|
|
168
|
+
latest: string
|
|
169
|
+
outdated: boolean
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface RezUpdateApplyResult extends RezUpdateStatus {
|
|
173
|
+
restarting: boolean
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
/** Route paths the client calls (shared literals). */
|
|
165
177
|
export const REZ_API_BASE = '/api/dsh-rez-suite' as const
|
|
166
178
|
|
|
@@ -172,4 +184,5 @@ export const REZ_API = {
|
|
|
172
184
|
auditReset: REZ_API_BASE + '/audit/reset',
|
|
173
185
|
weixin: REZ_API_BASE + '/weixin',
|
|
174
186
|
weixinVerify: REZ_API_BASE + '/weixin/verify',
|
|
187
|
+
update: REZ_API_BASE + '/update',
|
|
175
188
|
} as const
|