@rezti/dsh-rez-suite 0.1.5 → 0.1.7

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/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
+ import { REZ_CREDENTIAL_REFS, REZ_DEFAULT_CONNECTIONS, REZ_SYSTEMS, mergeConnections, secretConfigured, writeManagedCredential } from "@rezti/dsh-rez-sso";
4
5
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
5
6
  import { DatabaseSync } from "node:sqlite";
6
7
  //#region ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js
@@ -2841,6 +2842,98 @@ defineMethod("transform", [
2841
2842
  "preserve"
2842
2843
  ], ({ inner }, isInner) => inner.toString(isInner));
2843
2844
  //#endregion
2845
+ //#region src/observe-mcp.ts
2846
+ /**
2847
+ * Live MCP status without a second host.
2848
+ *
2849
+ * Official @deepseek-ai/dsh-mcp-client registers tools as mcp__<server>__*.
2850
+ * The settings Status tab used to hard-code disconnected after MCP ownership
2851
+ * moved; this module reads the same registry the agent sees.
2852
+ */
2853
+ const COMPOSED_SERVERS = [
2854
+ "odoo",
2855
+ "nextcloud",
2856
+ "wechat",
2857
+ "homeassistant"
2858
+ ];
2859
+ function mcpToolPrefix(server) {
2860
+ return "mcp__" + server + "__";
2861
+ }
2862
+ /** Count tools whose public name belongs to one composed MCP server. */
2863
+ function countMcpTools(toolNames, server) {
2864
+ const prefix = mcpToolPrefix(server);
2865
+ let count = 0;
2866
+ for (const name of toolNames) if (name.startsWith(prefix)) count += 1;
2867
+ return count;
2868
+ }
2869
+ function listRegisteredToolNames(tools) {
2870
+ if (tools === void 0 || typeof tools.schemas !== "function") return [];
2871
+ try {
2872
+ return tools.schemas().map((schema) => schema.name);
2873
+ } catch {
2874
+ return [];
2875
+ }
2876
+ }
2877
+ function isEnabled(config, server) {
2878
+ return config.connections[server].enabled !== false;
2879
+ }
2880
+ function observeServer(server, input) {
2881
+ const enabled = isEnabled(input.config, server);
2882
+ const secret = (input.secretPresent ?? secretConfigured)(server);
2883
+ const toolCount = countMcpTools(input.toolNames, server);
2884
+ const ref = REZ_CREDENTIAL_REFS[server];
2885
+ if (!enabled) return {
2886
+ server,
2887
+ enabled: false,
2888
+ state: "disconnected",
2889
+ toolCount: 0,
2890
+ registeredTools: 0,
2891
+ lastError: "已在设置中关闭"
2892
+ };
2893
+ if (toolCount > 0) return {
2894
+ server,
2895
+ enabled: true,
2896
+ state: "connected",
2897
+ toolCount,
2898
+ registeredTools: toolCount
2899
+ };
2900
+ if (!secret) return {
2901
+ server,
2902
+ enabled: true,
2903
+ state: "disconnected",
2904
+ toolCount: 0,
2905
+ registeredTools: 0,
2906
+ lastError: "未配置 " + ref
2907
+ };
2908
+ return {
2909
+ server,
2910
+ enabled: true,
2911
+ state: "disconnected",
2912
+ toolCount: 0,
2913
+ registeredTools: 0,
2914
+ lastError: ref + " 已配置,但本机尚未注册 " + mcpToolPrefix(server) + "*(dsh-mcp-client 未挂上)"
2915
+ };
2916
+ }
2917
+ function observeComposedServers(input) {
2918
+ return COMPOSED_SERVERS.map((server) => observeServer(server, input));
2919
+ }
2920
+ function testComposedServers(input) {
2921
+ return observeComposedServers(input).map((row) => {
2922
+ if (row.state === "connected") return {
2923
+ server: row.server,
2924
+ ok: true,
2925
+ toolCount: row.toolCount,
2926
+ serverInfo: row.toolCount + " tools registered (" + mcpToolPrefix(row.server) + "*)"
2927
+ };
2928
+ return {
2929
+ server: row.server,
2930
+ ok: false,
2931
+ toolCount: 0,
2932
+ error: row.lastError ?? "disconnected"
2933
+ };
2934
+ });
2935
+ }
2936
+ //#endregion
2844
2937
  //#region src/protocol.ts
2845
2938
  const REZ_API = {
2846
2939
  config: "/api/dsh-rez-suite/config",
@@ -2865,13 +2958,14 @@ const FORMAT_VERSION = 1;
2865
2958
  function rezStorePath() {
2866
2959
  return join(homedir(), ".dsh", "dsh-rez-suite.json");
2867
2960
  }
2868
- /** Defaults: panel-only. Company MCP rows live in cordis.patch.yml. */
2961
+ /** Defaults: company MCP endpoints. Secrets live in credentials.yaml / env. */
2869
2962
  function defaultConfig() {
2870
2963
  return {
2871
2964
  enabled: true,
2872
2965
  announceToAgent: true,
2873
2966
  role: "all",
2874
2967
  servers: {},
2968
+ connections: JSON.parse(JSON.stringify(REZ_DEFAULT_CONNECTIONS)),
2875
2969
  billing: {
2876
2970
  inputCostPer1k: .001,
2877
2971
  outputCostPer1k: .002,
@@ -2896,10 +2990,37 @@ function maskServer(server) {
2896
2990
  function publicConfig(config) {
2897
2991
  const servers = {};
2898
2992
  for (const [id, server] of Object.entries(config.servers)) servers[id] = maskServer(server);
2993
+ const connections = mergeConnections(config.connections);
2899
2994
  return {
2900
2995
  enabled: config.enabled,
2901
2996
  role: config.role,
2902
2997
  servers,
2998
+ connections: {
2999
+ odoo: {
3000
+ enabled: connections.odoo.enabled,
3001
+ url: connections.odoo.url,
3002
+ secretConfigured: secretConfigured("odoo"),
3003
+ secretRef: REZ_CREDENTIAL_REFS.odoo
3004
+ },
3005
+ nextcloud: {
3006
+ enabled: connections.nextcloud.enabled,
3007
+ url: connections.nextcloud.url,
3008
+ username: connections.nextcloud.username,
3009
+ secretConfigured: secretConfigured("nextcloud"),
3010
+ secretRef: REZ_CREDENTIAL_REFS.nextcloud
3011
+ },
3012
+ wechat: {
3013
+ enabled: connections.wechat.enabled,
3014
+ secretConfigured: secretConfigured("wechat"),
3015
+ secretRef: REZ_CREDENTIAL_REFS.wechat
3016
+ },
3017
+ homeassistant: {
3018
+ enabled: connections.homeassistant.enabled,
3019
+ url: connections.homeassistant.url,
3020
+ secretConfigured: secretConfigured("homeassistant"),
3021
+ secretRef: REZ_CREDENTIAL_REFS.homeassistant
3022
+ }
3023
+ },
2903
3024
  billing: { ...config.billing }
2904
3025
  };
2905
3026
  }
@@ -2911,6 +3032,7 @@ function mergeConfig(base, override) {
2911
3032
  if (typeof value.enabled === "boolean") result.enabled = value.enabled;
2912
3033
  if (typeof value.announceToAgent === "boolean") result.announceToAgent = value.announceToAgent;
2913
3034
  if (value.role === "engineer" || value.role === "sales" || value.role === "operations" || value.role === "all") result.role = value.role;
3035
+ result.connections = mergeConnections(value.connections ?? result.connections);
2914
3036
  if (typeof value.servers === "object" && value.servers !== null) for (const [id, raw] of Object.entries(value.servers)) {
2915
3037
  if (typeof raw !== "object" || raw === null) continue;
2916
3038
  const incoming = raw;
@@ -3019,8 +3141,25 @@ function mergeMaskedRecord(base, incoming) {
3019
3141
  /** Apply a browser-safe public patch without overwriting masked secrets. */
3020
3142
  function applyPublicPatch(current, patch) {
3021
3143
  const next = JSON.parse(JSON.stringify(current));
3144
+ const secrets = [];
3022
3145
  if (typeof patch.enabled === "boolean") next.enabled = patch.enabled;
3023
3146
  if (patch.role === "engineer" || patch.role === "sales" || patch.role === "operations" || patch.role === "all") next.role = patch.role;
3147
+ if (typeof patch.connections === "object" && patch.connections !== null) {
3148
+ const incoming = patch.connections;
3149
+ next.connections = mergeConnections({
3150
+ ...next.connections,
3151
+ ...incoming
3152
+ });
3153
+ for (const system of Object.keys(REZ_CREDENTIAL_REFS)) {
3154
+ const row = incoming[system];
3155
+ if (typeof row !== "object" || row === null) continue;
3156
+ const secret = row.secret;
3157
+ if (typeof secret === "string" && secret.length > 0 && secret !== SECRET_MASK) secrets.push({
3158
+ system,
3159
+ value: secret
3160
+ });
3161
+ }
3162
+ }
3024
3163
  if (typeof patch.servers === "object" && patch.servers !== null) for (const [id, raw] of Object.entries(patch.servers)) {
3025
3164
  if (typeof raw !== "object" || raw === null) continue;
3026
3165
  const incoming = raw;
@@ -3046,11 +3185,14 @@ function applyPublicPatch(current, patch) {
3046
3185
  if (typeof value.outputCostPer1k === "number") next.billing.outputCostPer1k = value.outputCostPer1k;
3047
3186
  if (typeof value.monthlyBudget === "number") next.billing.monthlyBudget = value.monthlyBudget;
3048
3187
  }
3049
- return next;
3188
+ return {
3189
+ next,
3190
+ secrets
3191
+ };
3050
3192
  }
3051
3193
  /** Build every /api/dsh-rez-suite route (exact paths). */
3052
3194
  function makeRoutes(deps) {
3053
- const { getConfig, updateConfig, host, tokens } = deps;
3195
+ const { getConfig, updateConfig, host, tokens, onCredentialWritten } = deps;
3054
3196
  const guard = (req, res, method) => {
3055
3197
  if (!isLoopbackRequest(req)) {
3056
3198
  writeJson(res, 403, { error: "forbidden: loopback-only" });
@@ -3077,8 +3219,11 @@ function makeRoutes(deps) {
3077
3219
  writeJson(res, 400, { error: "invalid JSON body" });
3078
3220
  return;
3079
3221
  }
3080
- const next = applyPublicPatch(getConfig(), body);
3081
- writeJson(res, 200, { config: publicConfig(await updateConfig(next)) });
3222
+ const { next, secrets } = applyPublicPatch(getConfig(), body);
3223
+ const saved = await updateConfig(next);
3224
+ for (const item of secrets) writeManagedCredential(REZ_CREDENTIAL_REFS[item.system], item.value);
3225
+ for (const system of REZ_SYSTEMS) onCredentialWritten?.(REZ_CREDENTIAL_REFS[system]);
3226
+ writeJson(res, 200, { config: publicConfig(saved) });
3082
3227
  }
3083
3228
  },
3084
3229
  {
@@ -7537,6 +7682,22 @@ const Config = Schema.object({
7537
7682
  "all"
7538
7683
  ]).default("all"),
7539
7684
  servers: Schema.dict(serverSchema).default({}),
7685
+ connections: Schema.object({
7686
+ odoo: Schema.object({
7687
+ enabled: Schema.boolean().default(true),
7688
+ url: Schema.string().default(REZ_DEFAULT_CONNECTIONS.odoo.url)
7689
+ }).default(REZ_DEFAULT_CONNECTIONS.odoo),
7690
+ nextcloud: Schema.object({
7691
+ enabled: Schema.boolean().default(true),
7692
+ url: Schema.string().default(REZ_DEFAULT_CONNECTIONS.nextcloud.url),
7693
+ username: Schema.string().default(REZ_DEFAULT_CONNECTIONS.nextcloud.username)
7694
+ }).default(REZ_DEFAULT_CONNECTIONS.nextcloud),
7695
+ wechat: Schema.object({ enabled: Schema.boolean().default(true) }).default(REZ_DEFAULT_CONNECTIONS.wechat),
7696
+ homeassistant: Schema.object({
7697
+ enabled: Schema.boolean().default(true),
7698
+ url: Schema.string().default(REZ_DEFAULT_CONNECTIONS.homeassistant.url)
7699
+ }).default(REZ_DEFAULT_CONNECTIONS.homeassistant)
7700
+ }).default(REZ_DEFAULT_CONNECTIONS),
7540
7701
  billing: Schema.object({
7541
7702
  inputCostPer1k: Schema.number().default(.001),
7542
7703
  outputCostPer1k: Schema.number().default(.002),
@@ -7548,12 +7709,6 @@ const Config = Schema.object({
7548
7709
  })
7549
7710
  });
7550
7711
  const SECTION_ORDER = 150;
7551
- const COMPOSED_SERVERS = [
7552
- "odoo",
7553
- "nextcloud",
7554
- "wechat",
7555
- "homeassistant"
7556
- ];
7557
7712
  const REZ_GUIDANCE = [
7558
7713
  "本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。",
7559
7714
  "工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。",
@@ -7565,23 +7720,17 @@ const REZ_GUIDANCE = [
7565
7720
  function normalize(input) {
7566
7721
  return mergeConfig(defaultConfig(), input ?? {});
7567
7722
  }
7568
- function delegatedHost() {
7569
- const status = () => COMPOSED_SERVERS.map((server) => ({
7570
- server,
7571
- enabled: true,
7572
- state: "disconnected",
7573
- toolCount: 0,
7574
- registeredTools: 0,
7575
- lastError: "owned by @deepseek-ai/dsh-mcp-client"
7576
- }));
7577
- const test = async () => COMPOSED_SERVERS.map((server) => ({
7578
- server,
7579
- ok: false,
7580
- error: "MCP is composed by dsh-mcp-client; set REZ_* credentials and check dsh logs"
7581
- }));
7723
+ function delegatedHost(ctx, getConfig) {
7724
+ const snapshot = () => observeComposedServers({
7725
+ config: getConfig(),
7726
+ toolNames: listRegisteredToolNames(ctx.tools)
7727
+ });
7582
7728
  return {
7583
- status,
7584
- test,
7729
+ status: () => snapshot(),
7730
+ test: async () => testComposedServers({
7731
+ config: getConfig(),
7732
+ toolNames: listRegisteredToolNames(ctx.tools)
7733
+ }),
7585
7734
  sync: async () => void 0,
7586
7735
  dispose: async () => void 0
7587
7736
  };
@@ -7589,7 +7738,7 @@ function delegatedHost() {
7589
7738
  function apply(ctx, config) {
7590
7739
  let current = () => normalize(config);
7591
7740
  const tokens = new TokenManager(joinTokenDb());
7592
- const host = delegatedHost();
7741
+ const host = delegatedHost(ctx, () => current());
7593
7742
  ctx.effect(() => () => {
7594
7743
  tokens.close();
7595
7744
  }, "dsh-rez-suite: ledger view");
@@ -7609,7 +7758,11 @@ function apply(ctx, config) {
7609
7758
  return value;
7610
7759
  },
7611
7760
  host,
7612
- tokens
7761
+ tokens,
7762
+ onCredentialWritten: (ref) => {
7763
+ const emit = ctx.emit;
7764
+ emit("credentials/updated", ref);
7765
+ }
7613
7766
  });
7614
7767
  let disposeRoutes;
7615
7768
  let disposeTools;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rezti/dsh-rez-suite",
3
3
  "description": "ReZ-TI employee plugin for DeepSeek Harness: one install enables company MCP (Odoo / Nextcloud / WeCom / Weixin-ClawBot / Home Assistant) and work identity.",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
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-ha-bridge": "0.1.4",
42
- "@rezti/dsh-rez-sso": "0.1.4",
43
- "@rezti/dsh-rez-nextcloud": "0.1.4",
41
+ "@rezti/dsh-rez-ha-bridge": "0.1.5",
44
42
  "@rezti/dsh-rez-intent": "0.1.3",
45
- "@rezti/dsh-rez-odoo": "0.1.4",
43
+ "@rezti/dsh-rez-nextcloud": "0.1.5",
44
+ "@rezti/dsh-rez-sso": "0.1.5",
45
+ "@rezti/dsh-rez-odoo": "0.1.5",
46
46
  "@rezti/dsh-rez-token-manager": "0.1.3",
47
- "@rezti/dsh-rez-wechat": "0.1.4"
47
+ "@rezti/dsh-rez-wechat": "0.1.5"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": "^18.2.0",
@@ -27,16 +27,23 @@ export const zh = {
27
27
  'config.env': '环境变量 (JSON)',
28
28
  'config.headers': 'Headers (JSON)',
29
29
  'config.jsonInvalid': 'JSON 格式错误:{error}',
30
+ 'config.mcpHint': '四个公司 MCP 在这里配。密钥写入 ~/.dsh/.credentials.yaml(REZ_*)。新填的密钥会立刻拉起连接;改 URL 后如果已经连上,需要重启 dsh web。',
31
+ 'config.secretConfigured': '已配置(留空则保持)',
32
+ 'config.secretMissing': '未配置',
30
33
  'config.odoo': 'Odoo',
31
- 'config.odoo.url': 'Odoo URL',
32
- 'config.odoo.apiKey': 'API Key',
34
+ 'config.odoo.url': 'Odoo MCP URL',
35
+ 'config.odoo.apiKey': 'API Token',
33
36
  'config.nextcloud': 'Nextcloud',
34
- 'config.nextcloud.url': 'Nextcloud URL',
37
+ 'config.nextcloud.url': 'Nextcloud 主机',
35
38
  'config.nextcloud.username': '用户名',
36
39
  'config.nextcloud.appPassword': 'App Password',
37
40
  'config.wecom': '企业微信',
38
41
  'config.wecom.botId': 'Bot ID',
39
42
  'config.wecom.secret': 'Secret',
43
+ 'config.wecom.webhook': '群机器人 Webhook',
44
+ 'config.ha': 'Home Assistant',
45
+ 'config.ha.url': 'HA MCP URL',
46
+ 'config.ha.token': '长期访问令牌',
40
47
  'config.fs': '本地文件 (fs-mcp-server)',
41
48
  'config.fs.root': '根目录',
42
49
  'config.sqlite': 'SQLite (sqlite-mcp-server)',
@@ -53,6 +60,7 @@ export const zh = {
53
60
  'config.testOk': '成功({tools} 个工具, {latency}ms)',
54
61
  'config.testFail': '失败:{error}',
55
62
  // status
63
+ 'status.hint': '绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。',
56
64
  'status.total': '当前角色 {role},共注册 {count} 个 MCP 工具',
57
65
  'status.col.server': 'Server',
58
66
  'status.col.state': '状态',
@@ -122,16 +130,23 @@ export const en: Record<RezKey, string> = {
122
130
  'config.env': 'Env vars (JSON)',
123
131
  'config.headers': 'Headers (JSON)',
124
132
  'config.jsonInvalid': 'Invalid JSON: {error}',
133
+ 'config.mcpHint': 'Company MCP lives here. Secrets go to ~/.dsh/.credentials.yaml (REZ_*). A newly saved secret starts the connection immediately; URL changes after a live connection need a dsh web restart.',
134
+ 'config.secretConfigured': 'Configured (leave blank to keep)',
135
+ 'config.secretMissing': 'Not configured',
125
136
  'config.odoo': 'Odoo',
126
- 'config.odoo.url': 'Odoo URL',
127
- 'config.odoo.apiKey': 'API Key',
137
+ 'config.odoo.url': 'Odoo MCP URL',
138
+ 'config.odoo.apiKey': 'API token',
128
139
  'config.nextcloud': 'Nextcloud',
129
- 'config.nextcloud.url': 'Nextcloud URL',
140
+ 'config.nextcloud.url': 'Nextcloud host',
130
141
  'config.nextcloud.username': 'Username',
131
- 'config.nextcloud.appPassword': 'App Password',
142
+ 'config.nextcloud.appPassword': 'App password',
132
143
  'config.wecom': 'WeCom',
133
144
  'config.wecom.botId': 'Bot ID',
134
145
  'config.wecom.secret': 'Secret',
146
+ 'config.wecom.webhook': 'Group bot webhook',
147
+ 'config.ha': 'Home Assistant',
148
+ 'config.ha.url': 'HA MCP URL',
149
+ 'config.ha.token': 'Long-lived access token',
135
150
  'config.fs': 'Local files (fs-mcp-server)',
136
151
  'config.fs.root': 'Root directory',
137
152
  'config.sqlite': 'SQLite (sqlite-mcp-server)',
@@ -147,6 +162,7 @@ export const en: Record<RezKey, string> = {
147
162
  'config.testResult': '{server}: {status}',
148
163
  'config.testOk': 'OK ({tools} tools, {latency}ms)',
149
164
  'config.testFail': 'Failed: {error}',
165
+ 'status.hint': 'Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet.',
150
166
  'status.total': 'Role {role}, {count} MCP tools registered',
151
167
  'status.col.server': 'Server',
152
168
  'status.col.state': 'State',