@rezti/dsh-rez-suite 0.1.17 → 0.1.19
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 +1 -1
- package/lib/client.d.ts +2 -1
- package/lib/client.js +8 -2
- package/lib/index.d.ts +5 -3
- package/lib/index.js +83 -27
- package/package.json +5 -5
- package/src/boss/mount.ts +44 -18
- package/src/boss/register.ts +38 -2
- package/src/client/locales.ts +4 -2
- package/src/client/panel/ConfigTab.tsx +1 -0
- package/src/index.ts +15 -7
- package/src/presets.ts +3 -3
- package/src/protocol.ts +6 -1
- package/src/routes.ts +3 -3
- package/src/store.ts +2 -1
package/README.md
CHANGED
|
@@ -12,4 +12,4 @@ dsh plugin --profile web update @rezti/dsh-rez-suite
|
|
|
12
12
|
|
|
13
13
|
包名是 `@rezti/dsh-rez-suite`,不是 `dsh-rezti-suite`。不要对同一 profile 再单独 add 子包,会重复注册。
|
|
14
14
|
|
|
15
|
-
拆分包(`dsh-rez-odoo` 等)给开发改代码用。Web
|
|
15
|
+
拆分包(`dsh-rez-odoo` 等)给开发改代码用。Web 面板(含微信扫码)暂时仍在本包。设置里角色选「老板」后,才会在侧栏登记搞钱 / 产品 / 运营;切走老板会拿掉这三间(文件夹保留)。**不要**再装 `@rezti/dsh-rez-boss-desk`。
|
package/lib/client.d.ts
CHANGED
|
@@ -7262,8 +7262,9 @@ declare const zh: {
|
|
|
7262
7262
|
readonly 'tab.status': "状态";
|
|
7263
7263
|
readonly 'tab.audit': "用量";
|
|
7264
7264
|
readonly 'config.role': "角色";
|
|
7265
|
-
readonly 'config.roleHint': "
|
|
7265
|
+
readonly 'config.roleHint': "选「老板」会在左侧出现搞钱 / 产品 / 运营三室;其他角色决定 MCP 工具";
|
|
7266
7266
|
readonly 'role.all': "全部";
|
|
7267
|
+
readonly 'role.boss': "老板";
|
|
7267
7268
|
readonly 'role.engineer': "工程师";
|
|
7268
7269
|
readonly 'role.sales': "销售";
|
|
7269
7270
|
readonly 'role.operations': "运营";
|
package/lib/client.js
CHANGED
|
@@ -23,8 +23,9 @@ const zh = {
|
|
|
23
23
|
"tab.status": "状态",
|
|
24
24
|
"tab.audit": "用量",
|
|
25
25
|
"config.role": "角色",
|
|
26
|
-
"config.roleHint": "
|
|
26
|
+
"config.roleHint": "选「老板」会在左侧出现搞钱 / 产品 / 运营三室;其他角色决定 MCP 工具",
|
|
27
27
|
"role.all": "全部",
|
|
28
|
+
"role.boss": "老板",
|
|
28
29
|
"role.engineer": "工程师",
|
|
29
30
|
"role.sales": "销售",
|
|
30
31
|
"role.operations": "运营",
|
|
@@ -121,8 +122,9 @@ const en = {
|
|
|
121
122
|
"tab.status": "Status",
|
|
122
123
|
"tab.audit": "Usage",
|
|
123
124
|
"config.role": "Role",
|
|
124
|
-
"config.roleHint": "
|
|
125
|
+
"config.roleHint": "Boss adds 搞钱 / 产品 / 运营 workspaces; other roles filter MCP tools",
|
|
125
126
|
"role.all": "All",
|
|
127
|
+
"role.boss": "Boss",
|
|
126
128
|
"role.engineer": "Engineer",
|
|
127
129
|
"role.sales": "Sales",
|
|
128
130
|
"role.operations": "Operations",
|
|
@@ -515,6 +517,10 @@ const ROLES = [
|
|
|
515
517
|
value: "all",
|
|
516
518
|
label: "role.all"
|
|
517
519
|
},
|
|
520
|
+
{
|
|
521
|
+
value: "boss",
|
|
522
|
+
label: "role.boss"
|
|
523
|
+
},
|
|
518
524
|
{
|
|
519
525
|
value: "engineer",
|
|
520
526
|
label: "role.engineer"
|
package/lib/index.d.ts
CHANGED
|
@@ -1678,7 +1678,8 @@ declare abstract class SettingsProvider extends Service {
|
|
|
1678
1678
|
//#endregion
|
|
1679
1679
|
//#region src/protocol.d.ts
|
|
1680
1680
|
/** Agent role, the preset-loader key. */
|
|
1681
|
-
|
|
1681
|
+
declare const REZ_ROLE_IDS: readonly ["all", "boss", "engineer", "sales", "operations"];
|
|
1682
|
+
type RezRoleId = (typeof REZ_ROLE_IDS)[number];
|
|
1682
1683
|
/** Transport kinds the in-plugin MCP host can connect. */
|
|
1683
1684
|
type RezMcpTransport = 'stdio' | 'streamable-http';
|
|
1684
1685
|
/** One MCP server launch descriptor (standard MCP-style config). */
|
|
@@ -1740,7 +1741,8 @@ declare const name = "rez-suite";
|
|
|
1740
1741
|
declare const inject: string[];
|
|
1741
1742
|
declare const REZ_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
1742
1743
|
declare const Config: Schema<RezConfig>;
|
|
1743
|
-
declare const
|
|
1744
|
+
declare const REZ_GUIDANCE_CORE: string;
|
|
1745
|
+
declare function rezGuidanceFor(role: RezConfig['role']): string;
|
|
1744
1746
|
declare function apply(ctx: Context, config?: RezConfig): void;
|
|
1745
1747
|
//#endregion
|
|
1746
|
-
export { Config,
|
|
1748
|
+
export { Config, REZ_GUIDANCE_CORE, REZ_SETTINGS_NAMESPACE, apply, inject, name, rezGuidanceFor };
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, relative } from "node:path";
|
|
4
4
|
import { REZ_CREDENTIAL_REFS, REZ_DEFAULT_CONNECTIONS, REZ_SYSTEMS, lastMcpStartError, mergeConnections, secretConfigured, writeManagedCredential } from "@rezti/dsh-rez-sso";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { DatabaseSync } from "node:sqlite";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
//#region ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
@@ -2937,6 +2937,17 @@ function testComposedServers(input) {
|
|
|
2937
2937
|
}
|
|
2938
2938
|
//#endregion
|
|
2939
2939
|
//#region src/protocol.ts
|
|
2940
|
+
/** Agent role, the preset-loader key. */
|
|
2941
|
+
const REZ_ROLE_IDS = [
|
|
2942
|
+
"all",
|
|
2943
|
+
"boss",
|
|
2944
|
+
"engineer",
|
|
2945
|
+
"sales",
|
|
2946
|
+
"operations"
|
|
2947
|
+
];
|
|
2948
|
+
function isRezRoleId(value) {
|
|
2949
|
+
return typeof value === "string" && REZ_ROLE_IDS.includes(value);
|
|
2950
|
+
}
|
|
2940
2951
|
const REZ_API = {
|
|
2941
2952
|
config: "/api/dsh-rez-suite/config",
|
|
2942
2953
|
test: "/api/dsh-rez-suite/test",
|
|
@@ -3033,7 +3044,7 @@ function mergeConfig(base, override) {
|
|
|
3033
3044
|
const result = JSON.parse(JSON.stringify(base));
|
|
3034
3045
|
if (typeof value.enabled === "boolean") result.enabled = value.enabled;
|
|
3035
3046
|
if (typeof value.announceToAgent === "boolean") result.announceToAgent = value.announceToAgent;
|
|
3036
|
-
if (value.role
|
|
3047
|
+
if (isRezRoleId(value.role)) result.role = value.role;
|
|
3037
3048
|
result.connections = mergeConnections(value.connections ?? result.connections);
|
|
3038
3049
|
if (typeof value.servers === "object" && value.servers !== null) for (const [id, raw] of Object.entries(value.servers)) {
|
|
3039
3050
|
if (typeof raw !== "object" || raw === null) continue;
|
|
@@ -3145,7 +3156,7 @@ function applyPublicPatch(current, patch) {
|
|
|
3145
3156
|
const next = JSON.parse(JSON.stringify(current));
|
|
3146
3157
|
const secrets = [];
|
|
3147
3158
|
if (typeof patch.enabled === "boolean") next.enabled = patch.enabled;
|
|
3148
|
-
if (patch.role
|
|
3159
|
+
if (isRezRoleId(patch.role)) next.role = patch.role;
|
|
3149
3160
|
if (typeof patch.connections === "object" && patch.connections !== null) {
|
|
3150
3161
|
const incoming = patch.connections;
|
|
3151
3162
|
next.connections = mergeConnections({
|
|
@@ -7656,9 +7667,32 @@ function rezConfigTool(config) {
|
|
|
7656
7667
|
}
|
|
7657
7668
|
//#endregion
|
|
7658
7669
|
//#region src/boss/register.ts
|
|
7670
|
+
/**
|
|
7671
|
+
* Register or drop the three boss rooms in the official workspace sidebar.
|
|
7672
|
+
* `workspaceRegistry.create` is idempotent: same canonical path returns the
|
|
7673
|
+
* existing record. New records are prepended, so we register in reverse to
|
|
7674
|
+
* keep 搞钱 → 产品 → 运营 at the top of the sidebar.
|
|
7675
|
+
* `delete` only drops the registry row — directories and session logs stay.
|
|
7676
|
+
*/
|
|
7677
|
+
function canon(path) {
|
|
7678
|
+
try {
|
|
7679
|
+
return realpathSync(path);
|
|
7680
|
+
} catch {
|
|
7681
|
+
return path;
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
function isRegisteredBossRoom(workspace, rooms) {
|
|
7685
|
+
return new Set(rooms.map((room) => canon(room.path))).has(canon(workspace.path));
|
|
7686
|
+
}
|
|
7659
7687
|
async function registerBossWorkspaces(registry, rooms) {
|
|
7660
7688
|
for (const room of [...rooms].reverse()) await registry.create(room.path, room.folder);
|
|
7661
7689
|
}
|
|
7690
|
+
async function unregisterBossWorkspaces(registry, rooms) {
|
|
7691
|
+
for (const workspace of registry.list()) {
|
|
7692
|
+
if (!isRegisteredBossRoom(workspace, rooms)) continue;
|
|
7693
|
+
await registry.delete(workspace.id);
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7662
7696
|
//#endregion
|
|
7663
7697
|
//#region src/boss/seed.ts
|
|
7664
7698
|
const BOSS_ROOMS = [
|
|
@@ -7755,33 +7789,47 @@ function listFiles(root) {
|
|
|
7755
7789
|
}
|
|
7756
7790
|
//#endregion
|
|
7757
7791
|
//#region src/boss/mount.ts
|
|
7792
|
+
const BOSS_ROLE_HINT = "设置 → 插件 → ReZ-TI → 角色选「老板」后,左侧会出现搞钱 / 产品 / 运营三室。不必手动「添加工作区」。";
|
|
7758
7793
|
const BOSS_GUIDANCE = [
|
|
7759
|
-
"
|
|
7760
|
-
"
|
|
7794
|
+
"当前角色是老板。侧栏已登记三室:~/.dsh/workspaces/搞钱、产品、运营。不必手动「添加工作区」。",
|
|
7795
|
+
"每个文件夹是独立作战室,官方 dsh 会注入该目录 AGENTS.md。",
|
|
7761
7796
|
"若该室仍有 BOOTSTRAP.md:本会话先做短问三拍,沉淀到 PRIORITIES.md / MEMORY.md,然后删除 BOOTSTRAP.md。",
|
|
7762
7797
|
"已初始化后:读 PRIORITIES.md + MEMORY.md + 今日/昨日 memory/;回答固定四行:结论 → 数字(哪家公司、哪段时间)→ 拍板 1~3 件事 → 未执行任何过账/付款/对外发送。",
|
|
7763
7798
|
"微信文件夹仍是收件箱,不要在那边查账或砍 SKU。"
|
|
7764
7799
|
].join("");
|
|
7800
|
+
function bossGuidanceFor(role) {
|
|
7801
|
+
return role === "boss" ? BOSS_GUIDANCE : BOSS_ROLE_HINT;
|
|
7802
|
+
}
|
|
7765
7803
|
function destRoot(env = process.env) {
|
|
7766
7804
|
if (env.REZ_BOSS_WORKSPACES !== void 0 && env.REZ_BOSS_WORKSPACES !== "") return env.REZ_BOSS_WORKSPACES;
|
|
7767
7805
|
return join(env.DSH_HOME !== void 0 && env.DSH_HOME !== "" ? env.DSH_HOME : join(homedir(), ".dsh"), "workspaces");
|
|
7768
7806
|
}
|
|
7769
|
-
function
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7807
|
+
function expectedBossRooms(env = process.env) {
|
|
7808
|
+
const root = destRoot(env);
|
|
7809
|
+
return BOSS_ROOMS.map((room) => ({
|
|
7810
|
+
path: join(root, room.folder),
|
|
7811
|
+
folder: room.folder
|
|
7812
|
+
}));
|
|
7813
|
+
}
|
|
7814
|
+
async function syncBossWorkspaces(registry, role, env = process.env) {
|
|
7815
|
+
if (role === "boss") {
|
|
7816
|
+
await registerBossWorkspaces(registry, seedBossWorkspaces({ destRoot: destRoot(env) }).rooms);
|
|
7773
7817
|
return;
|
|
7774
7818
|
}
|
|
7819
|
+
await unregisterBossWorkspaces(registry, expectedBossRooms(env));
|
|
7775
7820
|
}
|
|
7776
|
-
/** Seed
|
|
7777
|
-
function mountBossDesk(ctx) {
|
|
7778
|
-
const
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7821
|
+
/** Seed/register 三室 only for role=boss; drop sidebar rows otherwise. Returns a re-sync function. */
|
|
7822
|
+
function mountBossDesk(ctx, getRole) {
|
|
7823
|
+
const run = () => {
|
|
7824
|
+
ctx.inject(["workspaceRegistry"], (inner) => {
|
|
7825
|
+
syncBossWorkspaces(inner.get("workspaceRegistry"), getRole()).catch((error) => {
|
|
7826
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
7827
|
+
console.error("[dsh-rez-suite] boss workspace sync failed:", why);
|
|
7828
|
+
});
|
|
7829
|
+
});
|
|
7830
|
+
};
|
|
7831
|
+
run();
|
|
7832
|
+
return run;
|
|
7785
7833
|
}
|
|
7786
7834
|
//#endregion
|
|
7787
7835
|
//#region src/index.ts
|
|
@@ -7807,10 +7855,11 @@ const Config = Schema.object({
|
|
|
7807
7855
|
enabled: Schema.boolean().default(true),
|
|
7808
7856
|
announceToAgent: Schema.boolean().default(true),
|
|
7809
7857
|
role: Schema.union([
|
|
7858
|
+
"all",
|
|
7859
|
+
"boss",
|
|
7810
7860
|
"engineer",
|
|
7811
7861
|
"sales",
|
|
7812
|
-
"operations"
|
|
7813
|
-
"all"
|
|
7862
|
+
"operations"
|
|
7814
7863
|
]).default("all"),
|
|
7815
7864
|
servers: Schema.dict(serverSchema).default({}),
|
|
7816
7865
|
connections: Schema.object({
|
|
@@ -7840,15 +7889,17 @@ const Config = Schema.object({
|
|
|
7840
7889
|
})
|
|
7841
7890
|
});
|
|
7842
7891
|
const SECTION_ORDER = 150;
|
|
7843
|
-
const
|
|
7892
|
+
const REZ_GUIDANCE_CORE = [
|
|
7844
7893
|
"本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。",
|
|
7845
7894
|
"工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。",
|
|
7846
7895
|
"密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN。",
|
|
7847
7896
|
"个人微信(QClaw/ClawBot):打开 设置 → 插件 → ReZ-TI → 微信 扫码,然后直接在微信里说话。消息进入网页左侧「微信」文件夹并延续同一会话,模型跟网页默认。网页归档了再从微信说话会自动恢复到侧栏。发「重启对话」会新开一条可见会话并把旧会话归档。网页端必须开着。不必再跑命令、也不要调用微信 MCP 工具。",
|
|
7848
7897
|
"意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。",
|
|
7849
|
-
"涉及资金、对外发送、设备控制、生产写入时先征得用户批准。"
|
|
7850
|
-
BOSS_GUIDANCE
|
|
7898
|
+
"涉及资金、对外发送、设备控制、生产写入时先征得用户批准。"
|
|
7851
7899
|
].join("");
|
|
7900
|
+
function rezGuidanceFor(role) {
|
|
7901
|
+
return REZ_GUIDANCE_CORE + bossGuidanceFor(role);
|
|
7902
|
+
}
|
|
7852
7903
|
function normalize(input) {
|
|
7853
7904
|
return mergeConfig(defaultConfig(), input ?? {});
|
|
7854
7905
|
}
|
|
@@ -7868,8 +7919,8 @@ function delegatedHost(ctx, getConfig) {
|
|
|
7868
7919
|
};
|
|
7869
7920
|
}
|
|
7870
7921
|
function apply(ctx, config) {
|
|
7871
|
-
mountBossDesk(ctx);
|
|
7872
7922
|
let current = () => normalize(config);
|
|
7923
|
+
const syncBossDesk = mountBossDesk(ctx, () => current().role);
|
|
7873
7924
|
const tokens = new TokenManager(joinTokenDb());
|
|
7874
7925
|
const host = delegatedHost(ctx, () => current());
|
|
7875
7926
|
ctx.effect(() => () => {
|
|
@@ -7882,6 +7933,7 @@ function apply(ctx, config) {
|
|
|
7882
7933
|
const value = normalize(next);
|
|
7883
7934
|
saveConfig(value);
|
|
7884
7935
|
current = () => value;
|
|
7936
|
+
syncBossDesk();
|
|
7885
7937
|
try {
|
|
7886
7938
|
const settings = ctx.get("settings");
|
|
7887
7939
|
if (settings?.update !== void 0) await settings.update(REZ_SETTINGS_NAMESPACE, value);
|
|
@@ -7918,7 +7970,7 @@ function apply(ctx, config) {
|
|
|
7918
7970
|
if (value.announceToAgent) disposeSection = ctx.systemPrompt.section({
|
|
7919
7971
|
name: "plugin:dsh-rez-suite",
|
|
7920
7972
|
order: SECTION_ORDER,
|
|
7921
|
-
text:
|
|
7973
|
+
text: rezGuidanceFor(value.role)
|
|
7922
7974
|
});
|
|
7923
7975
|
disposeRoutes = ctx.effect(() => {
|
|
7924
7976
|
const disposers = routes.map((route) => ctx.webServer.register(route));
|
|
@@ -7937,8 +7989,12 @@ function apply(ctx, config) {
|
|
|
7937
7989
|
setSource: (source) => {
|
|
7938
7990
|
current = source;
|
|
7939
7991
|
sync();
|
|
7992
|
+
syncBossDesk();
|
|
7940
7993
|
},
|
|
7941
|
-
onChange:
|
|
7994
|
+
onChange: () => {
|
|
7995
|
+
sync();
|
|
7996
|
+
syncBossDesk();
|
|
7997
|
+
}
|
|
7942
7998
|
});
|
|
7943
7999
|
sync();
|
|
7944
8000
|
}
|
|
@@ -7946,4 +8002,4 @@ function joinTokenDb() {
|
|
|
7946
8002
|
return join(homedir(), ".dsh", "dsh-rez-token-manager.sqlite");
|
|
7947
8003
|
}
|
|
7948
8004
|
//#endregion
|
|
7949
|
-
export { Config,
|
|
8005
|
+
export { Config, REZ_GUIDANCE_CORE, REZ_SETTINGS_NAMESPACE, apply, inject, name, rezGuidanceFor };
|
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.19",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
40
40
|
"zod": "^4.4.3",
|
|
41
|
-
"@rezti/dsh-rez-
|
|
42
|
-
"@rezti/dsh-rez-sso": "0.1.7",
|
|
43
|
-
"@rezti/dsh-rez-token-manager": "0.1.4",
|
|
41
|
+
"@rezti/dsh-rez-ha-bridge": "0.1.7",
|
|
44
42
|
"@rezti/dsh-rez-odoo": "0.1.7",
|
|
43
|
+
"@rezti/dsh-rez-intent": "0.1.4",
|
|
45
44
|
"@rezti/dsh-rez-nextcloud": "0.1.7",
|
|
45
|
+
"@rezti/dsh-rez-sso": "0.1.7",
|
|
46
46
|
"@rezti/dsh-rez-wechat": "0.1.11",
|
|
47
|
-
"@rezti/dsh-rez-
|
|
47
|
+
"@rezti/dsh-rez-token-manager": "0.1.4"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^18.2.0",
|
package/src/boss/mount.ts
CHANGED
|
@@ -1,38 +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
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
26
47
|
}
|
|
48
|
+
await unregisterBossWorkspaces(registry, expectedBossRooms(env))
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
/** Seed
|
|
30
|
-
export function mountBossDesk(ctx: Context): void {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
run()
|
|
63
|
+
return run
|
|
38
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/locales.ts
CHANGED
|
@@ -14,8 +14,9 @@ export const zh = {
|
|
|
14
14
|
'tab.audit': '用量',
|
|
15
15
|
// config
|
|
16
16
|
'config.role': '角色',
|
|
17
|
-
'config.roleHint': '
|
|
17
|
+
'config.roleHint': '选「老板」会在左侧出现搞钱 / 产品 / 运营三室;其他角色决定 MCP 工具',
|
|
18
18
|
'role.all': '全部',
|
|
19
|
+
'role.boss': '老板',
|
|
19
20
|
'role.engineer': '工程师',
|
|
20
21
|
'role.sales': '销售',
|
|
21
22
|
'role.operations': '运营',
|
|
@@ -118,8 +119,9 @@ export const en: Record<RezKey, string> = {
|
|
|
118
119
|
'tab.status': 'Status',
|
|
119
120
|
'tab.audit': 'Usage',
|
|
120
121
|
'config.role': 'Role',
|
|
121
|
-
'config.roleHint': '
|
|
122
|
+
'config.roleHint': 'Boss adds 搞钱 / 产品 / 运营 workspaces; other roles filter MCP tools',
|
|
122
123
|
'role.all': 'All',
|
|
124
|
+
'role.boss': 'Boss',
|
|
123
125
|
'role.engineer': 'Engineer',
|
|
124
126
|
'role.sales': 'Sales',
|
|
125
127
|
'role.operations': 'Operations',
|
|
@@ -11,6 +11,7 @@ import css from './panel.module.css'
|
|
|
11
11
|
|
|
12
12
|
const ROLES: Array<{ value: RezRoleId; label: string }> = [
|
|
13
13
|
{ value: 'all', label: 'role.all' },
|
|
14
|
+
{ value: 'boss', label: 'role.boss' },
|
|
14
15
|
{ value: 'engineer', label: 'role.engineer' },
|
|
15
16
|
{ value: 'sales', label: 'role.sales' },
|
|
16
17
|
{ value: 'operations', label: 'role.operations' },
|
package/src/index.ts
CHANGED
|
@@ -27,7 +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 {
|
|
30
|
+
import { bossGuidanceFor, mountBossDesk } from './boss/mount.ts'
|
|
31
31
|
|
|
32
32
|
export const name = 'rez-suite'
|
|
33
33
|
|
|
@@ -50,7 +50,7 @@ const serverSchema = z.object({
|
|
|
50
50
|
export const Config: z<RezConfig> = z.object({
|
|
51
51
|
enabled: z.boolean().default(true),
|
|
52
52
|
announceToAgent: z.boolean().default(true),
|
|
53
|
-
role: z.union(['
|
|
53
|
+
role: z.union(['all', 'boss', 'engineer', 'sales', 'operations'] as const).default('all'),
|
|
54
54
|
servers: z.dict(serverSchema).default({}),
|
|
55
55
|
connections: z.object({
|
|
56
56
|
odoo: z.object({
|
|
@@ -79,16 +79,19 @@ export const Config: z<RezConfig> = z.object({
|
|
|
79
79
|
|
|
80
80
|
const SECTION_ORDER = 150
|
|
81
81
|
|
|
82
|
-
export const
|
|
82
|
+
export const REZ_GUIDANCE_CORE = [
|
|
83
83
|
'本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。',
|
|
84
84
|
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。',
|
|
85
85
|
'密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN。',
|
|
86
86
|
'个人微信(QClaw/ClawBot):打开 设置 → 插件 → ReZ-TI → 微信 扫码,然后直接在微信里说话。消息进入网页左侧「微信」文件夹并延续同一会话,模型跟网页默认。网页归档了再从微信说话会自动恢复到侧栏。发「重启对话」会新开一条可见会话并把旧会话归档。网页端必须开着。不必再跑命令、也不要调用微信 MCP 工具。',
|
|
87
87
|
'意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。',
|
|
88
88
|
'涉及资金、对外发送、设备控制、生产写入时先征得用户批准。',
|
|
89
|
-
BOSS_GUIDANCE,
|
|
90
89
|
].join('')
|
|
91
90
|
|
|
91
|
+
export function rezGuidanceFor(role: RezConfig['role']): string {
|
|
92
|
+
return REZ_GUIDANCE_CORE + bossGuidanceFor(role)
|
|
93
|
+
}
|
|
94
|
+
|
|
92
95
|
function normalize(input: Partial<RezConfig> | undefined): RezConfig {
|
|
93
96
|
return mergeConfig(defaultConfig(), input ?? {})
|
|
94
97
|
}
|
|
@@ -110,8 +113,8 @@ function delegatedHost(ctx: Context, getConfig: () => RezConfig): McpHost {
|
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
export function apply(ctx: Context, config?: RezConfig): void {
|
|
113
|
-
mountBossDesk(ctx)
|
|
114
116
|
let current: () => RezConfig = () => normalize(config)
|
|
117
|
+
const syncBossDesk = mountBossDesk(ctx, () => current().role)
|
|
115
118
|
|
|
116
119
|
const tokens = new TokenManager(joinTokenDb())
|
|
117
120
|
const host = delegatedHost(ctx, () => current())
|
|
@@ -128,6 +131,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
128
131
|
const value = normalize(next as Partial<RezConfig>)
|
|
129
132
|
saveConfig(value)
|
|
130
133
|
current = () => value
|
|
134
|
+
syncBossDesk()
|
|
131
135
|
try {
|
|
132
136
|
const settings = ctx.get('settings') as { update?: (namespace: unknown, value: unknown) => Promise<void> } | undefined
|
|
133
137
|
if (settings?.update !== undefined) await settings.update(REZ_SETTINGS_NAMESPACE, value)
|
|
@@ -160,7 +164,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
160
164
|
disposeSection = ctx.systemPrompt.section({
|
|
161
165
|
name: 'plugin:dsh-rez-suite',
|
|
162
166
|
order: SECTION_ORDER,
|
|
163
|
-
text:
|
|
167
|
+
text: rezGuidanceFor(value.role),
|
|
164
168
|
})
|
|
165
169
|
}
|
|
166
170
|
|
|
@@ -179,8 +183,12 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
179
183
|
setSource: (source) => {
|
|
180
184
|
current = source
|
|
181
185
|
sync()
|
|
186
|
+
syncBossDesk()
|
|
187
|
+
},
|
|
188
|
+
onChange: () => {
|
|
189
|
+
sync()
|
|
190
|
+
syncBossDesk()
|
|
182
191
|
},
|
|
183
|
-
onChange: sync,
|
|
184
192
|
})
|
|
185
193
|
|
|
186
194
|
sync()
|
package/src/presets.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface RoleRule {
|
|
|
13
13
|
pattern: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const RULES: Record<Exclude<RezRoleId, 'all'>, RoleRule[]> = {
|
|
16
|
+
const RULES: Record<Exclude<RezRoleId, 'all' | 'boss'>, RoleRule[]> = {
|
|
17
17
|
engineer: [
|
|
18
18
|
// Odoo lookups and reads.
|
|
19
19
|
{ server: 'odoo', pattern: '*search*' },
|
|
@@ -111,7 +111,7 @@ function compilePattern(pattern: string): RegExp {
|
|
|
111
111
|
return new RegExp(source, 'i')
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const COMPILED: Record<Exclude<RezRoleId, 'all'>, { server: RezServerId; test: RegExp }[]> = {
|
|
114
|
+
const COMPILED: Record<Exclude<RezRoleId, 'all' | 'boss'>, { server: RezServerId; test: RegExp }[]> = {
|
|
115
115
|
engineer: RULES.engineer.map(rule => ({ server: rule.server, test: compilePattern(rule.pattern) })),
|
|
116
116
|
sales: RULES.sales.map(rule => ({ server: rule.server, test: compilePattern(rule.pattern) })),
|
|
117
117
|
operations: RULES.operations.map(rule => ({ server: rule.server, test: compilePattern(rule.pattern) })),
|
|
@@ -119,7 +119,7 @@ const COMPILED: Record<Exclude<RezRoleId, 'all'>, { server: RezServerId; test: R
|
|
|
119
119
|
|
|
120
120
|
/** Whether one MCP tool may be registered for the selected role. */
|
|
121
121
|
export function isToolAllowed(role: RezRoleId, server: string, rawName: string): boolean {
|
|
122
|
-
if (role === 'all') return true
|
|
122
|
+
if (role === 'all' || role === 'boss') return true
|
|
123
123
|
const rules = COMPILED[role]
|
|
124
124
|
return rules.some(rule => rule.server === server && rule.test.test(rawName))
|
|
125
125
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
export type RezServerId = string
|
|
9
9
|
|
|
10
10
|
/** Agent role, the preset-loader key. */
|
|
11
|
-
export
|
|
11
|
+
export const REZ_ROLE_IDS = ['all', 'boss', 'engineer', 'sales', 'operations'] as const
|
|
12
|
+
export type RezRoleId = (typeof REZ_ROLE_IDS)[number]
|
|
13
|
+
|
|
14
|
+
export function isRezRoleId(value: unknown): value is RezRoleId {
|
|
15
|
+
return typeof value === 'string' && (REZ_ROLE_IDS as readonly string[]).includes(value)
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
/** Transport kinds the in-plugin MCP host can connect. */
|
|
14
19
|
export type RezMcpTransport = 'stdio' | 'streamable-http'
|
package/src/routes.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
writeManagedCredential,
|
|
14
14
|
type RezSystem,
|
|
15
15
|
} from '@rezti/dsh-rez-sso'
|
|
16
|
-
import type { RezConfig, RezMcpServerSpec
|
|
17
|
-
import { REZ_API } from './protocol.ts'
|
|
16
|
+
import type { RezConfig, RezMcpServerSpec } from './protocol.ts'
|
|
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'
|
|
20
20
|
import { publicConfig } from './store.ts'
|
|
@@ -85,7 +85,7 @@ function applyPublicPatch(current: RezConfig, patch: Record<string, unknown>): {
|
|
|
85
85
|
const next: RezConfig = JSON.parse(JSON.stringify(current)) as RezConfig
|
|
86
86
|
const secrets: Array<{ system: RezSystem; value: string }> = []
|
|
87
87
|
if (typeof patch.enabled === 'boolean') next.enabled = patch.enabled
|
|
88
|
-
if (patch.role
|
|
88
|
+
if (isRezRoleId(patch.role)) next.role = patch.role
|
|
89
89
|
|
|
90
90
|
if (typeof patch.connections === 'object' && patch.connections !== null) {
|
|
91
91
|
const incoming = patch.connections as Record<string, unknown>
|
package/src/store.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
secretConfigured,
|
|
16
16
|
} from '@rezti/dsh-rez-sso'
|
|
17
17
|
import type { RezConfig, RezMcpServerSpec, RezPublicConfig } from './protocol.ts'
|
|
18
|
+
import { isRezRoleId } from './protocol.ts'
|
|
18
19
|
|
|
19
20
|
/** File format version. */
|
|
20
21
|
const FORMAT_VERSION = 1
|
|
@@ -128,7 +129,7 @@ export function mergeConfig(base: RezConfig, override: unknown): RezConfig {
|
|
|
128
129
|
const result: RezConfig = JSON.parse(JSON.stringify(base)) as RezConfig
|
|
129
130
|
if (typeof value.enabled === 'boolean') result.enabled = value.enabled
|
|
130
131
|
if (typeof value.announceToAgent === 'boolean') result.announceToAgent = value.announceToAgent
|
|
131
|
-
if (value.role
|
|
132
|
+
if (isRezRoleId(value.role)) result.role = value.role
|
|
132
133
|
|
|
133
134
|
result.connections = mergeConnections(value.connections ?? result.connections)
|
|
134
135
|
|