vps-work 2.5.0 → 2.5.1

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.
Files changed (119) hide show
  1. package/dist/packages/vps-work/src/bin/vps-work.js +271 -0
  2. package/dist/packages/vps-work/src/commands/agent/index.js +264 -0
  3. package/dist/packages/vps-work/src/commands/approval/index.js +7 -0
  4. package/dist/packages/vps-work/src/commands/approval/shortcuts.js +259 -0
  5. package/dist/packages/vps-work/src/commands/attendance/applications.js +21 -0
  6. package/dist/packages/vps-work/src/commands/attendance/index.js +9 -0
  7. package/dist/packages/vps-work/src/commands/attendance/scheduling.js +183 -0
  8. package/dist/packages/vps-work/src/commands/attendance/shortcuts.js +131 -0
  9. package/dist/packages/vps-work/src/commands/auth/check.js +34 -0
  10. package/dist/packages/vps-work/src/commands/auth/index.js +21 -0
  11. package/dist/packages/vps-work/src/commands/auth/list.js +50 -0
  12. package/dist/packages/vps-work/src/commands/auth/scopes.js +17 -0
  13. package/dist/packages/vps-work/src/commands/auth/status.js +127 -0
  14. package/dist/packages/vps-work/src/commands/auth/tenants.js +35 -0
  15. package/dist/packages/vps-work/src/commands/auth/use.js +40 -0
  16. package/dist/packages/vps-work/src/commands/calendar/index.js +7 -0
  17. package/dist/packages/vps-work/src/commands/calendar/shortcuts.js +598 -0
  18. package/dist/packages/vps-work/src/commands/catalog.js +29 -0
  19. package/dist/packages/vps-work/src/commands/cli-install.js +70 -0
  20. package/dist/packages/vps-work/src/commands/collab-invoke.js +19 -0
  21. package/dist/packages/vps-work/src/commands/corporate/index.js +204 -0
  22. package/dist/packages/vps-work/src/commands/creative/index.js +369 -0
  23. package/dist/packages/vps-work/src/commands/documents/index.js +991 -0
  24. package/dist/packages/vps-work/src/commands/drive/index.js +435 -0
  25. package/dist/packages/vps-work/src/commands/exec.js +46 -0
  26. package/dist/packages/vps-work/src/commands/hr/business.js +22 -0
  27. package/dist/packages/vps-work/src/commands/hr/data-view-shortcuts.js +519 -0
  28. package/dist/packages/vps-work/src/commands/hr/index.js +15 -0
  29. package/dist/packages/vps-work/src/commands/hr/job-responsibilities.js +266 -0
  30. package/dist/packages/vps-work/src/commands/hr/payslip.js +45 -0
  31. package/dist/packages/vps-work/src/commands/hr/shortcuts.js +146 -0
  32. package/dist/packages/vps-work/src/commands/im/im-message-payload.js +150 -0
  33. package/dist/packages/vps-work/src/commands/im/index.js +7 -0
  34. package/dist/packages/vps-work/src/commands/im/shortcuts.js +856 -0
  35. package/dist/packages/vps-work/src/commands/knowledge/index.js +36 -0
  36. package/dist/packages/vps-work/src/commands/login.js +52 -0
  37. package/dist/packages/vps-work/src/commands/logout.js +12 -0
  38. package/dist/packages/vps-work/src/commands/mail/email.js +132 -0
  39. package/dist/packages/vps-work/src/commands/mail/index.js +9 -0
  40. package/dist/packages/vps-work/src/commands/mail/shortcuts.js +43 -0
  41. package/dist/packages/vps-work/src/commands/meeting/index.js +7 -0
  42. package/dist/packages/vps-work/src/commands/meeting/shortcuts.js +120 -0
  43. package/dist/packages/vps-work/src/commands/odoo-invoke.js +21 -0
  44. package/dist/packages/vps-work/src/commands/odoo-leaf-registry.js +13 -0
  45. package/dist/packages/vps-work/src/commands/office/index.js +59 -0
  46. package/dist/packages/vps-work/src/commands/okr/index.js +7 -0
  47. package/dist/packages/vps-work/src/commands/okr/shortcuts.js +75 -0
  48. package/dist/packages/vps-work/src/commands/org-structure.js +347 -0
  49. package/dist/packages/vps-work/src/commands/popup/index.js +147 -0
  50. package/dist/packages/vps-work/src/commands/product/abnormal-statistics.js +250 -0
  51. package/dist/packages/vps-work/src/commands/product/data-view-shortcuts.js +253 -0
  52. package/dist/packages/vps-work/src/commands/product/index.js +11 -0
  53. package/dist/packages/vps-work/src/commands/purchase/index.js +45 -0
  54. package/dist/packages/vps-work/src/commands/reauth.js +127 -0
  55. package/dist/packages/vps-work/src/commands/report/index.js +7 -0
  56. package/dist/packages/vps-work/src/commands/report/missing-daily-reports.js +9 -0
  57. package/dist/packages/vps-work/src/commands/report/shortcuts.js +251 -0
  58. package/dist/packages/vps-work/src/commands/sales/index.js +11 -0
  59. package/dist/packages/vps-work/src/commands/sales/period-params.js +75 -0
  60. package/dist/packages/vps-work/src/commands/sales/shortcuts-personnel.js +124 -0
  61. package/dist/packages/vps-work/src/commands/sales/shortcuts.js +800 -0
  62. package/dist/packages/vps-work/src/commands/shortcut-common.js +88 -0
  63. package/dist/packages/vps-work/src/commands/shortcut-params.js +22 -0
  64. package/dist/packages/vps-work/src/commands/skill/index.js +136 -0
  65. package/dist/packages/vps-work/src/commands/skill/personal.js +167 -0
  66. package/dist/packages/vps-work/src/commands/store/index.js +82 -0
  67. package/dist/packages/vps-work/src/commands/task/index.js +7 -0
  68. package/dist/packages/vps-work/src/commands/task/task-center.js +595 -0
  69. package/dist/packages/vps-work/src/commands/tools/index.js +127 -0
  70. package/dist/packages/vps-work/src/commands/vemory/index.js +7 -0
  71. package/dist/packages/vps-work/src/commands/vemory/shortcuts.js +53 -0
  72. package/dist/packages/vps-work/src/commands/vertu-aisales.js +30 -0
  73. package/dist/packages/vps-work/src/commands/vertu-erp.js +30 -0
  74. package/dist/packages/vps-work/src/core/aisales-mcp-client.js +35 -0
  75. package/dist/packages/vps-work/src/core/auth.js +240 -0
  76. package/dist/packages/vps-work/src/core/cli-bin.js +11 -0
  77. package/dist/packages/vps-work/src/core/cli-root-help.js +190 -0
  78. package/dist/packages/vps-work/src/core/client.js +158 -0
  79. package/dist/packages/vps-work/src/core/config.js +183 -0
  80. package/dist/packages/vps-work/src/core/corporate-client.js +52 -0
  81. package/dist/packages/vps-work/src/core/creative-attachment-download.js +48 -0
  82. package/dist/packages/vps-work/src/core/documents-client.js +57 -0
  83. package/dist/packages/vps-work/src/core/endpoints.js +46 -0
  84. package/dist/packages/vps-work/src/core/erp-mcp-client.js +11 -0
  85. package/dist/packages/vps-work/src/core/errors.js +107 -0
  86. package/dist/packages/vps-work/src/core/execute-catalog-client.js +42 -0
  87. package/dist/packages/vps-work/src/core/hermes-client.js +70 -0
  88. package/dist/packages/vps-work/src/core/im-attachment-download.js +109 -0
  89. package/dist/packages/vps-work/src/core/im-client.js +64 -0
  90. package/dist/packages/vps-work/src/core/keychain.js +58 -0
  91. package/dist/packages/vps-work/src/core/mail-client.js +82 -0
  92. package/dist/packages/vps-work/src/core/mcp-client.js +129 -0
  93. package/dist/packages/vps-work/src/core/office-client.js +25 -0
  94. package/dist/packages/vps-work/src/core/office-files.js +82 -0
  95. package/dist/packages/vps-work/src/core/paths.js +70 -0
  96. package/dist/packages/vps-work/src/core/public-result.js +43 -0
  97. package/dist/packages/vps-work/src/core/sales-invoice-attachments-client.js +75 -0
  98. package/dist/packages/vps-work/src/core/sales-query-client.js +452 -0
  99. package/dist/packages/vps-work/src/core/skill-catalog.js +158 -0
  100. package/dist/packages/vps-work/src/core/skill-key.js +19 -0
  101. package/dist/packages/vps-work/src/core/v-drive-store.js +392 -0
  102. package/dist/packages/vps-work/src/core/vertu-agent-env.js +32 -0
  103. package/dist/packages/vps-work/src/core/vps-auth-scopes.js +73 -0
  104. package/dist/packages/vps-work/src/core/vps-service-auth.js +382 -0
  105. package/dist/packages/vps-work/src/core/vps-service-config.js +83 -0
  106. package/dist/packages/vps-work/src/core/vps-service-http.js +187 -0
  107. package/dist/packages/vps-work/src/utils/at-file.js +34 -0
  108. package/dist/packages/vps-work/src/utils/cli-option-helpers.js +52 -0
  109. package/dist/packages/vps-work/src/utils/frontmatter.js +26 -0
  110. package/dist/packages/vps-work/src/utils/normalize-argv.js +51 -0
  111. package/dist/packages/vps-work/src/utils/parse-json-arg.js +85 -0
  112. package/dist/packages/vps-work/src/utils/prompt.js +46 -0
  113. package/dist/packages/vps-work/src/utils/read-text.js +44 -0
  114. package/dist/packages/vps-work/src/utils/schema-to-options.js +307 -0
  115. package/dist/packages/vps-work/src/utils/unwrap-envelope.js +31 -0
  116. package/dist/shared/im-org-tree/index.js +200 -0
  117. package/dist/vps-work.cjs +3 -3
  118. package/package.json +1 -1
  119. package/skills/vertu-aisales/SKILL.md +4 -2
@@ -0,0 +1,271 @@
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
+ }
@@ -0,0 +1,264 @@
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
+ }
@@ -0,0 +1,7 @@
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
+ }