vps-work 2.5.1 → 2.7.0
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/.well-known/skills/index.json +14 -4
- package/README.md +1 -0
- package/USAGE.md +22 -0
- package/dist/vps-work.cjs +824 -546
- package/package.json +1 -1
- package/skills/vps-documents/references/api.md +4 -0
- package/skills/vps-okr/SKILL.md +12 -2
- package/skills/vps-okr-management-assistant/SKILL.md +2 -0
- package/skills/vps-store-management/references/store-reception.md +14 -2
- package/dist/packages/vps-work/src/bin/vps-work.js +0 -271
- package/dist/packages/vps-work/src/commands/agent/index.js +0 -264
- package/dist/packages/vps-work/src/commands/approval/index.js +0 -7
- package/dist/packages/vps-work/src/commands/approval/shortcuts.js +0 -259
- package/dist/packages/vps-work/src/commands/attendance/applications.js +0 -21
- package/dist/packages/vps-work/src/commands/attendance/index.js +0 -9
- package/dist/packages/vps-work/src/commands/attendance/scheduling.js +0 -183
- package/dist/packages/vps-work/src/commands/attendance/shortcuts.js +0 -131
- package/dist/packages/vps-work/src/commands/auth/check.js +0 -34
- package/dist/packages/vps-work/src/commands/auth/index.js +0 -21
- package/dist/packages/vps-work/src/commands/auth/list.js +0 -50
- package/dist/packages/vps-work/src/commands/auth/scopes.js +0 -17
- package/dist/packages/vps-work/src/commands/auth/status.js +0 -127
- package/dist/packages/vps-work/src/commands/auth/tenants.js +0 -35
- package/dist/packages/vps-work/src/commands/auth/use.js +0 -40
- package/dist/packages/vps-work/src/commands/calendar/index.js +0 -7
- package/dist/packages/vps-work/src/commands/calendar/shortcuts.js +0 -598
- package/dist/packages/vps-work/src/commands/catalog.js +0 -29
- package/dist/packages/vps-work/src/commands/cli-install.js +0 -70
- package/dist/packages/vps-work/src/commands/collab-invoke.js +0 -19
- package/dist/packages/vps-work/src/commands/corporate/index.js +0 -204
- package/dist/packages/vps-work/src/commands/creative/index.js +0 -369
- package/dist/packages/vps-work/src/commands/documents/index.js +0 -991
- package/dist/packages/vps-work/src/commands/drive/index.js +0 -435
- package/dist/packages/vps-work/src/commands/exec.js +0 -46
- package/dist/packages/vps-work/src/commands/hr/business.js +0 -22
- package/dist/packages/vps-work/src/commands/hr/data-view-shortcuts.js +0 -519
- package/dist/packages/vps-work/src/commands/hr/index.js +0 -15
- package/dist/packages/vps-work/src/commands/hr/job-responsibilities.js +0 -266
- package/dist/packages/vps-work/src/commands/hr/payslip.js +0 -45
- package/dist/packages/vps-work/src/commands/hr/shortcuts.js +0 -146
- package/dist/packages/vps-work/src/commands/im/im-message-payload.js +0 -150
- package/dist/packages/vps-work/src/commands/im/index.js +0 -7
- package/dist/packages/vps-work/src/commands/im/shortcuts.js +0 -856
- package/dist/packages/vps-work/src/commands/knowledge/index.js +0 -36
- package/dist/packages/vps-work/src/commands/login.js +0 -52
- package/dist/packages/vps-work/src/commands/logout.js +0 -12
- package/dist/packages/vps-work/src/commands/mail/email.js +0 -132
- package/dist/packages/vps-work/src/commands/mail/index.js +0 -9
- package/dist/packages/vps-work/src/commands/mail/shortcuts.js +0 -43
- package/dist/packages/vps-work/src/commands/meeting/index.js +0 -7
- package/dist/packages/vps-work/src/commands/meeting/shortcuts.js +0 -120
- package/dist/packages/vps-work/src/commands/odoo-invoke.js +0 -21
- package/dist/packages/vps-work/src/commands/odoo-leaf-registry.js +0 -13
- package/dist/packages/vps-work/src/commands/office/index.js +0 -59
- package/dist/packages/vps-work/src/commands/okr/index.js +0 -7
- package/dist/packages/vps-work/src/commands/okr/shortcuts.js +0 -75
- package/dist/packages/vps-work/src/commands/org-structure.js +0 -347
- package/dist/packages/vps-work/src/commands/popup/index.js +0 -147
- package/dist/packages/vps-work/src/commands/product/abnormal-statistics.js +0 -250
- package/dist/packages/vps-work/src/commands/product/data-view-shortcuts.js +0 -253
- package/dist/packages/vps-work/src/commands/product/index.js +0 -11
- package/dist/packages/vps-work/src/commands/purchase/index.js +0 -45
- package/dist/packages/vps-work/src/commands/reauth.js +0 -127
- package/dist/packages/vps-work/src/commands/report/index.js +0 -7
- package/dist/packages/vps-work/src/commands/report/missing-daily-reports.js +0 -9
- package/dist/packages/vps-work/src/commands/report/shortcuts.js +0 -251
- package/dist/packages/vps-work/src/commands/sales/index.js +0 -11
- package/dist/packages/vps-work/src/commands/sales/period-params.js +0 -75
- package/dist/packages/vps-work/src/commands/sales/shortcuts-personnel.js +0 -124
- package/dist/packages/vps-work/src/commands/sales/shortcuts.js +0 -800
- package/dist/packages/vps-work/src/commands/shortcut-common.js +0 -88
- package/dist/packages/vps-work/src/commands/shortcut-params.js +0 -22
- package/dist/packages/vps-work/src/commands/skill/index.js +0 -136
- package/dist/packages/vps-work/src/commands/skill/personal.js +0 -167
- package/dist/packages/vps-work/src/commands/store/index.js +0 -82
- package/dist/packages/vps-work/src/commands/task/index.js +0 -7
- package/dist/packages/vps-work/src/commands/task/task-center.js +0 -595
- package/dist/packages/vps-work/src/commands/tools/index.js +0 -127
- package/dist/packages/vps-work/src/commands/vemory/index.js +0 -7
- package/dist/packages/vps-work/src/commands/vemory/shortcuts.js +0 -53
- package/dist/packages/vps-work/src/commands/vertu-aisales.js +0 -30
- package/dist/packages/vps-work/src/commands/vertu-erp.js +0 -30
- package/dist/packages/vps-work/src/core/aisales-mcp-client.js +0 -35
- package/dist/packages/vps-work/src/core/auth.js +0 -240
- package/dist/packages/vps-work/src/core/cli-bin.js +0 -11
- package/dist/packages/vps-work/src/core/cli-root-help.js +0 -190
- package/dist/packages/vps-work/src/core/client.js +0 -158
- package/dist/packages/vps-work/src/core/config.js +0 -183
- package/dist/packages/vps-work/src/core/corporate-client.js +0 -52
- package/dist/packages/vps-work/src/core/creative-attachment-download.js +0 -48
- package/dist/packages/vps-work/src/core/documents-client.js +0 -57
- package/dist/packages/vps-work/src/core/endpoints.js +0 -46
- package/dist/packages/vps-work/src/core/erp-mcp-client.js +0 -11
- package/dist/packages/vps-work/src/core/errors.js +0 -107
- package/dist/packages/vps-work/src/core/execute-catalog-client.js +0 -42
- package/dist/packages/vps-work/src/core/hermes-client.js +0 -70
- package/dist/packages/vps-work/src/core/im-attachment-download.js +0 -109
- package/dist/packages/vps-work/src/core/im-client.js +0 -64
- package/dist/packages/vps-work/src/core/keychain.js +0 -58
- package/dist/packages/vps-work/src/core/mail-client.js +0 -82
- package/dist/packages/vps-work/src/core/mcp-client.js +0 -129
- package/dist/packages/vps-work/src/core/office-client.js +0 -25
- package/dist/packages/vps-work/src/core/office-files.js +0 -82
- package/dist/packages/vps-work/src/core/paths.js +0 -70
- package/dist/packages/vps-work/src/core/public-result.js +0 -43
- package/dist/packages/vps-work/src/core/sales-invoice-attachments-client.js +0 -75
- package/dist/packages/vps-work/src/core/sales-query-client.js +0 -452
- package/dist/packages/vps-work/src/core/skill-catalog.js +0 -158
- package/dist/packages/vps-work/src/core/skill-key.js +0 -19
- package/dist/packages/vps-work/src/core/v-drive-store.js +0 -392
- package/dist/packages/vps-work/src/core/vertu-agent-env.js +0 -32
- package/dist/packages/vps-work/src/core/vps-auth-scopes.js +0 -73
- package/dist/packages/vps-work/src/core/vps-service-auth.js +0 -382
- package/dist/packages/vps-work/src/core/vps-service-config.js +0 -83
- package/dist/packages/vps-work/src/core/vps-service-http.js +0 -187
- package/dist/packages/vps-work/src/utils/at-file.js +0 -34
- package/dist/packages/vps-work/src/utils/cli-option-helpers.js +0 -52
- package/dist/packages/vps-work/src/utils/frontmatter.js +0 -26
- package/dist/packages/vps-work/src/utils/normalize-argv.js +0 -51
- package/dist/packages/vps-work/src/utils/parse-json-arg.js +0 -85
- package/dist/packages/vps-work/src/utils/prompt.js +0 -46
- package/dist/packages/vps-work/src/utils/read-text.js +0 -44
- package/dist/packages/vps-work/src/utils/schema-to-options.js +0 -307
- package/dist/packages/vps-work/src/utils/unwrap-envelope.js +0 -31
- package/dist/shared/im-org-tree/index.js +0 -200
package/package.json
CHANGED
|
@@ -142,3 +142,7 @@ vps-work docs +update-block \
|
|
|
142
142
|
- `textAlignment` 为 `left`、`center` 或 `right`;引用和代码块不支持对齐。
|
|
143
143
|
- `backgroundColor` 为 `default` 或 `yellow`;代码块不支持背景设置。
|
|
144
144
|
- 版本不匹配时重新执行 `+get-blocks`,确认目标块后再提交。
|
|
145
|
+
|
|
146
|
+
## 外部系统的受限多维表 Token
|
|
147
|
+
|
|
148
|
+
第三方服务可在多维表「API」面板创建按表、按操作授权的 `vpsb_` Token,调用 `/v1/open/bases/:docId`。此凭证与本 CLI 的用户/Agent 鉴权分离,不能用于 `/v1/documents`。写入须提供 `Idempotency-Key`;详见仓库 `services/vps-base/EXTERNAL_API.md`。CLI 继续使用原有用户身份链路。
|
package/skills/vps-okr/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vps-okr
|
|
3
|
-
version: 1.
|
|
4
|
-
description: 部门与员工 OKR
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: 部门与员工 OKR 查询及独立授权的正式 OKR 打分。`vps-work okr +…`。
|
|
5
5
|
metadata:
|
|
6
6
|
requires:
|
|
7
7
|
bins: ['vps-work']
|
|
@@ -20,3 +20,13 @@ metadata:
|
|
|
20
20
|
| `+personal-structured` | 同 `+employee-okr` |
|
|
21
21
|
|
|
22
22
|
Agent / Bot 必须使用宿主注入的 app_key,禁止执行账号密码登录。仅人工用户可在交互终端执行 `vps-work auth login`。
|
|
23
|
+
|
|
24
|
+
## 正式 OKR 打分
|
|
25
|
+
|
|
26
|
+
需要独立权限 `okr.scoring:write`(角色 `vps_okr_scorer`),普通 OKR 读写权限不替代此授权。权限只开放入口,原评分人、评分阶段及窗口限制继续生效。
|
|
27
|
+
|
|
28
|
+
先用 `vps-work okr +score-context --period YYYY-MM --owner-type person --owner-id ID` 读取正式评分依据;部门用 `--owner-type department`。读取 KR 本级评分的 revision;该级没有评分时使用 0,不使用计划 revision。
|
|
29
|
+
|
|
30
|
+
`vps-work okr +score --key-result-id ID --level self --score 85 --comment '已核实的评分依据' --expected-revision REV` 默认仅预览;用户已授权实际打分且分数、依据明确后加 `--yes`。阶段为 self(自评)、primary(初评)、secondary(复评)。代理部门仅能使用已有授权的 `--acting-department-id`。
|
|
31
|
+
|
|
32
|
+
403 停止,不换身份绕过;409 重新读取并核对差异,不盲目提高 revision 后重发。超时或连接失败可能已经提交,先读回分数与版本确认结果,不自动重复写入。不要从历史只读表猜测正式 KR ID,也不要因获准查询而自行打分。
|
|
@@ -43,3 +43,5 @@ description: "面向 Vertu 部门和员工 OKR 的结构化取数与分析。用
|
|
|
43
43
|
- `employee_id` 是 `res.users.id`,`department_id` 是 `hr.department.id`。
|
|
44
44
|
- 查询结果受当前用户权限和服务端数据范围限制。
|
|
45
45
|
- 2026-07 及以后不回退 PMS;正式表不可用时服务端会明确报错。
|
|
46
|
+
|
|
47
|
+
正式 OKR 打分已提供独立入口 `okr +score-context` / `okr +score`。需要打分时读取 [OKR 打分指引](../vps-okr/SKILL.md),使用独立 `okr.scoring:write` 权限;本技能的历史/结构化查询不能用于推断评分版本或代替评分授权。
|
|
@@ -24,8 +24,8 @@ vps-work store +reception [options]
|
|
|
24
24
|
- 微信添加数、有效客户数、有效客户添加数、触摸人数、试用人数:累计 `crm_walk_in_portrait` 客户画像行。
|
|
25
25
|
- 成交组数/成交金额:仅 `is_transaction='yes'`。
|
|
26
26
|
- 成交金额按接待类型拆分:自然流、售后、邀约、下线转介绍、其他。
|
|
27
|
-
-
|
|
28
|
-
-
|
|
27
|
+
- 日期直接使用 `reception_date`(PostgreSQL DATE 接待自然日),含首尾日期,不叠加时区偏移。
|
|
28
|
+
- 组织范围固定为国内渠道(兼容调整后名称“国内渠道中心”)东/西/北/南四区,并兼容“区域直属门店”和“国内直营门店/区域/门店”两种组织层级后按区域、门店汇总;仅包含有效组织路径,停用部门及范围外记录不自动混入。
|
|
29
29
|
- 门店名称优先使用表单的 `walk_in_store`,未填写时回退组织门店;北京 SKP 旧值归入“北京SKP 老店”。
|
|
30
30
|
- 代理商入口与门店共用主表,默认排除 `is_dealer_submission=true`;需要时使用 `--source dealer/all`。
|
|
31
31
|
|
|
@@ -36,5 +36,17 @@ vps-work store +reception [options]
|
|
|
36
36
|
- `rows`:区域/门店明细。
|
|
37
37
|
- `breakdowns`:接待类型、时段、状态、客户画像、微信状态和意向商品(含意向金额)。
|
|
38
38
|
- `notes`:口径说明。
|
|
39
|
+
- `data_status`:`ok` 表示有匹配记录;`empty` 表示组织映射有效但筛选无记录。
|
|
40
|
+
- `organization_scope`:组织范围有效状态及部门、区域数量,与指标在同一数据库快照计算。
|
|
41
|
+
|
|
42
|
+
组织根缺失、停用、改为未兼容名称或四区映射不完整时返回 HTTP 503、`STORE_RECEPTION_SCOPE_UNAVAILABLE`,不能显示为“零接待”。周报调用方必须保留此失败状态,显示“数据暂不可用”,不得捕获错误后把指标补零。收到 `empty` 时可显示“无匹配接待记录”;这仍不等于独立核实了业务应有值为零。
|
|
43
|
+
|
|
44
|
+
显式 `department_id` 不在有效四区路径中(包括部门停用、缺失、归属范围外)时返回 HTTP 422、`STORE_RECEPTION_DEPARTMENT_OUT_OF_SCOPE`,不能与 Walk-in 的全范围部门枚举直接按零合并。开始日期晚于结束日期、无效自然日返回 HTTP 400。合并前必须核对两次查询的起止日期、source 和部门过滤;`reception.rows` 是区域/门店聚合,不含可直接联接的部门 ID,不能仅按同名门店猜测部门关系。未核实映射、接口失败或同范围 Walk-in 有客流而 reception 为空时,应保留缺失状态及原因。
|
|
45
|
+
|
|
46
|
+
新版 CLI 会拒绝缺少组织健康/数据状态、筛选与请求不符的接待响应,错误为 `STORE_RECEPTION_RESULT_UNVERIFIED`。服务端与 CLI 需要配套验证;旧版服务返回的成功空结果不作为真实零值放行。脚本调用时必须检查非零退出,不得用 `catch`、空对象或 `|| 0` 将失败改成业务零值。真实原周报生成器尚需独立核对这些约束。
|
|
47
|
+
|
|
48
|
+
周报合并验收还须检查:固定两来源相同的显式起止日期、source 和有效部门范围;Walk-in 分组完整翻页后按 `department_id` 去重(同名部门不能合并);`summary` 才是完整筛选总计。接待按区域/门店聚合,必须用已核实的部门关系或逐部门查询,不能按门店文本猜联接。停用/范围外部门应单列范围说明,不能补零或擅自混入有效四区总计。
|
|
49
|
+
|
|
50
|
+
接口失败、未经核实的空值及未归属项在报表中显示“暂不可用”或“—”。`empty` 只能解释为无匹配接待记录;任一转化率分母为零时应显示“—/无基数”,不能断言真实转化率为零。已有接待但无成交时,成交组数及成交额可显示真实数值 0。生成器取得前,这些页面及合并规则仍属待验收。
|
|
39
51
|
|
|
40
52
|
门店接待过程数据不能与 `sales +agent-walkin` 的经销商订单金额直接相加。
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { buildPurchaseCommand } from '../commands/purchase/index.js';
|
|
3
|
-
import { formatErrorForCli, VertuError } from '../core/errors.js';
|
|
4
|
-
import { buildAuthCommand } from '../commands/auth/index.js';
|
|
5
|
-
import { buildAgentCommand } from '../commands/agent/index.js';
|
|
6
|
-
import { buildApprovalCommand } from '../commands/approval/index.js';
|
|
7
|
-
import { buildImCommand } from '../commands/im/index.js';
|
|
8
|
-
import { buildMailCommand } from '../commands/mail/index.js';
|
|
9
|
-
import { buildOkrCommand } from '../commands/okr/index.js';
|
|
10
|
-
import { buildReportCommand } from '../commands/report/index.js';
|
|
11
|
-
import { buildAttendanceCommand } from '../commands/attendance/index.js';
|
|
12
|
-
import { buildTaskCommand } from '../commands/task/index.js';
|
|
13
|
-
import { buildToolsCommand } from '../commands/tools/index.js';
|
|
14
|
-
// `dev` 命令已弃用并并入 `task`(task-center / task-assistant 直连库)。
|
|
15
|
-
import { buildHrCommand } from '../commands/hr/index.js';
|
|
16
|
-
import { buildProductCommand } from '../commands/product/index.js';
|
|
17
|
-
import { buildSalesCommand } from '../commands/sales/index.js';
|
|
18
|
-
import { buildStoreCommand } from '../commands/store/index.js';
|
|
19
|
-
import { buildVemoryCommand } from '../commands/vemory/index.js';
|
|
20
|
-
import { buildMeetingCommand } from '../commands/meeting/index.js';
|
|
21
|
-
import { buildCalendarCommand } from '../commands/calendar/index.js';
|
|
22
|
-
import { buildCreativeCommand } from '../commands/creative/index.js';
|
|
23
|
-
import { buildCorporateCommand } from '../commands/corporate/index.js';
|
|
24
|
-
import { buildDocumentsCommand } from '../commands/documents/index.js';
|
|
25
|
-
import { buildOfficeCommand } from '../commands/office/index.js';
|
|
26
|
-
import { buildKnowledgeCommand } from '../commands/knowledge/index.js';
|
|
27
|
-
import { buildDriveCommand } from '../commands/drive/index.js';
|
|
28
|
-
import { buildPopupCommand } from '../commands/popup/index.js';
|
|
29
|
-
import { buildSkillCommand } from '../commands/skill/index.js';
|
|
30
|
-
import { buildCatalogCommand } from '../commands/catalog.js';
|
|
31
|
-
import { buildExecCommand } from '../commands/exec.js';
|
|
32
|
-
import { buildVertuAiSalesCommand } from '../commands/vertu-aisales.js';
|
|
33
|
-
import { buildVertuErpCommand } from '../commands/vertu-erp.js';
|
|
34
|
-
import { buildCliInstallCommand } from '../commands/cli-install.js';
|
|
35
|
-
import { CLI_BIN, CLI_BIN_ALIASES } from '../core/cli-bin.js';
|
|
36
|
-
import { buildCliRootHelpAfter, buildCliRootHelpBefore } from '../core/cli-root-help.js';
|
|
37
|
-
import { normalizeSnakeFlags } from '../utils/normalize-argv.js';
|
|
38
|
-
const PKG_VERSION = typeof __VERTU_VERSION__ === 'string' && __VERTU_VERSION__ ? __VERTU_VERSION__ : '0.0.0-dev';
|
|
39
|
-
/**
|
|
40
|
-
* Compute a suggestion for an unknown long flag.
|
|
41
|
-
*
|
|
42
|
-
* commander 13's built-in `suggestSimilar` uses Damerau–Levenshtein with a
|
|
43
|
-
* hard-coded `maxDistance = 3`, which silently fails the most common AI
|
|
44
|
-
* mistake on this CLI: typing the schema-shortened form (`--model`) when the
|
|
45
|
-
* registered flag is the longer kebab-cased schema key (`--model-name`,
|
|
46
|
-
* `--knowledge-types`, `--start-date`). The edit distance there is ≥4, so
|
|
47
|
-
* commander emits no hint at all.
|
|
48
|
-
*
|
|
49
|
-
* We replace it with a two-stage matcher tuned for that pattern:
|
|
50
|
-
* 1. **Prefix-of-candidate** (`--model` → `--model-name`): treat candidates
|
|
51
|
-
* that start with `<flag>-` as the strongest hit. AI agents typing the
|
|
52
|
-
* shortened form are almost always after the prefix-extended flag.
|
|
53
|
-
* 2. **Levenshtein with cap=6**: catch typos and snake/kebab swaps the
|
|
54
|
-
* argv normalizer didn't already canonicalize. Min similarity 0.4 keeps
|
|
55
|
-
* the bar for spurious matches similar to commander's default.
|
|
56
|
-
*
|
|
57
|
-
* Returns the suggestion line (with leading `\n` to match commander's format)
|
|
58
|
-
* or an empty string when no candidate is close enough.
|
|
59
|
-
*/
|
|
60
|
-
function smartFlagSuggest(flag, candidates) {
|
|
61
|
-
if (!flag.startsWith('--') || flag.length < 4)
|
|
62
|
-
return '';
|
|
63
|
-
const word = flag.slice(2);
|
|
64
|
-
const stripped = candidates
|
|
65
|
-
.filter((c) => c.startsWith('--') && c.length > 3)
|
|
66
|
-
.map((c) => c.slice(2));
|
|
67
|
-
const seen = new Set();
|
|
68
|
-
// Defensively drop exact matches: in real usage commander wouldn't reach the
|
|
69
|
-
// unknown-option path for a registered flag, but keeping the suggester pure
|
|
70
|
-
// makes it safe to compose elsewhere.
|
|
71
|
-
const cands = stripped.filter((c) => {
|
|
72
|
-
if (c === word || seen.has(c))
|
|
73
|
-
return false;
|
|
74
|
-
seen.add(c);
|
|
75
|
-
return true;
|
|
76
|
-
});
|
|
77
|
-
if (cands.length === 0)
|
|
78
|
-
return '';
|
|
79
|
-
const prefixHits = cands
|
|
80
|
-
.filter((c) => c.length > word.length && c.startsWith(`${word}-`))
|
|
81
|
-
.sort();
|
|
82
|
-
if (prefixHits.length) {
|
|
83
|
-
const formatted = prefixHits.map((c) => `--${c}`);
|
|
84
|
-
return prefixHits.length === 1
|
|
85
|
-
? `\n(Did you mean ${formatted[0]}?)`
|
|
86
|
-
: `\n(Did you mean one of ${formatted.join(', ')}?)`;
|
|
87
|
-
}
|
|
88
|
-
const editDistance = (a, b) => {
|
|
89
|
-
if (Math.abs(a.length - b.length) > 8)
|
|
90
|
-
return Math.max(a.length, b.length);
|
|
91
|
-
// Flat 1-D matrix indexed as `(rows+1) cells per row`; avoids
|
|
92
|
-
// `noUncheckedIndexedAccess` complaints from nested array slots that
|
|
93
|
-
// TypeScript otherwise types as `number | undefined`.
|
|
94
|
-
const w = b.length + 1;
|
|
95
|
-
const d = new Array((a.length + 1) * w);
|
|
96
|
-
for (let i = 0; i <= a.length; i++)
|
|
97
|
-
d[i * w] = i;
|
|
98
|
-
for (let j = 0; j <= b.length; j++)
|
|
99
|
-
d[j] = j;
|
|
100
|
-
for (let i = 1; i <= a.length; i++) {
|
|
101
|
-
for (let j = 1; j <= b.length; j++) {
|
|
102
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
103
|
-
const del = d[(i - 1) * w + j] + 1;
|
|
104
|
-
const ins = d[i * w + (j - 1)] + 1;
|
|
105
|
-
const sub = d[(i - 1) * w + (j - 1)] + cost;
|
|
106
|
-
let v = Math.min(del, ins, sub);
|
|
107
|
-
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
108
|
-
v = Math.min(v, d[(i - 2) * w + (j - 2)] + 1);
|
|
109
|
-
}
|
|
110
|
-
d[i * w + j] = v;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return d[a.length * w + b.length];
|
|
114
|
-
};
|
|
115
|
-
const MAX_DIST = 6;
|
|
116
|
-
const MIN_SIM = 0.4;
|
|
117
|
-
let bestDist = MAX_DIST;
|
|
118
|
-
let best = [];
|
|
119
|
-
for (const c of cands) {
|
|
120
|
-
const dist = editDistance(word, c);
|
|
121
|
-
const len = Math.max(word.length, c.length);
|
|
122
|
-
if ((len - dist) / len <= MIN_SIM)
|
|
123
|
-
continue;
|
|
124
|
-
if (dist < bestDist) {
|
|
125
|
-
bestDist = dist;
|
|
126
|
-
best = [c];
|
|
127
|
-
}
|
|
128
|
-
else if (dist === bestDist) {
|
|
129
|
-
best.push(c);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (!best.length)
|
|
133
|
-
return '';
|
|
134
|
-
best.sort();
|
|
135
|
-
const formatted = best.map((c) => `--${c}`);
|
|
136
|
-
return best.length === 1
|
|
137
|
-
? `\n(Did you mean ${formatted[0]}?)`
|
|
138
|
-
: `\n(Did you mean one of ${formatted.join(', ')}?)`;
|
|
139
|
-
}
|
|
140
|
-
function asInternal(cmd) {
|
|
141
|
-
return cmd;
|
|
142
|
-
}
|
|
143
|
-
/** 沿命令树向上收集所有可用长选项(含 --help),供拼写建议使用。 */
|
|
144
|
-
function collectLongFlags(start) {
|
|
145
|
-
const candidateFlags = [];
|
|
146
|
-
for (let cur = start; cur; cur = cur.parent ? asInternal(cur.parent) : null) {
|
|
147
|
-
for (const opt of cur.options ?? []) {
|
|
148
|
-
if (opt.long)
|
|
149
|
-
candidateFlags.push(opt.long);
|
|
150
|
-
}
|
|
151
|
-
candidateFlags.push('--help');
|
|
152
|
-
}
|
|
153
|
-
return candidateFlags;
|
|
154
|
-
}
|
|
155
|
-
function patchSmartSuggester(cmd) {
|
|
156
|
-
const target = asInternal(cmd);
|
|
157
|
-
const original = target.unknownOption.bind(target);
|
|
158
|
-
target.unknownOption = function patched(flag) {
|
|
159
|
-
if (this._allowUnknownOption)
|
|
160
|
-
return;
|
|
161
|
-
if (!flag.startsWith('--'))
|
|
162
|
-
return original(flag);
|
|
163
|
-
const suggestion = smartFlagSuggest(flag, collectLongFlags(this));
|
|
164
|
-
if (!suggestion)
|
|
165
|
-
return original(flag);
|
|
166
|
-
this.error(`error: unknown option '${flag}'${suggestion}`, {
|
|
167
|
-
code: 'commander.unknownOption',
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Apply commander's "did you mean" affordances to every command in the tree.
|
|
173
|
-
*
|
|
174
|
-
* commander 13 stores `_showSuggestionAfterError` and `_showHelpAfterError`
|
|
175
|
-
* per-Command and **does not** inherit them from the parent. So setting them
|
|
176
|
-
* only on the root program would leave nested subcommands
|
|
177
|
-
* dynamic leaves, …) printing the bare `unknown option '--model'` without any
|
|
178
|
-
* hint. We walk the registered tree once, after every subcommand has been
|
|
179
|
-
* attached (built-ins + dynamic), and turn both behaviours on. We also patch
|
|
180
|
-
* each command's `unknownOption` with the smarter suggester (see above).
|
|
181
|
-
*
|
|
182
|
-
* The tree walk is intentionally idempotent: calling `showSuggestionAfterError`
|
|
183
|
-
* twice is a no-op, so re-running this after late additions stays safe.
|
|
184
|
-
*/
|
|
185
|
-
function applyErrorAffordances(root) {
|
|
186
|
-
const stack = [root];
|
|
187
|
-
while (stack.length) {
|
|
188
|
-
const node = stack.pop();
|
|
189
|
-
if (!node)
|
|
190
|
-
continue;
|
|
191
|
-
node.showSuggestionAfterError(true);
|
|
192
|
-
node.showHelpAfterError('(use -h or --help for usage)');
|
|
193
|
-
patchSmartSuggester(node);
|
|
194
|
-
stack.push(...node.commands);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// Exposed for unit tests; keeping the implementation co-located with the
|
|
198
|
-
// command-tree walker so the heuristic can evolve next to the integration.
|
|
199
|
-
export { smartFlagSuggest };
|
|
200
|
-
async function main() {
|
|
201
|
-
const program = new Command(CLI_BIN)
|
|
202
|
-
.version(PKG_VERSION, '-V, --version')
|
|
203
|
-
.description('VPS business CLI for enterprise tenants, employees and partners.')
|
|
204
|
-
.option('--base-url <url>', 'Override Vertu service URL for shortcuts and auth')
|
|
205
|
-
.addHelpText('beforeAll', buildCliRootHelpBefore())
|
|
206
|
-
.addHelpText('after', buildCliRootHelpAfter());
|
|
207
|
-
program.addCommand(buildCliInstallCommand());
|
|
208
|
-
program.addCommand(buildAuthCommand());
|
|
209
|
-
program.addCommand(buildAgentCommand());
|
|
210
|
-
program.addCommand(buildSalesCommand());
|
|
211
|
-
program.addCommand(buildStoreCommand());
|
|
212
|
-
program.addCommand(buildHrCommand());
|
|
213
|
-
program.addCommand(buildProductCommand());
|
|
214
|
-
program.addCommand(buildPurchaseCommand());
|
|
215
|
-
program.addCommand(buildApprovalCommand());
|
|
216
|
-
program.addCommand(buildOkrCommand());
|
|
217
|
-
program.addCommand(buildReportCommand());
|
|
218
|
-
program.addCommand(buildAttendanceCommand());
|
|
219
|
-
program.addCommand(buildTaskCommand());
|
|
220
|
-
program.addCommand(buildToolsCommand());
|
|
221
|
-
program.addCommand(buildImCommand());
|
|
222
|
-
program.addCommand(buildMailCommand());
|
|
223
|
-
program.addCommand(buildVemoryCommand());
|
|
224
|
-
program.addCommand(buildMeetingCommand());
|
|
225
|
-
program.addCommand(buildCalendarCommand());
|
|
226
|
-
program.addCommand(buildCreativeCommand());
|
|
227
|
-
program.addCommand(buildCorporateCommand());
|
|
228
|
-
program.addCommand(buildDocumentsCommand());
|
|
229
|
-
program.addCommand(buildOfficeCommand());
|
|
230
|
-
program.addCommand(buildKnowledgeCommand());
|
|
231
|
-
program.addCommand(buildDriveCommand());
|
|
232
|
-
program.addCommand(buildPopupCommand());
|
|
233
|
-
program.addCommand(buildCatalogCommand());
|
|
234
|
-
program.addCommand(buildExecCommand());
|
|
235
|
-
program.addCommand(buildVertuErpCommand());
|
|
236
|
-
program.addCommand(buildVertuAiSalesCommand());
|
|
237
|
-
program.addCommand(buildSkillCommand());
|
|
238
|
-
// Accept snake_case long flags (e.g. `--start_date`) as aliases of the
|
|
239
|
-
// canonical kebab-case form. server-side `parameters_schema` keys are
|
|
240
|
-
// snake_case, so users / AI agents reading the schema often type that form
|
|
241
|
-
// first. Normalizing once at the entry point keeps commander unaware of the
|
|
242
|
-
// alternate spelling and avoids touching every dynamic option registration.
|
|
243
|
-
const userArgv = normalizeSnakeFlags(process.argv.slice(2));
|
|
244
|
-
const fullArgv = [...process.argv.slice(0, 2), ...userArgv];
|
|
245
|
-
// Apply after built-in commands are registered, so the
|
|
246
|
-
// walker covers the whole tree in one pass.
|
|
247
|
-
applyErrorAffordances(program);
|
|
248
|
-
try {
|
|
249
|
-
await program.parseAsync(fullArgv);
|
|
250
|
-
}
|
|
251
|
-
catch (err) {
|
|
252
|
-
handleError(err);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function handleError(err) {
|
|
256
|
-
process.stderr.write(`${formatErrorForCli(err)}\n`);
|
|
257
|
-
if (err instanceof VertuError) {
|
|
258
|
-
process.exit(err.exitCode);
|
|
259
|
-
}
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
function isCliEntryPoint() {
|
|
263
|
-
const entry = process.argv[1] ?? '';
|
|
264
|
-
return (entry.includes('vps-work.ts') ||
|
|
265
|
-
entry.includes('vps-work.cjs') ||
|
|
266
|
-
entry.endsWith(`${CLI_BIN}`) ||
|
|
267
|
-
CLI_BIN_ALIASES.some((alias) => entry.endsWith(alias)));
|
|
268
|
-
}
|
|
269
|
-
if (isCliEntryPoint()) {
|
|
270
|
-
void main().catch(handleError);
|
|
271
|
-
}
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { VertuError } from '../../core/errors.js';
|
|
5
|
-
import { DEFAULT_AGENT_APP_ID, formatAgentEnvExports, VERTU_APP_ID_ENV, VERTU_APP_KEY_ENV, } from '../../core/vertu-agent-env.js';
|
|
6
|
-
import { bindVpsServiceAgentCredential, loadStoredAgentKey, persistVpsServiceAgentCredential, } from '../../core/vps-service-auth.js';
|
|
7
|
-
import { readVpsServiceConfig } from '../../core/vps-service-config.js';
|
|
8
|
-
import { resolveVpsServiceUrl } from '../../core/sales-query-client.js';
|
|
9
|
-
import { promptText } from '../../utils/prompt.js';
|
|
10
|
-
import { expandAtFileToken } from '../../utils/at-file.js';
|
|
11
|
-
import { imRequest } from '../../core/im-client.js';
|
|
12
|
-
const HARNESS_COLLABORATION_KIND = 'vps-harness-agent-collaboration';
|
|
13
|
-
function isVpsSessionExpired(expiresAt) {
|
|
14
|
-
if (!expiresAt) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
const parsed = Date.parse(expiresAt);
|
|
18
|
-
return !Number.isFinite(parsed) || parsed <= Date.now() + 30_000;
|
|
19
|
-
}
|
|
20
|
-
function requiredUserId(raw) {
|
|
21
|
-
const value = Number(raw);
|
|
22
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
23
|
-
throw new VertuError('--user-id 必须是正整数。');
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
export function collaborationTurnFromHistory(out, requestId) {
|
|
28
|
-
if (!out || typeof out !== 'object' || !('messages' in out))
|
|
29
|
-
return null;
|
|
30
|
-
const messages = out.messages;
|
|
31
|
-
if (!Array.isArray(messages))
|
|
32
|
-
return null;
|
|
33
|
-
for (const value of [...messages].reverse()) {
|
|
34
|
-
if (!value || typeof value !== 'object')
|
|
35
|
-
continue;
|
|
36
|
-
const message = value;
|
|
37
|
-
const metadata = message.metadata;
|
|
38
|
-
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))
|
|
39
|
-
continue;
|
|
40
|
-
const envelope = metadata[HARNESS_COLLABORATION_KIND];
|
|
41
|
-
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope))
|
|
42
|
-
continue;
|
|
43
|
-
const record = envelope;
|
|
44
|
-
const turn = Number(record.turn);
|
|
45
|
-
if (record.request_id !== requestId
|
|
46
|
-
|| (record.type !== 'request' && record.type !== 'response')
|
|
47
|
-
|| !Number.isInteger(turn)
|
|
48
|
-
|| turn <= 0)
|
|
49
|
-
continue;
|
|
50
|
-
return {
|
|
51
|
-
body: typeof message.body === 'string' ? message.body : '',
|
|
52
|
-
messageId: typeof message.id === 'string' ? message.id : '',
|
|
53
|
-
turn,
|
|
54
|
-
type: record.type,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
export function buildAgentCommand() {
|
|
60
|
-
const cmd = new Command('agent').description('Agent host credentials (app_id + app_key) after human sign-in.');
|
|
61
|
-
cmd
|
|
62
|
-
.command('bind')
|
|
63
|
-
.description('用当前 Vertu 会话为指定 app_id 签发/轮换 app_key')
|
|
64
|
-
.option('--base-url <url>', 'Vertu 服务地址')
|
|
65
|
-
.option('--app-id <id>', `可信应用 ID(默认 ${DEFAULT_AGENT_APP_ID},可用环境变量 ${VERTU_APP_ID_ENV})`)
|
|
66
|
-
.action(async (opts, command) => {
|
|
67
|
-
const merged = command.optsWithGlobals();
|
|
68
|
-
const cfg = await readVpsServiceConfig();
|
|
69
|
-
const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
|
|
70
|
-
const sessionToken = cfg.sessionToken?.trim();
|
|
71
|
-
if (!sessionToken || isVpsSessionExpired(cfg.expiresAt)) {
|
|
72
|
-
throw new VertuError('签发 app_key 需要人工登录会话;Agent 不得自行执行账号密码登录。');
|
|
73
|
-
}
|
|
74
|
-
const appId = opts.appId?.trim() ??
|
|
75
|
-
merged.appId?.trim() ??
|
|
76
|
-
process.env[VERTU_APP_ID_ENV]?.trim() ??
|
|
77
|
-
cfg.agentAppId ??
|
|
78
|
-
(await promptText('Agent app_id', DEFAULT_AGENT_APP_ID));
|
|
79
|
-
const bound = await bindVpsServiceAgentCredential(serviceUrl, sessionToken, appId);
|
|
80
|
-
await persistVpsServiceAgentCredential(serviceUrl, bound);
|
|
81
|
-
console.log(`已绑定 Agent:app_id=${bound.appId},login=${bound.login}`);
|
|
82
|
-
console.log(`过期:${bound.expiresAt ?? '长效(永不过期)'}`);
|
|
83
|
-
console.log('宿主环境:执行 `vps-work agent env` 查看 export 语句');
|
|
84
|
-
});
|
|
85
|
-
cmd
|
|
86
|
-
.command('env')
|
|
87
|
-
.description('输出 Agent 宿主应注入的环境变量(export / $env:)')
|
|
88
|
-
.option('--base-url <url>', 'Vertu 服务地址')
|
|
89
|
-
.option('--app-id <id>', '应用 ID(默认读配置或 cursor)')
|
|
90
|
-
.option('--shell <name>', 'bash | powershell', 'bash')
|
|
91
|
-
.action(async (opts, command) => {
|
|
92
|
-
const merged = command.optsWithGlobals();
|
|
93
|
-
const cfg = await readVpsServiceConfig();
|
|
94
|
-
const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
|
|
95
|
-
const appId = opts.appId?.trim() ??
|
|
96
|
-
merged.appId?.trim() ??
|
|
97
|
-
cfg.agentAppId ??
|
|
98
|
-
DEFAULT_AGENT_APP_ID;
|
|
99
|
-
const login = cfg.login?.trim();
|
|
100
|
-
if (!login) {
|
|
101
|
-
throw new VertuError('未找到宿主注入的 Agent 账号;Agent 不得自行执行账号密码登录。');
|
|
102
|
-
}
|
|
103
|
-
const appKey = process.env[VERTU_APP_KEY_ENV]?.trim() ??
|
|
104
|
-
(await loadStoredAgentKey(serviceUrl, appId));
|
|
105
|
-
if (!appKey) {
|
|
106
|
-
throw new VertuError('无 app_key。请由宿主或人工管理员完成绑定;Agent 不得自行执行账号密码登录。');
|
|
107
|
-
}
|
|
108
|
-
const shell = opts.shell ?? merged.shell ?? 'bash';
|
|
109
|
-
if (shell === 'powershell') {
|
|
110
|
-
const esc = (s) => s.replace(/'/g, "''");
|
|
111
|
-
console.log(`$env:VERTU_VPS_SERVICE_URL='${esc(serviceUrl)}'`);
|
|
112
|
-
console.log(`$env:VERTU_APP_ID='${esc(appId)}'`);
|
|
113
|
-
console.log(`$env:VERTU_APP_KEY='${esc(appKey)}'`);
|
|
114
|
-
console.log(`$env:VERTU_USER_LOGIN='${esc(login)}'`);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
process.stdout.write(formatAgentEnvExports(serviceUrl, appId, appKey, login));
|
|
118
|
-
});
|
|
119
|
-
cmd
|
|
120
|
-
.command('ask')
|
|
121
|
-
.description('向另一位员工的 VPS Harness Agent 发起请求并等待答复')
|
|
122
|
-
.requiredOption('--user-id <id>', '对方 user_id')
|
|
123
|
-
.requiredOption('--message <text|@file>', '请求内容;长内容可使用 @file / @-')
|
|
124
|
-
.option('--base-url <url>', 'Vertu 服务地址')
|
|
125
|
-
.option('--stop-at <datetime>', '循环协作截止时间(ISO 8601 或本地 YYYY-MM-DDTHH:mm)')
|
|
126
|
-
.option('--duration-minutes <n>', '从现在起循环协作多少分钟(与 --stop-at 二选一)')
|
|
127
|
-
.option('--timeout <seconds>', '等待对方首轮答复的秒数', '180')
|
|
128
|
-
.option('--no-wait', '只发送请求,不等待答复')
|
|
129
|
-
.action(async (opts, command) => {
|
|
130
|
-
const merged = command.optsWithGlobals();
|
|
131
|
-
const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
|
|
132
|
-
const userId = requiredUserId(opts.userId);
|
|
133
|
-
const message = (await expandAtFileToken(opts.message ?? '', '--message')).trim();
|
|
134
|
-
if (!message)
|
|
135
|
-
throw new VertuError('--message 不能为空。');
|
|
136
|
-
if (message.length > 20_000)
|
|
137
|
-
throw new VertuError('--message 不能超过 20000 个字符。');
|
|
138
|
-
if (opts.stopAt && opts.durationMinutes) {
|
|
139
|
-
throw new VertuError('--stop-at 与 --duration-minutes 只能使用一个。');
|
|
140
|
-
}
|
|
141
|
-
let stopAt;
|
|
142
|
-
if (opts.stopAt) {
|
|
143
|
-
const parsed = Date.parse(opts.stopAt);
|
|
144
|
-
if (!Number.isFinite(parsed) || parsed <= Date.now()) {
|
|
145
|
-
throw new VertuError('--stop-at 必须是未来的有效日期时间。');
|
|
146
|
-
}
|
|
147
|
-
stopAt = new Date(parsed).toISOString();
|
|
148
|
-
}
|
|
149
|
-
else if (opts.durationMinutes) {
|
|
150
|
-
const minutes = Number(opts.durationMinutes);
|
|
151
|
-
if (!Number.isFinite(minutes) || minutes <= 0 || minutes > 1440) {
|
|
152
|
-
throw new VertuError('--duration-minutes 必须大于 0 且不超过 1440 分钟。');
|
|
153
|
-
}
|
|
154
|
-
stopAt = new Date(Date.now() + minutes * 60_000).toISOString();
|
|
155
|
-
}
|
|
156
|
-
const timeoutSeconds = Number(opts.timeout);
|
|
157
|
-
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 1800) {
|
|
158
|
-
throw new VertuError('--timeout 必须在 1 到 1800 秒之间。');
|
|
159
|
-
}
|
|
160
|
-
const requestId = randomUUID();
|
|
161
|
-
const sent = await imRequest('POST', '/v1/im/user-robots/harness/send', {
|
|
162
|
-
baseUrl: serviceUrl,
|
|
163
|
-
body: {
|
|
164
|
-
body: message,
|
|
165
|
-
idempotency_key: `vps-harness-request-${requestId}`,
|
|
166
|
-
message_type: 'text',
|
|
167
|
-
target_user_id: userId,
|
|
168
|
-
metadata: {
|
|
169
|
-
[HARNESS_COLLABORATION_KIND]: {
|
|
170
|
-
objective: message,
|
|
171
|
-
protocol: 1,
|
|
172
|
-
request_id: requestId,
|
|
173
|
-
...(stopAt ? { stop_at: stopAt } : {}),
|
|
174
|
-
turn: 0,
|
|
175
|
-
type: 'request',
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
const channelId = extractAgentAskChannelId(sent);
|
|
181
|
-
if (opts.wait === false) {
|
|
182
|
-
console.log(JSON.stringify({ channel_id: channelId, request_id: requestId, status: 'sent' }, null, 2));
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
186
|
-
while (Date.now() < deadline) {
|
|
187
|
-
await sleep(1_000);
|
|
188
|
-
const history = await imRequest('GET', `/v1/im/channels/${encodeURIComponent(channelId)}/messages`, { baseUrl: serviceUrl, query: { limit: 50 } });
|
|
189
|
-
const response = collaborationTurnFromHistory(history, requestId);
|
|
190
|
-
if (response) {
|
|
191
|
-
console.log(JSON.stringify({
|
|
192
|
-
answer: response.body,
|
|
193
|
-
channel_id: channelId,
|
|
194
|
-
message_id: response.messageId,
|
|
195
|
-
request_id: requestId,
|
|
196
|
-
status: response.type === 'request' ? 'continuing' : 'completed',
|
|
197
|
-
turn: response.turn,
|
|
198
|
-
}, null, 2));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
throw new VertuError(`等待员工 VPS Harness Agent 答复超时(${timeoutSeconds} 秒)。请确认对方在线且已开启 Agent 联系开关。`);
|
|
203
|
-
});
|
|
204
|
-
cmd
|
|
205
|
-
.command('message-user')
|
|
206
|
-
.description('以我的 VPS Harness Agent 身份给员工发起私聊')
|
|
207
|
-
.requiredOption('--user-id <id>', '对方 user_id')
|
|
208
|
-
.requiredOption('--message <text|@file>', '消息内容;长内容可使用 @file / @-')
|
|
209
|
-
.option('--base-url <url>', 'Vertu 服务地址')
|
|
210
|
-
.action(async (opts, command) => {
|
|
211
|
-
const merged = command.optsWithGlobals();
|
|
212
|
-
const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
|
|
213
|
-
const userId = requiredUserId(opts.userId);
|
|
214
|
-
const message = (await expandAtFileToken(opts.message ?? '', '--message')).trim();
|
|
215
|
-
if (!message)
|
|
216
|
-
throw new VertuError('--message 不能为空。');
|
|
217
|
-
if (message.length > 20_000)
|
|
218
|
-
throw new VertuError('--message 不能超过 20000 个字符。');
|
|
219
|
-
const requestId = randomUUID();
|
|
220
|
-
const sent = await imRequest('POST', '/v1/im/user-robots/harness/message-user', {
|
|
221
|
-
baseUrl: serviceUrl,
|
|
222
|
-
body: {
|
|
223
|
-
body: message,
|
|
224
|
-
idempotency_key: `vps-harness-human-${requestId}`,
|
|
225
|
-
message_type: 'text',
|
|
226
|
-
target_user_id: userId,
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
console.log(JSON.stringify({
|
|
230
|
-
channel_id: extractAgentAskChannelId(sent),
|
|
231
|
-
message_id: extractHarnessMessageId(sent),
|
|
232
|
-
request_id: requestId,
|
|
233
|
-
status: 'sent',
|
|
234
|
-
}, null, 2));
|
|
235
|
-
});
|
|
236
|
-
return cmd;
|
|
237
|
-
}
|
|
238
|
-
function extractHarnessMessageId(out) {
|
|
239
|
-
if (!out || typeof out !== 'object' || !('message' in out))
|
|
240
|
-
return null;
|
|
241
|
-
const message = out.message;
|
|
242
|
-
if (!message || typeof message !== 'object' || !('id' in message))
|
|
243
|
-
return null;
|
|
244
|
-
const id = message.id;
|
|
245
|
-
return typeof id === 'string' && id ? id : null;
|
|
246
|
-
}
|
|
247
|
-
function extractAgentAskChannelId(out) {
|
|
248
|
-
if (out && typeof out === 'object' && 'channel_id' in out) {
|
|
249
|
-
const id = out.channel_id;
|
|
250
|
-
if (typeof id === 'string' && id)
|
|
251
|
-
return id;
|
|
252
|
-
}
|
|
253
|
-
if (!out || typeof out !== 'object' || !('channel' in out)) {
|
|
254
|
-
throw new VertuError('Vertu IM 未返回 channel_id。');
|
|
255
|
-
}
|
|
256
|
-
const channel = out.channel;
|
|
257
|
-
if (!channel || typeof channel !== 'object' || !('id' in channel)) {
|
|
258
|
-
throw new VertuError('Vertu IM 未返回 channel.id。');
|
|
259
|
-
}
|
|
260
|
-
const id = channel.id;
|
|
261
|
-
if (typeof id !== 'string' || !id)
|
|
262
|
-
throw new VertuError('Vertu IM 返回了无效的 channel.id。');
|
|
263
|
-
return id;
|
|
264
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { attachApprovalShortcuts } from './shortcuts.js';
|
|
3
|
-
export function buildApprovalCommand() {
|
|
4
|
-
const approval = new Command('approval').description('Approval shortcuts: pending queue, approval history, pass/reject guidance.');
|
|
5
|
-
attachApprovalShortcuts(approval);
|
|
6
|
-
return approval;
|
|
7
|
-
}
|