@bty/customer-service-cli 0.5.5 → 0.6.2

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.2 (2026-06-15)
4
+
5
+ - `workspace` 新增 `points-consumes` 子命令:分页查询当前工作空间的积分账户消耗流水(`POST /v1/workspace/points_consumes`),支持 `--page` / `--page-size`,CLI 固定传 `debug: true`,返回中包含服务端 `extra` 调试字段。
6
+ - `customer-servhub-api` 的 `POST /v1/workspace/points_consumes` 新增 `debug` 请求参数:默认 `false` 不返回 `extra`,传 `true` 时返回 `extra` 方便调试。
7
+
8
+ ## 0.6.1 (2026-06-11)
9
+
10
+ - `product` 新增 `related-knowledge` 子命令组,支持按商品维护“关联知识库”文件级绑定:
11
+ - `product related-knowledge list --agent <id> --product-id <id>`:列出商品绑定的 QA / 扩展知识库文件。
12
+ - `product related-knowledge add --agent <id> --product-id <id> --type qa|common (--file <name> | --file-id <id> --partition-id <id>)`:给商品追加绑定文件,重复文件幂等去重。
13
+ - `product related-knowledge remove --agent <id> --product-id <id> --type qa|common --file-id <id>`:移除指定绑定,目标不存在时保持幂等。
14
+ - `product related-knowledge set --agent <id> --product-id <id> --bindings <json|@file>`:覆盖商品的全部关联知识库文件。
15
+ - `faq` / `knowledge` 新增文件侧 `bind-products` 子命令组,分别固定写入 `knowledge_type=qa` / `knowledge_type=common`:
16
+ - `list`:按知识库文件反查绑定商品。
17
+ - `add` / `remove`:read-modify-replace 维护绑定商品,重复添加或移除不存在的商品保持幂等。
18
+ - `set`:覆盖该文件绑定的全部商品。
19
+ - 新增 `product-knowledge-binding-api` 客户端封装,对接 customer-servhub-api `/v1/knowledge/product-bindings*` 现有接口;CLI 不改变服务端绑定表或 sop-engine 命中逻辑。
20
+
21
+ - 新增 `node-template describe [--stage <s>]`:节点编写说明书命令(AI 编写前必读)——输出每个节点的职责/可编辑面/输出契约/平台锁定段,以及该节点可 `@引用` 的变量目录。纯静态、与具体 agent 无关,无需 `--agent`。
22
+ - `node-template` 节点 stage 新增 `image_understanding` 伪 stage(图片理解方法论可配置化,spec 2026-06-08):`describe` / `get` / `set-draft` / `publish` / `versions` / `version` / `restore` 均纳入;发布前 `{{}}` 合法性校验白名单同步放行。
23
+ - **下线 `node-template enroll` / `enroll-status` 命令**:高级模式开关从「运维 CLI 灰度准入」改为「客户在 cs-client 内自助开关」(spec 2026-06-09),CLI 不再提供开启路径;servhub `enrolled` 端点仍在,供 cs-client 使用。
24
+
3
25
  ## 0.5.5 (2026-06-07)
4
26
 
5
27
  - 修复 `debug ask --messages` 重放多轮对话时,历史消息时间戳晚于当前时间导致本次 Agent 回复插入历史消息中间、review 页面消息乱序的问题。
package/README.md CHANGED
@@ -227,6 +227,10 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
227
227
  | `product identify-all --agent <id>` | 一键增强学习当前 Agent 下所有待增强商品 |
228
228
  | `product sync-taobao --agent <id> [--skip-hash-check] [--sync-type <full\|incremental>]` | 触发淘宝店铺商品同步 |
229
229
  | `product sync-taobao-item --agent <id> --product-id <商品ID>` | 触发单个淘宝商品同步 |
230
+ | `product related-knowledge list --agent <id> --product-id <商品ID>` | 列出商品详情绑定的 QA / 扩展知识库文件 |
231
+ | `product related-knowledge add --agent <id> --product-id <商品ID> --type <qa\|common> (--file <文件名> \| --file-id <id> --partition-id <id>)` | 给商品追加关联知识库文件 |
232
+ | `product related-knowledge remove --agent <id> --product-id <商品ID> --type <qa\|common> --file-id <id>` | 移除商品绑定的指定知识库文件 |
233
+ | `product related-knowledge set --agent <id> --product-id <商品ID> --bindings <json\|@file>` | 覆盖商品绑定的全部知识库文件 |
230
234
 
231
235
  `product update` 支持更新的字段包括:`卖点`、`补充知识`、`tag`、`参数`、`轮播图识别结果`、`商品详情页识别结果` 等。`--update` 接受 JSON 字符串或 `@文件路径`。
232
236
 
@@ -240,6 +244,8 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
240
244
 
241
245
  注意:这两个命令仅对已开通“高级工具”的店铺 Agent 生效。未开通高级工具时,即使 `--agent` 已绑定淘宝授权店铺,请求也可能在服务端失败。
242
246
 
247
+ `product related-knowledge` 管理商品详情的“关联知识库”文件级绑定。`--type qa` 对应 FAQ 知识库,`--type common` 对应扩展知识库。`add` / `remove` 会先读取当前绑定,再通过服务端全量替换接口写回;重复添加或移除不存在的文件保持幂等。`--file <文件名>` 会按当前 Agent 的知识库文件列表做精确匹配;文件名不存在或不唯一时,改用 `--file-id` + `--partition-id` 显式模式。
248
+
243
249
  ```bash
244
250
  # 更新卖点
245
251
  cs-cli product update --agent <id> --product-id <商品ID> --update '{"卖点":"新卖点内容"}'
@@ -255,6 +261,12 @@ cs-cli product update-sku --agent <id> --sku "颜色分类:粉红" --update '{"
255
261
 
256
262
  # 更新 SKU 补充知识(按 sku_id 定位,推荐)
257
263
  cs-cli product update-sku --agent <id> --sku-id 6072595054179 --update '{"补充知识":"说明内容"}'
264
+
265
+ # 给商品绑定一个 FAQ 文件
266
+ cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type qa --file "常见问题.xlsx"
267
+
268
+ # 显式 ID 模式绑定扩展知识文件
269
+ cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type common --file-id 622474 --partition-id 12345 --file-name "售后规则.md"
258
270
  ```
259
271
 
260
272
 
@@ -299,6 +311,24 @@ cs-cli activity delete 6
299
311
  cs-cli activity v2-enabled --agent <cfg_id> --enabled true
300
312
  ```
301
313
 
314
+ ### 节点模板 (`node-template`)
315
+
316
+ 高级模板(4 真节点提示词 + `image_understanding` 图片理解方法论伪 stage)操作:编写说明书 + 获取内容 + 改动发布 + 历史/回溯。服务端 `customer-servhub-api /v1/customer-agent/{config_id}/customer_config/node-templates*`;契约见 [`docs/devkit/specs/2026-06-08-节点模板-cli-操作面.md`](../../docs/devkit/specs/2026-06-08-节点模板-cli-操作面.md)。**节点 stage:`planner|composer|qa_inspector|image_understanding`(`tool_selector` 暂不放出)。**
317
+
318
+ > 高级模式开关已改为**客户自助**(在 cs-client 内开关),CLI 不再提供 `enroll` / `enroll-status` 开启路径(已下线)。
319
+
320
+ | 命令 | 说明 |
321
+ | --- | --- |
322
+ | `node-template describe [--stage <s>]` | 节点编写说明书(AI 编写前必读):每节点职责/可编辑面/输出契约/平台锁定段 + 可 `@引用` 变量目录;纯静态、无需 `--agent` |
323
+ | `node-template get --agent <id> [--stage <s>]` | 获取内容(不传 stage=全部已开放节点) |
324
+ | `node-template set-draft --agent <id> --stage <s> (--content <t>\|--content-file <p>\|--stdin)` | 写单节点草稿(整段覆盖;空串=回落出厂默认) |
325
+ | `node-template publish --agent <id> --stage <s> [--force]` | 发布草稿上线(发布前校验 `{{}}`,`--force` 越过) |
326
+ | `node-template versions --agent <id> --stage <s>` | 列该节点历史 |
327
+ | `node-template version --agent <id> --stage <s> --version <n>` | 取某版本正文 |
328
+ | `node-template restore --agent <id> --stage <s> --version <n>` | 回溯某版本成草稿(需再 publish) |
329
+
330
+ 典型流(该 agent 已由客户在 cs-client 开启高级模式后):`get` 看现状 → `set-draft` 改草稿 → `publish` 上线。`publish` 会拒绝含非法 `{{插值}}` 名的草稿(笔误防呆)。
331
+
302
332
  ### 工单管理 (`issue`)
303
333
 
304
334
 
@@ -387,11 +417,17 @@ CLI 把 `description` + `expected` 拼成 `content`:
387
417
  | `faq add --agent <id> --file <文件名> --questions <问题1,问题2> --answers <答案=xxx>` | 添加 FAQ 内容 |
388
418
  | `faq update --agent <id> --file <文件名> --group-id <id> [--questions <...>] [--answers <...>] [--delete-chunks <id1,id2>]` | 更新已有 FAQ 答案组(`--questions` / `--answers` / `--delete-chunks` 至少一个) |
389
419
  | `faq delete --agent <id> --file <文件名> --group-id <id>` | 删除 FAQ 答案组 |
420
+ | `faq bind-products list --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>)` | 列出 FAQ 文件绑定的商品 |
421
+ | `faq bind-products add --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 给 FAQ 文件追加绑定商品 |
422
+ | `faq bind-products remove --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 从 FAQ 文件移除绑定商品 |
423
+ | `faq bind-products set --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 覆盖 FAQ 文件绑定的全部商品 |
390
424
 
391
425
  `faq content` 走 `GET /mebsuta/api/v1/dataset/{knowledge_id}/faq_contents`,列出文件内的答案组及其 `answer_group_id`,是 `faq update` / `faq delete` 拿 `--group-id` 的来源(`faq list` 只给文件,文件内的 group-id 要靠这条命令查)。支持 `--keyword` 搜索、`--page-size` 分页、`--next-group-id` 游标续翻。
392
426
 
393
427
  `faq update` 按 `answer_group_id` 定位目标组,POST 同一个 `faq_contents/save` 接口完成字段替换、问题替换与 chunk 级删除。`faq delete` 走 `DELETE /faq_contents/{group_id}` 整组删除。
394
428
 
429
+ `faq bind-products` 固定写入 `knowledge_type=qa` 的商品绑定关系,适合从 FAQ 文件视角批量维护关联商品。`--file` 为精确文件名匹配;文件名不唯一时请传 `--file-id` 和 `--partition-id`。
430
+
395
431
  ### 扩展知识库 (`knowledge`)
396
432
 
397
433
  Agent 关联的通用规则知识库(`suffix_type=_common`),与 FAQ / 商品知识并列的第三类知识源,支持段落(chunk)级 CRUD,统一走 `/mebsuta/api/v1/dataset/{id}/contents/perform-operations`。
@@ -403,9 +439,15 @@ Agent 关联的通用规则知识库(`suffix_type=_common`),与 FAQ / 商
403
439
  | `knowledge content add --knowledge-id <id> --content <text\|@file> [--keyword <k...>] [--after <chunk_id>]` | 新增段落 |
404
440
  | `knowledge content update --knowledge-id <id> --chunk-id <id> [--content <text\|@file>] [--keyword <k...>] [--enable \| --disable]` | 更新段落(四个变更字段至少一个;未指定 `--enable` / `--disable` 时自动保留当前启停状态,避免后端 enable 必填校验失败) |
405
441
  | `knowledge content delete --knowledge-id <id> --chunk-id <id>` | 删除段落 |
442
+ | `knowledge bind-products list --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>)` | 列出扩展知识文件绑定的商品 |
443
+ | `knowledge bind-products add --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 给扩展知识文件追加绑定商品 |
444
+ | `knowledge bind-products remove --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 从扩展知识文件移除绑定商品 |
445
+ | `knowledge bind-products set --agent <id> (--file <文件名> \| --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 覆盖扩展知识文件绑定的全部商品 |
406
446
 
407
447
  `knowledge content add` / `update` 的 `--content` 支持 `@路径` 前缀从文件读取**纯文本**(不是 JSON)。`--keyword` 为 variadic 选项,空格分隔多个关键词。
408
448
 
449
+ `knowledge bind-products` 固定写入 `knowledge_type=common` 的商品绑定关系,适合从扩展知识文件视角批量维护关联商品。`add` / `remove` 会 read-modify-replace,重复添加或移除不存在的商品保持幂等。
450
+
409
451
 
410
452
  ### 会话管理 (`conversation`)
411
453
 
@@ -435,6 +477,14 @@ Agent 关联的通用规则知识库(`suffix_type=_common`),与 FAQ / 商
435
477
 
436
478
  `debug trace`(v0.4.0 起,**行为变更**)不再由 CLI 直连 Langfuse REST API,改为请求 customer-servhub-api 的 `GET /v1/debug/langfuse/trace/{trace_id}?env=<env>`,走 CLI 标准 Bearer 鉴权,由服务端按 env 选择 Langfuse 凭据并代理拉取 Trace。Trace ID 从 `debug record` 返回的 `trace_id` 字段获取。
437
479
 
480
+ ### 工作空间 (`workspace`)
481
+
482
+ | 命令 | 说明 |
483
+ | --- | --- |
484
+ | `workspace list` | 列出当前账号可访问的工作空间 |
485
+ | `workspace points-consumes [--page <n>] [--page-size <n>]` | 分页查询积分账户消耗流水,固定返回 `extra` 调试字段 |
486
+ | `workspace points-consumes-daily [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>]` | 按天查询积分消耗趋势 |
487
+
438
488
  - `--env <dev|prod>`:指定 Langfuse 环境,默认 `prod`。只接受 `dev` / `prod`,传其它值命令直接报错并以非 0 退出(不发请求)。
439
489
 
440
490
  ```bash
package/dist/bin.js CHANGED
@@ -31,7 +31,24 @@ function formatOutput(response, table) {
31
31
  if (Array.isArray(data)) {
32
32
  renderArrayTable(data);
33
33
  } else if (data !== null && typeof data === "object") {
34
- renderObjectTable(data);
34
+ const obj = data;
35
+ const innerArray = findInnerArray(obj);
36
+ if (innerArray) {
37
+ renderArrayTable(innerArray);
38
+ const total = obj.total ?? obj.count;
39
+ const pageNo = obj.page_no ?? obj.page;
40
+ const pageSize = obj.page_size;
41
+ if (total !== void 0) {
42
+ const paginationParts = [`total: ${total}`];
43
+ if (pageNo !== void 0) paginationParts.push(`page: ${pageNo}`);
44
+ if (pageSize !== void 0) paginationParts.push(`page_size: ${pageSize}`);
45
+ process.stdout.write(`
46
+ ${paginationParts.join(" | ")}
47
+ `);
48
+ }
49
+ } else {
50
+ renderObjectTable(obj);
51
+ }
35
52
  } else {
36
53
  process.stdout.write(JSON.stringify(response, null, 2));
37
54
  return;
@@ -43,6 +60,16 @@ Page ${page} | ${pageSize} per page | ${total} total
43
60
  `);
44
61
  }
45
62
  }
63
+ function findInnerArray(obj) {
64
+ const paginationKeys = /* @__PURE__ */ new Set(["total", "count", "page_no", "page", "page_size"]);
65
+ const arrayKey = Object.keys(obj).find(
66
+ (k) => Array.isArray(obj[k]) && !paginationKeys.has(k)
67
+ );
68
+ if (!arrayKey) return null;
69
+ const arr = obj[arrayKey];
70
+ if (arr.length === 0 || typeof arr[0] !== "object" || arr[0] === null) return null;
71
+ return arr;
72
+ }
46
73
  function renderArrayTable(data) {
47
74
  const first = data[0];
48
75
  if (first == null) {
@@ -52,7 +79,14 @@ function renderArrayTable(data) {
52
79
  const headers = Object.keys(first);
53
80
  const table = new Table({ head: headers });
54
81
  for (const row of data) {
55
- table.push(headers.map((h) => String(row[h] ?? "")));
82
+ table.push(
83
+ headers.map((h) => {
84
+ const val = row[h];
85
+ if (Array.isArray(val)) return val.length === 0 ? "[]" : `[${val.length} items]`;
86
+ if (val !== null && typeof val === "object") return JSON.stringify(val);
87
+ return String(val ?? "");
88
+ })
89
+ );
56
90
  }
57
91
  process.stdout.write(`${table.toString()}
58
92
  `);
@@ -60,7 +94,15 @@ function renderArrayTable(data) {
60
94
  function renderObjectTable(data) {
61
95
  const table = new Table();
62
96
  for (const [key, value] of Object.entries(data)) {
63
- table.push({ [key]: String(value ?? "") });
97
+ let display;
98
+ if (Array.isArray(value)) {
99
+ display = value.length === 0 ? "[]" : `[${value.length} items]`;
100
+ } else if (value !== null && typeof value === "object") {
101
+ display = JSON.stringify(value);
102
+ } else {
103
+ display = String(value ?? "");
104
+ }
105
+ table.push({ [key]: display });
64
106
  }
65
107
  process.stdout.write(`${table.toString()}
66
108
  `);
@@ -436,7 +478,6 @@ function isTokenExpired(expiresAt) {
436
478
  // src/client/helpers.ts
437
479
  var DEFAULT_CS_API_URL = "https://customer-servhub-api.betteryeah.com";
438
480
  var DEFAULT_AI_API_URL = "https://ai-api.betteryeah.com";
439
- var DEFAULT_CUSTOMER_AGENT_API_URL = "https://customer-agent.bantouyan.com";
440
481
  var DEFAULT_CS_ADMIN_URL = "https://customer-service-admin.betteryeah.com";
441
482
  function getCustomerServiceUrl() {
442
483
  const envUrl = process.env.CS_CS_API_URL;
@@ -450,12 +491,6 @@ function getAiApiUrl() {
450
491
  const config = readConfig();
451
492
  return config?.aiApiUrl ?? DEFAULT_AI_API_URL;
452
493
  }
453
- function getCustomerAgentUrl() {
454
- const envUrl = process.env.CS_CUSTOMER_AGENT_URL;
455
- if (envUrl) return envUrl;
456
- const config = readConfig();
457
- return config?.customerAgentApiUrl ?? DEFAULT_CUSTOMER_AGENT_API_URL;
458
- }
459
494
  function getCsAdminUrl() {
460
495
  const envUrl = process.env.CS_CS_ADMIN_URL;
461
496
  if (envUrl) return envUrl;
@@ -477,15 +512,6 @@ function getWorkspaceId(overrideWorkspaceId) {
477
512
  if (globalConfig?.defaultWorkspaceId) return globalConfig.defaultWorkspaceId;
478
513
  throw new APIError(1, "\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8BF7\u8FD0\u884C: cs-cli config set-workspace <id>");
479
514
  }
480
- function buildCookieHeaders() {
481
- const creds = readCredentials();
482
- if (!creds) throw new APIError(2, "\u672A\u767B\u5F55\uFF0C\u8BF7\u8FD0\u884C: cs-cli auth login");
483
- if (isTokenExpired(creds.expiresAt)) {
484
- clearCredentials();
485
- throw new APIError(2, "Token \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u8FD0\u884C: cs-cli auth login");
486
- }
487
- return { Cookie: `auth_token=${creds.accessToken}` };
488
- }
489
515
 
490
516
  // src/client/activity-api.ts
491
517
  var PATH_PREFIX = "/v1/activity";
@@ -1104,6 +1130,17 @@ async function getDailyDeduction(opts) {
1104
1130
  }
1105
1131
  );
1106
1132
  }
1133
+ async function getPointsConsumes(opts) {
1134
+ const request = createRequest();
1135
+ return request(
1136
+ getCustomerServiceUrl(),
1137
+ "/v1/workspace/points_consumes",
1138
+ {
1139
+ method: "POST",
1140
+ body: { page: opts.page, page_size: opts.pageSize, debug: true }
1141
+ }
1142
+ );
1143
+ }
1107
1144
 
1108
1145
  // src/commands/config.ts
1109
1146
  function registerConfigCommand(program2) {
@@ -2064,6 +2101,442 @@ async function deleteFaqGroup(opts) {
2064
2101
  );
2065
2102
  }
2066
2103
 
2104
+ // src/client/knowledge-api.ts
2105
+ async function listExternalKnowledge(opts) {
2106
+ const request = createRequest();
2107
+ return request(getCustomerServiceUrl(), "/v1/knowledge/external", {
2108
+ method: "GET",
2109
+ query: {
2110
+ customer_agent_config_id: opts.agentConfigId,
2111
+ page: opts.page ?? 1,
2112
+ page_size: opts.pageSize ?? 20,
2113
+ suffix_type: "_common"
2114
+ }
2115
+ });
2116
+ }
2117
+ async function listKnowledgeContents(opts) {
2118
+ const request = createRequest();
2119
+ return request(getAiApiUrl(), `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents`, {
2120
+ method: "GET",
2121
+ query: {
2122
+ page_size: opts.pageSize ?? 20,
2123
+ need_total: opts.needTotal ?? true,
2124
+ direction: opts.direction ?? "down"
2125
+ }
2126
+ });
2127
+ }
2128
+ async function addKnowledgeContent(opts) {
2129
+ const request = createRequest();
2130
+ const ts = (opts.now ?? Date.now)();
2131
+ const identifier = `temp_${ts}`;
2132
+ const body = {
2133
+ operations: [
2134
+ {
2135
+ action: "add",
2136
+ original_identifier: opts.afterChunkId ?? "0",
2137
+ identifier
2138
+ }
2139
+ ],
2140
+ content_updates: { [identifier]: opts.content },
2141
+ keywords_updates: { [identifier]: opts.keywords ?? [] },
2142
+ extra_info_updates: { [identifier]: { chunk_status: "done" } }
2143
+ };
2144
+ return request(
2145
+ getAiApiUrl(),
2146
+ `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
2147
+ {
2148
+ method: "POST",
2149
+ body
2150
+ }
2151
+ );
2152
+ }
2153
+ async function updateKnowledgeContent(opts) {
2154
+ const request = createRequest();
2155
+ const operation = {
2156
+ action: "update",
2157
+ identifier: opts.chunkId,
2158
+ enable: opts.enable
2159
+ };
2160
+ const contentUpdates = opts.content !== void 0 ? { [opts.chunkId]: opts.content } : {};
2161
+ const keywordsUpdates = opts.keywords !== void 0 ? { [opts.chunkId]: opts.keywords } : {};
2162
+ const extraInfoUpdates = opts.content !== void 0 || opts.keywords !== void 0 ? { [opts.chunkId]: { chunk_status: "done" } } : {};
2163
+ const body = {
2164
+ operations: [operation],
2165
+ content_updates: contentUpdates,
2166
+ keywords_updates: keywordsUpdates,
2167
+ extra_info_updates: extraInfoUpdates
2168
+ };
2169
+ return request(
2170
+ getAiApiUrl(),
2171
+ `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
2172
+ {
2173
+ method: "POST",
2174
+ body
2175
+ }
2176
+ );
2177
+ }
2178
+ async function findKnowledgeChunk(opts) {
2179
+ const result = await listKnowledgeContents({
2180
+ knowledgeId: opts.knowledgeId,
2181
+ pageSize: opts.pageSize ?? 500,
2182
+ needTotal: true,
2183
+ direction: "down"
2184
+ });
2185
+ return result.contents?.find((c) => c.chunk_id === opts.chunkId);
2186
+ }
2187
+ async function deleteKnowledgeContent(opts) {
2188
+ const request = createRequest();
2189
+ const body = {
2190
+ operations: [{ action: "delete", identifier: opts.chunkId }],
2191
+ content_updates: {},
2192
+ keywords_updates: {},
2193
+ extra_info_updates: {}
2194
+ };
2195
+ return request(
2196
+ getAiApiUrl(),
2197
+ `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
2198
+ {
2199
+ method: "POST",
2200
+ body
2201
+ }
2202
+ );
2203
+ }
2204
+
2205
+ // src/client/product-knowledge-binding-api.ts
2206
+ var PATH_PREFIX2 = "/v1/knowledge/product-bindings";
2207
+ async function listProductKnowledgeBindings(opts) {
2208
+ const request = createRequest();
2209
+ return request(getCustomerServiceUrl(), PATH_PREFIX2, {
2210
+ method: "GET",
2211
+ query: {
2212
+ customer_agent_config_id: opts.agentConfigId,
2213
+ product_id: opts.productId,
2214
+ knowledge_type: opts.knowledgeType,
2215
+ file_id: opts.fileId
2216
+ }
2217
+ });
2218
+ }
2219
+ async function replaceProductKnowledgeBindingsByProduct(opts) {
2220
+ const request = createRequest();
2221
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX2}/by-product`, {
2222
+ method: "PUT",
2223
+ body: {
2224
+ customer_agent_config_id: opts.agentConfigId,
2225
+ product_id: opts.productId,
2226
+ bindings: opts.bindings
2227
+ }
2228
+ });
2229
+ }
2230
+ async function replaceProductKnowledgeBindingsByFile(opts) {
2231
+ const request = createRequest();
2232
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX2}/by-file`, {
2233
+ method: "PUT",
2234
+ body: {
2235
+ customer_agent_config_id: opts.agentConfigId,
2236
+ knowledge_type: opts.knowledgeType,
2237
+ file_id: opts.fileId,
2238
+ file_name: opts.fileName,
2239
+ partition_id: opts.partitionId,
2240
+ partition_name: opts.partitionName,
2241
+ products: opts.productIds.map((product_id) => ({ product_id }))
2242
+ }
2243
+ });
2244
+ }
2245
+
2246
+ // src/commands/product-knowledge-binding.ts
2247
+ function parsePositiveInt(value, fieldName) {
2248
+ const n = Number(value);
2249
+ if (!Number.isInteger(n) || n <= 0) {
2250
+ throw new Error(`${fieldName} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`);
2251
+ }
2252
+ return n;
2253
+ }
2254
+ function parseKnowledgeType(value) {
2255
+ if (value === "qa" || value === "common") return value;
2256
+ throw new Error("--type \u4EC5\u63A5\u53D7 qa | common");
2257
+ }
2258
+ function dedupeStrings(values) {
2259
+ const seen = /* @__PURE__ */ new Set();
2260
+ const result = [];
2261
+ for (const value of values) {
2262
+ const trimmed = String(value || "").trim();
2263
+ if (!trimmed || seen.has(trimmed)) continue;
2264
+ seen.add(trimmed);
2265
+ result.push(trimmed);
2266
+ }
2267
+ return result;
2268
+ }
2269
+ function bindingKey(item) {
2270
+ return `${item.knowledge_type}:${item.file_id}`;
2271
+ }
2272
+ function toBindingInput(item) {
2273
+ return {
2274
+ knowledge_type: item.knowledge_type,
2275
+ file_id: parsePositiveInt(item.file_id, "file_id"),
2276
+ file_name: item.file_name ?? null,
2277
+ partition_id: parsePositiveInt(item.partition_id, "partition_id"),
2278
+ partition_name: item.partition_name ?? null
2279
+ };
2280
+ }
2281
+ function normalizeBindingInput(value) {
2282
+ if (!value || typeof value !== "object") {
2283
+ throw new Error("bindings \u6BCF\u9879\u5FC5\u987B\u662F\u5BF9\u8C61");
2284
+ }
2285
+ const item = value;
2286
+ return {
2287
+ knowledge_type: parseKnowledgeType(String(item.knowledge_type ?? "")),
2288
+ file_id: parsePositiveInt(item.file_id, "file_id"),
2289
+ file_name: item.file_name ?? null,
2290
+ partition_id: parsePositiveInt(item.partition_id, "partition_id"),
2291
+ partition_name: item.partition_name ?? null
2292
+ };
2293
+ }
2294
+ function parseBindingsOption(raw) {
2295
+ const parsed = parseDataOption(raw);
2296
+ if (!Array.isArray(parsed)) {
2297
+ throw new Error("--bindings \u5FC5\u987B\u662F\u6570\u7EC4");
2298
+ }
2299
+ return parsed.map(normalizeBindingInput);
2300
+ }
2301
+ function fileIdOf(item) {
2302
+ return parsePositiveInt(item.knowledge_id ?? item.file_id, "file_id");
2303
+ }
2304
+ function partitionIdOf(item) {
2305
+ return parsePositiveInt(item.partition_id, "partition_id");
2306
+ }
2307
+ function failWithMessage(message) {
2308
+ outputError(1, message);
2309
+ process.exit(1);
2310
+ }
2311
+ async function listFilesByType(knowledgeType, agentConfigId) {
2312
+ const result = knowledgeType === "qa" ? await listFaqFiles({ agentConfigId, page: 1, pageSize: 100 }) : await listExternalKnowledge({ agentConfigId, page: 1, pageSize: 100 });
2313
+ return result.items ?? [];
2314
+ }
2315
+ async function resolveFile(opts) {
2316
+ if (opts.file && (opts.fileId || opts.partitionId)) {
2317
+ throw new Error("--file \u4E0E --file-id/--partition-id \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
2318
+ }
2319
+ if (opts.file) {
2320
+ const files = await listFilesByType(opts.knowledgeType, opts.agentConfigId);
2321
+ const matched = files.filter((item2) => item2.file_name === opts.file);
2322
+ if (matched.length === 0) {
2323
+ failWithMessage(`\u672A\u627E\u5230\u77E5\u8BC6\u5E93\u6587\u4EF6: ${opts.file}`);
2324
+ }
2325
+ if (matched.length > 1) {
2326
+ failWithMessage(`\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\u4E0D\u552F\u4E00: ${opts.file}\uFF0C\u8BF7\u6539\u7528 --file-id/--partition-id`);
2327
+ }
2328
+ const item = matched[0];
2329
+ return {
2330
+ fileId: fileIdOf(item),
2331
+ fileName: item.file_name ?? opts.file,
2332
+ partitionId: partitionIdOf(item),
2333
+ partitionName: item.partition_name ?? null
2334
+ };
2335
+ }
2336
+ if (!opts.fileId || !opts.partitionId) {
2337
+ throw new Error("\u8BF7\u63D0\u4F9B --file \u6216\u540C\u65F6\u63D0\u4F9B --file-id \u4E0E --partition-id");
2338
+ }
2339
+ return {
2340
+ fileId: parsePositiveInt(opts.fileId, "file_id"),
2341
+ fileName: opts.fileName,
2342
+ partitionId: parsePositiveInt(opts.partitionId, "partition_id"),
2343
+ partitionName: opts.partitionName
2344
+ };
2345
+ }
2346
+ async function replaceProductBindings(opts) {
2347
+ return replaceProductKnowledgeBindingsByProduct({
2348
+ agentConfigId: opts.agentConfigId,
2349
+ productId: opts.productId,
2350
+ bindings: opts.bindings
2351
+ });
2352
+ }
2353
+ async function getProductBindingInputs(opts) {
2354
+ const current = await listProductKnowledgeBindings({
2355
+ agentConfigId: opts.agentConfigId,
2356
+ productId: opts.productId
2357
+ });
2358
+ return (current.items ?? []).map(toBindingInput);
2359
+ }
2360
+ async function replaceFileBindings(opts) {
2361
+ return replaceProductKnowledgeBindingsByFile({
2362
+ agentConfigId: opts.agentConfigId,
2363
+ knowledgeType: opts.knowledgeType,
2364
+ fileId: opts.file.fileId,
2365
+ fileName: opts.file.fileName,
2366
+ partitionId: opts.file.partitionId,
2367
+ partitionName: opts.file.partitionName,
2368
+ productIds: opts.productIds
2369
+ });
2370
+ }
2371
+ async function getFileProductIds(opts) {
2372
+ const current = await listProductKnowledgeBindings({
2373
+ agentConfigId: opts.agentConfigId,
2374
+ knowledgeType: opts.knowledgeType,
2375
+ fileId: opts.fileId
2376
+ });
2377
+ return dedupeStrings((current.items ?? []).map((item) => item.product_id));
2378
+ }
2379
+ function handleError(err) {
2380
+ reportCaughtError(err);
2381
+ process.exit(toExitCode(err));
2382
+ }
2383
+ function registerProductRelatedKnowledgeCommand(product, program2) {
2384
+ const related = product.command("related-knowledge").description("\u5546\u54C1\u8BE6\u60C5\u5173\u8054\u77E5\u8BC6\u5E93\u6587\u4EF6\u7BA1\u7406\uFF1Alist / add / remove / set");
2385
+ related.command("list").description("\u5217\u51FA\u6307\u5B9A\u5546\u54C1\u7ED1\u5B9A\u7684 QA / \u6269\u5C55\u77E5\u8BC6\u5E93\u6587\u4EF6").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID").action(async (opts) => {
2386
+ try {
2387
+ const data = await listProductKnowledgeBindings({
2388
+ agentConfigId: opts.agent,
2389
+ productId: opts.productId
2390
+ });
2391
+ formatOutput({ success: true, data }, program2.opts().table);
2392
+ } catch (err) {
2393
+ handleError(err);
2394
+ }
2395
+ });
2396
+ related.command("add").description(
2397
+ "\u7ED9\u5546\u54C1\u8FFD\u52A0\u4E00\u4E2A\u5173\u8054\u77E5\u8BC6\u5E93\u6587\u4EF6\u3002\u652F\u6301 --file \u7CBE\u786E\u5339\u914D\uFF0C\u6216\u663E\u5F0F\u4F20 --file-id + --partition-id"
2398
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID").requiredOption("--type <qa|common>", "\u77E5\u8BC6\u5E93\u7C7B\u578B\uFF1Aqa \u6216 common").option("--file <exact_name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\uFF0C\u7CBE\u786E\u5339\u914D").option("--file-id <id>", "\u77E5\u8BC6\u5E93\u6587\u4EF6 ID").option("--file-name <name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\u5FEB\u7167").option("--partition-id <id>", "\u77E5\u8BC6\u5E93\u5206\u533A ID").option("--partition-name <name>", "\u77E5\u8BC6\u5E93\u5206\u533A\u540D\u5FEB\u7167").action(async (opts) => {
2399
+ try {
2400
+ const knowledgeType = parseKnowledgeType(opts.type);
2401
+ const file = await resolveFile({ ...opts, agentConfigId: opts.agent, knowledgeType });
2402
+ const current = await getProductBindingInputs({
2403
+ agentConfigId: opts.agent,
2404
+ productId: opts.productId
2405
+ });
2406
+ const byKey = new Map(current.map((item) => [bindingKey(item), item]));
2407
+ byKey.set(bindingKey({ knowledge_type: knowledgeType, file_id: file.fileId }), {
2408
+ knowledge_type: knowledgeType,
2409
+ file_id: file.fileId,
2410
+ file_name: file.fileName ?? null,
2411
+ partition_id: file.partitionId,
2412
+ partition_name: file.partitionName ?? null
2413
+ });
2414
+ const data = await replaceProductBindings({
2415
+ agentConfigId: opts.agent,
2416
+ productId: opts.productId,
2417
+ bindings: Array.from(byKey.values())
2418
+ });
2419
+ formatOutput({ success: true, data }, program2.opts().table);
2420
+ } catch (err) {
2421
+ handleError(err);
2422
+ }
2423
+ });
2424
+ related.command("remove").description("\u79FB\u9664\u5546\u54C1\u7ED1\u5B9A\u7684\u4E00\u4E2A\u77E5\u8BC6\u5E93\u6587\u4EF6\uFF1B\u76EE\u6807\u4E0D\u5B58\u5728\u65F6\u4FDD\u6301\u5E42\u7B49\u6210\u529F").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID").requiredOption("--type <qa|common>", "\u77E5\u8BC6\u5E93\u7C7B\u578B\uFF1Aqa \u6216 common").requiredOption("--file-id <id>", "\u77E5\u8BC6\u5E93\u6587\u4EF6 ID").action(async (opts) => {
2425
+ try {
2426
+ const knowledgeType = parseKnowledgeType(opts.type);
2427
+ const fileId = parsePositiveInt(opts.fileId, "file_id");
2428
+ const current = await getProductBindingInputs({
2429
+ agentConfigId: opts.agent,
2430
+ productId: opts.productId
2431
+ });
2432
+ const next = current.filter(
2433
+ (item) => !(item.knowledge_type === knowledgeType && item.file_id === fileId)
2434
+ );
2435
+ const data = await replaceProductBindings({
2436
+ agentConfigId: opts.agent,
2437
+ productId: opts.productId,
2438
+ bindings: next
2439
+ });
2440
+ formatOutput({ success: true, data }, program2.opts().table);
2441
+ } catch (err) {
2442
+ handleError(err);
2443
+ }
2444
+ });
2445
+ related.command("set").description("\u8986\u76D6\u6307\u5B9A\u5546\u54C1\u7684\u5168\u90E8\u5173\u8054\u77E5\u8BC6\u5E93\u6587\u4EF6\u3002--bindings \u652F\u6301 JSON \u6216 @file").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID").requiredOption("--bindings <json>", "\u7ED1\u5B9A\u6570\u7EC4 JSON \u6216 @\u6587\u4EF6\u8DEF\u5F84").action(async (opts) => {
2446
+ try {
2447
+ const bindings = parseBindingsOption(opts.bindings);
2448
+ const data = await replaceProductBindings({
2449
+ agentConfigId: opts.agent,
2450
+ productId: opts.productId,
2451
+ bindings
2452
+ });
2453
+ formatOutput({ success: true, data }, program2.opts().table);
2454
+ } catch (err) {
2455
+ handleError(err);
2456
+ }
2457
+ });
2458
+ }
2459
+ function registerKnowledgeFileBindProductsCommand(parent, program2, kind) {
2460
+ const knowledgeType = kind === "faq" ? "qa" : "common";
2461
+ const bind = parent.command("bind-products").description("\u77E5\u8BC6\u5E93\u6587\u4EF6\u7ED1\u5B9A\u5546\u54C1\u7BA1\u7406\uFF1Alist / add / remove / set");
2462
+ function addFileOptions(command) {
2463
+ return command.requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").option("--file <exact_name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\uFF0C\u7CBE\u786E\u5339\u914D").option("--file-id <id>", "\u77E5\u8BC6\u5E93\u6587\u4EF6 ID").option("--file-name <name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\u5FEB\u7167").option("--partition-id <id>", "\u77E5\u8BC6\u5E93\u5206\u533A ID").option("--partition-name <name>", "\u77E5\u8BC6\u5E93\u5206\u533A\u540D\u5FEB\u7167");
2464
+ }
2465
+ bind.command("list").description("\u5217\u51FA\u6307\u5B9A\u77E5\u8BC6\u5E93\u6587\u4EF6\u7ED1\u5B9A\u7684\u5546\u54C1").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID").option("--file <exact_name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\uFF0C\u7CBE\u786E\u5339\u914D").option("--file-id <id>", "\u77E5\u8BC6\u5E93\u6587\u4EF6 ID").option("--file-name <name>", "\u77E5\u8BC6\u5E93\u6587\u4EF6\u540D\u5FEB\u7167").option("--partition-id <id>", "\u77E5\u8BC6\u5E93\u5206\u533A ID").option("--partition-name <name>", "\u77E5\u8BC6\u5E93\u5206\u533A\u540D\u5FEB\u7167").action(async (opts) => {
2466
+ try {
2467
+ const file = await resolveFile({ ...opts, agentConfigId: opts.agent, knowledgeType });
2468
+ const data = await listProductKnowledgeBindings({
2469
+ agentConfigId: opts.agent,
2470
+ knowledgeType,
2471
+ fileId: file.fileId
2472
+ });
2473
+ formatOutput({ success: true, data }, program2.opts().table);
2474
+ } catch (err) {
2475
+ handleError(err);
2476
+ }
2477
+ });
2478
+ addFileOptions(
2479
+ bind.command("add").description("\u7ED9\u77E5\u8BC6\u5E93\u6587\u4EF6\u8FFD\u52A0\u7ED1\u5B9A\u5546\u54C1\uFF1B\u5DF2\u7ED1\u5B9A\u5546\u54C1\u4F1A\u81EA\u52A8\u53BB\u91CD").requiredOption("--product-id <id...>", "\u5546\u54C1 ID\uFF0C\u53EF\u4F20\u591A\u4E2A")
2480
+ ).action(async (opts) => {
2481
+ try {
2482
+ const file = await resolveFile({ ...opts, agentConfigId: opts.agent, knowledgeType });
2483
+ const currentIds = await getFileProductIds({
2484
+ agentConfigId: opts.agent,
2485
+ knowledgeType,
2486
+ fileId: file.fileId
2487
+ });
2488
+ const productIds = dedupeStrings([...currentIds, ...opts.productId ?? []]);
2489
+ const data = await replaceFileBindings({
2490
+ agentConfigId: opts.agent,
2491
+ knowledgeType,
2492
+ file,
2493
+ productIds
2494
+ });
2495
+ formatOutput({ success: true, data }, program2.opts().table);
2496
+ } catch (err) {
2497
+ handleError(err);
2498
+ }
2499
+ });
2500
+ addFileOptions(
2501
+ bind.command("remove").description("\u4ECE\u77E5\u8BC6\u5E93\u6587\u4EF6\u79FB\u9664\u7ED1\u5B9A\u5546\u54C1\uFF1B\u76EE\u6807\u4E0D\u5B58\u5728\u65F6\u4FDD\u6301\u5E42\u7B49\u6210\u529F").requiredOption("--product-id <id...>", "\u5546\u54C1 ID\uFF0C\u53EF\u4F20\u591A\u4E2A")
2502
+ ).action(async (opts) => {
2503
+ try {
2504
+ const file = await resolveFile({ ...opts, agentConfigId: opts.agent, knowledgeType });
2505
+ const removing = new Set(dedupeStrings(opts.productId ?? []));
2506
+ const currentIds = await getFileProductIds({
2507
+ agentConfigId: opts.agent,
2508
+ knowledgeType,
2509
+ fileId: file.fileId
2510
+ });
2511
+ const data = await replaceFileBindings({
2512
+ agentConfigId: opts.agent,
2513
+ knowledgeType,
2514
+ file,
2515
+ productIds: currentIds.filter((productId) => !removing.has(productId))
2516
+ });
2517
+ formatOutput({ success: true, data }, program2.opts().table);
2518
+ } catch (err) {
2519
+ handleError(err);
2520
+ }
2521
+ });
2522
+ addFileOptions(
2523
+ bind.command("set").description("\u8986\u76D6\u77E5\u8BC6\u5E93\u6587\u4EF6\u7ED1\u5B9A\u7684\u5168\u90E8\u5546\u54C1").requiredOption("--product-id <id...>", "\u5546\u54C1 ID\uFF0C\u53EF\u4F20\u591A\u4E2A")
2524
+ ).action(async (opts) => {
2525
+ try {
2526
+ const file = await resolveFile({ ...opts, agentConfigId: opts.agent, knowledgeType });
2527
+ const data = await replaceFileBindings({
2528
+ agentConfigId: opts.agent,
2529
+ knowledgeType,
2530
+ file,
2531
+ productIds: dedupeStrings(opts.productId ?? [])
2532
+ });
2533
+ formatOutput({ success: true, data }, program2.opts().table);
2534
+ } catch (err) {
2535
+ handleError(err);
2536
+ }
2537
+ });
2538
+ }
2539
+
2067
2540
  // src/commands/faq.ts
2068
2541
  function parseQuestions(input) {
2069
2542
  return input.split(",").map((q) => q.trim()).filter((q) => q.length > 0).map((q) => ({ content: q, keywords: [], enable: true }));
@@ -2196,6 +2669,7 @@ function registerFaqCommand(program2) {
2196
2669
  process.exit(toExitCode(err));
2197
2670
  }
2198
2671
  });
2672
+ registerKnowledgeFileBindProductsCommand(faq, program2, "faq");
2199
2673
  }
2200
2674
 
2201
2675
  // src/client/issue-api.ts
@@ -2261,11 +2735,9 @@ async function getIssueStats(opts) {
2261
2735
  const query = {};
2262
2736
  if (opts.dateStart) query.date_start = opts.dateStart;
2263
2737
  if (opts.dateEnd) query.date_end = opts.dateEnd;
2264
- const result = await request(getCustomerAgentUrl(), "/api/issues/stats", {
2738
+ const result = await request(getCsAdminUrl(), "/api/issues/stats", {
2265
2739
  method: "GET",
2266
- query,
2267
- headers: buildCookieHeaders(),
2268
- skipAuth: true
2740
+ query
2269
2741
  });
2270
2742
  return result.data ?? result;
2271
2743
  }
@@ -2965,109 +3437,6 @@ async function resolveSessionsForIssues(issues, workspaceId) {
2965
3437
 
2966
3438
  // src/commands/knowledge.ts
2967
3439
  import fs8 from "fs";
2968
-
2969
- // src/client/knowledge-api.ts
2970
- async function listExternalKnowledge(opts) {
2971
- const request = createRequest();
2972
- return request(getCustomerServiceUrl(), "/v1/knowledge/external", {
2973
- method: "GET",
2974
- query: {
2975
- customer_agent_config_id: opts.agentConfigId,
2976
- page: opts.page ?? 1,
2977
- page_size: opts.pageSize ?? 20,
2978
- suffix_type: "_common"
2979
- }
2980
- });
2981
- }
2982
- async function listKnowledgeContents(opts) {
2983
- const request = createRequest();
2984
- return request(getAiApiUrl(), `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents`, {
2985
- method: "GET",
2986
- query: {
2987
- page_size: opts.pageSize ?? 20,
2988
- need_total: opts.needTotal ?? true,
2989
- direction: opts.direction ?? "down"
2990
- }
2991
- });
2992
- }
2993
- async function addKnowledgeContent(opts) {
2994
- const request = createRequest();
2995
- const ts = (opts.now ?? Date.now)();
2996
- const identifier = `temp_${ts}`;
2997
- const body = {
2998
- operations: [
2999
- {
3000
- action: "add",
3001
- original_identifier: opts.afterChunkId ?? "0",
3002
- identifier
3003
- }
3004
- ],
3005
- content_updates: { [identifier]: opts.content },
3006
- keywords_updates: { [identifier]: opts.keywords ?? [] },
3007
- extra_info_updates: { [identifier]: { chunk_status: "done" } }
3008
- };
3009
- return request(
3010
- getAiApiUrl(),
3011
- `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
3012
- {
3013
- method: "POST",
3014
- body
3015
- }
3016
- );
3017
- }
3018
- async function updateKnowledgeContent(opts) {
3019
- const request = createRequest();
3020
- const operation = {
3021
- action: "update",
3022
- identifier: opts.chunkId,
3023
- enable: opts.enable
3024
- };
3025
- const contentUpdates = opts.content !== void 0 ? { [opts.chunkId]: opts.content } : {};
3026
- const keywordsUpdates = opts.keywords !== void 0 ? { [opts.chunkId]: opts.keywords } : {};
3027
- const extraInfoUpdates = opts.content !== void 0 || opts.keywords !== void 0 ? { [opts.chunkId]: { chunk_status: "done" } } : {};
3028
- const body = {
3029
- operations: [operation],
3030
- content_updates: contentUpdates,
3031
- keywords_updates: keywordsUpdates,
3032
- extra_info_updates: extraInfoUpdates
3033
- };
3034
- return request(
3035
- getAiApiUrl(),
3036
- `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
3037
- {
3038
- method: "POST",
3039
- body
3040
- }
3041
- );
3042
- }
3043
- async function findKnowledgeChunk(opts) {
3044
- const result = await listKnowledgeContents({
3045
- knowledgeId: opts.knowledgeId,
3046
- pageSize: opts.pageSize ?? 500,
3047
- needTotal: true,
3048
- direction: "down"
3049
- });
3050
- return result.contents?.find((c) => c.chunk_id === opts.chunkId);
3051
- }
3052
- async function deleteKnowledgeContent(opts) {
3053
- const request = createRequest();
3054
- const body = {
3055
- operations: [{ action: "delete", identifier: opts.chunkId }],
3056
- content_updates: {},
3057
- keywords_updates: {},
3058
- extra_info_updates: {}
3059
- };
3060
- return request(
3061
- getAiApiUrl(),
3062
- `/mebsuta/api/v1/dataset/${opts.knowledgeId}/contents/perform-operations`,
3063
- {
3064
- method: "POST",
3065
- body
3066
- }
3067
- );
3068
- }
3069
-
3070
- // src/commands/knowledge.ts
3071
3440
  function readContentArg(value) {
3072
3441
  if (value.startsWith("@")) {
3073
3442
  return fs8.readFileSync(value.slice(1), "utf-8");
@@ -3203,6 +3572,7 @@ function registerKnowledgeCommand(program2) {
3203
3572
  process.exit(toExitCode(err));
3204
3573
  }
3205
3574
  });
3575
+ registerKnowledgeFileBindProductsCommand(knowledge, program2, "common");
3206
3576
  }
3207
3577
 
3208
3578
  // src/client/monitor-api.ts
@@ -3786,6 +4156,317 @@ function registerMonitorCommand(program2) {
3786
4156
  });
3787
4157
  }
3788
4158
 
4159
+ // src/commands/node-template.ts
4160
+ import { readFileSync } from "fs";
4161
+
4162
+ // src/client/node-template-api.ts
4163
+ function base(configId) {
4164
+ return `/v1/customer-agent/${configId}/customer_config/node-templates`;
4165
+ }
4166
+ async function getNodeTemplates(configId) {
4167
+ const request = createRequest();
4168
+ return request(getCustomerServiceUrl(), base(configId), {
4169
+ method: "GET"
4170
+ });
4171
+ }
4172
+ async function setNodeTemplateDraft(configId, stage, content) {
4173
+ const request = createRequest();
4174
+ return request(
4175
+ getCustomerServiceUrl(),
4176
+ `${base(configId)}/${stage}/draft`,
4177
+ { method: "PUT", body: { content } }
4178
+ );
4179
+ }
4180
+ async function publishNodeTemplate(configId, stage) {
4181
+ const request = createRequest();
4182
+ return request(
4183
+ getCustomerServiceUrl(),
4184
+ `${base(configId)}/${stage}/publish`,
4185
+ { method: "POST", body: {} }
4186
+ );
4187
+ }
4188
+ async function listNodeTemplateVersions(configId, stage) {
4189
+ const request = createRequest();
4190
+ return request(
4191
+ getCustomerServiceUrl(),
4192
+ `${base(configId)}/${stage}/versions`,
4193
+ { method: "GET" }
4194
+ );
4195
+ }
4196
+ async function getNodeTemplateVersion(configId, stage, versionNo) {
4197
+ const request = createRequest();
4198
+ return request(
4199
+ getCustomerServiceUrl(),
4200
+ `${base(configId)}/${stage}/versions/${versionNo}`,
4201
+ { method: "GET" }
4202
+ );
4203
+ }
4204
+ async function restoreNodeTemplateVersion(configId, stage, versionNo) {
4205
+ const request = createRequest();
4206
+ return request(
4207
+ getCustomerServiceUrl(),
4208
+ `${base(configId)}/${stage}/versions/${versionNo}/restore`,
4209
+ { method: "POST", body: {} }
4210
+ );
4211
+ }
4212
+
4213
+ // src/lib/node-template-references.ts
4214
+ var NODE_TEMPLATE_REFERENCES = [
4215
+ // ── 数据源 fact_alias(当轮实时事实,运行时取最新数据) ──
4216
+ { name: "product_data", label: "\u5546\u54C1\u4E8B\u5B9E", desc: "\u5546\u54C1\u53C2\u6570/\u5E93\u5B58/\u4EF7\u683C\u7B49\u5546\u54C1\u4E8B\u5B9E\u6570\u636E", category: "fact_alias", stages: ["tool_selector", "composer"] },
4217
+ { name: "order_status", label: "\u8BA2\u5355/\u7269\u6D41\u72B6\u6001", desc: "\u8BA2\u5355\u72B6\u6001\u3001\u7269\u6D41\u8FDB\u5EA6\u7B49", category: "fact_alias", stages: ["tool_selector", "composer"] },
4218
+ { name: "faq_knowledge", label: "FAQ/\u5386\u53F2\u95EE\u7B54", desc: "\u5E97\u94FA\u5E38\u89C1\u95EE\u7B54\u4E0E\u5386\u53F2\u95EE\u7B54", category: "fact_alias", stages: ["tool_selector", "composer"] },
4219
+ { name: "common_knowledge", label: "\u901A\u7528\u77E5\u8BC6", desc: "\u5E97\u94FA\u901A\u7528\u77E5\u8BC6/\u8865\u5145\u77E5\u8BC6", category: "fact_alias", stages: ["tool_selector", "composer"] },
4220
+ { name: "activity_knowledge", label: "\u6D3B\u52A8\u77E5\u8BC6", desc: "\u5F53\u524D\u5E97\u94FA\u6D3B\u52A8\u4FE1\u606F", category: "fact_alias", stages: ["tool_selector", "composer"] },
4221
+ { name: "product_recommendation", label: "\u5546\u54C1\u63A8\u8350", desc: "\u627E\u76F8\u4F3C\u6B3E/\u63A8\u8350\u5546\u54C1", category: "fact_alias", stages: ["tool_selector", "composer"] },
4222
+ { name: "size_recommendation", label: "\u5C3A\u7801\u63A8\u8350", desc: "\u6309\u8EAB\u9AD8\u4F53\u91CD\u7B49\u63A8\u8350\u5C3A\u7801", category: "fact_alias", stages: ["tool_selector", "composer"] },
4223
+ // ── 店铺设置 customer_slot(你在「个性化」里填的 SOP 文本,可引用进模板) ──
4224
+ { name: "intent_instruction", label: "\u610F\u56FE\u8BC6\u522B\u89C4\u5219", desc: "\u4F60\u586B\u7684\u5E97\u94FA\u7EA7\u610F\u56FE\u8DEF\u7531 SOP", category: "customer_slot", stages: ["planner"] },
4225
+ { name: "general_qa_instruction", label: "\u901A\u7528\u54A8\u8BE2\u8BDD\u672F", desc: "\u4F60\u586B\u7684\u901A\u7528\u54A8\u8BE2/\u5BA2\u670D\u8EAB\u4EFD SOP", category: "customer_slot", stages: ["planner", "composer"] },
4226
+ { name: "recommendation_instruction", label: "\u63A8\u8350\u8BDD\u672F", desc: "\u4F60\u586B\u7684\u5546\u54C1\u63A8\u8350\u8BDD\u672F\u89C4\u5219", category: "customer_slot", stages: ["composer"] },
4227
+ { name: "takeover_rule_instruction", label: "\u8F6C\u4EBA\u5DE5\u63A5\u7BA1\u7B56\u7565", desc: "\u4F60\u586B\u7684\u633D\u7559/\u8F6C\u4EBA\u5DE5\u89C4\u5219", category: "customer_slot", stages: ["planner", "composer"] },
4228
+ { name: "image_rule_instruction", label: "\u56FE\u7247\u573A\u666F\u89C4\u5219", desc: "\u4F60\u586B\u7684\u56FE\u7247\u76F8\u5173\u4E1A\u52A1 SOP", category: "customer_slot", stages: ["planner", "composer", "qa_inspector"] },
4229
+ { name: "forbidden_words", label: "\u8FDD\u7981\u8BCD", desc: "\u4F60\u914D\u7F6E\u7684\u8FDD\u7981\u8BCD\u6E05\u5355", category: "customer_slot", stages: ["composer"] },
4230
+ // ── 平台只读块 platform_block(平台运行时注入,内容锁定,作者只决定是否/在哪引用) ──
4231
+ { name: "glossary_block", label: "\u672F\u8BED\u8868", desc: "\u5E73\u53F0\u672F\u8BED/\u6982\u5FF5\u5B9A\u4E49", category: "platform_block", stages: ["planner", "tool_selector", "qa_inspector"], readOnly: true },
4232
+ { name: "sa_summaries_block", label: "SA \u6458\u8981\u7D22\u5F15", desc: "\u5F53\u8F6E\u547D\u4E2D\u7684\u573A\u666F\u6458\u8981\u5217\u8868", category: "platform_block", stages: ["planner"], readOnly: true },
4233
+ { name: "tool_results_block", label: "\u5F53\u8F6E\u5DE5\u5177\u7ED3\u679C", desc: "\u672C\u8F6E\u5DF2\u53D6\u56DE\u7684\u5DE5\u5177\u4E8B\u5B9E\u6458\u8981", category: "platform_block", stages: ["composer", "qa_inspector"], readOnly: true },
4234
+ { name: "environment_awareness_block", label: "\u73AF\u5883/\u65F6\u95F4", desc: "\u5E73\u53F0\u4E0E\u5F53\u524D\u65F6\u95F4\u4FE1\u606F", category: "platform_block", stages: ["planner", "composer", "qa_inspector"], readOnly: true },
4235
+ { name: "image_understanding_rules", label: "\u56FE\u7247\u4FE1\u53F7\u89C4\u5219", desc: "\u5E73\u53F0\u56FE\u7247\u7406\u89E3\u89C4\u5219", category: "platform_block", stages: ["planner", "tool_selector", "composer", "qa_inspector"], readOnly: true },
4236
+ { name: "shop_image_rule_block", label: "\u5E97\u94FA\u56FE\u7247\u89C4\u5219", desc: "\u6709\u56FE\u7247\u4FE1\u53F7\u65F6\u6CE8\u5165\u7684\u5E97\u94FA\u56FE\u7247\u89C4\u5219", category: "platform_block", stages: ["composer", "qa_inspector"], readOnly: true },
4237
+ // 「① 背景层下放」(spec 2026-06-08)新增 @平台块:原 ① shell 直注的背景,现作者可 @引用 / 删 / 换。
4238
+ { name: "internal_terms_blacklist", label: "\u5185\u90E8\u672F\u8BED\u9ED1\u540D\u5355", desc: "\u7981\u8BCD/\u6CC4\u9732\u8BCD\u6E05\u7406\u5F15\u7528\u7684\u5185\u90E8\u672F\u8BED\u6E05\u5355", category: "platform_block", stages: ["composer", "qa_inspector"], readOnly: true },
4239
+ { name: "dialog_consumption_rules", label: "\u591A\u8F6E\u6D88\u8D39\u89C4\u5219", desc: "recent_chat / \u5BF9\u8BDD\u5386\u53F2\u7684\u6D88\u8D39\u89C4\u5219", category: "platform_block", stages: ["composer", "planner"], readOnly: true },
4240
+ { name: "takeover_entry_background", label: "\u63A5\u7BA1\u5165\u53E3\u80CC\u666F", desc: "\u7528\u6237\u4E3B\u52A8\u8981\u4EBA\u5DE5\u65F6\u627F\u63A5\u7684\u5E73\u53F0\u80CC\u666F\u5B9A\u4E49", category: "platform_block", stages: ["composer"], readOnly: true },
4241
+ { name: "takeover_reversal_background", label: "\u63A5\u7BA1\u53CD\u8F6C\u80CC\u666F", desc: "takeover \u573A\u666F\u53CD\u8F6C\u6821\u9A8C\u7684\u5E73\u53F0\u80CC\u666F\u5B9A\u4E49", category: "platform_block", stages: ["qa_inspector"], readOnly: true },
4242
+ { name: "action_description_signal_rules", label: "\u52A8\u4F5C\u63CF\u8FF0\u4FE1\u53F7\u89C4\u5219", desc: "\u8DEF\u7531\u7528\u52A8\u4F5C\u63CF\u8FF0\u4FE1\u53F7\u7684\u6D88\u8D39\u89C4\u5219", category: "platform_block", stages: ["planner"], readOnly: true },
4243
+ { name: "fact_trust_rules", label: "\u4E8B\u5B9E\u4FE1\u4EFB\u5C42\u7EA7", desc: "\u5F53\u524D\u8F6E\u4E8B\u5B9E vs \u5386\u53F2\u4E8B\u5B9E\u7684\u4FE1\u4EFB\u89C4\u5219", category: "platform_block", stages: ["planner"], readOnly: true },
4244
+ { name: "takeover_trigger_background", label: "\u63A5\u7BA1\u89E6\u53D1\u80CC\u666F", desc: "\u5E97\u94FA\u8F6C\u4EBA\u5DE5\u63A5\u7BA1\u89E6\u53D1\u6761\u4EF6\u80CC\u666F\u5B9A\u4E49", category: "platform_block", stages: ["planner"], readOnly: true }
4245
+ ];
4246
+ var ALL_REFERENCE_NAMES = NODE_TEMPLATE_REFERENCES.map((r) => r.name);
4247
+ function referencesForStage(stage) {
4248
+ return NODE_TEMPLATE_REFERENCES.filter((r) => r.stages.includes(stage));
4249
+ }
4250
+ var NODE_CHARTERS = {
4251
+ planner: {
4252
+ role: "\u610F\u56FE\u8BC6\u522B / \u8DEF\u7531\u8282\u70B9\uFF1A\u5224\u5B9A\u672C\u8F6E\u8D70\u5B8C\u6574\u4E1A\u52A1\u94FE\u8DEF\u8FD8\u662F\u8F6C\u4EBA\u5DE5\uFF0C\u5E76\u4EA7\u51FA\u300CSA \u6458\u8981 \u2192 \u547D\u4E2D scene\u300D\u7684\u8DEF\u7531\u3002",
4253
+ editable: "\u2461 \u4F5C\u8005\u6B63\u6587\uFF1A\u8DEF\u7531\u5224\u65AD\u903B\u8F91\u3001\u8DEF\u7531\u7EAA\u5F8B\u3001\u552E\u540E\u573A\u666F\u5224\u65AD\u3001@\u5F15\u7528\u5E97\u94FA\u610F\u56FE/\u63A5\u7BA1 SOP\u3002",
4254
+ mustOutput: "JSON \u5BF9\u8C61 {d, a, r}\uFF1Ad=\u51B3\u7B56\u7F16\u7801\uFF08business_full\u21922 / transfer\u21923\uFF09\uFF0Ca=\u662F\u5426\u552E\u540E\u573A\u666F\uFF08bool\uFF09\uFF0Cr=\u53CC\u4E0B\u6807\u8DEF\u7531\u6570\u7EC4 [[sa, sc], ...]\u3002\u683C\u5F0F\u7531\u5E73\u53F0 \u2462 \u9501\u5B9A\u2014\u2014\u4F60\u7684\u6B63\u6587\u7528\u51B3\u7B56\u540D\u5224\u65AD\u5373\u53EF\uFF0C\u4E0D\u8981\u81EA\u9020\u8F93\u51FA\u683C\u5F0F\u3002",
4255
+ locked: ["\u2462 \u8F93\u51FA\u5951\u7EA6 / JSON Schema\uFF08{d,a,r} \u7F16\u7801\u4E0E r \u53CC\u4E0B\u6807\u89C4\u5219\uFF09", "\u2463 \u8FD0\u884C\u65F6\u6570\u636E\u533A\uFF08SA \u6458\u8981 / \u8DEF\u7531\u4FE1\u53F7 / \u73AF\u5883\u65F6\u95F4\uFF0C\u6BCF\u8F6E\u81EA\u52A8\u6CE8\u5165\uFF09"]
4256
+ },
4257
+ composer: {
4258
+ role: "\u6210\u6587\u8282\u70B9\uFF1A\u4F9D\u636E\u5F53\u8F6E\u4E8B\u5B9E/\u6D41\u7A0B\uFF0C\u751F\u6210\u6700\u7EC8\u53D1\u7ED9\u7528\u6237\u7684\u5BA2\u670D\u8BDD\u672F\uFF08\u542B\u8F6C\u4EBA\u5DE5\u4FE1\u53F7\uFF09\u3002",
4259
+ editable: "\u2461 \u4F5C\u8005\u6B63\u6587\uFF1A\u56DE\u590D\u7B56\u7565\u3001\u8C03\u6027\u3001\u4E8B\u5B9E\u6D88\u8D39\u89C4\u5219\u3001@\u5F15\u7528\u5E97\u94FA\u901A\u7528\u8BDD\u672F/\u63A8\u8350/\u63A5\u7BA1 SOP\u3002",
4260
+ mustOutput: "JSON \u5BF9\u8C61 {messages:[...], transfer_to_human:bool}\uFF1Amessages \u4E3A\u7EAF\u6587\u672C\u6D88\u606F\u6570\u7EC4\uFF08\u56FE\u7247 URL \u5355\u72EC\u6210\u6761\uFF09\uFF0C\u8F6C\u4EBA\u5DE5\u7F6E transfer_to_human=true\u3002\u683C\u5F0F\u7531\u5E73\u53F0 \u2462 \u9501\u5B9A\u3002",
4261
+ locked: ["\u2462 \u8F93\u51FA\u5951\u7EA6 / JSON Schema\uFF08messages + transfer_to_human + \u5E73\u53F0\u8C03\u6027\u793A\u4F8B\uFF09", "\u2463 \u8FD0\u884C\u65F6\u6570\u636E\u533A\uFF08\u5DE5\u5177\u7ED3\u679C / SA action / \u4E8B\u5B9E\u5206\u7EA7\uFF0C\u6BCF\u8F6E\u81EA\u52A8\u6CE8\u5165\uFF09"]
4262
+ },
4263
+ qa_inspector: {
4264
+ role: "\u8D28\u68C0\u8282\u70B9\uFF1A\u5BF9 composer \u4EA7\u51FA\u7684\u7528\u6237\u53EF\u89C1\u56DE\u590D\u505A\u5408\u89C4/\u91CD\u590D\u6821\u9A8C\uFF0C\u51B3\u5B9A\u653E\u884C / \u6539\u5199 / \u8F6C\u4EBA\u5DE5\u3002",
4265
+ editable: "\u2461 \u4F5C\u8005\u6B63\u6587\uFF1A\u5224\u5B9A\u6807\u51C6\uFF08\u4F55\u4E3A\u5408\u683C / \u95EE\u9898 / \u8BED\u4E49\u91CD\u590D / \u8BE5\u8F6C\u4EBA\u5DE5\uFF09\u3001\u6E05\u7406\u89C4\u5219\u3001@\u5F15\u7528\u56FE\u7247\u89C4\u5219\u3002",
4266
+ mustOutput: '\u7ED3\u8BBA\u6309\u5E73\u53F0 \u2462 \u7F16\u7801\uFF1A\u5408\u683C\u2192`0`\uFF0C\u8BED\u4E49\u91CD\u590D\u2192`-1`\uFF0C\u6539\u5199\u2192`{"messages":[...]}`\uFF0C\u8F6C\u4EBA\u5DE5\u2192`{"messages":[...,"TRANSFER"]}`\uFF08\u672B\u9879\u6052\u4E3A "TRANSFER"\uFF09\u3002\u2462 \u53EA\u5B9A\u4E49\u300C\u7ED3\u8BBA\u2192\u7F16\u7801\u300D\u63A5\u53E3\uFF0C\u5224\u5B9A\u6807\u51C6\u5168\u5728\u4F60\u7684\u6B63\u6587\u3002',
4267
+ locked: ["\u2462 \u8F93\u51FA\u5951\u7EA6\uFF08\u7ED3\u8BBA\u2192\u7F16\u7801\uFF1A0 / -1 / JSON / TRANSFER \u534F\u8BAE\uFF09", "\u2463 \u8FD0\u884C\u65F6\u6570\u636E\u533A\uFF08\u73AF\u5883 / \u65F6\u95F4\uFF0C\u6BCF\u8F6E\u81EA\u52A8\u6CE8\u5165\uFF09"]
4268
+ },
4269
+ // 伪 stage(spec 2026-06-08):不是产出完整 system prompt 的真节点,而是被 planner/tool_selector/
4270
+ // composer/qa 四节点共享注入的「图片处理方法论」片段。
4271
+ image_understanding: {
4272
+ role: "\u56FE\u7247\u7406\u89E3\u65B9\u6CD5\u8BBA\uFF08\u4F2A stage\uFF09\uFF1A\u5B9A\u4E49\u770B\u5230\u56FE\u7247\u540E\u600E\u4E48\u63D0\u53D6\u4E8B\u5B9E / \u7ED3\u5408\u610F\u56FE / \u8DEF\u7531\uFF0C\u88AB 4 \u4E2A\u8282\u70B9\u5171\u4EAB\u6CE8\u5165\u3002",
4273
+ editable: "\u6574\u6BB5\u65B9\u6CD5\u8BBA\uFF1A\u56FE\u7247\u4E09\u6B65\u6D41\u7A0B\u7684\u63AA\u8F9E\u3001\u5E97\u94FA\u8BC6\u522B\u8868 / \u6613\u6DF7\u6DC6\u89C4\u5219 / \u56DE\u7B54\u5185\u5BB9\u5E93\u3002\u6BCF\u5BB6\u5E97\u56FE\u7247\u4E1A\u52A1\u4E0D\u540C\uFF0C\u53EF\u81EA\u7531\u6539\u5199\u3002",
4274
+ mustOutput: "\u65E0\u72EC\u7ACB\u8F93\u51FA\u5951\u7EA6\u2014\u2014\u672C\u914D\u7F6E\u662F\u88AB\u6CE8\u5165\u8FDB {{image_understanding_rules}} \u7684\u65B9\u6CD5\u8BBA\u7247\u6BB5\uFF0C\u4E0D\u4EA7\u51FA JSON\u3002\u9501\u5B9A\u7684\u54C1\u724C\u5F52\u5C5E\u7EA2\u7EBF / \u56FE\u7247\u4E8B\u5B9E\u7EA2\u7EBF / \u56FE\u7247\u5448\u73B0\u673A\u5236\u7531\u5E73\u53F0 code-assemble \u5305\u88F9\uFF0C\u4E0D\u5728\u4F60\u7684\u53EF\u7F16\u8F91\u5185\u5BB9\u91CC\u3001\u4E5F\u65E0\u6CD5\u88AB\u5220\u6539\u3002",
4275
+ locked: ["\u54C1\u724C\u5F52\u5C5E\u7EA2\u7EBF\uFF08\u4EC5 composer/qa reply \u8282\u70B9 code-assemble\uFF09", "\u56FE\u7247\u4E8B\u5B9E\u7EA2\u7EBF reply_fact\uFF08\u542B\u7EAF\u56FE\u5148\u6F84\u6E05\uFF0C\u4EC5 reply \u8282\u70B9\uFF09", "\u56FE\u7247\u5448\u73B0\u673A\u5236\uFF08\u6700\u8FD1\u56FE\u7247\u76F4\u63A5\u770B / \u66F4\u65E9\u56FE\u7247 [\u5386\u53F2\u56FE\u7247] \u5360\u4F4D\uFF09"]
4276
+ }
4277
+ };
4278
+ var EDITING_RULES = "\u6B63\u6587\u91CC @\u5F15\u7528 \u5199 {{name}}\uFF08\u4EC5 ASCII \u6807\u8BC6\u7B26\u88AB\u89E3\u6790\uFF1B\u4E2D\u6587/\u591A\u8BCD\u6309\u5B57\u9762\u6587\u672C\u4E0D\u89E3\u6790\uFF09\uFF1B\u53EA\u80FD\u5F15\u7528 variables \u91CC\u7684 name\uFF1B\u2462\u2463 \u5E73\u53F0\u9501\u5B9A\u3001\u4E0D\u51FA\u73B0\u5728\u4F60\u7684\u6B63\u6587\u91CC\uFF0C\u522B\u5C1D\u8BD5\u6539\uFF1B\u7A7A\u4E32\u8349\u7A3F = \u56DE\u843D\u5E73\u53F0\u51FA\u5382\u9ED8\u8BA4\u3002";
4279
+ function buildNodeDescribe(stage) {
4280
+ const charter = NODE_CHARTERS[stage];
4281
+ if (!charter) {
4282
+ throw new Error(`\u65E0\u8BE5\u8282\u70B9 charter\uFF1A${stage}`);
4283
+ }
4284
+ return {
4285
+ stage,
4286
+ charter,
4287
+ variables: referencesForStage(stage).map((r) => ({
4288
+ name: r.name,
4289
+ label: r.label,
4290
+ desc: r.desc,
4291
+ category: r.category,
4292
+ readOnly: !!r.readOnly
4293
+ })),
4294
+ editing_rules: EDITING_RULES
4295
+ };
4296
+ }
4297
+
4298
+ // src/utils/node-template-validate.ts
4299
+ var EXPOSED_NODE_STAGES = ["planner", "composer", "qa_inspector", "image_understanding"];
4300
+ function isExposedStage(stage) {
4301
+ return EXPOSED_NODE_STAGES.includes(stage);
4302
+ }
4303
+ var INTERP_RE = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
4304
+ function extractInterpolationNames(text) {
4305
+ const seen = /* @__PURE__ */ new Set();
4306
+ const out = [];
4307
+ for (const m of text.matchAll(INTERP_RE)) {
4308
+ const name = m[1];
4309
+ if (!seen.has(name)) {
4310
+ seen.add(name);
4311
+ out.push(name);
4312
+ }
4313
+ }
4314
+ return out;
4315
+ }
4316
+ function computeUnknownNames(content, factoryDefault) {
4317
+ const legal = /* @__PURE__ */ new Set([
4318
+ ...ALL_REFERENCE_NAMES,
4319
+ ...extractInterpolationNames(factoryDefault)
4320
+ ]);
4321
+ return extractInterpolationNames(content).filter((n) => !legal.has(n));
4322
+ }
4323
+
4324
+ // src/commands/node-template.ts
4325
+ var STAGE_HINT = EXPOSED_NODE_STAGES.join("|");
4326
+ function assertStage(stage) {
4327
+ if (!isExposedStage(stage)) {
4328
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u8282\u70B9\uFF1A${stage}\uFF08\u5F53\u524D\u53EF\u7528\uFF1A${STAGE_HINT}\uFF09`);
4329
+ }
4330
+ return stage;
4331
+ }
4332
+ function parseVersionNo(raw) {
4333
+ const n = Number(raw);
4334
+ if (!Number.isInteger(n) || n <= 0) {
4335
+ throw new Error(`\u975E\u6CD5\u7684 version_no: ${raw}\uFF08\u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF09`);
4336
+ }
4337
+ return n;
4338
+ }
4339
+ function resolveContent(opts) {
4340
+ const provided = [opts.content !== void 0, !!opts.contentFile, !!opts.stdin].filter(Boolean).length;
4341
+ if (provided !== 1) {
4342
+ throw new Error("\u5185\u5BB9\u6765\u6E90\u987B\u6070\u597D\u4E00\u4E2A\uFF1A--content <\u6587\u672C> | --content-file <\u8DEF\u5F84> | --stdin");
4343
+ }
4344
+ if (opts.content !== void 0) return opts.content;
4345
+ if (opts.contentFile) return readFileSync(opts.contentFile, "utf8");
4346
+ return readFileSync(0, "utf8");
4347
+ }
4348
+ function registerNodeTemplateCommand(program2) {
4349
+ const nt = program2.command("node-template").description(
4350
+ `\u8282\u70B9\u6A21\u677F\uFF08\u9AD8\u7EA7\u6A21\u677F\uFF09\u64CD\u4F5C \u2014\u2014 \u83B7\u53D6\u5185\u5BB9\u3001\u6539\u52A8\u53D1\u5E03\u3001\u5386\u53F2/\u56DE\u6EAF\u3002\u8282\u70B9 stage\uFF1A${STAGE_HINT}\uFF08tool_selector \u6682\u4E0D\u653E\u51FA\uFF09\u3002\u9AD8\u7EA7\u6A21\u5F0F\u5F00\u5173\u5DF2\u6539\u4E3A\u5BA2\u6237\u81EA\u52A9\uFF08cs-client \u5185\u5F00\u5173\uFF09\uFF0CCLI \u4E0D\u518D\u63D0\u4F9B enroll \u5F00\u542F\u8DEF\u5F84\u3002\u670D\u52A1\u7AEF\uFF1Acustomer-servhub-api /v1/customer-agent/{config_id}/customer_config/node-templates*\uFF1B\u5951\u7EA6 docs/devkit/specs/2026-06-08-\u8282\u70B9\u6A21\u677F-cli-\u64CD\u4F5C\u9762.md`
4351
+ );
4352
+ nt.command("describe").description(
4353
+ `\u8282\u70B9\u7F16\u5199\u8BF4\u660E\u4E66\uFF08AI \u7F16\u5199\u524D\u5FC5\u8BFB\uFF09\uFF1A\u6BCF\u4E2A\u8282\u70B9\u7684\u804C\u8D23/\u53EF\u7F16\u8F91\u9762/\u8F93\u51FA\u5951\u7EA6/\u5E73\u53F0\u9501\u5B9A\u6BB5 + \u672C\u8282\u70B9\u53EF @\u5F15\u7528 \u7684\u53D8\u91CF\u76EE\u5F55\u3002\u4E0D\u4F20 --stage = \u5168\u90E8\u5DF2\u5F00\u653E\u8282\u70B9\uFF08${STAGE_HINT}\uFF09\u3002\u7EAF\u9759\u6001\u3001\u4E0E\u5177\u4F53 agent \u65E0\u5173\uFF0C\u65E0\u9700 --agent\u3002`
4354
+ ).option("--stage <stage>", `\u5355\u8282\u70B9\uFF1A${STAGE_HINT}`).action((opts) => {
4355
+ try {
4356
+ if (opts.stage) {
4357
+ const stage = assertStage(opts.stage);
4358
+ formatOutput({ success: true, data: buildNodeDescribe(stage) }, program2.opts().table);
4359
+ return;
4360
+ }
4361
+ const data = EXPOSED_NODE_STAGES.map((s) => buildNodeDescribe(s));
4362
+ formatOutput({ success: true, data }, program2.opts().table);
4363
+ } catch (err) {
4364
+ reportCaughtError(err);
4365
+ process.exit(toExitCode(err));
4366
+ }
4367
+ });
4368
+ nt.command("get").description("\u83B7\u53D6\u8282\u70B9\u6A21\u677F\u5185\u5BB9\uFF08\u4E0D\u4F20 --stage=\u5168\u90E8\u5DF2\u5F00\u653E\u8282\u70B9\uFF1Btool_selector \u4E00\u5F8B\u6EE4\u9664\uFF09").requiredOption("--agent <id>", "customer_agent_config_id").option("--stage <stage>", `\u5355\u8282\u70B9\u8FC7\u6EE4\uFF1A${STAGE_HINT}`).action(async (opts) => {
4369
+ try {
4370
+ const payload = await getNodeTemplates(opts.agent);
4371
+ if (opts.stage) {
4372
+ const stage = assertStage(opts.stage);
4373
+ const data2 = {
4374
+ config_id: payload.config_id,
4375
+ stage,
4376
+ node: payload.nodes?.[stage],
4377
+ factory_default: payload.factory_defaults?.[stage] ?? ""
4378
+ };
4379
+ formatOutput({ success: true, data: data2 }, program2.opts().table);
4380
+ return;
4381
+ }
4382
+ const exposed = new Set(EXPOSED_NODE_STAGES);
4383
+ const nodes = {};
4384
+ for (const [k, v] of Object.entries(payload.nodes ?? {})) {
4385
+ if (exposed.has(k)) nodes[k] = v;
4386
+ }
4387
+ const factory = {};
4388
+ for (const [k, v] of Object.entries(payload.factory_defaults ?? {})) {
4389
+ if (exposed.has(k)) factory[k] = v;
4390
+ }
4391
+ const data = {
4392
+ config_id: payload.config_id,
4393
+ stages: (payload.stages ?? []).filter((s) => exposed.has(s)),
4394
+ nodes,
4395
+ factory_defaults: factory
4396
+ };
4397
+ formatOutput({ success: true, data }, program2.opts().table);
4398
+ } catch (err) {
4399
+ reportCaughtError(err);
4400
+ process.exit(toExitCode(err));
4401
+ }
4402
+ });
4403
+ nt.command("set-draft").description("\u5199\u5355\u8282\u70B9\u8349\u7A3F\uFF08\u6574\u6BB5\u8986\u76D6\uFF1B\u7A7A\u4E32=\u56DE\u843D\u51FA\u5382\u9ED8\u8BA4\uFF09\u3002\u5185\u5BB9\u4E09\u9009\u4E00\uFF0C\u957F prompt \u5EFA\u8BAE --content-file").requiredOption("--agent <id>", "customer_agent_config_id").requiredOption("--stage <stage>", `\u8282\u70B9\uFF1A${STAGE_HINT}`).option("--content <text>", "\u5185\u8054\u6B63\u6587").option("--content-file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u6B63\u6587").option("--stdin", "\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB\u6B63\u6587", false).action(async (opts) => {
4404
+ try {
4405
+ const stage = assertStage(opts.stage);
4406
+ const content = resolveContent(opts);
4407
+ const data = await setNodeTemplateDraft(opts.agent, stage, content);
4408
+ formatOutput({ success: true, data }, program2.opts().table);
4409
+ } catch (err) {
4410
+ reportCaughtError(err);
4411
+ process.exit(toExitCode(err));
4412
+ }
4413
+ });
4414
+ nt.command("publish").description("\u53D1\u5E03\u8BE5\u8282\u70B9\u5F53\u524D\u8349\u7A3F\u4E0A\u7EBF\uFF08\u53D1\u5E03\u524D\u6821\u9A8C {{}} \u5408\u6CD5\u6027\uFF0C\u4E0D\u5408\u6CD5\u62D2\u7EDD\uFF1B--force \u8D8A\u8FC7\u6821\u9A8C\uFF09").requiredOption("--agent <id>", "customer_agent_config_id").requiredOption("--stage <stage>", `\u8282\u70B9\uFF1A${STAGE_HINT}`).option("--force", "\u8DF3\u8FC7 {{}} \u5408\u6CD5\u6027\u6821\u9A8C\u76F4\u63A5\u53D1\u5E03", false).action(async (opts) => {
4415
+ try {
4416
+ const stage = assertStage(opts.stage);
4417
+ if (!opts.force) {
4418
+ const payload = await getNodeTemplates(opts.agent);
4419
+ const node = payload.nodes?.[stage];
4420
+ const draft = node?.content ?? "";
4421
+ const factory = payload.factory_defaults?.[stage] ?? "";
4422
+ const unknown = computeUnknownNames(draft, factory);
4423
+ if (unknown.length > 0) {
4424
+ throw new Error(
4425
+ `\u8349\u7A3F\u542B\u975E\u6CD5\u63D2\u503C\u540D\uFF0C\u62D2\u7EDD\u53D1\u5E03\uFF1A${unknown.map((n) => `{{${n}}}`).join(" ")}\u3002\u8BF7\u4FEE\u6B63\u4E3A\u5408\u6CD5\u5F15\u7528\u540D\uFF0C\u6216\u786E\u8BA4\u65E0\u8BEF\u540E\u52A0 --force \u8D8A\u8FC7\u6821\u9A8C\u3002`
4426
+ );
4427
+ }
4428
+ }
4429
+ const data = await publishNodeTemplate(opts.agent, stage);
4430
+ formatOutput({ success: true, data }, program2.opts().table);
4431
+ } catch (err) {
4432
+ reportCaughtError(err);
4433
+ process.exit(toExitCode(err));
4434
+ }
4435
+ });
4436
+ nt.command("versions").description("\u5217\u8BE5\u8282\u70B9\u5386\u53F2\u7248\u672C\u65F6\u95F4\u7EBF").requiredOption("--agent <id>", "customer_agent_config_id").requiredOption("--stage <stage>", `\u8282\u70B9\uFF1A${STAGE_HINT}`).action(async (opts) => {
4437
+ try {
4438
+ const stage = assertStage(opts.stage);
4439
+ const data = await listNodeTemplateVersions(opts.agent, stage);
4440
+ formatOutput({ success: true, data }, program2.opts().table);
4441
+ } catch (err) {
4442
+ reportCaughtError(err);
4443
+ process.exit(toExitCode(err));
4444
+ }
4445
+ });
4446
+ nt.command("version").description("\u53D6\u8BE5\u8282\u70B9\u67D0\u7248\u672C\u6B63\u6587\uFF08\u9884\u89C8/diff\uFF09").requiredOption("--agent <id>", "customer_agent_config_id").requiredOption("--stage <stage>", `\u8282\u70B9\uFF1A${STAGE_HINT}`).requiredOption("--version <no>", "\u7248\u672C\u53F7\uFF08\u6B63\u6574\u6570\uFF09").action(async (opts) => {
4447
+ try {
4448
+ const stage = assertStage(opts.stage);
4449
+ const versionNo = parseVersionNo(opts.version);
4450
+ const data = await getNodeTemplateVersion(opts.agent, stage, versionNo);
4451
+ formatOutput({ success: true, data }, program2.opts().table);
4452
+ } catch (err) {
4453
+ reportCaughtError(err);
4454
+ process.exit(toExitCode(err));
4455
+ }
4456
+ });
4457
+ nt.command("restore").description("\u628A\u8BE5\u8282\u70B9\u67D0\u5386\u53F2\u7248\u672C\u590D\u5236\u6210\u8349\u7A3F\uFF08\u4E0D\u76F4\u63A5\u751F\u6548\uFF0C\u9700\u518D publish\uFF09").requiredOption("--agent <id>", "customer_agent_config_id").requiredOption("--stage <stage>", `\u8282\u70B9\uFF1A${STAGE_HINT}`).requiredOption("--version <no>", "\u8981\u56DE\u6EAF\u5230\u7684\u7248\u672C\u53F7\uFF08\u6B63\u6574\u6570\uFF09").action(async (opts) => {
4458
+ try {
4459
+ const stage = assertStage(opts.stage);
4460
+ const versionNo = parseVersionNo(opts.version);
4461
+ const data = await restoreNodeTemplateVersion(opts.agent, stage, versionNo);
4462
+ formatOutput({ success: true, data }, program2.opts().table);
4463
+ } catch (err) {
4464
+ reportCaughtError(err);
4465
+ process.exit(toExitCode(err));
4466
+ }
4467
+ });
4468
+ }
4469
+
3789
4470
  // src/client/ops-agent-api.ts
3790
4471
  async function listOpsAgentConversations(opts) {
3791
4472
  const workspaceId = getWorkspaceId(opts.workspaceId);
@@ -3825,7 +4506,7 @@ function registerOpsAgentCommand(program2) {
3825
4506
  }
3826
4507
 
3827
4508
  // src/client/operations-record-api.ts
3828
- var PATH_PREFIX2 = "/v1/agent_operations_records";
4509
+ var PATH_PREFIX3 = "/v1/agent_operations_records";
3829
4510
  async function listOperationsRecords(opts) {
3830
4511
  const request = createRequest();
3831
4512
  const query = {};
@@ -3833,34 +4514,34 @@ async function listOperationsRecords(opts) {
3833
4514
  if (opts.agentId) query.agent_id = opts.agentId;
3834
4515
  if (opts.page) query.page = opts.page;
3835
4516
  if (opts.pageSize) query.page_size = opts.pageSize;
3836
- return request(getCustomerServiceUrl(), PATH_PREFIX2, {
4517
+ return request(getCustomerServiceUrl(), PATH_PREFIX3, {
3837
4518
  method: "GET",
3838
4519
  query
3839
4520
  });
3840
4521
  }
3841
4522
  async function getOperationsRecord(recordId) {
3842
4523
  const request = createRequest();
3843
- return request(getCustomerServiceUrl(), `${PATH_PREFIX2}/${recordId}`, {
4524
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX3}/${recordId}`, {
3844
4525
  method: "GET"
3845
4526
  });
3846
4527
  }
3847
4528
  async function createOperationsRecord(data) {
3848
4529
  const request = createRequest();
3849
- return request(getCustomerServiceUrl(), PATH_PREFIX2, {
4530
+ return request(getCustomerServiceUrl(), PATH_PREFIX3, {
3850
4531
  method: "POST",
3851
4532
  body: data
3852
4533
  });
3853
4534
  }
3854
4535
  async function updateOperationsRecord(recordId, data) {
3855
4536
  const request = createRequest();
3856
- return request(getCustomerServiceUrl(), `${PATH_PREFIX2}/${recordId}`, {
4537
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX3}/${recordId}`, {
3857
4538
  method: "PUT",
3858
4539
  body: data
3859
4540
  });
3860
4541
  }
3861
4542
  async function deleteOperationsRecord(recordId) {
3862
4543
  const request = createRequest();
3863
- return request(getCustomerServiceUrl(), `${PATH_PREFIX2}/${recordId}`, {
4544
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX3}/${recordId}`, {
3864
4545
  method: "DELETE"
3865
4546
  });
3866
4547
  }
@@ -4247,10 +4928,11 @@ function registerProductCommand(program2) {
4247
4928
  process.exit(toExitCode(err));
4248
4929
  }
4249
4930
  });
4931
+ registerProductRelatedKnowledgeCommand(product, program2);
4250
4932
  }
4251
4933
 
4252
4934
  // src/client/repair-record-api.ts
4253
- var PATH_PREFIX3 = "/api/repair-records";
4935
+ var PATH_PREFIX4 = "/api/repair-records";
4254
4936
  async function listRepairRecords(opts) {
4255
4937
  const request = createRequest();
4256
4938
  const query = {};
@@ -4259,21 +4941,21 @@ async function listRepairRecords(opts) {
4259
4941
  if (opts.workspaceId) query.workspace_id = opts.workspaceId;
4260
4942
  if (opts.page) query.page = opts.page;
4261
4943
  if (opts.pageSize) query.page_size = opts.pageSize;
4262
- return request(getCsAdminUrl(), PATH_PREFIX3, {
4944
+ return request(getCsAdminUrl(), PATH_PREFIX4, {
4263
4945
  method: "GET",
4264
4946
  query
4265
4947
  });
4266
4948
  }
4267
4949
  async function createRepairRecord(data) {
4268
4950
  const request = createRequest();
4269
- return request(getCsAdminUrl(), PATH_PREFIX3, {
4951
+ return request(getCsAdminUrl(), PATH_PREFIX4, {
4270
4952
  method: "POST",
4271
4953
  body: data
4272
4954
  });
4273
4955
  }
4274
4956
  async function updateRepairRecord(recordId, data) {
4275
4957
  const request = createRequest();
4276
- return request(getCsAdminUrl(), `${PATH_PREFIX3}/${recordId}`, {
4958
+ return request(getCsAdminUrl(), `${PATH_PREFIX4}/${recordId}`, {
4277
4959
  method: "PUT",
4278
4960
  body: data
4279
4961
  });
@@ -5350,6 +6032,23 @@ function registerWorkspaceCommand(program2) {
5350
6032
  process.exit(toExitCode(err));
5351
6033
  }
5352
6034
  });
6035
+ workspace.command("points-consumes").description("\u5206\u9875\u67E5\u8BE2\u5F53\u524D\u5DE5\u4F5C\u7A7A\u95F4\u7684\u79EF\u5206\u8D26\u6237\u6D88\u8017\u6D41\u6C34\uFF0C\u56FA\u5B9A\u8FD4\u56DE extra \u8C03\u8BD5\u5B57\u6BB5").option("--page <number>", "\u9875\u7801\uFF0C\u9ED8\u8BA4 1", "1").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 20", "20").action(async (opts) => {
6036
+ try {
6037
+ const page = Number(opts.page);
6038
+ const pageSize = Number(opts.pageSize);
6039
+ if (!Number.isInteger(page) || page <= 0) {
6040
+ throw new Error("--page \u5FC5\u987B\u4E3A\u6B63\u6574\u6570");
6041
+ }
6042
+ if (!Number.isInteger(pageSize) || pageSize <= 0) {
6043
+ throw new Error("--page-size \u5FC5\u987B\u4E3A\u6B63\u6574\u6570");
6044
+ }
6045
+ const data = await getPointsConsumes({ page, pageSize });
6046
+ formatOutput({ success: true, data }, program2.opts().table);
6047
+ } catch (err) {
6048
+ reportCaughtError(err);
6049
+ process.exit(toExitCode(err));
6050
+ }
6051
+ });
5353
6052
  workspace.command("points-consumes-daily").description(
5354
6053
  "\u6309\u5929\u67E5\u8BE2\u5F53\u524D\u5DE5\u4F5C\u7A7A\u95F4\u7684\u79EF\u5206\u6D88\u8017\u3002\u9ED8\u8BA4\u6700\u8FD1 30 \u5929\uFF08end=\u4ECA\u5929\uFF0Cstart=\u4ECA\u5929-29d\uFF09\uFF0C\u8FD4\u56DE consume_date / total_consume_points \u5217\u8868"
5355
6054
  ).option("--start <YYYY-MM-DD>", "\u8D77\u59CB\u65E5\u671F\uFF08\u542B\uFF09\uFF0C\u9ED8\u8BA4\u4ECA\u5929-29d").option("--end <YYYY-MM-DD>", "\u7ED3\u675F\u65E5\u671F\uFF08\u542B\uFF09\uFF0C\u9ED8\u8BA4\u4ECA\u5929").action(async (opts) => {
@@ -5416,6 +6115,7 @@ registerFaqCommand(program);
5416
6115
  registerConversationCommand(program);
5417
6116
  registerDebugCommand(program);
5418
6117
  registerMonitorCommand(program);
6118
+ registerNodeTemplateCommand(program);
5419
6119
  registerDashboardCommand(program);
5420
6120
  registerRepairRecordCommand(program);
5421
6121
  registerOperationsRecordCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.5.5",
3
+ "version": "0.6.2",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",