@rezti/dsh-rez-suite 0.1.48 → 0.1.50
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/CHANGELOG.md +12 -2
- package/README.md +3 -3
- package/README.zh.md +2 -2
- package/cordis.patch.yml +1 -1
- package/lib/client.d.ts +37 -3
- package/lib/client.js +533 -8
- package/lib/index.js +247 -19
- package/lib/style.css +18 -0
- package/package.json +4 -3
- package/src/boss/mount.ts +4 -3
- package/src/boss/register.ts +1 -1
- package/src/boss/seed.ts +29 -0
- package/src/changelog.ts +48 -0
- package/src/channel-board.ts +55 -0
- package/src/client/api.ts +15 -0
- package/src/client/locales.ts +74 -6
- package/src/client/panel/BoardTab.tsx +136 -0
- package/src/client/panel/ConfigTab.tsx +137 -2
- package/src/client/panel/StatusTab.tsx +31 -0
- package/src/client/panel/panel.module.css +18 -0
- package/src/client/settings-card.tsx +4 -1
- package/src/esphome-lab.ts +4 -3
- package/src/index.ts +3 -2
- package/src/observe-mcp.ts +1 -1
- package/src/protocol.ts +37 -0
- package/src/routes.ts +7 -1
- package/src/tools.ts +49 -6
- package/src/wecom-cli.ts +125 -0
- package/templates/boss/ops/AGENTS.md +2 -0
- package/templates/shared/wecom-cli.SOURCE.md +5 -0
- package/templates/shared/wecom-office/SKILL.md +31 -0
- package/templates/shared/wecomcli-calendar/SKILL.md +303 -0
- package/templates/shared/wecomcli-contact/SKILL.md +58 -0
- package/templates/shared/wecomcli-disk/SKILL.md +389 -0
- package/templates/shared/wecomcli-doc/SKILL.md +137 -0
- package/templates/shared/wecomcli-doc-manage/SKILL.md +132 -0
- package/templates/shared/wecomcli-email/SKILL.md +218 -0
- package/templates/shared/wecomcli-media/SKILL.md +98 -0
- package/templates/shared/wecomcli-meeting/SKILL.md +373 -0
- package/templates/shared/wecomcli-message/SKILL.md +200 -0
- package/templates/shared/wecomcli-shared/SKILL.md +73 -0
- package/templates/shared/wecomcli-sheet/SKILL.md +172 -0
- package/templates/shared/wecomcli-smartpage/SKILL.md +170 -0
- package/templates/shared/wecomcli-smartsheet/SKILL.md +154 -0
- package/templates/shared/wecomcli-todo/SKILL.md +76 -0
- package/templates/staff/design/AGENTS.md +2 -1
- package/templates/staff/ecommerce/.agents/skills/ops-ecommerce/SKILL.md +6 -0
- package/templates/staff/ecommerce/AGENTS.md +48 -0
- package/templates/staff/ecommerce/BOOTSTRAP.md +23 -0
- package/templates/staff/ecommerce/IDENTITY.md +8 -0
- package/templates/staff/ecommerce/MEMORY.md +9 -0
- package/templates/staff/ecommerce/PRIORITIES.md +3 -0
- package/templates/staff/ecommerce/SOUL.md +5 -0
- package/templates/staff/ecommerce/USER.md +8 -0
- package/templates/staff/hr/.agents/skills/staff-onboard-keys/SKILL.md +9 -7
- package/templates/staff/lab/AGENTS.md +1 -1
- package/templates/staff/publish/.agents/skills/ops-publish/SKILL.md +6 -0
- package/templates/staff/publish/AGENTS.md +56 -0
- package/templates/staff/publish/BOOTSTRAP.md +23 -0
- package/templates/staff/publish/IDENTITY.md +8 -0
- package/templates/staff/publish/MEMORY.md +9 -0
- package/templates/staff/publish/PRIORITIES.md +3 -0
- package/templates/staff/publish/SOUL.md +6 -0
- package/templates/staff/publish/USER.md +8 -0
package/lib/index.js
CHANGED
|
@@ -3,9 +3,9 @@ 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, loadRezConnections, mergeConnections, readManagedCredential, secretConfigured, startMcpWhenSecret, writeManagedCredential } from "@rezti/dsh-rez-sso";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
7
|
import { DatabaseSync } from "node:sqlite";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
9
|
//#region ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
10
10
|
/** Return true when a value is `null` or `undefined`. */
|
|
11
11
|
function isNullable(value) {
|
|
@@ -2935,7 +2935,7 @@ const COMPOSED_SERVERS = [
|
|
|
2935
2935
|
"tianyancha",
|
|
2936
2936
|
"esphome"
|
|
2937
2937
|
];
|
|
2938
|
-
/** Front panel hides
|
|
2938
|
+
/** Front panel hides webhook MCP wechat status; AI bots live in the Config WeCom bindings. TAPD 1154488930001001231. */
|
|
2939
2939
|
const FRONT_HIDDEN_SERVERS = /* @__PURE__ */ new Set(["wechat"]);
|
|
2940
2940
|
function mcpToolPrefix(server) {
|
|
2941
2941
|
if (server === "qcc") return "mcp__qcc-";
|
|
@@ -3062,6 +3062,7 @@ const REZ_API = {
|
|
|
3062
3062
|
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
3063
3063
|
weixin: "/api/dsh-rez-suite/weixin",
|
|
3064
3064
|
weixinVerify: "/api/dsh-rez-suite/weixin/verify",
|
|
3065
|
+
wecomChannels: "/api/dsh-rez-suite/wecom/channels",
|
|
3065
3066
|
update: "/api/dsh-rez-suite/update"
|
|
3066
3067
|
};
|
|
3067
3068
|
//#endregion
|
|
@@ -3229,6 +3230,145 @@ function saveConfig(config) {
|
|
|
3229
3230
|
renameSync(tmp, path);
|
|
3230
3231
|
}
|
|
3231
3232
|
//#endregion
|
|
3233
|
+
//#region src/wecom-cli.ts
|
|
3234
|
+
/**
|
|
3235
|
+
* Official WeCom office APIs are @wecom/cli, not mcp__wechat__*.
|
|
3236
|
+
* TAPD bug 1154488930001000054: missing MCP row was treated as “no WeCom”.
|
|
3237
|
+
* Official CLI v1.1+ opened 通讯录 / 在线文档 etc. for every company size
|
|
3238
|
+
* (https://github.com/WecomTeam/wecom-cli). Kelvin enabled those modules.
|
|
3239
|
+
*/
|
|
3240
|
+
/** Official @wecom/cli >=1.1 catalog. Do not invent extra HTTP APIs. */
|
|
3241
|
+
const WECOM_CLI_CATALOG = [
|
|
3242
|
+
{
|
|
3243
|
+
id: "contact",
|
|
3244
|
+
label: "通讯录"
|
|
3245
|
+
},
|
|
3246
|
+
{
|
|
3247
|
+
id: "doc",
|
|
3248
|
+
label: "在线文档"
|
|
3249
|
+
},
|
|
3250
|
+
{
|
|
3251
|
+
id: "doc-manage",
|
|
3252
|
+
label: "文档管理"
|
|
3253
|
+
},
|
|
3254
|
+
{
|
|
3255
|
+
id: "sheet",
|
|
3256
|
+
label: "在线表格"
|
|
3257
|
+
},
|
|
3258
|
+
{
|
|
3259
|
+
id: "smartsheet",
|
|
3260
|
+
label: "智能表格"
|
|
3261
|
+
},
|
|
3262
|
+
{
|
|
3263
|
+
id: "smartpage",
|
|
3264
|
+
label: "智能文档"
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
id: "todo",
|
|
3268
|
+
label: "待办"
|
|
3269
|
+
},
|
|
3270
|
+
{
|
|
3271
|
+
id: "calendar",
|
|
3272
|
+
label: "日程"
|
|
3273
|
+
},
|
|
3274
|
+
{
|
|
3275
|
+
id: "meeting",
|
|
3276
|
+
label: "会议"
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
id: "email",
|
|
3280
|
+
label: "邮件"
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
id: "disk",
|
|
3284
|
+
label: "微盘"
|
|
3285
|
+
},
|
|
3286
|
+
{
|
|
3287
|
+
id: "message",
|
|
3288
|
+
label: "消息"
|
|
3289
|
+
},
|
|
3290
|
+
{
|
|
3291
|
+
id: "media",
|
|
3292
|
+
label: "媒体文件"
|
|
3293
|
+
}
|
|
3294
|
+
];
|
|
3295
|
+
const MODULE_LABELS = WECOM_CLI_CATALOG.map((row) => row.label).join("/");
|
|
3296
|
+
const HINT_MISSING = [
|
|
3297
|
+
`未安装官方 wecom-cli(@wecom/cli >=1.1)。企微办公能力(${MODULE_LABELS})走 CLI,不是 MCP。`,
|
|
3298
|
+
"安装:npm i -g @wecom/cli ,然后 wecom-cli auth init。",
|
|
3299
|
+
"官方已面向各规模企业开放通讯录等能力,不要再说「超过 10 人只能用文档」。",
|
|
3300
|
+
"不要因为 rez_status 没有 wechat MCP 行就说没有企微能力。"
|
|
3301
|
+
].join("");
|
|
3302
|
+
const HINT_UNAUTH = [
|
|
3303
|
+
"已安装 wecom-cli,尚未授权。跑 wecom-cli auth init。",
|
|
3304
|
+
`授权后可用:${MODULE_LABELS}。本企业已开通这些品类。`,
|
|
3305
|
+
"CLI 报某模块未授权才说该能力未开通;不要说「没有企微 MCP」,也不要说 10 人限制。"
|
|
3306
|
+
].join("");
|
|
3307
|
+
const HINT_OK = [
|
|
3308
|
+
`企微办公能力走 wecom-cli >=1.1(${MODULE_LABELS})。本企业已开通。`,
|
|
3309
|
+
"按房间里官方 wecomcli-* 技能执行,命令以 wecom-cli <模块> --help 为准。",
|
|
3310
|
+
"不要用旧帮助中心「超过 10 人只能用文档」的说法。CLI 报未授权才说该模块未开通。",
|
|
3311
|
+
"设置页 BotID+Secret 只是房间消息通道。不要把没有 mcp__wechat__* 当成没有企微能力。"
|
|
3312
|
+
].join("");
|
|
3313
|
+
function wecomCliCatalog() {
|
|
3314
|
+
return WECOM_CLI_CATALOG.map((row) => ({ ...row }));
|
|
3315
|
+
}
|
|
3316
|
+
function defaultWecomCliRunner(argv) {
|
|
3317
|
+
const result = spawnSync("wecom-cli", argv, {
|
|
3318
|
+
encoding: "utf8",
|
|
3319
|
+
timeout: 4e3,
|
|
3320
|
+
env: process.env
|
|
3321
|
+
});
|
|
3322
|
+
const error = result.error;
|
|
3323
|
+
return {
|
|
3324
|
+
status: result.status,
|
|
3325
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
3326
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
3327
|
+
errorCode: error?.code
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
3330
|
+
function looksUnauthorized(text, status) {
|
|
3331
|
+
if (status !== 0) return true;
|
|
3332
|
+
return /未授权|未登录|未初始化|not (logged|auth)|unauthorized|请先.*auth|auth init/i.test(text);
|
|
3333
|
+
}
|
|
3334
|
+
function withCatalog(partial) {
|
|
3335
|
+
return {
|
|
3336
|
+
...partial,
|
|
3337
|
+
modules: wecomCliCatalog()
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
function probeWecomCli(run = defaultWecomCliRunner) {
|
|
3341
|
+
const help = run(["--help"]);
|
|
3342
|
+
if (help.errorCode === "ENOENT") return withCatalog({
|
|
3343
|
+
installed: false,
|
|
3344
|
+
authorized: false,
|
|
3345
|
+
hint: HINT_MISSING
|
|
3346
|
+
});
|
|
3347
|
+
const helpText = help.stdout + help.stderr;
|
|
3348
|
+
if (help.status !== 0 && helpText.trim().length === 0) return withCatalog({
|
|
3349
|
+
installed: false,
|
|
3350
|
+
authorized: false,
|
|
3351
|
+
hint: HINT_MISSING
|
|
3352
|
+
});
|
|
3353
|
+
const auth = run(["auth", "show"]);
|
|
3354
|
+
const text = auth.stdout + auth.stderr;
|
|
3355
|
+
if (auth.errorCode === "ENOENT") return withCatalog({
|
|
3356
|
+
installed: false,
|
|
3357
|
+
authorized: false,
|
|
3358
|
+
hint: HINT_MISSING
|
|
3359
|
+
});
|
|
3360
|
+
if (looksUnauthorized(text, auth.status)) return withCatalog({
|
|
3361
|
+
installed: true,
|
|
3362
|
+
authorized: false,
|
|
3363
|
+
hint: HINT_UNAUTH
|
|
3364
|
+
});
|
|
3365
|
+
return withCatalog({
|
|
3366
|
+
installed: true,
|
|
3367
|
+
authorized: true,
|
|
3368
|
+
hint: HINT_OK
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
//#endregion
|
|
3232
3372
|
//#region src/routes.ts
|
|
3233
3373
|
const MAX_JSON_BODY_BYTES = 256 * 1024;
|
|
3234
3374
|
const SECRET_MASK = "********";
|
|
@@ -3395,7 +3535,8 @@ function makeRoutes(deps) {
|
|
|
3395
3535
|
writeJson(res, 200, {
|
|
3396
3536
|
role: getConfig().role,
|
|
3397
3537
|
servers,
|
|
3398
|
-
totalRegisteredTools
|
|
3538
|
+
totalRegisteredTools,
|
|
3539
|
+
wecomCli: probeWecomCli()
|
|
3399
3540
|
});
|
|
3400
3541
|
}
|
|
3401
3542
|
},
|
|
@@ -7719,9 +7860,10 @@ function toolAbortedBeforeDispatchResult(prior) {
|
|
|
7719
7860
|
//#endregion
|
|
7720
7861
|
//#region src/esphome-lab.ts
|
|
7721
7862
|
/**
|
|
7722
|
-
* ESPHome lab: official dsh-mcp-client → lab MCP
|
|
7723
|
-
*
|
|
7724
|
-
*
|
|
7863
|
+
* ESPHome lab client in Harness: official dsh-mcp-client → rezti-lab MCP.
|
|
7864
|
+
* The compiler lives in Mac Mini Docker (`deploy/lab`, compose name rezti-lab).
|
|
7865
|
+
* Missing lab / missing token must not block `dsh web` (failOnStartupError: false).
|
|
7866
|
+
* Never puts firmware bytes into the model context; the tool returns a click-later download link.
|
|
7725
7867
|
*/
|
|
7726
7868
|
const ESPHOME_MCP_URL = "http://127.0.0.1:8100/mcp";
|
|
7727
7869
|
/** POST /v1/compile 只交 YAML、立刻回链接。真机 `esphome compile` 在后台,最长见实验室 `ESPHOME_COMPILE_TIMEOUT`(默认 20 分钟)。 */
|
|
@@ -7852,10 +7994,24 @@ function text(value) {
|
|
|
7852
7994
|
text: value
|
|
7853
7995
|
}];
|
|
7854
7996
|
}
|
|
7855
|
-
function
|
|
7997
|
+
function renderWecomCli(cli) {
|
|
7998
|
+
const row = cli ?? {
|
|
7999
|
+
installed: false,
|
|
8000
|
+
authorized: false,
|
|
8001
|
+
modules: [],
|
|
8002
|
+
hint: ""
|
|
8003
|
+
};
|
|
8004
|
+
const modules = (row.modules ?? []).map((item) => `${item.label}(${item.id})`).join("、");
|
|
8005
|
+
return [
|
|
8006
|
+
"wecom-cli(企微办公能力,不是 MCP): installed=" + (row.installed ? "yes" : "no") + " authorized=" + (row.authorized ? "yes" : "no"),
|
|
8007
|
+
modules.length > 0 ? "modules: " + modules : "modules: (官方目录见 wecomcli-* 技能)",
|
|
8008
|
+
row.hint
|
|
8009
|
+
].join("\n");
|
|
8010
|
+
}
|
|
8011
|
+
function rezStatusTool(host, config, wecomCli = probeWecomCli) {
|
|
7856
8012
|
return defineTool({
|
|
7857
8013
|
name: "rez_status",
|
|
7858
|
-
description: "List
|
|
8014
|
+
description: "List MCP server statuses plus WeCom office APIs (official wecom-cli, not mcp__wechat__). Triggers: rez, MCP, 连接状态, 企业微信, 企微能力.",
|
|
7859
8015
|
parameters: {},
|
|
7860
8016
|
output: {
|
|
7861
8017
|
schema: {
|
|
@@ -7896,6 +8052,43 @@ function rezStatusTool(host, config) {
|
|
|
7896
8052
|
startedAt: { type: "integer" }
|
|
7897
8053
|
}
|
|
7898
8054
|
}
|
|
8055
|
+
},
|
|
8056
|
+
wecomCli: {
|
|
8057
|
+
type: "object",
|
|
8058
|
+
required: true,
|
|
8059
|
+
additionalProperties: false,
|
|
8060
|
+
properties: {
|
|
8061
|
+
installed: {
|
|
8062
|
+
type: "boolean",
|
|
8063
|
+
required: true
|
|
8064
|
+
},
|
|
8065
|
+
authorized: {
|
|
8066
|
+
type: "boolean",
|
|
8067
|
+
required: true
|
|
8068
|
+
},
|
|
8069
|
+
hint: {
|
|
8070
|
+
type: "string",
|
|
8071
|
+
required: true
|
|
8072
|
+
},
|
|
8073
|
+
modules: {
|
|
8074
|
+
type: "array",
|
|
8075
|
+
required: true,
|
|
8076
|
+
items: {
|
|
8077
|
+
type: "object",
|
|
8078
|
+
additionalProperties: false,
|
|
8079
|
+
properties: {
|
|
8080
|
+
id: {
|
|
8081
|
+
type: "string",
|
|
8082
|
+
required: true
|
|
8083
|
+
},
|
|
8084
|
+
label: {
|
|
8085
|
+
type: "string",
|
|
8086
|
+
required: true
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
7899
8092
|
}
|
|
7900
8093
|
},
|
|
7901
8094
|
additionalProperties: false
|
|
@@ -7912,7 +8105,7 @@ function rezStatusTool(host, config) {
|
|
|
7912
8105
|
"server | state | discovered | registered | error",
|
|
7913
8106
|
"--- | --- | --- | --- | ---",
|
|
7914
8107
|
...rows
|
|
7915
|
-
].join("\n"));
|
|
8108
|
+
].join("\n") + "\n\n" + renderWecomCli(value.wecomCli));
|
|
7916
8109
|
}
|
|
7917
8110
|
},
|
|
7918
8111
|
async execute(_args, exec) {
|
|
@@ -7924,7 +8117,8 @@ function rezStatusTool(host, config) {
|
|
|
7924
8117
|
});
|
|
7925
8118
|
return {
|
|
7926
8119
|
role: config().role,
|
|
7927
|
-
servers
|
|
8120
|
+
servers,
|
|
8121
|
+
wecomCli: wecomCli()
|
|
7928
8122
|
};
|
|
7929
8123
|
}
|
|
7930
8124
|
});
|
|
@@ -8007,7 +8201,7 @@ function rezEsphomeCompileTool() {
|
|
|
8007
8201
|
* `workspaceRegistry.create` is idempotent: same canonical path returns the
|
|
8008
8202
|
* existing record. New records are prepended, so we register in reverse then
|
|
8009
8203
|
* `insertBefore` to pin 搞钱 → 产品 → 运营 → 人事助理 → 财务助理 → 法务助理
|
|
8010
|
-
* (运营身份:设计 → 人事助理 → 财务助理 → 法务助理;
|
|
8204
|
+
* (运营身份:设计 → 发布 → 电商 → 人事助理 → 财务助理 → 法务助理;
|
|
8011
8205
|
* 工程师身份:实验室 → 人事助理 → 财务助理 → 法务助理)
|
|
8012
8206
|
* (微信 stays after that block). `delete` only drops the registry row —
|
|
8013
8207
|
* directories and session logs stay.
|
|
@@ -8085,10 +8279,20 @@ const STAFF_ROOMS = [
|
|
|
8085
8279
|
}
|
|
8086
8280
|
];
|
|
8087
8281
|
/** 运营身份专用。不是全员助理,也不是老板「运营」简报室。 */
|
|
8088
|
-
const OPS_ROOMS = [
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8282
|
+
const OPS_ROOMS = [
|
|
8283
|
+
{
|
|
8284
|
+
id: "design",
|
|
8285
|
+
folder: "设计"
|
|
8286
|
+
},
|
|
8287
|
+
{
|
|
8288
|
+
id: "publish",
|
|
8289
|
+
folder: "发布"
|
|
8290
|
+
},
|
|
8291
|
+
{
|
|
8292
|
+
id: "ecommerce",
|
|
8293
|
+
folder: "电商"
|
|
8294
|
+
}
|
|
8295
|
+
];
|
|
8092
8296
|
/** 工程师身份专用。ESPHome YAML → 实验室服务器编译 firmware.bin。 */
|
|
8093
8297
|
const ENGINEER_ROOMS = [{
|
|
8094
8298
|
id: "lab",
|
|
@@ -8166,6 +8370,7 @@ function seedRoom(templatesRoot, destRoot, room) {
|
|
|
8166
8370
|
writeFileSync(target, readFileSync(abs));
|
|
8167
8371
|
written.push(rel);
|
|
8168
8372
|
}
|
|
8373
|
+
seedSharedSkills(dest, written);
|
|
8169
8374
|
return {
|
|
8170
8375
|
id: room.id,
|
|
8171
8376
|
folder: room.folder,
|
|
@@ -8176,6 +8381,24 @@ function seedRoom(templatesRoot, destRoot, room) {
|
|
|
8176
8381
|
skipped
|
|
8177
8382
|
};
|
|
8178
8383
|
}
|
|
8384
|
+
function defaultSharedTemplatesRoot() {
|
|
8385
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
8386
|
+
const candidates = [join(here, "../templates/shared"), join(here, "../../templates/shared")];
|
|
8387
|
+
for (const dir of candidates) if (existsSync(join(dir, "wecom-office", "SKILL.md"))) return dir;
|
|
8388
|
+
throw new Error("missing shared workspace templates (expected templates/shared/wecom-office/SKILL.md)");
|
|
8389
|
+
}
|
|
8390
|
+
function seedSharedSkills(dest, written) {
|
|
8391
|
+
const root = defaultSharedTemplatesRoot();
|
|
8392
|
+
for (const name of readdirSync(root).sort()) {
|
|
8393
|
+
const src = join(root, name, "SKILL.md");
|
|
8394
|
+
if (!existsSync(src)) continue;
|
|
8395
|
+
const rel = join(".agents", "skills", name, "SKILL.md");
|
|
8396
|
+
const target = join(dest, rel);
|
|
8397
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
8398
|
+
writeFileSync(target, readFileSync(src));
|
|
8399
|
+
written.push(rel);
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8179
8402
|
function listFiles(root) {
|
|
8180
8403
|
const out = [];
|
|
8181
8404
|
const walk = (dir) => {
|
|
@@ -8206,9 +8429,13 @@ const BOSS_GUIDANCE = [
|
|
|
8206
8429
|
"微信文件夹仍是收件箱,不要在那边查账、报销或砍 SKU。",
|
|
8207
8430
|
"用户用中文就中文、用英文就英文;不要因为工作区文件是中文就强制中文。"
|
|
8208
8431
|
].join("");
|
|
8209
|
-
const BOSS_ROLE_HINT = "搞钱 / 产品 / 运营仅角色选「老板」时出现。人事 / 财务 /
|
|
8432
|
+
const BOSS_ROLE_HINT = "搞钱 / 产品 / 运营仅角色选「老板」时出现。人事 / 财务 / 法务助理所有角色都会登记。设计 / 发布 / 电商工作房间仅「运营」身份出现。实验室仅「工程师」身份出现。";
|
|
8210
8433
|
const ENGINEER_LAB_GUIDANCE = ["当前身份是工程师。侧栏第一间是实验室:对方说板型/功能就自己写 ESPHome YAML,或用回形针/粘贴。调 mcp__esphome__compile(或 rez_esphome_compile)。", "立刻把下载链接发给对方。编译要时间:先点开可能是 README.md,编完刷新就是 firmware.bin;失败也是 README(含日志)。不要把 bin 贴进对话。未开通就说未开通。不要自写编译器,不要自写 Device Builder WebSocket,不要默认 OTA 刷机。"].join("");
|
|
8211
|
-
const OPS_DESIGN_GUIDANCE = [
|
|
8434
|
+
const OPS_DESIGN_GUIDANCE = [
|
|
8435
|
+
"当前身份是运营。侧栏先是设计工作房间:产品拍摄图、品牌规范、公司 VI 都在这里交。拍原片或 VI 图用官方 Comfy MCP 只修一次;规范 PDF / 源文件用回形针、不修。员工声明类型、Inbox 待审并转凯文。终审只在老板产品室。",
|
|
8436
|
+
"接着是发布工作间(媒体连接):内容/社媒 PCP 与 MediaJob,preview 后确认才 submit。不要另建图库,不要做日历 UI;排期写企微一个共享发布日历。",
|
|
8437
|
+
"再接着是电商工作间(ec operation):一期只做阿里巴巴国际站 ListingJob。Amazon 现在没有号,看板标后续,有号再开。设置页「看板」看两室连接与 Odoo/企微权限。不要代批、不要直接写 RAW、不要做图生场景或白底。搞钱 / 产品 / 运营仅老板身份出现。"
|
|
8438
|
+
].join("");
|
|
8212
8439
|
function workspaceGuidanceFor(role) {
|
|
8213
8440
|
if (role === "boss") return BOSS_GUIDANCE;
|
|
8214
8441
|
if (role === "operations") return STAFF_GUIDANCE + OPS_DESIGN_GUIDANCE;
|
|
@@ -8643,8 +8870,9 @@ const Config = Schema.object({
|
|
|
8643
8870
|
});
|
|
8644
8871
|
const SECTION_ORDER = 150;
|
|
8645
8872
|
const REZ_GUIDANCE_CORE = [
|
|
8646
|
-
"本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud /
|
|
8647
|
-
"工具名 mcp__odoo__*、mcp__nextcloud__*、
|
|
8873
|
+
"本机已安装 ReZ-TI 拆分包:工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / Home Assistant / TAPD / GSC。",
|
|
8874
|
+
"工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__homeassistant__*、mcp__tapd__*、mcp__gsc__*。",
|
|
8875
|
+
"企业微信办公能力不是 MCP:走官方 wecom-cli >=1.1(通讯录/在线文档/在线表格/智能表格/智能文档/待办/日程/会议/邮件/微盘/消息)。本企业已开通这些品类。看 rez_status 的 wecomCli.modules,按房间 wecomcli-* 技能执行。不要因为没有 mcp__wechat__* 就说没有企微能力,也不要说「超过 10 人只能用文档」。CLI 报某模块未授权才说该能力未开通。设置页 BotID+Secret 只是房间消息通道。旧群 webhook 兼容才会出现 mcp__wechat__*。",
|
|
8648
8876
|
"密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN、REZ_TAPD_TOKEN、REZ_GSC_KEY。",
|
|
8649
8877
|
"个人微信(QClaw/ClawBot):打开 设置 → 插件 → ReZ-TI → 微信 扫码,然后直接在微信里说话。消息进入网页左侧「微信」文件夹并延续同一会话,模型跟网页默认。网页归档了再从微信说话会自动恢复到侧栏。发「重启对话」会新开一条可见会话并把旧会话归档。网页端必须开着。不必再跑命令、也不要调用微信 MCP 工具。",
|
|
8650
8878
|
"意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。",
|
package/lib/style.css
CHANGED
|
@@ -209,6 +209,17 @@
|
|
|
209
209
|
height: 15px;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
.LOjPTW_wecomRow {
|
|
213
|
+
grid-template-columns: minmax(7rem, .8fr) minmax(8rem, 1fr) minmax(8rem, 1fr) auto;
|
|
214
|
+
align-items: end;
|
|
215
|
+
gap: 8px;
|
|
216
|
+
display: grid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.LOjPTW_wecomRow .LOjPTW_message {
|
|
220
|
+
grid-column: 1 / -1;
|
|
221
|
+
}
|
|
222
|
+
|
|
212
223
|
.LOjPTW_toolbar {
|
|
213
224
|
flex-wrap: wrap;
|
|
214
225
|
flex: none;
|
|
@@ -295,6 +306,13 @@
|
|
|
295
306
|
padding: 8px 10px;
|
|
296
307
|
}
|
|
297
308
|
|
|
309
|
+
.LOjPTW_boardTitle {
|
|
310
|
+
color: var(--dsw-alias-label-primary);
|
|
311
|
+
margin: 16px 0 4px;
|
|
312
|
+
font-size: 13px;
|
|
313
|
+
font-weight: 600;
|
|
314
|
+
}
|
|
315
|
+
|
|
298
316
|
.LOjPTW_badge {
|
|
299
317
|
border-radius: 999px;
|
|
300
318
|
padding: 2px 8px;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-suite",
|
|
3
|
-
"description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web add @rezti/dsh-rez-suite@0.1.50",
|
|
4
|
+
"version": "0.1.50",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@rezti/dsh-rez-sso": "^0.1.14",
|
|
46
46
|
"@rezti/dsh-rez-tapd": "^0.1.1",
|
|
47
47
|
"@rezti/dsh-rez-token-manager": "^0.1.4",
|
|
48
|
-
"@rezti/dsh-rez-wechat": "^0.1.
|
|
48
|
+
"@rezti/dsh-rez-wechat": "^0.1.16",
|
|
49
49
|
"@rezti/dsh-web-file-uploader": "0.2.3",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
51
51
|
"zod": "^4.4.3"
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"servers",
|
|
89
89
|
"templates/boss/**/*",
|
|
90
90
|
"templates/staff/**/*",
|
|
91
|
+
"templates/shared/**/*",
|
|
91
92
|
"cordis.patch.yml",
|
|
92
93
|
"README.md",
|
|
93
94
|
"README.zh.md",
|
package/src/boss/mount.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const BOSS_GUIDANCE = [
|
|
|
28
28
|
'用户用中文就中文、用英文就英文;不要因为工作区文件是中文就强制中文。',
|
|
29
29
|
].join('')
|
|
30
30
|
|
|
31
|
-
export const BOSS_ROLE_HINT = '搞钱 / 产品 / 运营仅角色选「老板」时出现。人事 / 财务 /
|
|
31
|
+
export const BOSS_ROLE_HINT = '搞钱 / 产品 / 运营仅角色选「老板」时出现。人事 / 财务 / 法务助理所有角色都会登记。设计 / 发布 / 电商工作房间仅「运营」身份出现。实验室仅「工程师」身份出现。'
|
|
32
32
|
|
|
33
33
|
export const ENGINEER_LAB_GUIDANCE = [
|
|
34
34
|
'当前身份是工程师。侧栏第一间是实验室:对方说板型/功能就自己写 ESPHome YAML,或用回形针/粘贴。调 mcp__esphome__compile(或 rez_esphome_compile)。',
|
|
@@ -36,8 +36,9 @@ export const ENGINEER_LAB_GUIDANCE = [
|
|
|
36
36
|
].join('')
|
|
37
37
|
|
|
38
38
|
export const OPS_DESIGN_GUIDANCE = [
|
|
39
|
-
'
|
|
40
|
-
'
|
|
39
|
+
'当前身份是运营。侧栏先是设计工作房间:产品拍摄图、品牌规范、公司 VI 都在这里交。拍原片或 VI 图用官方 Comfy MCP 只修一次;规范 PDF / 源文件用回形针、不修。员工声明类型、Inbox 待审并转凯文。终审只在老板产品室。',
|
|
40
|
+
'接着是发布工作间(媒体连接):内容/社媒 PCP 与 MediaJob,preview 后确认才 submit。不要另建图库,不要做日历 UI;排期写企微一个共享发布日历。',
|
|
41
|
+
'再接着是电商工作间(ec operation):一期只做阿里巴巴国际站 ListingJob。Amazon 现在没有号,看板标后续,有号再开。设置页「看板」看两室连接与 Odoo/企微权限。不要代批、不要直接写 RAW、不要做图生场景或白底。搞钱 / 产品 / 运营仅老板身份出现。',
|
|
41
42
|
].join('')
|
|
42
43
|
|
|
43
44
|
export function workspaceGuidanceFor(role: RezRoleId): string {
|
package/src/boss/register.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `workspaceRegistry.create` is idempotent: same canonical path returns the
|
|
4
4
|
* existing record. New records are prepended, so we register in reverse then
|
|
5
5
|
* `insertBefore` to pin 搞钱 → 产品 → 运营 → 人事助理 → 财务助理 → 法务助理
|
|
6
|
-
* (运营身份:设计 → 人事助理 → 财务助理 → 法务助理;
|
|
6
|
+
* (运营身份:设计 → 发布 → 电商 → 人事助理 → 财务助理 → 法务助理;
|
|
7
7
|
* 工程师身份:实验室 → 人事助理 → 财务助理 → 法务助理)
|
|
8
8
|
* (微信 stays after that block). `delete` only drops the registry row —
|
|
9
9
|
* directories and session logs stay.
|
package/src/boss/seed.ts
CHANGED
|
@@ -17,6 +17,8 @@ export const STAFF_ROOMS = [
|
|
|
17
17
|
/** 运营身份专用。不是全员助理,也不是老板「运营」简报室。 */
|
|
18
18
|
export const OPS_ROOMS = [
|
|
19
19
|
{ id: 'design', folder: '设计' },
|
|
20
|
+
{ id: 'publish', folder: '发布' },
|
|
21
|
+
{ id: 'ecommerce', folder: '电商' },
|
|
20
22
|
] as const
|
|
21
23
|
|
|
22
24
|
/** 工程师身份专用。ESPHome YAML → 实验室服务器编译 firmware.bin。 */
|
|
@@ -162,6 +164,8 @@ function seedRoom(templatesRoot: string, destRoot: string, room: RoomSpec): Seed
|
|
|
162
164
|
written.push(rel)
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
seedSharedSkills(dest, written)
|
|
168
|
+
|
|
165
169
|
return {
|
|
166
170
|
id: room.id,
|
|
167
171
|
folder: room.folder,
|
|
@@ -173,6 +177,31 @@ function seedRoom(templatesRoot: string, destRoot: string, room: RoomSpec): Seed
|
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
179
|
|
|
180
|
+
export function defaultSharedTemplatesRoot(): string {
|
|
181
|
+
const here = fileURLToPath(new URL('.', import.meta.url))
|
|
182
|
+
const candidates = [
|
|
183
|
+
join(here, '../templates/shared'),
|
|
184
|
+
join(here, '../../templates/shared'),
|
|
185
|
+
]
|
|
186
|
+
for (const dir of candidates) {
|
|
187
|
+
if (existsSync(join(dir, 'wecom-office', 'SKILL.md'))) return dir
|
|
188
|
+
}
|
|
189
|
+
throw new Error('missing shared workspace templates (expected templates/shared/wecom-office/SKILL.md)')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function seedSharedSkills(dest: string, written: string[]): void {
|
|
193
|
+
const root = defaultSharedTemplatesRoot()
|
|
194
|
+
for (const name of readdirSync(root).sort()) {
|
|
195
|
+
const src = join(root, name, 'SKILL.md')
|
|
196
|
+
if (!existsSync(src)) continue
|
|
197
|
+
const rel = join('.agents', 'skills', name, 'SKILL.md')
|
|
198
|
+
const target = join(dest, rel)
|
|
199
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
200
|
+
writeFileSync(target, readFileSync(src))
|
|
201
|
+
written.push(rel)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
176
205
|
function listFiles(root: string): string[] {
|
|
177
206
|
const out: string[] = []
|
|
178
207
|
const walk = (dir: string): void => {
|
package/src/changelog.ts
CHANGED
|
@@ -20,6 +20,54 @@ export interface ChangelogRelease {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const SUITE_CHANGELOG: readonly ChangelogRelease[] = [
|
|
23
|
+
{
|
|
24
|
+
version: '0.1.50',
|
|
25
|
+
date: '2026-08-20',
|
|
26
|
+
sections: [
|
|
27
|
+
{
|
|
28
|
+
title: { zh: '运营发布工作间', en: 'Operations publish room' },
|
|
29
|
+
items: [
|
|
30
|
+
{
|
|
31
|
+
zh: '运营身份侧栏多了「发布」房间(合同 Post editor / 媒体连接)和「电商」房间(一期只阿里巴巴国际站;Amazon 等有号)。设置页「看板」看两室状态与 Odoo/企微权限。不建图库,排期写企微共享日历。员工机安装包仍钉已发布版本,不要改审批镜像。',
|
|
32
|
+
en: 'Operations now gets 发布 (media / Post editor) and 电商 (phase 1: Alibaba.com; Amazon waits for an account). The settings Board tab shows channel status plus Odoo/WeCom identity. No media library; schedule goes to one shared WeCom calendar. Do not bump the installer SUITE_VERSION.',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: { zh: '企微固定对话框', en: 'Sticky WeCom sessions' },
|
|
38
|
+
items: [
|
|
39
|
+
{
|
|
40
|
+
zh: '同一房间连续说话复用网页左侧同一条会话。只有「重启对话」才新开。',
|
|
41
|
+
en: 'WeCom turns in one room reuse the same sidebar session. Only “重启对话” opens a new one.',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: { zh: '企微办公能力', en: 'WeCom office APIs' },
|
|
47
|
+
items: [
|
|
48
|
+
{
|
|
49
|
+
zh: 'rez_status / 状态页报官方 wecom-cli >=1.1 品类(通讯录/在线文档/表格/待办/日程/会议/邮件/微盘等)。房间种入官方 wecomcli-* 技能。不要把「没有 wechat MCP」或旧的 10 人限制当成没有企微能力。BotID 只是房间消息通道。不改员工机安装包。',
|
|
50
|
+
en: 'rez_status lists official wecom-cli >=1.1 modules (contacts/docs/sheets/todos/calendar/meetings/mail/disk). Rooms get upstream wecomcli-* skills. A missing wechat MCP row is not “no WeCom”. BotID is only the room chat channel. Do not bump the installer.',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
version: '0.1.49',
|
|
58
|
+
date: '2026-08-19',
|
|
59
|
+
sections: [
|
|
60
|
+
{
|
|
61
|
+
title: { zh: '企业微信智能机器人', en: 'WeCom AI bots' },
|
|
62
|
+
items: [
|
|
63
|
+
{
|
|
64
|
+
zh: '设置页按房间绑定企微智能机器人(BotID + Secret)。同事在企微跟哪个 bot 说话,就进网页左侧那个房间。密钥只存本机,不进 compose。',
|
|
65
|
+
en: 'Bind a WeCom AI bot (BotID + Secret) per Harness room. Talk to that bot in WeCom and the thread lands in that sidebar folder. Secrets stay on disk, not in compose.',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
23
71
|
{
|
|
24
72
|
version: '0.1.48',
|
|
25
73
|
date: '2026-08-19',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract Account connections → two ops rooms + the settings 看板.
|
|
3
|
+
* Phase-1 ecommerce is Alibaba.com only (Amazon waits for an account). Media stays in 发布.
|
|
4
|
+
* Secrets never live here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ChannelKind = 'media' | 'ecommerce'
|
|
8
|
+
export type ChannelPhase = '1' | 'later'
|
|
9
|
+
|
|
10
|
+
export interface ChannelSpec {
|
|
11
|
+
id: string
|
|
12
|
+
nameZh: string
|
|
13
|
+
nameEn: string
|
|
14
|
+
kind: ChannelKind
|
|
15
|
+
room: '发布' | '电商'
|
|
16
|
+
phase: ChannelPhase
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const MEDIA_ROOM = '发布'
|
|
20
|
+
export const ECOMMERCE_ROOM = '电商'
|
|
21
|
+
|
|
22
|
+
export const CHANNEL_BOARD: readonly ChannelSpec[] = [
|
|
23
|
+
{
|
|
24
|
+
id: 'media_via_publish',
|
|
25
|
+
nameZh: '内容 / 社媒分发',
|
|
26
|
+
nameEn: 'Content / social',
|
|
27
|
+
kind: 'media',
|
|
28
|
+
room: MEDIA_ROOM,
|
|
29
|
+
phase: '1',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'alibaba_icbu',
|
|
33
|
+
nameZh: '阿里巴巴国际站',
|
|
34
|
+
nameEn: 'Alibaba.com',
|
|
35
|
+
kind: 'ecommerce',
|
|
36
|
+
room: ECOMMERCE_ROOM,
|
|
37
|
+
phase: '1',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'amazon',
|
|
41
|
+
nameZh: 'Amazon',
|
|
42
|
+
nameEn: 'Amazon',
|
|
43
|
+
kind: 'ecommerce',
|
|
44
|
+
room: ECOMMERCE_ROOM,
|
|
45
|
+
phase: 'later',
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
export function channelsFor(kind: ChannelKind): ChannelSpec[] {
|
|
50
|
+
return CHANNEL_BOARD.filter((row) => row.kind === kind)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function wecomBound(room: string, bots: ReadonlyArray<{ room: string }>): boolean {
|
|
54
|
+
return bots.some((bot) => bot.room === room)
|
|
55
|
+
}
|
package/src/client/api.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type RezUpdateApplyResult,
|
|
13
13
|
type RezUpdateStatus,
|
|
14
14
|
type RezWeixinStatus,
|
|
15
|
+
type RezWecomChannelsResponse,
|
|
15
16
|
} from '../protocol.ts'
|
|
16
17
|
|
|
17
18
|
export class RezApiError extends Error {
|
|
@@ -100,6 +101,20 @@ export class RezApi {
|
|
|
100
101
|
return readJson<RezWeixinStatus>(response)
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
async wecomChannels(): Promise<RezWecomChannelsResponse> {
|
|
105
|
+
const response = await fetch(REZ_API.wecomChannels)
|
|
106
|
+
return readJson<RezWecomChannelsResponse>(response)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async saveWecomChannels(bots: Array<{ room: string; botId: string; secret?: string }>): Promise<RezWecomChannelsResponse> {
|
|
110
|
+
const response = await fetch(REZ_API.wecomChannels, {
|
|
111
|
+
method: 'PUT',
|
|
112
|
+
headers: { 'content-type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({ bots }),
|
|
114
|
+
})
|
|
115
|
+
return readJson<RezWecomChannelsResponse>(response)
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
async updateStatus(): Promise<RezUpdateStatus> {
|
|
104
119
|
const response = await fetch(REZ_API.update)
|
|
105
120
|
return readJson<RezUpdateStatus>(response)
|