@rezti/dsh-rez-suite 0.1.22 → 0.1.24
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 +5 -3
- package/lib/client.d.ts +2 -16
- package/lib/client.js +9 -145
- package/lib/index.d.ts +3 -1
- package/lib/index.js +108 -222
- package/lib/style.css +0 -81
- package/package.json +10 -10
- package/src/boss/mount.ts +54 -20
- package/src/boss/register.ts +50 -11
- package/src/boss/seed.ts +55 -18
- package/src/client/api.ts +0 -12
- package/src/client/index.ts +3 -16
- package/src/client/locales.ts +2 -16
- package/src/client/panel/panel.module.css +0 -82
- package/src/index.ts +9 -8
- package/src/protocol.ts +0 -13
- package/src/routes.ts +2 -30
- package/templates/staff/finance/.agents/skills/staff-finance/SKILL.md +6 -0
- package/templates/staff/finance/AGENTS.md +68 -0
- package/templates/staff/finance/BOOTSTRAP.md +23 -0
- package/templates/staff/finance/IDENTITY.md +8 -0
- package/templates/staff/finance/MEMORY.md +9 -0
- package/templates/staff/finance/PRIORITIES.md +3 -0
- package/templates/staff/finance/SOUL.md +6 -0
- package/templates/staff/finance/USER.md +8 -0
- package/templates/staff/finance/memory/.gitkeep +0 -0
- package/templates/staff/hr/.agents/skills/staff-hr/SKILL.md +6 -0
- package/templates/staff/hr/AGENTS.md +52 -0
- package/templates/staff/hr/BOOTSTRAP.md +23 -0
- package/templates/staff/hr/IDENTITY.md +8 -0
- package/templates/staff/hr/MEMORY.md +9 -0
- package/templates/staff/hr/PRIORITIES.md +3 -0
- package/templates/staff/hr/SOUL.md +6 -0
- package/templates/staff/hr/USER.md +8 -0
- package/templates/staff/hr/memory/.gitkeep +0 -0
- package/templates/staff/legal/.agents/skills/staff-legal/SKILL.md +6 -0
- package/templates/staff/legal/AGENTS.md +48 -0
- package/templates/staff/legal/BOOTSTRAP.md +23 -0
- package/templates/staff/legal/IDENTITY.md +8 -0
- package/templates/staff/legal/MEMORY.md +9 -0
- package/templates/staff/legal/PRIORITIES.md +3 -0
- package/templates/staff/legal/SOUL.md +6 -0
- package/templates/staff/legal/USER.md +8 -0
- package/templates/staff/legal/memory/.gitkeep +0 -0
- package/src/client/upgrade-button.tsx +0 -115
- package/src/self-update.ts +0 -155
package/src/boss/mount.ts
CHANGED
|
@@ -3,25 +3,37 @@ import { join } from 'node:path'
|
|
|
3
3
|
import type { Context } from '@deepseek-ai/cordis'
|
|
4
4
|
import type { RezRoleId } from '../protocol.ts'
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
orderWorkspaces,
|
|
7
|
+
registerWorkspaces,
|
|
8
|
+
unregisterWorkspaces,
|
|
9
|
+
type RoomRef,
|
|
9
10
|
type WorkspaceRegistrar,
|
|
10
11
|
} from './register.ts'
|
|
11
|
-
import { BOSS_ROOMS, seedBossWorkspaces } from './seed.ts'
|
|
12
|
+
import { BOSS_ROOMS, STAFF_ROOMS, seedBossWorkspaces, seedStaffWorkspaces } from './seed.ts'
|
|
12
13
|
|
|
13
|
-
export const
|
|
14
|
+
export const STAFF_GUIDANCE = [
|
|
15
|
+
'侧栏已登记人事助理 / 财务助理 / 法务助理。装完、选身份后自动出现,不必手动「添加工作区」、也不必拖排序。',
|
|
16
|
+
'三个助理给所有人用。能做什么看当前 Odoo 帐号的可视范围;Harness 只列选项,不自审、不自过账。',
|
|
17
|
+
'报销走财务助理。搞钱室是战略数字,不要在助理房间里做。',
|
|
18
|
+
].join('')
|
|
14
19
|
|
|
15
20
|
export const BOSS_GUIDANCE = [
|
|
16
|
-
'
|
|
21
|
+
'当前角色是老板。侧栏已登记搞钱 / 产品 / 运营,后面是人事助理 / 财务助理 / 法务助理。不必手动「添加工作区」。',
|
|
17
22
|
'每个文件夹是独立作战室,官方 dsh 会注入该目录 AGENTS.md。',
|
|
18
23
|
'若该室仍有 BOOTSTRAP.md:本会话先做短问三拍,沉淀到 PRIORITIES.md / MEMORY.md,然后删除 BOOTSTRAP.md。',
|
|
19
|
-
'已初始化后:读 PRIORITIES.md + MEMORY.md + 今日/昨日 memory
|
|
20
|
-
'
|
|
24
|
+
'已初始化后:读 PRIORITIES.md + MEMORY.md + 今日/昨日 memory/;老板三室回答固定四行:结论 → 数字(哪家公司、哪段时间)→ 拍板 1~3 件事 → 未执行任何过账/付款/对外发送。',
|
|
25
|
+
'微信文件夹仍是收件箱,不要在那边查账、报销或砍 SKU。',
|
|
21
26
|
].join('')
|
|
22
27
|
|
|
28
|
+
export const BOSS_ROLE_HINT = '搞钱 / 产品 / 运营仅角色选「老板」时出现。人事 / 财务 / 法务助理所有角色都会登记。'
|
|
29
|
+
|
|
30
|
+
export function workspaceGuidanceFor(role: RezRoleId): string {
|
|
31
|
+
return role === 'boss' ? BOSS_GUIDANCE : STAFF_GUIDANCE + BOSS_ROLE_HINT
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @deprecated use workspaceGuidanceFor */
|
|
23
35
|
export function bossGuidanceFor(role: RezRoleId): string {
|
|
24
|
-
return role
|
|
36
|
+
return workspaceGuidanceFor(role)
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
function destRoot(env: NodeJS.ProcessEnv = process.env): string {
|
|
@@ -30,35 +42,57 @@ function destRoot(env: NodeJS.ProcessEnv = process.env): string {
|
|
|
30
42
|
return join(home, 'workspaces')
|
|
31
43
|
}
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
function refsFor(rooms: ReadonlyArray<{ folder: string }>, env: NodeJS.ProcessEnv): RoomRef[] {
|
|
34
46
|
const root = destRoot(env)
|
|
35
|
-
return
|
|
47
|
+
return rooms.map((room) => ({ path: join(root, room.folder), folder: room.folder }))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function expectedBossRooms(env: NodeJS.ProcessEnv = process.env): RoomRef[] {
|
|
51
|
+
return refsFor(BOSS_ROOMS, env)
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
export
|
|
54
|
+
export function expectedStaffRooms(env: NodeJS.ProcessEnv = process.env): RoomRef[] {
|
|
55
|
+
return refsFor(STAFF_ROOMS, env)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function syncRezWorkspaces(
|
|
39
59
|
registry: WorkspaceRegistrar,
|
|
40
60
|
role: RezRoleId,
|
|
41
61
|
env: NodeJS.ProcessEnv = process.env,
|
|
42
62
|
): Promise<void> {
|
|
63
|
+
const staff = seedStaffWorkspaces({ destRoot: destRoot(env) })
|
|
64
|
+
await registerWorkspaces(registry, staff.rooms)
|
|
65
|
+
|
|
43
66
|
if (role === 'boss') {
|
|
44
|
-
const
|
|
45
|
-
await
|
|
46
|
-
|
|
67
|
+
const boss = seedBossWorkspaces({ destRoot: destRoot(env) })
|
|
68
|
+
await registerWorkspaces(registry, boss.rooms)
|
|
69
|
+
} else {
|
|
70
|
+
await unregisterWorkspaces(registry, expectedBossRooms(env))
|
|
47
71
|
}
|
|
48
|
-
|
|
72
|
+
|
|
73
|
+
const visible = role === 'boss'
|
|
74
|
+
? [...expectedBossRooms(env), ...expectedStaffRooms(env)]
|
|
75
|
+
: expectedStaffRooms(env)
|
|
76
|
+
await orderWorkspaces(registry, visible)
|
|
49
77
|
}
|
|
50
78
|
|
|
51
|
-
/**
|
|
52
|
-
export
|
|
79
|
+
/** @deprecated use syncRezWorkspaces */
|
|
80
|
+
export const syncBossWorkspaces = syncRezWorkspaces
|
|
81
|
+
|
|
82
|
+
/** Seed/register staff for every role; boss rooms only when role=boss. */
|
|
83
|
+
export function mountRezWorkspaces(ctx: Context, getRole: () => RezRoleId): () => void {
|
|
53
84
|
const run = (): void => {
|
|
54
85
|
ctx.inject(['workspaceRegistry'], (inner) => {
|
|
55
86
|
const registry = inner.get('workspaceRegistry') as WorkspaceRegistrar
|
|
56
|
-
void
|
|
87
|
+
void syncRezWorkspaces(registry, getRole()).catch((error: unknown) => {
|
|
57
88
|
const why = error instanceof Error ? error.message : String(error)
|
|
58
|
-
console.error('[dsh-rez-suite]
|
|
89
|
+
console.error('[dsh-rez-suite] workspace sync failed:', why)
|
|
59
90
|
})
|
|
60
91
|
})
|
|
61
92
|
}
|
|
62
93
|
run()
|
|
63
94
|
return run
|
|
64
95
|
}
|
|
96
|
+
|
|
97
|
+
/** @deprecated use mountRezWorkspaces */
|
|
98
|
+
export const mountBossDesk = mountRezWorkspaces
|
package/src/boss/register.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Register or drop
|
|
2
|
+
* Register or drop workspace rows in the official sidebar.
|
|
3
3
|
* `workspaceRegistry.create` is idempotent: same canonical path returns the
|
|
4
|
-
* existing record. New records are prepended, so we register in reverse
|
|
5
|
-
*
|
|
6
|
-
* `delete` only drops the registry row —
|
|
4
|
+
* existing record. New records are prepended, so we register in reverse then
|
|
5
|
+
* `insertBefore` to pin 搞钱 → 产品 → 运营 → 人事助理 → 财务助理 → 法务助理
|
|
6
|
+
* (微信 stays after that block). `delete` only drops the registry row —
|
|
7
|
+
* directories and session logs stay.
|
|
7
8
|
*/
|
|
8
9
|
import { realpathSync } from 'node:fs'
|
|
9
10
|
|
|
@@ -17,9 +18,11 @@ export interface WorkspaceRegistrar {
|
|
|
17
18
|
create(path: string, title?: string): Promise<unknown>
|
|
18
19
|
list(): WorkspaceRecord[]
|
|
19
20
|
delete(id: string): Promise<boolean>
|
|
21
|
+
/** Host/client primitive: move a row; omitted beforeId appends. */
|
|
22
|
+
insertBefore?(workspaceId: string, beforeWorkspaceId?: string): Promise<void>
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
export type
|
|
25
|
+
export type RoomRef = { path: string; folder: string }
|
|
23
26
|
|
|
24
27
|
function canon(path: string): string {
|
|
25
28
|
try {
|
|
@@ -29,26 +32,62 @@ function canon(path: string): string {
|
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
export function
|
|
35
|
+
export function isRegisteredRoom(workspace: WorkspaceRecord, rooms: ReadonlyArray<RoomRef>): boolean {
|
|
33
36
|
const wanted = new Set(rooms.map((room) => canon(room.path)))
|
|
34
37
|
return wanted.has(canon(workspace.path))
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export
|
|
40
|
+
export const isRegisteredBossRoom = isRegisteredRoom
|
|
41
|
+
|
|
42
|
+
export async function registerWorkspaces(
|
|
38
43
|
registry: WorkspaceRegistrar,
|
|
39
|
-
rooms: ReadonlyArray<
|
|
44
|
+
rooms: ReadonlyArray<RoomRef>,
|
|
40
45
|
): Promise<void> {
|
|
41
46
|
for (const room of [...rooms].reverse()) {
|
|
42
47
|
await registry.create(room.path, room.folder)
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
export
|
|
51
|
+
export const registerBossWorkspaces = registerWorkspaces
|
|
52
|
+
|
|
53
|
+
export async function unregisterWorkspaces(
|
|
47
54
|
registry: WorkspaceRegistrar,
|
|
48
|
-
rooms: ReadonlyArray<
|
|
55
|
+
rooms: ReadonlyArray<RoomRef>,
|
|
49
56
|
): Promise<void> {
|
|
50
57
|
for (const workspace of registry.list()) {
|
|
51
|
-
if (!
|
|
58
|
+
if (!isRegisteredRoom(workspace, rooms)) continue
|
|
52
59
|
await registry.delete(workspace.id)
|
|
53
60
|
}
|
|
54
61
|
}
|
|
62
|
+
|
|
63
|
+
export const unregisterBossWorkspaces = unregisterWorkspaces
|
|
64
|
+
|
|
65
|
+
export function findWechatWorkspace(listed: ReadonlyArray<WorkspaceRecord>): WorkspaceRecord | undefined {
|
|
66
|
+
return listed.find((workspace) => (
|
|
67
|
+
workspace.title === '微信' || /(?:^|\/)微信$/.test(canon(workspace.path).replace(/\\/g, '/'))
|
|
68
|
+
))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Place `rooms` in the given order, immediately before 微信 when that folder
|
|
73
|
+
* exists. No-op when insertBefore is missing (create-prepend fallback).
|
|
74
|
+
*/
|
|
75
|
+
export async function orderWorkspaces(
|
|
76
|
+
registry: WorkspaceRegistrar,
|
|
77
|
+
rooms: ReadonlyArray<RoomRef>,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
if (typeof registry.insertBefore !== 'function' || rooms.length === 0) return
|
|
80
|
+
const listed = registry.list()
|
|
81
|
+
const wechat = findWechatWorkspace(listed)
|
|
82
|
+
const ids: string[] = []
|
|
83
|
+
for (const room of rooms) {
|
|
84
|
+
const rec = listed.find((workspace) => canon(workspace.path) === canon(room.path))
|
|
85
|
+
if (rec !== undefined) ids.push(rec.id)
|
|
86
|
+
}
|
|
87
|
+
if (ids.length === 0) return
|
|
88
|
+
const beforeLast = wechat !== undefined && !ids.includes(wechat.id) ? wechat.id : undefined
|
|
89
|
+
for (let index = ids.length - 1; index >= 0; index -= 1) {
|
|
90
|
+
const before = index === ids.length - 1 ? beforeLast : ids[index + 1]
|
|
91
|
+
await registry.insertBefore(ids[index], before)
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/boss/seed.ts
CHANGED
|
@@ -8,7 +8,16 @@ export const BOSS_ROOMS = [
|
|
|
8
8
|
{ id: 'ops', folder: '运营' },
|
|
9
9
|
] as const
|
|
10
10
|
|
|
11
|
+
export const STAFF_ROOMS = [
|
|
12
|
+
{ id: 'hr', folder: '人事助理' },
|
|
13
|
+
{ id: 'finance', folder: '财务助理' },
|
|
14
|
+
{ id: 'legal', folder: '法务助理' },
|
|
15
|
+
] as const
|
|
16
|
+
|
|
11
17
|
export type BossRoomId = (typeof BOSS_ROOMS)[number]['id']
|
|
18
|
+
export type StaffRoomId = (typeof STAFF_ROOMS)[number]['id']
|
|
19
|
+
export type RoomId = BossRoomId | StaffRoomId
|
|
20
|
+
export type RoomSpec = { id: string; folder: string }
|
|
12
21
|
|
|
13
22
|
/** User-owned files. Re-seeding never overwrites these. */
|
|
14
23
|
export const LIVING_BASENAMES = new Set(['USER.md', 'MEMORY.md', 'PRIORITIES.md', 'IDENTITY.md'])
|
|
@@ -19,7 +28,7 @@ export interface SeedOptions {
|
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export interface SeedRoomResult {
|
|
22
|
-
id:
|
|
31
|
+
id: string
|
|
23
32
|
folder: string
|
|
24
33
|
path: string
|
|
25
34
|
created: boolean
|
|
@@ -33,37 +42,65 @@ export interface SeedResult {
|
|
|
33
42
|
rooms: SeedRoomResult[]
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
function findTemplates(kind: 'boss' | 'staff', marker: string): string {
|
|
37
46
|
const here = fileURLToPath(new URL('.', import.meta.url))
|
|
38
47
|
const candidates = [
|
|
39
|
-
join(here, '../templates
|
|
40
|
-
join(here, '../../templates
|
|
41
|
-
join(here, 'templates
|
|
48
|
+
join(here, '../templates', kind),
|
|
49
|
+
join(here, '../../templates', kind),
|
|
50
|
+
join(here, 'templates', kind),
|
|
42
51
|
]
|
|
43
52
|
for (const dir of candidates) {
|
|
44
|
-
if (existsSync(join(dir,
|
|
53
|
+
if (existsSync(join(dir, marker))) return dir
|
|
45
54
|
}
|
|
46
|
-
throw new Error(
|
|
55
|
+
throw new Error(`missing ${kind} workspace templates (expected templates/${kind}/${marker})`)
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
export function
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
export function defaultBossTemplatesRoot(): string {
|
|
59
|
+
return findTemplates('boss', 'money')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function defaultStaffTemplatesRoot(): string {
|
|
63
|
+
return findTemplates('staff', 'hr')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @deprecated alias — production uses defaultBossTemplatesRoot */
|
|
67
|
+
export function defaultTemplatesRoot(): string {
|
|
68
|
+
return defaultBossTemplatesRoot()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function seedRooms(
|
|
72
|
+
destRoot: string,
|
|
73
|
+
rooms: ReadonlyArray<RoomSpec>,
|
|
74
|
+
templatesRoot: string,
|
|
75
|
+
): SeedResult {
|
|
76
|
+
mkdirSync(destRoot, { recursive: true })
|
|
52
77
|
return {
|
|
53
|
-
destRoot
|
|
54
|
-
rooms:
|
|
78
|
+
destRoot,
|
|
79
|
+
rooms: rooms.map((room) => seedRoom(templatesRoot, destRoot, room)),
|
|
55
80
|
}
|
|
56
81
|
}
|
|
57
82
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
export function seedBossWorkspaces(options: SeedOptions): SeedResult {
|
|
84
|
+
return seedRooms(
|
|
85
|
+
options.destRoot,
|
|
86
|
+
BOSS_ROOMS,
|
|
87
|
+
options.templatesRoot ?? defaultBossTemplatesRoot(),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function seedStaffWorkspaces(options: SeedOptions): SeedResult {
|
|
92
|
+
return seedRooms(
|
|
93
|
+
options.destRoot,
|
|
94
|
+
STAFF_ROOMS,
|
|
95
|
+
options.templatesRoot ?? defaultStaffTemplatesRoot(),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function seedRoom(templatesRoot: string, destRoot: string, room: RoomSpec): SeedRoomResult {
|
|
63
100
|
const src = join(templatesRoot, room.id)
|
|
64
101
|
const dest = join(destRoot, room.folder)
|
|
65
102
|
if (!existsSync(src) || !statSync(src).isDirectory()) {
|
|
66
|
-
throw new Error('missing
|
|
103
|
+
throw new Error('missing workspace template: ' + src)
|
|
67
104
|
}
|
|
68
105
|
|
|
69
106
|
const created = !existsSync(join(dest, 'AGENTS.md'))
|
package/src/client/api.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
type RezServerStatus,
|
|
10
10
|
type RezStatusResponse,
|
|
11
11
|
type RezTestResult,
|
|
12
|
-
type RezUpdateApplyResult,
|
|
13
|
-
type RezUpdateStatus,
|
|
14
12
|
type RezWeixinStatus,
|
|
15
13
|
} from '../protocol.ts'
|
|
16
14
|
|
|
@@ -99,14 +97,4 @@ export class RezApi {
|
|
|
99
97
|
const response = await fetch(REZ_API.weixin, { method: 'DELETE' })
|
|
100
98
|
return readJson<RezWeixinStatus>(response)
|
|
101
99
|
}
|
|
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
|
-
}
|
|
112
100
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ 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'
|
|
15
14
|
|
|
16
15
|
const NS = 'dsh-rez-suite'
|
|
17
16
|
|
|
@@ -21,7 +20,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
21
20
|
}
|
|
22
21
|
interface SlotMap {
|
|
23
22
|
'settings.plugin.item': {
|
|
24
|
-
kind: '
|
|
23
|
+
kind: 'list'
|
|
25
24
|
scope: 'root'
|
|
26
25
|
owner: { children?: never }
|
|
27
26
|
}
|
|
@@ -30,11 +29,6 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
30
29
|
scope: 'root'
|
|
31
30
|
owner: { children?: never }
|
|
32
31
|
}
|
|
33
|
-
'sidebar.footer.action': {
|
|
34
|
-
kind: 'list'
|
|
35
|
-
scope: 'root'
|
|
36
|
-
owner: { wide: boolean }
|
|
37
|
-
}
|
|
38
32
|
}
|
|
39
33
|
}
|
|
40
34
|
|
|
@@ -48,7 +42,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
48
42
|
|
|
49
43
|
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
|
50
44
|
name: 'settings.plugin.item',
|
|
51
|
-
|
|
45
|
+
id: 'rez-suite',
|
|
46
|
+
order: 40,
|
|
52
47
|
locale: NS,
|
|
53
48
|
}, RezPluginCard))
|
|
54
49
|
|
|
@@ -59,12 +54,4 @@ export function apply(ctx: ClientContext): void {
|
|
|
59
54
|
label: () => t('settings.title'),
|
|
60
55
|
locale: NS,
|
|
61
56
|
}, 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))
|
|
70
57
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const zh = {
|
|
|
14
14
|
'tab.audit': '用量',
|
|
15
15
|
// config
|
|
16
16
|
'config.role': '角色',
|
|
17
|
-
'config.roleHint': '
|
|
17
|
+
'config.roleHint': '选身份后左侧自动出现工作区。人事/财务/法务助理全员可见;搞钱/产品/运营仅「老板」。',
|
|
18
18
|
'role.all': '全部',
|
|
19
19
|
'role.boss': '老板',
|
|
20
20
|
'role.engineer': '工程师',
|
|
@@ -111,13 +111,6 @@ export const zh = {
|
|
|
111
111
|
'weixin.listening': '通道在听:微信 → 网页「微信」会话(同一条,不另开窗口)→ 回微信。',
|
|
112
112
|
'weixin.loggedIn': '已登录,通道未在听。点「重新连接」即可拉起,不必再扫码。',
|
|
113
113
|
'weixin.loggedOut': '尚未登录。',
|
|
114
|
-
'upgrade.label': '升级',
|
|
115
|
-
'upgrade.current': '已最新',
|
|
116
|
-
'upgrade.aria': '升级 ReZ-TI 插件',
|
|
117
|
-
'upgrade.running': '升级中…',
|
|
118
|
-
'upgrade.reloading': '正在重载',
|
|
119
|
-
'upgrade.already': '已是 {version}',
|
|
120
|
-
'upgrade.reloadTimeout': '升级完成,但网页还没重新连上。请刷新这一页。',
|
|
121
114
|
} as const
|
|
122
115
|
|
|
123
116
|
export type RezKey = keyof typeof zh
|
|
@@ -133,7 +126,7 @@ export const en: Record<RezKey, string> = {
|
|
|
133
126
|
'tab.status': 'Status',
|
|
134
127
|
'tab.audit': 'Usage',
|
|
135
128
|
'config.role': 'Role',
|
|
136
|
-
'config.roleHint': '
|
|
129
|
+
'config.roleHint': 'Identity registers workspaces in the sidebar. Staff assistants for every role; 搞钱 / 产品 / 运营 for Boss only.',
|
|
137
130
|
'role.all': 'All',
|
|
138
131
|
'role.boss': 'Boss',
|
|
139
132
|
'role.engineer': 'Engineer',
|
|
@@ -227,13 +220,6 @@ export const en: Record<RezKey, string> = {
|
|
|
227
220
|
'weixin.listening': 'Channel is live: WeChat → the WeChat folder session on the left → WeChat.',
|
|
228
221
|
'weixin.loggedIn': 'Logged in, listener idle. Click Reconnect — no need to scan again.',
|
|
229
222
|
'weixin.loggedOut': 'Not logged in.',
|
|
230
|
-
'upgrade.label': 'Upgrade',
|
|
231
|
-
'upgrade.current': 'Up to date',
|
|
232
|
-
'upgrade.aria': 'Upgrade the ReZ-TI plugin',
|
|
233
|
-
'upgrade.running': 'Updating…',
|
|
234
|
-
'upgrade.reloading': 'Reloading',
|
|
235
|
-
'upgrade.already': 'Already {version}',
|
|
236
|
-
'upgrade.reloadTimeout': 'Update finished, but the page did not reconnect. Refresh this tab.',
|
|
237
223
|
}
|
|
238
224
|
|
|
239
225
|
/** Minimal {name} interpolator shared by the panel helpers. */
|
|
@@ -294,85 +294,3 @@
|
|
|
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
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -27,8 +27,7 @@ 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 {
|
|
31
|
-
import { liveSelfUpdate } from './self-update.ts'
|
|
30
|
+
import { workspaceGuidanceFor, mountRezWorkspaces } from './boss/mount.ts'
|
|
32
31
|
|
|
33
32
|
export const name = 'rez-suite'
|
|
34
33
|
|
|
@@ -99,9 +98,12 @@ export const REZ_GUIDANCE_CORE = [
|
|
|
99
98
|
].join('')
|
|
100
99
|
|
|
101
100
|
export function rezGuidanceFor(role: RezConfig['role']): string {
|
|
102
|
-
return REZ_GUIDANCE_CORE +
|
|
101
|
+
return REZ_GUIDANCE_CORE + workspaceGuidanceFor(role)
|
|
103
102
|
}
|
|
104
103
|
|
|
104
|
+
/** @deprecated use rezGuidanceFor */
|
|
105
|
+
export const REZ_GUIDANCE = REZ_GUIDANCE_CORE
|
|
106
|
+
|
|
105
107
|
function normalize(input: Partial<RezConfig> | undefined): RezConfig {
|
|
106
108
|
return mergeConfig(defaultConfig(), input ?? {})
|
|
107
109
|
}
|
|
@@ -124,7 +126,7 @@ function delegatedHost(ctx: Context, getConfig: () => RezConfig): McpHost {
|
|
|
124
126
|
|
|
125
127
|
export function apply(ctx: Context, config?: RezConfig): void {
|
|
126
128
|
let current: () => RezConfig = () => normalize(config)
|
|
127
|
-
const
|
|
129
|
+
const syncWorkspaces = mountRezWorkspaces(ctx, () => current().role)
|
|
128
130
|
|
|
129
131
|
const tokens = new TokenManager(joinTokenDb())
|
|
130
132
|
const host = delegatedHost(ctx, () => current())
|
|
@@ -141,7 +143,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
141
143
|
const value = normalize(next as Partial<RezConfig>)
|
|
142
144
|
saveConfig(value)
|
|
143
145
|
current = () => value
|
|
144
|
-
|
|
146
|
+
syncWorkspaces()
|
|
145
147
|
try {
|
|
146
148
|
const settings = ctx.get('settings') as { update?: (namespace: unknown, value: unknown) => Promise<void> } | undefined
|
|
147
149
|
if (settings?.update !== undefined) await settings.update(REZ_SETTINGS_NAMESPACE, value)
|
|
@@ -156,7 +158,6 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
156
158
|
const emit = ctx.emit as unknown as (event: string, value: string) => void
|
|
157
159
|
emit('credentials/updated', ref)
|
|
158
160
|
},
|
|
159
|
-
selfUpdate: liveSelfUpdate(),
|
|
160
161
|
})
|
|
161
162
|
|
|
162
163
|
let disposeRoutes: (() => void) | undefined
|
|
@@ -194,11 +195,11 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
194
195
|
setSource: (source) => {
|
|
195
196
|
current = source
|
|
196
197
|
sync()
|
|
197
|
-
|
|
198
|
+
syncWorkspaces()
|
|
198
199
|
},
|
|
199
200
|
onChange: () => {
|
|
200
201
|
sync()
|
|
201
|
-
|
|
202
|
+
syncWorkspaces()
|
|
202
203
|
},
|
|
203
204
|
})
|
|
204
205
|
|
package/src/protocol.ts
CHANGED
|
@@ -168,18 +168,6 @@ export interface ApiErrorBody {
|
|
|
168
168
|
error: string
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/** Installed vs npm latest for the suite package. */
|
|
172
|
-
export interface RezUpdateStatus {
|
|
173
|
-
package: string
|
|
174
|
-
installed: string
|
|
175
|
-
latest: string
|
|
176
|
-
outdated: boolean
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export interface RezUpdateApplyResult extends RezUpdateStatus {
|
|
180
|
-
restarting: boolean
|
|
181
|
-
}
|
|
182
|
-
|
|
183
171
|
/** Route paths the client calls (shared literals). */
|
|
184
172
|
export const REZ_API_BASE = '/api/dsh-rez-suite' as const
|
|
185
173
|
|
|
@@ -191,5 +179,4 @@ export const REZ_API = {
|
|
|
191
179
|
auditReset: REZ_API_BASE + '/audit/reset',
|
|
192
180
|
weixin: REZ_API_BASE + '/weixin',
|
|
193
181
|
weixinVerify: REZ_API_BASE + '/weixin/verify',
|
|
194
|
-
update: REZ_API_BASE + '/update',
|
|
195
182
|
} as const
|
package/src/routes.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
writeManagedCredential,
|
|
14
14
|
type RezSystem,
|
|
15
15
|
} from '@rezti/dsh-rez-sso'
|
|
16
|
-
import type { RezConfig, RezMcpServerSpec
|
|
16
|
+
import type { RezConfig, RezMcpServerSpec } from './protocol.ts'
|
|
17
17
|
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'
|
|
@@ -135,16 +135,11 @@ export interface RezRoutesDeps {
|
|
|
135
135
|
host: McpHost
|
|
136
136
|
tokens: TokenManager
|
|
137
137
|
onCredentialWritten?: (ref: string) => void
|
|
138
|
-
selfUpdate?: {
|
|
139
|
-
status: () => Promise<RezUpdateStatus>
|
|
140
|
-
apply: () => Promise<RezUpdateApplyResult>
|
|
141
|
-
relaunch: () => void
|
|
142
|
-
}
|
|
143
138
|
}
|
|
144
139
|
|
|
145
140
|
/** Build every /api/dsh-rez-suite route (exact paths). */
|
|
146
141
|
export function makeRoutes(deps: RezRoutesDeps): WebRoute[] {
|
|
147
|
-
const { getConfig, updateConfig, host, tokens, onCredentialWritten
|
|
142
|
+
const { getConfig, updateConfig, host, tokens, onCredentialWritten } = deps
|
|
148
143
|
|
|
149
144
|
const guard = (req: IncomingMessage, res: ServerResponse, method: string): boolean => {
|
|
150
145
|
if (!isLoopbackRequest(req)) {
|
|
@@ -220,28 +215,5 @@ export function makeRoutes(deps: RezRoutesDeps): WebRoute[] {
|
|
|
220
215
|
writeJson(res, 200, { ok: true })
|
|
221
216
|
},
|
|
222
217
|
},
|
|
223
|
-
{
|
|
224
|
-
kind: 'exact',
|
|
225
|
-
path: REZ_API.update,
|
|
226
|
-
handler: async (req, res) => {
|
|
227
|
-
const method = req.method === 'POST' ? 'POST' : 'GET'
|
|
228
|
-
if (!guard(req, res, method)) return
|
|
229
|
-
if (selfUpdate === undefined) {
|
|
230
|
-
writeJson(res, 501, { error: 'self-update is not available' })
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
try {
|
|
234
|
-
if (method === 'GET') {
|
|
235
|
-
writeJson(res, 200, await selfUpdate.status())
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
const result = await selfUpdate.apply()
|
|
239
|
-
writeJson(res, 200, result)
|
|
240
|
-
if (result.restarting) setTimeout(() => { selfUpdate.relaunch() }, 400)
|
|
241
|
-
} catch (error) {
|
|
242
|
-
writeJson(res, 502, { error: error instanceof Error ? error.message : String(error) })
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
218
|
]
|
|
247
219
|
}
|