@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.
Files changed (46) hide show
  1. package/README.md +5 -3
  2. package/lib/client.d.ts +2 -16
  3. package/lib/client.js +9 -145
  4. package/lib/index.d.ts +3 -1
  5. package/lib/index.js +108 -222
  6. package/lib/style.css +0 -81
  7. package/package.json +10 -10
  8. package/src/boss/mount.ts +54 -20
  9. package/src/boss/register.ts +50 -11
  10. package/src/boss/seed.ts +55 -18
  11. package/src/client/api.ts +0 -12
  12. package/src/client/index.ts +3 -16
  13. package/src/client/locales.ts +2 -16
  14. package/src/client/panel/panel.module.css +0 -82
  15. package/src/index.ts +9 -8
  16. package/src/protocol.ts +0 -13
  17. package/src/routes.ts +2 -30
  18. package/templates/staff/finance/.agents/skills/staff-finance/SKILL.md +6 -0
  19. package/templates/staff/finance/AGENTS.md +68 -0
  20. package/templates/staff/finance/BOOTSTRAP.md +23 -0
  21. package/templates/staff/finance/IDENTITY.md +8 -0
  22. package/templates/staff/finance/MEMORY.md +9 -0
  23. package/templates/staff/finance/PRIORITIES.md +3 -0
  24. package/templates/staff/finance/SOUL.md +6 -0
  25. package/templates/staff/finance/USER.md +8 -0
  26. package/templates/staff/finance/memory/.gitkeep +0 -0
  27. package/templates/staff/hr/.agents/skills/staff-hr/SKILL.md +6 -0
  28. package/templates/staff/hr/AGENTS.md +52 -0
  29. package/templates/staff/hr/BOOTSTRAP.md +23 -0
  30. package/templates/staff/hr/IDENTITY.md +8 -0
  31. package/templates/staff/hr/MEMORY.md +9 -0
  32. package/templates/staff/hr/PRIORITIES.md +3 -0
  33. package/templates/staff/hr/SOUL.md +6 -0
  34. package/templates/staff/hr/USER.md +8 -0
  35. package/templates/staff/hr/memory/.gitkeep +0 -0
  36. package/templates/staff/legal/.agents/skills/staff-legal/SKILL.md +6 -0
  37. package/templates/staff/legal/AGENTS.md +48 -0
  38. package/templates/staff/legal/BOOTSTRAP.md +23 -0
  39. package/templates/staff/legal/IDENTITY.md +8 -0
  40. package/templates/staff/legal/MEMORY.md +9 -0
  41. package/templates/staff/legal/PRIORITIES.md +3 -0
  42. package/templates/staff/legal/SOUL.md +6 -0
  43. package/templates/staff/legal/USER.md +8 -0
  44. package/templates/staff/legal/memory/.gitkeep +0 -0
  45. package/src/client/upgrade-button.tsx +0 -115
  46. 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
- registerBossWorkspaces,
7
- unregisterBossWorkspaces,
8
- type BossRoomRef,
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 BOSS_ROLE_HINT = '设置 → 插件 → ReZ-TI → 角色选「老板」后,左侧会出现搞钱 / 产品 / 运营三室。不必手动「添加工作区」。'
14
+ export const STAFF_GUIDANCE = [
15
+ '侧栏已登记人事助理 / 财务助理 / 法务助理。装完、选身份后自动出现,不必手动「添加工作区」、也不必拖排序。',
16
+ '三个助理给所有人用。能做什么看当前 Odoo 帐号的可视范围;Harness 只列选项,不自审、不自过账。',
17
+ '报销走财务助理。搞钱室是战略数字,不要在助理房间里做。',
18
+ ].join('')
14
19
 
15
20
  export const BOSS_GUIDANCE = [
16
- '当前角色是老板。侧栏已登记三室:~/.dsh/workspaces/搞钱、产品、运营。不必手动「添加工作区」。',
21
+ '当前角色是老板。侧栏已登记搞钱 / 产品 / 运营,后面是人事助理 / 财务助理 / 法务助理。不必手动「添加工作区」。',
17
22
  '每个文件夹是独立作战室,官方 dsh 会注入该目录 AGENTS.md。',
18
23
  '若该室仍有 BOOTSTRAP.md:本会话先做短问三拍,沉淀到 PRIORITIES.md / MEMORY.md,然后删除 BOOTSTRAP.md。',
19
- '已初始化后:读 PRIORITIES.md + MEMORY.md + 今日/昨日 memory/;回答固定四行:结论 → 数字(哪家公司、哪段时间)→ 拍板 1~3 件事 → 未执行任何过账/付款/对外发送。',
20
- '微信文件夹仍是收件箱,不要在那边查账或砍 SKU。',
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 === 'boss' ? BOSS_GUIDANCE : BOSS_ROLE_HINT
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
- export function expectedBossRooms(env: NodeJS.ProcessEnv = process.env): BossRoomRef[] {
45
+ function refsFor(rooms: ReadonlyArray<{ folder: string }>, env: NodeJS.ProcessEnv): RoomRef[] {
34
46
  const root = destRoot(env)
35
- return BOSS_ROOMS.map((room) => ({ path: join(root, room.folder), folder: room.folder }))
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 async function syncBossWorkspaces(
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 seeded = seedBossWorkspaces({ destRoot: destRoot(env) })
45
- await registerBossWorkspaces(registry, seeded.rooms)
46
- return
67
+ const boss = seedBossWorkspaces({ destRoot: destRoot(env) })
68
+ await registerWorkspaces(registry, boss.rooms)
69
+ } else {
70
+ await unregisterWorkspaces(registry, expectedBossRooms(env))
47
71
  }
48
- await unregisterBossWorkspaces(registry, expectedBossRooms(env))
72
+
73
+ const visible = role === 'boss'
74
+ ? [...expectedBossRooms(env), ...expectedStaffRooms(env)]
75
+ : expectedStaffRooms(env)
76
+ await orderWorkspaces(registry, visible)
49
77
  }
50
78
 
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 {
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 syncBossWorkspaces(registry, getRole()).catch((error: unknown) => {
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] boss workspace sync failed:', why)
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
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Register or drop the three boss rooms in the official workspace sidebar.
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 to
5
- * keep 搞钱 → 产品 → 运营 at the top of the sidebar.
6
- * `delete` only drops the registry row — directories and session logs stay.
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 BossRoomRef = { path: string; folder: string }
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 isRegisteredBossRoom(workspace: WorkspaceRecord, rooms: ReadonlyArray<BossRoomRef>): boolean {
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 async function registerBossWorkspaces(
40
+ export const isRegisteredBossRoom = isRegisteredRoom
41
+
42
+ export async function registerWorkspaces(
38
43
  registry: WorkspaceRegistrar,
39
- rooms: ReadonlyArray<BossRoomRef>,
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 async function unregisterBossWorkspaces(
51
+ export const registerBossWorkspaces = registerWorkspaces
52
+
53
+ export async function unregisterWorkspaces(
47
54
  registry: WorkspaceRegistrar,
48
- rooms: ReadonlyArray<BossRoomRef>,
55
+ rooms: ReadonlyArray<RoomRef>,
49
56
  ): Promise<void> {
50
57
  for (const workspace of registry.list()) {
51
- if (!isRegisteredBossRoom(workspace, rooms)) continue
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: BossRoomId
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
- export function defaultTemplatesRoot(): string {
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/boss'),
40
- join(here, '../../templates/boss'),
41
- join(here, 'templates/boss'),
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, 'money'))) return dir
53
+ if (existsSync(join(dir, marker))) return dir
45
54
  }
46
- throw new Error('missing boss workspace templates (expected templates/boss/money)')
55
+ throw new Error(`missing ${kind} workspace templates (expected templates/${kind}/${marker})`)
47
56
  }
48
57
 
49
- export function seedBossWorkspaces(options: SeedOptions): SeedResult {
50
- const templatesRoot = options.templatesRoot ?? defaultTemplatesRoot()
51
- mkdirSync(options.destRoot, { recursive: true })
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: options.destRoot,
54
- rooms: BOSS_ROOMS.map((room) => seedRoom(templatesRoot, options.destRoot, room)),
78
+ destRoot,
79
+ rooms: rooms.map((room) => seedRoom(templatesRoot, destRoot, room)),
55
80
  }
56
81
  }
57
82
 
58
- function seedRoom(
59
- templatesRoot: string,
60
- destRoot: string,
61
- room: (typeof BOSS_ROOMS)[number],
62
- ): SeedRoomResult {
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 boss workspace template: ' + src)
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
  }
@@ -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: 'keyed'
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
- key: NS,
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
  }
@@ -14,7 +14,7 @@ export const zh = {
14
14
  'tab.audit': '用量',
15
15
  // config
16
16
  'config.role': '角色',
17
- 'config.roleHint': '选「老板」会在左侧出现搞钱 / 产品 / 运营三室;其他角色决定 MCP 工具',
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': 'Boss adds 搞钱 / 产品 / 运营 workspaces; other roles filter MCP tools',
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 { bossGuidanceFor, mountBossDesk } from './boss/mount.ts'
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 + bossGuidanceFor(role)
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 syncBossDesk = mountBossDesk(ctx, () => current().role)
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
- syncBossDesk()
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
- syncBossDesk()
198
+ syncWorkspaces()
198
199
  },
199
200
  onChange: () => {
200
201
  sync()
201
- syncBossDesk()
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, RezUpdateApplyResult, RezUpdateStatus } from './protocol.ts'
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, selfUpdate } = deps
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
  }