codewave-openclaw-installer 2.2.3 → 2.3.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/README.md +10 -8
- package/bin/check.mjs +26 -9
- package/bin/install-lib.mjs +61 -2
- package/bin/install.mjs +237 -77
- package/package.json +2 -1
- package/pkg/lcap-delivery-razel-cli-0.0.4.tgz +0 -0
- package/pkg/razel_py_cli-0.0.4-py3-none-any.whl +0 -0
- package/skills/acceptance-doc-entry/SKILL.md +70 -5
- package/skills/smart-customer-service/SKILL.md +96 -9
package/README.md
CHANGED
|
@@ -16,17 +16,17 @@ npx codewave-openclaw-installer
|
|
|
16
16
|
|
|
17
17
|
| 工具 | 包管理器 | 说明 |
|
|
18
18
|
|------|---------|------|
|
|
19
|
-
| `razel-py-cli` |
|
|
20
|
-
| `razel-cli` |
|
|
19
|
+
| `razel-py-cli` | 插件私有运行时 | Python CLI(验收文档、音视频转写等) |
|
|
20
|
+
| `razel-cli` | 插件私有运行时 | Node CLI(PoPo 文档、PoPo 机器人、腾讯会议等) |
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
说明:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
|
|
26
|
-
pipx ensurepath
|
|
27
|
-
pipx install razel-py-cli
|
|
25
|
+
npx codewave-openclaw-installer
|
|
28
26
|
```
|
|
29
27
|
|
|
28
|
+
安装器会优先使用仓库 `pkg/` 里的离线包,在 `~/.openclaw/runtime/codewave-openclaw-installer/` 下创建插件私有 Python venv 和 Node 运行目录,并在 `~/.local/bin/` 写入 `razel-py-cli` / `razel-cli` wrapper。
|
|
29
|
+
|
|
30
30
|
### OpenClaw Skills(自动注册)
|
|
31
31
|
|
|
32
32
|
安装后 Skills 作为 OpenClaw Extension 自动加载,无需手动复制:
|
|
@@ -66,7 +66,7 @@ pipx install razel-py-cli
|
|
|
66
66
|
|
|
67
67
|
1. `preflight`:检测本机依赖、配置和渠道线索
|
|
68
68
|
2. `core-install`:安装并初始化 `razel-py-cli` / `razel-cli`
|
|
69
|
-
3. `extension-install
|
|
69
|
+
3. `extension-install`:通过 OpenClaw 官方插件安装流程将本包注册到 `~/.openclaw/extensions/codewave-openclaw-installer/`
|
|
70
70
|
4. `postflight`:重启 OpenClaw Gateway,并运行统一检查输出最终状态
|
|
71
71
|
5. `channel-bootstrap`:检测到飞书或钉钉线索时,按官方入口自动安装并初始化
|
|
72
72
|
|
|
@@ -107,7 +107,9 @@ codewave-check --json
|
|
|
107
107
|
## 卸载
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
rm -rf ~/.openclaw/extensions/codewave-
|
|
110
|
+
rm -rf ~/.openclaw/extensions/codewave-openclaw-installer
|
|
111
|
+
rm -rf ~/.openclaw/runtime/codewave-openclaw-installer
|
|
112
|
+
rm -f ~/.local/bin/razel-py-cli ~/.local/bin/razel-cli
|
|
111
113
|
openclaw gateway restart
|
|
112
114
|
```
|
|
113
115
|
|
package/bin/check.mjs
CHANGED
|
@@ -17,7 +17,9 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { homedir, platform } from 'node:os';
|
|
19
19
|
import {
|
|
20
|
+
CODEWAVE_TOOLKIT_RUNTIME_ROOT,
|
|
20
21
|
CHANNEL_DEFINITIONS,
|
|
22
|
+
getManagedCommandPath,
|
|
21
23
|
detectDesktopAppInstallMatches,
|
|
22
24
|
getDesktopAppCandidates,
|
|
23
25
|
} from './install-lib.mjs';
|
|
@@ -47,6 +49,10 @@ let windowsUninstallDisplayNamesPromise = null;
|
|
|
47
49
|
// ─── 工具函数 ───
|
|
48
50
|
|
|
49
51
|
function commandExists(cmd) {
|
|
52
|
+
const managed = getManagedCommandPath(cmd);
|
|
53
|
+
if (managed && existsSync(managed)) {
|
|
54
|
+
return Promise.resolve(true);
|
|
55
|
+
}
|
|
50
56
|
return new Promise((resolve) => {
|
|
51
57
|
const check = isWin ? 'where' : 'which';
|
|
52
58
|
const child = spawn(check, [cmd], { stdio: 'ignore', shell: isWin });
|
|
@@ -56,8 +62,9 @@ function commandExists(cmd) {
|
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
function getCommandVersion(cmd, args = ['--version']) {
|
|
65
|
+
const executable = (getManagedCommandPath(cmd) && existsSync(getManagedCommandPath(cmd))) ? getManagedCommandPath(cmd) : cmd;
|
|
59
66
|
return new Promise((resolve) => {
|
|
60
|
-
const child = spawn(
|
|
67
|
+
const child = spawn(executable, args, { stdio: ['ignore', 'pipe', 'pipe'], shell: isWin && !existsSync(executable) });
|
|
61
68
|
let stdout = '';
|
|
62
69
|
child.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
63
70
|
child.on('close', (code) => resolve(code === 0 ? stdout.trim().split('\n')[0] : null));
|
|
@@ -90,16 +97,18 @@ function openclawChannelHints() {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
function run(cmd, args) {
|
|
100
|
+
const executable = (getManagedCommandPath(cmd) && existsSync(getManagedCommandPath(cmd))) ? getManagedCommandPath(cmd) : cmd;
|
|
93
101
|
return new Promise((resolve) => {
|
|
94
|
-
const child = spawn(
|
|
102
|
+
const child = spawn(executable, args, { stdio: 'inherit', shell: isWin && !existsSync(executable) });
|
|
95
103
|
child.on('close', (code) => resolve(code ?? 1));
|
|
96
104
|
child.on('error', () => resolve(1));
|
|
97
105
|
});
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
function runCapture(cmd, args) {
|
|
109
|
+
const executable = (getManagedCommandPath(cmd) && existsSync(getManagedCommandPath(cmd))) ? getManagedCommandPath(cmd) : cmd;
|
|
101
110
|
return new Promise((resolve) => {
|
|
102
|
-
const child = spawn(
|
|
111
|
+
const child = spawn(executable, args, { stdio: ['ignore', 'pipe', 'pipe'], shell: isWin && !existsSync(executable) });
|
|
103
112
|
let stdout = '';
|
|
104
113
|
let stderr = '';
|
|
105
114
|
child.stdout?.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
@@ -202,9 +211,17 @@ async function checkOpenClawConfig() {
|
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
async function checkPythonDocx() {
|
|
214
|
+
const managedPython = join(
|
|
215
|
+
CODEWAVE_TOOLKIT_RUNTIME_ROOT,
|
|
216
|
+
'py',
|
|
217
|
+
isWin ? 'Scripts' : 'bin',
|
|
218
|
+
isWin ? 'python.exe' : 'python3',
|
|
219
|
+
);
|
|
220
|
+
const pythonExecutable = existsSync(managedPython) ? managedPython : 'python3';
|
|
205
221
|
return new Promise((resolve) => {
|
|
206
|
-
const child = spawn(
|
|
222
|
+
const child = spawn(pythonExecutable, ['-c', 'import docx; print(docx.__version__)'], {
|
|
207
223
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
224
|
+
shell: isWin && !existsSync(pythonExecutable),
|
|
208
225
|
});
|
|
209
226
|
let stdout = '';
|
|
210
227
|
child.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
@@ -216,12 +233,12 @@ async function checkPythonDocx() {
|
|
|
216
233
|
name: 'python-docx',
|
|
217
234
|
status: 'warn',
|
|
218
235
|
message: '未安装(DOCX 解析的 fallback 方案之一)',
|
|
219
|
-
fix: '
|
|
236
|
+
fix: '重新运行 npx codewave-openclaw-installer,或手动补齐 python-docx',
|
|
220
237
|
});
|
|
221
238
|
}
|
|
222
239
|
});
|
|
223
240
|
child.on('error', () => {
|
|
224
|
-
resolve({ name: 'python-docx', status: 'warn', message: '
|
|
241
|
+
resolve({ name: 'python-docx', status: 'warn', message: 'Python 运行时不可用' });
|
|
225
242
|
});
|
|
226
243
|
});
|
|
227
244
|
}
|
|
@@ -243,7 +260,7 @@ async function checkDocxPipeline() {
|
|
|
243
260
|
name: 'DOCX 解析工具链',
|
|
244
261
|
status: 'fail',
|
|
245
262
|
message: '缺少所有 DOCX 解析方案 (pandoc / textutil / python-docx)',
|
|
246
|
-
fix: isMac ? 'brew install pandoc' : isLinux ? 'sudo apt install pandoc' : '
|
|
263
|
+
fix: isMac ? 'brew install pandoc,或重新运行 npx codewave-openclaw-installer' : isLinux ? 'sudo apt install pandoc,或重新运行 npx codewave-openclaw-installer' : '重新运行 npx codewave-openclaw-installer',
|
|
247
264
|
};
|
|
248
265
|
}
|
|
249
266
|
return {
|
|
@@ -433,10 +450,10 @@ async function main() {
|
|
|
433
450
|
|
|
434
451
|
// CLI 工具
|
|
435
452
|
checks['razel-py-cli'] = await checkCli('razel-py-cli', {
|
|
436
|
-
installCmd: '
|
|
453
|
+
installCmd: '重新运行 npx codewave-openclaw-installer(将优先安装插件私有 razel-py-cli 运行时)',
|
|
437
454
|
});
|
|
438
455
|
checks['razel-cli'] = await checkCli('razel-cli', {
|
|
439
|
-
installCmd: '
|
|
456
|
+
installCmd: '重新运行 npx codewave-openclaw-installer(将优先安装插件私有 razel-cli 运行时)',
|
|
440
457
|
});
|
|
441
458
|
checks['jq'] = await checkCli('jq', {
|
|
442
459
|
installCmd: isMac ? 'brew install jq' : 'sudo apt install jq',
|
package/bin/install-lib.mjs
CHANGED
|
@@ -3,6 +3,12 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
|
|
6
|
+
export const CODEWAVE_TOOLKIT_CONFIG_PATH = join(homedir(), '.openclaw', 'codewave-toolkit.json');
|
|
7
|
+
export const CODEWAVE_TOOLKIT_EXTENSION_ID = 'codewave-openclaw-installer';
|
|
8
|
+
export const CODEWAVE_TOOLKIT_EXTENSION_ROOT = join(homedir(), '.openclaw', 'extensions', CODEWAVE_TOOLKIT_EXTENSION_ID);
|
|
9
|
+
export const CODEWAVE_TOOLKIT_RUNTIME_ROOT = join(homedir(), '.openclaw', 'runtime', CODEWAVE_TOOLKIT_EXTENSION_ID);
|
|
10
|
+
export const CODEWAVE_TOOLKIT_WRAPPER_BIN = join(homedir(), '.local', 'bin');
|
|
11
|
+
|
|
6
12
|
export const SKILL_SUMMARIES = [
|
|
7
13
|
'acceptance-doc-entry — 项目验收文档生成',
|
|
8
14
|
'pmo-weekly-report — PMO 项目看板周报',
|
|
@@ -199,13 +205,13 @@ export function classifyPythonInstallFailure(stderr = '') {
|
|
|
199
205
|
if (stderr.includes('externally-managed-environment') || stderr.includes('PEP 668')) {
|
|
200
206
|
return {
|
|
201
207
|
code: 'pep668',
|
|
202
|
-
message: '当前 Python 环境受 PEP 668
|
|
208
|
+
message: '当前 Python 环境受 PEP 668 管理,建议改用安装器创建插件私有运行时',
|
|
203
209
|
};
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
return {
|
|
207
213
|
code: 'generic',
|
|
208
|
-
message: 'Python CLI 安装失败,请检查 python3/
|
|
214
|
+
message: 'Python CLI 安装失败,请检查 python3/venv/pip 是否可用',
|
|
209
215
|
};
|
|
210
216
|
}
|
|
211
217
|
|
|
@@ -511,3 +517,56 @@ export function buildRazelConfigBootstrap({
|
|
|
511
517
|
|
|
512
518
|
return { writes, missing };
|
|
513
519
|
}
|
|
520
|
+
|
|
521
|
+
export function getSmartCustomerServiceDocsDir({
|
|
522
|
+
env = process.env,
|
|
523
|
+
toolkitConfig = {},
|
|
524
|
+
} = {}) {
|
|
525
|
+
const envDir = typeof env.SMART_CS_DOCS_DIR === 'string' ? env.SMART_CS_DOCS_DIR.trim() : '';
|
|
526
|
+
if (envDir) {
|
|
527
|
+
return envDir;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const configDir = typeof toolkitConfig?.smartCustomerService?.docsDir === 'string'
|
|
531
|
+
? toolkitConfig.smartCustomerService.docsDir.trim()
|
|
532
|
+
: '';
|
|
533
|
+
if (configDir) {
|
|
534
|
+
return configDir;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return '';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function getSmartCustomerServiceVectorConfig({
|
|
541
|
+
env = process.env,
|
|
542
|
+
toolkitConfig = {},
|
|
543
|
+
} = {}) {
|
|
544
|
+
const config = toolkitConfig?.smartCustomerService?.vector || {};
|
|
545
|
+
const enabled = env.SMART_CS_VECTOR_ENABLED
|
|
546
|
+
? ['1', 'true', 'yes', 'on'].includes(String(env.SMART_CS_VECTOR_ENABLED).trim().toLowerCase())
|
|
547
|
+
: config.enabled === true;
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
enabled,
|
|
551
|
+
baseUrl: (env.SMART_CS_VECTOR_BASE_URL || config.baseUrl || '').trim?.() || '',
|
|
552
|
+
apiKey: (env.SMART_CS_VECTOR_API_KEY || config.apiKey || '').trim?.() || '',
|
|
553
|
+
model: (env.SMART_CS_VECTOR_MODEL || config.model || '').trim?.() || '',
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function getManagedCommandPath(command, {
|
|
558
|
+
platform = process.platform,
|
|
559
|
+
runtimeRoot = CODEWAVE_TOOLKIT_RUNTIME_ROOT,
|
|
560
|
+
} = {}) {
|
|
561
|
+
const isWindows = platform === 'win32';
|
|
562
|
+
|
|
563
|
+
if (command === 'razel-py-cli') {
|
|
564
|
+
return join(runtimeRoot, 'py', isWindows ? 'Scripts' : 'bin', isWindows ? 'razel-py-cli.exe' : 'razel-py-cli');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (command === 'razel-cli') {
|
|
568
|
+
return join(runtimeRoot, 'node', 'node_modules', '.bin', isWindows ? 'razel-cli.cmd' : 'razel-cli');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return null;
|
|
572
|
+
}
|
package/bin/install.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmodSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, resolve, join } from 'node:path';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
@@ -9,16 +9,21 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { createInterface } from 'node:readline/promises';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
CODEWAVE_TOOLKIT_CONFIG_PATH,
|
|
13
|
+
CODEWAVE_TOOLKIT_EXTENSION_ROOT,
|
|
14
|
+
CODEWAVE_TOOLKIT_RUNTIME_ROOT,
|
|
15
|
+
CODEWAVE_TOOLKIT_WRAPPER_BIN,
|
|
12
16
|
CHANNEL_DEFINITIONS,
|
|
13
17
|
SKILL_SUMMARIES,
|
|
14
18
|
buildRazelConfigBootstrap,
|
|
15
|
-
classifyPythonInstallFailure,
|
|
16
19
|
createInstallContext,
|
|
17
20
|
detectDesktopAppInstallMatches,
|
|
18
21
|
evaluateChannelDetection,
|
|
19
22
|
getCoreBootstrapPlan,
|
|
20
23
|
getDesktopAppCandidates,
|
|
21
|
-
|
|
24
|
+
getManagedCommandPath,
|
|
25
|
+
getSmartCustomerServiceDocsDir,
|
|
26
|
+
getSmartCustomerServiceVectorConfig,
|
|
22
27
|
preserveEnabledTruePreference,
|
|
23
28
|
resolvePromptDefault,
|
|
24
29
|
shouldDryRun,
|
|
@@ -56,7 +61,21 @@ function refreshLocalToolPaths() {
|
|
|
56
61
|
: path.posix.join(homedir(), '.local', 'bin'));
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
refreshLocalToolPaths();
|
|
65
|
+
|
|
66
|
+
function resolveManagedExecutable(cmd) {
|
|
67
|
+
const managedPath = getManagedCommandPath(cmd);
|
|
68
|
+
if (managedPath && existsSync(managedPath)) {
|
|
69
|
+
return managedPath;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
function resolveExecutable(cmd) {
|
|
75
|
+
const managed = resolveManagedExecutable(cmd);
|
|
76
|
+
if (managed) {
|
|
77
|
+
return managed;
|
|
78
|
+
}
|
|
60
79
|
if (!isWin) {
|
|
61
80
|
return cmd;
|
|
62
81
|
}
|
|
@@ -114,6 +133,10 @@ function runCapture(cmd, args, opts = {}) {
|
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
function commandExists(cmd) {
|
|
136
|
+
const managed = resolveManagedExecutable(cmd);
|
|
137
|
+
if (managed) {
|
|
138
|
+
return Promise.resolve(true);
|
|
139
|
+
}
|
|
117
140
|
return new Promise((resolve) => {
|
|
118
141
|
const check = isWin ? 'where' : 'which';
|
|
119
142
|
const child = spawn(check, [cmd], { stdio: 'ignore', shell: false });
|
|
@@ -134,6 +157,69 @@ function readJson(filePath) {
|
|
|
134
157
|
}
|
|
135
158
|
}
|
|
136
159
|
|
|
160
|
+
function ensureParentDir(filePath) {
|
|
161
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function writeJson(filePath, value) {
|
|
165
|
+
ensureParentDir(filePath);
|
|
166
|
+
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readToolkitConfig() {
|
|
170
|
+
return readJson(CODEWAVE_TOOLKIT_CONFIG_PATH) || {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function writeToolkitConfig(updater) {
|
|
174
|
+
if (DRY_RUN) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const current = readToolkitConfig();
|
|
178
|
+
const next = updater(current) || current;
|
|
179
|
+
writeJson(CODEWAVE_TOOLKIT_CONFIG_PATH, next);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getPrivatePythonBin(command) {
|
|
184
|
+
return getManagedCommandPath(command, { runtimeRoot: CODEWAVE_TOOLKIT_RUNTIME_ROOT });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getPrivatePythonPipPath() {
|
|
188
|
+
return join(CODEWAVE_TOOLKIT_RUNTIME_ROOT, 'py', isWin ? 'Scripts' : 'bin', isWin ? 'pip.exe' : 'pip');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getPrivateNodeRoot() {
|
|
192
|
+
return join(CODEWAVE_TOOLKIT_RUNTIME_ROOT, 'node');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getWrapperPath(command) {
|
|
196
|
+
const filename = isWin ? `${command}.cmd` : command;
|
|
197
|
+
return join(CODEWAVE_TOOLKIT_WRAPPER_BIN, filename);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ensureWrapper(command, targetPath) {
|
|
201
|
+
if (DRY_RUN || !targetPath) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
mkdirSync(CODEWAVE_TOOLKIT_WRAPPER_BIN, { recursive: true });
|
|
205
|
+
const wrapperPath = getWrapperPath(command);
|
|
206
|
+
try {
|
|
207
|
+
if (existsSync(wrapperPath) || lstatSync(wrapperPath).isSymbolicLink()) {
|
|
208
|
+
rmSync(wrapperPath, { force: true });
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// ignore cleanup errors and let writeFileSync surface real failures
|
|
212
|
+
}
|
|
213
|
+
const content = isWin
|
|
214
|
+
? `@echo off\r\n"${targetPath}" %*\r\n`
|
|
215
|
+
: `#!/bin/sh\nexec "${targetPath}" "$@"\n`;
|
|
216
|
+
writeFileSync(wrapperPath, content, 'utf8');
|
|
217
|
+
if (!isWin) {
|
|
218
|
+
chmodSync(wrapperPath, 0o755);
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
137
223
|
function clearPluginRegistration(pluginIds) {
|
|
138
224
|
if (DRY_RUN) {
|
|
139
225
|
return false;
|
|
@@ -548,94 +634,54 @@ async function resolvePythonRuntime() {
|
|
|
548
634
|
};
|
|
549
635
|
}
|
|
550
636
|
|
|
551
|
-
async function
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
};
|
|
637
|
+
async function installPythonCli(context) {
|
|
638
|
+
const managedCli = getPrivatePythonBin('razel-py-cli');
|
|
639
|
+
if (managedCli && existsSync(managedCli)) {
|
|
640
|
+
console.log(dim(' ↺ 已检测到私有 razel-py-cli 运行时,跳过安装'));
|
|
641
|
+
ensureWrapper('razel-py-cli', managedCli);
|
|
642
|
+
contextResult(context, 'razel-py-cli', 'reused (private)');
|
|
643
|
+
return;
|
|
559
644
|
}
|
|
560
645
|
|
|
561
646
|
const runtime = await resolvePythonRuntime();
|
|
562
|
-
if (!runtime.pythonCommand
|
|
563
|
-
console.log(yellow(' ⚠️ 缺少 Python
|
|
564
|
-
contextResult(context, '
|
|
565
|
-
return { available: false, invoke: null, pythonCommand: null };
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const pipxPlan = getPipxBootstrapPlan({
|
|
569
|
-
platform: process.platform,
|
|
570
|
-
hasBrew: await commandExists('brew'),
|
|
571
|
-
pythonCommand: runtime.pythonCommand || 'python3',
|
|
572
|
-
pythonLauncher: runtime.pythonLauncher,
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
if (!(await commandExists(pipxPlan.install.cmd))) {
|
|
576
|
-
console.log(yellow(` ⚠️ 缺少 ${pipxPlan.install.cmd},无法自动安装 pipx`));
|
|
577
|
-
contextResult(context, 'pipx', `blocked (${pipxPlan.install.cmd} missing)`);
|
|
578
|
-
return { available: false, invoke: null, pythonCommand: runtime.pythonCommand };
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
console.log(dim(' 正在安装 pipx...'));
|
|
582
|
-
const installCode = await run(pipxPlan.install.cmd, pipxPlan.install.args);
|
|
583
|
-
if (installCode !== 0) {
|
|
584
|
-
if (runtime.pipCommand) {
|
|
585
|
-
const classification = classifyPythonInstallFailure('externally-managed-environment');
|
|
586
|
-
console.log(dim(` 提示: ${classification.message}`));
|
|
587
|
-
}
|
|
588
|
-
contextResult(context, 'pipx', 'failed');
|
|
589
|
-
return { available: false, invoke: null, pythonCommand: runtime.pythonCommand };
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
await run(pipxPlan.ensurePath.cmd, pipxPlan.ensurePath.args);
|
|
593
|
-
refreshLocalToolPaths();
|
|
594
|
-
const pipxAvailable = await commandExists('pipx');
|
|
595
|
-
contextResult(context, 'pipx', pipxAvailable ? 'ok' : 'ok (path refresh may be needed)');
|
|
596
|
-
return {
|
|
597
|
-
available: true,
|
|
598
|
-
invoke: pipxAvailable ? { cmd: 'pipx', args: [] } : pipxPlan.invoke,
|
|
599
|
-
pythonCommand: runtime.pythonCommand,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async function installPythonCli(context) {
|
|
604
|
-
const pipxState = await ensurePipx(context);
|
|
605
|
-
|
|
606
|
-
if (await commandExists('razel-py-cli')) {
|
|
607
|
-
console.log(dim(' ↺ 已检测到 razel-py-cli,跳过安装'));
|
|
608
|
-
contextResult(context, 'razel-py-cli', 'reused');
|
|
647
|
+
if (!runtime.pythonCommand) {
|
|
648
|
+
console.log(yellow(' ⚠️ 缺少 Python 运行时,无法继续安装私有 razel-py-cli'));
|
|
649
|
+
contextResult(context, 'razel-py-cli', 'blocked (missing python3)');
|
|
609
650
|
return;
|
|
610
651
|
}
|
|
611
652
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
653
|
+
const privatePyRoot = join(CODEWAVE_TOOLKIT_RUNTIME_ROOT, 'py');
|
|
654
|
+
rmSync(privatePyRoot, { recursive: true, force: true });
|
|
655
|
+
mkdirSync(CODEWAVE_TOOLKIT_RUNTIME_ROOT, { recursive: true });
|
|
656
|
+
|
|
657
|
+
console.log(dim(' 正在创建私有 Python 运行时...'));
|
|
658
|
+
const venvCode = await run(runtime.pythonCommand, ['-m', 'venv', privatePyRoot]);
|
|
659
|
+
if (venvCode !== 0) {
|
|
660
|
+
console.log(yellow(` ⚠️ 私有 Python 运行时创建失败${runtime.pythonCommand ? ` (python=${runtime.pythonCommand})` : ''}`));
|
|
661
|
+
contextResult(context, 'razel-py-cli', 'failed (venv)');
|
|
615
662
|
return;
|
|
616
663
|
}
|
|
617
664
|
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
665
|
+
const pipPath = getPrivatePythonPipPath();
|
|
666
|
+
const upgradeCode = await run(pipPath, ['install', '--upgrade', 'pip']);
|
|
667
|
+
if (upgradeCode !== 0) {
|
|
668
|
+
console.log(dim(' 提示: pip 升级失败,继续尝试安装 razel-py-cli'));
|
|
621
669
|
}
|
|
622
|
-
pipxArgs.push('razel-py-cli');
|
|
623
670
|
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
671
|
+
const localWhl = join(PKG_ROOT, 'pkg', 'razel_py_cli-0.0.4-py3-none-any.whl');
|
|
672
|
+
const source = existsSync(localWhl) ? localWhl : 'razel-py-cli';
|
|
673
|
+
const installCode = await run(pipPath, ['install', source]);
|
|
674
|
+
if (installCode !== 0) {
|
|
675
|
+
console.log(yellow(` ⚠️ razel-py-cli 私有安装失败${runtime.pythonCommand ? ` (python=${runtime.pythonCommand})` : ''}`));
|
|
676
|
+
contextResult(context, 'razel-py-cli', 'failed (private)');
|
|
628
677
|
return;
|
|
629
678
|
}
|
|
630
679
|
|
|
631
|
-
|
|
632
|
-
|
|
680
|
+
ensureWrapper('razel-py-cli', managedCli);
|
|
681
|
+
console.log(green(' ✅ razel-py-cli 私有安装成功'));
|
|
633
682
|
console.log(dim(' 正在初始化...'));
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
initCode = await run(pipxState.invoke.cmd, [...pipxState.invoke.args, 'run', 'razel-py-cli', 'init']);
|
|
637
|
-
}
|
|
638
|
-
contextResult(context, 'razel-py-cli', initCode === 0 ? 'ok' : 'installed via pipx (init failed)');
|
|
683
|
+
const initCode = await run(managedCli, ['init']);
|
|
684
|
+
contextResult(context, 'razel-py-cli', initCode === 0 ? 'ok (private)' : 'installed (private init failed)');
|
|
639
685
|
}
|
|
640
686
|
|
|
641
687
|
async function applyConfigWrites(writes) {
|
|
@@ -786,6 +832,74 @@ async function collectInteractiveConfigWrites(missing, options = {}) {
|
|
|
786
832
|
console.log(dim(' 已选择跳过 PoPo 服务配置'));
|
|
787
833
|
}
|
|
788
834
|
|
|
835
|
+
const toolkitConfig = readToolkitConfig();
|
|
836
|
+
const configuredDocsDir = getSmartCustomerServiceDocsDir({
|
|
837
|
+
env: process.env,
|
|
838
|
+
toolkitConfig,
|
|
839
|
+
});
|
|
840
|
+
const configuredVector = getSmartCustomerServiceVectorConfig({
|
|
841
|
+
env: process.env,
|
|
842
|
+
toolkitConfig,
|
|
843
|
+
});
|
|
844
|
+
console.log(dim(' [smart-customer-service] 可配置外部知识目录;留空则继续使用 skill 自带 docs/'));
|
|
845
|
+
if (configuredDocsDir) {
|
|
846
|
+
console.log(dim(` 当前知识目录: ${configuredDocsDir}`));
|
|
847
|
+
}
|
|
848
|
+
const wantsDocsDir = await askYesNo(' 是否现在配置 smart-customer-service 知识目录?[Y/n] ', false);
|
|
849
|
+
if (wantsDocsDir) {
|
|
850
|
+
const docsDir = await ask(' SMART_CS_DOCS_DIR / docsDir absolute path: ');
|
|
851
|
+
if (docsDir) {
|
|
852
|
+
writeToolkitConfig((current) => ({
|
|
853
|
+
...current,
|
|
854
|
+
smartCustomerService: {
|
|
855
|
+
...(current.smartCustomerService || {}),
|
|
856
|
+
docsDir,
|
|
857
|
+
configuredAt: new Date().toISOString(),
|
|
858
|
+
},
|
|
859
|
+
}));
|
|
860
|
+
console.log(green(` ✅ 已写入 smart-customer-service 知识目录: ${docsDir}`));
|
|
861
|
+
} else {
|
|
862
|
+
console.log(dim(' 未填写知识目录,将继续使用默认 docs/'));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
console.log(dim(' [smart-customer-service] 可选配置向量检索;安装时只记录配置,不立即构建索引'));
|
|
867
|
+
if (configuredVector.enabled) {
|
|
868
|
+
console.log(dim(` 当前向量配置: enabled=true, model=${configuredVector.model || '(unset)'}`));
|
|
869
|
+
}
|
|
870
|
+
const wantsVector = await askYesNo(' 是否现在配置 smart-customer-service 向量模型?[y/N] ', true);
|
|
871
|
+
if (wantsVector) {
|
|
872
|
+
console.log(dim(' [Vector] 回车可跳过;若填写完整,首次使用时自动建索引,后续按文档变更增量重建'));
|
|
873
|
+
const baseUrl = await ask(' SMART_CS_VECTOR_BASE_URL: ');
|
|
874
|
+
const apiKey = await ask(' SMART_CS_VECTOR_API_KEY: ');
|
|
875
|
+
const model = await ask(' SMART_CS_VECTOR_MODEL: ');
|
|
876
|
+
if (baseUrl && apiKey && model) {
|
|
877
|
+
writeToolkitConfig((current) => ({
|
|
878
|
+
...current,
|
|
879
|
+
smartCustomerService: {
|
|
880
|
+
...(current.smartCustomerService || {}),
|
|
881
|
+
retrieval: {
|
|
882
|
+
mode: 'auto',
|
|
883
|
+
},
|
|
884
|
+
vector: {
|
|
885
|
+
enabled: true,
|
|
886
|
+
baseUrl,
|
|
887
|
+
apiKey,
|
|
888
|
+
model,
|
|
889
|
+
},
|
|
890
|
+
index: {
|
|
891
|
+
buildStrategy: 'lazy-on-first-use',
|
|
892
|
+
rebuildStrategy: 'incremental-by-mtime-or-hash',
|
|
893
|
+
},
|
|
894
|
+
configuredAt: new Date().toISOString(),
|
|
895
|
+
},
|
|
896
|
+
}));
|
|
897
|
+
console.log(green(` ✅ 已写入 smart-customer-service 向量配置: ${model}`));
|
|
898
|
+
} else {
|
|
899
|
+
console.log(dim(' 向量配置未填写完整,保持 grep / 默认检索路径'));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
789
903
|
rl.close();
|
|
790
904
|
return { writes, skipPopo };
|
|
791
905
|
}
|
|
@@ -1076,7 +1190,39 @@ async function runCoreInstall(context) {
|
|
|
1076
1190
|
await installCoreDependency(spec, context);
|
|
1077
1191
|
}
|
|
1078
1192
|
await installPythonCli(context);
|
|
1079
|
-
|
|
1193
|
+
const managedRazelCli = getManagedCommandPath('razel-cli', { runtimeRoot: CODEWAVE_TOOLKIT_RUNTIME_ROOT });
|
|
1194
|
+
if (managedRazelCli && existsSync(managedRazelCli)) {
|
|
1195
|
+
console.log(dim(' ↺ 已检测到私有 razel-cli 运行时,跳过安装'));
|
|
1196
|
+
ensureWrapper('razel-cli', managedRazelCli);
|
|
1197
|
+
contextResult(context, 'razel-cli', 'reused (private)');
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (!(await commandExists('npm'))) {
|
|
1202
|
+
console.log(yellow(' ⚠️ 未检测到 npm,无法继续安装私有 razel-cli'));
|
|
1203
|
+
contextResult(context, 'razel-cli', 'blocked (npm missing)');
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const privateNodeRoot = getPrivateNodeRoot();
|
|
1208
|
+
rmSync(privateNodeRoot, { recursive: true, force: true });
|
|
1209
|
+
mkdirSync(privateNodeRoot, { recursive: true });
|
|
1210
|
+
|
|
1211
|
+
const localTgz = join(PKG_ROOT, 'pkg', 'lcap-delivery-razel-cli-0.0.4.tgz');
|
|
1212
|
+
const razelCliSource = existsSync(localTgz) ? localTgz : '@lcap-delivery/razel-cli@latest';
|
|
1213
|
+
console.log(dim(' 正在安装私有 razel-cli 运行时...'));
|
|
1214
|
+
const installCode = await run(NPM, ['install', '--prefix', privateNodeRoot, '--no-audit', '--no-fund', razelCliSource]);
|
|
1215
|
+
if (installCode !== 0) {
|
|
1216
|
+
console.log(yellow(' ⚠️ 私有 razel-cli 安装失败'));
|
|
1217
|
+
contextResult(context, 'razel-cli', 'failed (private)');
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
ensureWrapper('razel-cli', managedRazelCli);
|
|
1222
|
+
console.log(green(' ✅ razel-cli 私有安装成功'));
|
|
1223
|
+
console.log(dim(' 正在初始化...'));
|
|
1224
|
+
const initCode = await run(managedRazelCli, ['init']);
|
|
1225
|
+
contextResult(context, 'razel-cli', initCode === 0 ? 'ok (private)' : 'installed (private init failed)');
|
|
1080
1226
|
}
|
|
1081
1227
|
|
|
1082
1228
|
async function runConfigBootstrap(context) {
|
|
@@ -1313,6 +1459,20 @@ async function main() {
|
|
|
1313
1459
|
console.log(` Skill 就绪情况: ${context.postflight.summary.ok} ok / ${context.postflight.summary.warn} warn / ${context.postflight.summary.fail} fail`);
|
|
1314
1460
|
console.log('');
|
|
1315
1461
|
}
|
|
1462
|
+
const toolkitConfig = readToolkitConfig();
|
|
1463
|
+
const smartDocsDir = getSmartCustomerServiceDocsDir({
|
|
1464
|
+
env: process.env,
|
|
1465
|
+
toolkitConfig,
|
|
1466
|
+
});
|
|
1467
|
+
const smartVector = getSmartCustomerServiceVectorConfig({
|
|
1468
|
+
env: process.env,
|
|
1469
|
+
toolkitConfig,
|
|
1470
|
+
});
|
|
1471
|
+
const smartMode = smartVector.enabled ? 'vector(auto)' : 'grep';
|
|
1472
|
+
console.log(' smart-customer-service:');
|
|
1473
|
+
console.log(` 知识目录 : ${smartDocsDir || '内置 docs/'}`);
|
|
1474
|
+
console.log(` 检索模式 : ${smartMode}`);
|
|
1475
|
+
console.log('');
|
|
1316
1476
|
console.log(' 使用方式:');
|
|
1317
1477
|
console.log(cyan(' razel-py-cli --help'));
|
|
1318
1478
|
console.log(cyan(' razel-cli --help'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codewave-openclaw-installer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "网易智企 CodeWave OpenClaw 扩展:Skills + CLI 依赖一键安装",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"index.js",
|
|
25
25
|
"bin/",
|
|
26
26
|
"skills/",
|
|
27
|
+
"pkg/",
|
|
27
28
|
"openclaw.plugin.json"
|
|
28
29
|
],
|
|
29
30
|
"peerDependencies": {
|
|
Binary file
|
|
Binary file
|
|
@@ -30,9 +30,9 @@ razel-py-cli acceptance-toolkit <command>
|
|
|
30
30
|
| 命令 | 必填参数 | 说明 |
|
|
31
31
|
|------|---------|------|
|
|
32
32
|
| `parse-template` | `--template <PATH>` | 解析模板结构,创建任务目录 |
|
|
33
|
-
| `plan` | `--task-dir <DIR> --materials <M1> [M2...]` | 解析素材 + LLM 语义映射 |
|
|
33
|
+
| `plan` | `--task-dir <DIR> --materials <M1> [--materials <M2> ...]` | 解析素材 + LLM 语义映射 |
|
|
34
34
|
| `build` | `--task-dir <DIR>` | 生成草稿 DOCX |
|
|
35
|
-
| `revise` | `--task-dir <DIR> --add-materials <M1> [M2...]` | 追加素材 → 重新 plan → 重新 build |
|
|
35
|
+
| `revise` | `--task-dir <DIR> --add-materials <M1> [--add-materials <M2> ...]` | 追加素材 → 重新 plan → 重新 build |
|
|
36
36
|
| `finalize` | `--task-dir <DIR> --confirmed` | 生成正式版(清理草稿标记) |
|
|
37
37
|
|
|
38
38
|
可选参数:`parse-template` 支持 `--task-id <ID>` 指定任务标识。
|
|
@@ -54,7 +54,61 @@ razel-py-cli acceptance-toolkit parse-template --template <TEMPLATE.docx> [--tas
|
|
|
54
54
|
### 2. plan — 解析素材 + 映射
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
razel-py-cli acceptance-toolkit plan --task-dir <TASK_DIR> --materials <M1.docx> <M2.docx>
|
|
57
|
+
razel-py-cli acceptance-toolkit plan --task-dir <TASK_DIR> --materials <M1.docx> --materials <M2.docx>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
⚠️ `plan` 的 `--materials` 当前实现为“可重复参数”,不是“一次接收多个文件”。
|
|
61
|
+
也就是说,正确写法是重复传 `--materials`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
razel-py-cli acceptance-toolkit plan \
|
|
65
|
+
--task-dir <TASK_DIR> \
|
|
66
|
+
--materials /path/a.docx \
|
|
67
|
+
--materials /path/b.docx \
|
|
68
|
+
--materials /path/c.docx
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
如果要把某个目录下的多个文件一起传入,应先在 Agent 或 shell 层展开成多次 `--materials`,不要写成:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 错误示例:当前 CLI 不支持
|
|
75
|
+
razel-py-cli acceptance-toolkit plan --task-dir <TASK_DIR> --materials a.docx b.docx c.docx
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 目录与批量素材规则
|
|
79
|
+
|
|
80
|
+
当用户给的是“目录”“文件夹”“一批素材”“多个附件”时,Agent 不要把目录路径原样传给 CLI,而要先展开成文件列表,再重复传 `--materials`。
|
|
81
|
+
|
|
82
|
+
执行规则:
|
|
83
|
+
- 如果用户给的是单个文件,直接传一次 `--materials <FILE>`
|
|
84
|
+
- 如果用户给的是多个文件,按文件数重复传 `--materials`
|
|
85
|
+
- 如果用户给的是目录,先枚举目录下的素材文件,再按文件数重复传 `--materials`
|
|
86
|
+
- 如果目录下包含子目录,默认递归查找常见素材类型;若用户明确只要当前层,则只扫当前层
|
|
87
|
+
- 默认只纳入验收相关常见素材:`.docx`、`.doc`、`.pdf`、`.xlsx`、`.xls`、`.csv`、`.md`、`.txt`
|
|
88
|
+
- 图片、聊天导出、录音转写等非 DOCX 素材,只有在底层 CLI 已支持或前置处理后才传入;否则先由 Agent 提炼或转换,再决定是否进入 `plan`
|
|
89
|
+
- 传参前先去重,并保持稳定顺序(建议按文件名或修改时间排序),避免同一素材重复进入 task
|
|
90
|
+
|
|
91
|
+
推荐做法:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 用户给目录时,先展开,再重复传参
|
|
95
|
+
razel-py-cli acceptance-toolkit plan \
|
|
96
|
+
--task-dir <TASK_DIR> \
|
|
97
|
+
--materials /materials/01-项目方案.docx \
|
|
98
|
+
--materials /materials/02-实施报告.docx \
|
|
99
|
+
--materials /materials/03-测试记录.pdf
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
错误做法:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# 错误:不要把目录原样传给 --materials
|
|
106
|
+
razel-py-cli acceptance-toolkit plan --task-dir <TASK_DIR> --materials /materials
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 错误:不要把多个文件堆在一个 --materials 后面
|
|
111
|
+
razel-py-cli acceptance-toolkit plan --task-dir <TASK_DIR> --materials a.docx b.docx c.docx
|
|
58
112
|
```
|
|
59
113
|
|
|
60
114
|
行为:
|
|
@@ -91,9 +145,20 @@ razel-py-cli acceptance-toolkit build --task-dir <TASK_DIR>
|
|
|
91
145
|
### 4. revise — 补充素材重新生成
|
|
92
146
|
|
|
93
147
|
```bash
|
|
94
|
-
razel-py-cli acceptance-toolkit revise --task-dir <TASK_DIR> --add-materials <M3.docx> [M4.docx...]
|
|
148
|
+
razel-py-cli acceptance-toolkit revise --task-dir <TASK_DIR> --add-materials <M3.docx> [--add-materials <M4.docx> ...]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
同理,`revise` 的 `--add-materials` 也需要重复传参:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
razel-py-cli acceptance-toolkit revise \
|
|
155
|
+
--task-dir <TASK_DIR> \
|
|
156
|
+
--add-materials /path/new-1.docx \
|
|
157
|
+
--add-materials /path/new-2.docx
|
|
95
158
|
```
|
|
96
159
|
|
|
160
|
+
如果用户补充的是一个目录,也要先展开成多个文件,再重复传 `--add-materials`,不要把目录路径直接传给 CLI。
|
|
161
|
+
|
|
97
162
|
行为(三步串联):
|
|
98
163
|
1. **backup** — 备份当前草稿为 `acceptance-doc.draft.prev.docx`
|
|
99
164
|
2. **plan** — 追加新素材到 input,重新解析全部素材,重新 LLM 映射
|
|
@@ -184,7 +249,7 @@ razel-py-cli acceptance-toolkit finalize --task-dir <TASK_DIR> --confirmed
|
|
|
184
249
|
|
|
185
250
|
`revise` 命令会自动串联 plan + build,跳过用户确认映射的环节。正确做法:
|
|
186
251
|
|
|
187
|
-
1. 用户补充素材时,使用 `plan --task-dir <DIR> --materials
|
|
252
|
+
1. 用户补充素材时,使用 `plan --task-dir <DIR> --materials <旧素材1> --materials <旧素材2> --materials <新素材...>`(包含新旧所有素材,并重复传 `--materials`)
|
|
188
253
|
2. 展示映射计划,等待用户确认
|
|
189
254
|
3. 用户确认后,再执行 `build`
|
|
190
255
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: smart-customer-service
|
|
3
|
-
description:
|
|
3
|
+
description: 严格依据已配置知识目录中的产品文档、开发手册等文档,为平台上线后提供技术答疑支持。安装时应要求用户配置知识目录,并可选配置向量模型;若未配置,则回退到 skill 自带 docs/ 与 grep 检索。文档未涵盖的问题一律拒绝回答。激活场景:平台使用、配置、部署、报错排查、技术答疑、产品文档等问题咨询。
|
|
4
4
|
version: 1.0.0
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,18 +8,102 @@ version: 1.0.0
|
|
|
8
8
|
|
|
9
9
|
## 概述
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- **职责**:平台上线后技术答疑,仅依据“已配置知识目录”或 skill 自带 `docs/` 作答
|
|
12
12
|
- **知识边界**:严禁编造、推断;文档未涵盖时明确无法解答并引导补充信息
|
|
13
13
|
- **激活**:用户提及平台使用、配置、部署、报错、故障、技术答疑、产品文档、开发手册等
|
|
14
14
|
|
|
15
|
+
## 安装与知识目录配置
|
|
16
|
+
|
|
17
|
+
此 skill 保留内置 `docs/` 作为默认知识源,但在安装或首次启用时,应优先要求用户配置业务文档目录。
|
|
18
|
+
|
|
19
|
+
### 推荐配置方式
|
|
20
|
+
|
|
21
|
+
- 用户显式提供知识目录路径,例如:`/data/customer-service-docs`
|
|
22
|
+
- 安装时将该目录记录为固定配置,例如环境变量 `SMART_CS_DOCS_DIR`
|
|
23
|
+
- 若安装器已写入 `~/.openclaw/codewave-toolkit.json`,则从其中的 `smartCustomerService.docsDir` 读取
|
|
24
|
+
- 若安装器已配置向量模型,则从同一配置文件中的 `smartCustomerService.vector` 读取
|
|
25
|
+
- 如果用户配置了多个知识目录,可按顺序逐个检索,或先检索用户指定目录,再回退内置 `docs/`
|
|
26
|
+
|
|
27
|
+
### 知识源优先级
|
|
28
|
+
|
|
29
|
+
按以下顺序选择知识源:
|
|
30
|
+
|
|
31
|
+
1. 用户在当前对话中明确指定的检索目录
|
|
32
|
+
2. 安装时配置的外部知识目录,例如 `SMART_CS_DOCS_DIR`
|
|
33
|
+
3. 安装器落盘的固定配置:`~/.openclaw/codewave-toolkit.json` 中的 `smartCustomerService.docsDir`
|
|
34
|
+
4. skill 自带默认目录:`smart-customer-service/docs/`
|
|
35
|
+
|
|
36
|
+
## 检索模式与索引策略
|
|
37
|
+
|
|
38
|
+
### 检索模式优先级
|
|
39
|
+
|
|
40
|
+
默认使用 `auto` 模式:
|
|
41
|
+
|
|
42
|
+
1. 若用户明确指定“只用 grep”或“只用向量检索”,按用户要求执行
|
|
43
|
+
2. 若已配置向量模型,优先尝试向量检索
|
|
44
|
+
3. 若向量配置缺失、索引不可用、建索引失败或检索失败,则自动回退到 `grep`
|
|
45
|
+
|
|
46
|
+
### 向量模型配置
|
|
47
|
+
|
|
48
|
+
推荐在安装时记录以下配置:
|
|
49
|
+
|
|
50
|
+
- `SMART_CS_VECTOR_BASE_URL`
|
|
51
|
+
- `SMART_CS_VECTOR_API_KEY`
|
|
52
|
+
- `SMART_CS_VECTOR_MODEL`
|
|
53
|
+
|
|
54
|
+
也可由安装器写入:
|
|
55
|
+
|
|
56
|
+
- `~/.openclaw/codewave-toolkit.json`
|
|
57
|
+
- `smartCustomerService.vector.enabled`
|
|
58
|
+
- `smartCustomerService.vector.baseUrl`
|
|
59
|
+
- `smartCustomerService.vector.apiKey`
|
|
60
|
+
- `smartCustomerService.vector.model`
|
|
61
|
+
|
|
62
|
+
### 索引构建策略
|
|
63
|
+
|
|
64
|
+
当向量模型已配置时,索引生命周期按以下规则执行:
|
|
65
|
+
|
|
66
|
+
1. 安装时只记录知识目录和向量模型配置,不立即建索引
|
|
67
|
+
2. 首次使用时,如果索引不存在,则自动构建索引
|
|
68
|
+
3. 后续每次使用前,检查知识目录变更
|
|
69
|
+
4. 若目录更新时间变化、文件数量变化、或文件 hash 变化,则执行增量重建
|
|
70
|
+
5. 若增量重建失败,则回退到全量重建;若仍失败,则降级为 `grep`
|
|
71
|
+
|
|
72
|
+
### Agent 执行要求
|
|
73
|
+
|
|
74
|
+
- 不要因为配置了向量模型,就跳过原有的原文引用校验
|
|
75
|
+
- 向量检索只负责召回候选片段,后续仍需做去重、扩展、强弱匹配和引用核对
|
|
76
|
+
- 若向量召回结果置信度低、来源分散或无明确原文支撑,必须回退到 `grep` 或判定为弱匹配
|
|
77
|
+
- 若用户明确要求“仅依据原文精确匹配”,优先使用 `grep` 或 `grep + 精读`
|
|
78
|
+
|
|
79
|
+
### 安装时要求
|
|
80
|
+
|
|
81
|
+
安装或启用本 skill 时,应主动要求用户确认以下内容:
|
|
82
|
+
|
|
83
|
+
1. 是否已有独立的产品/交付/运维知识库目录
|
|
84
|
+
2. 该目录的绝对路径是什么
|
|
85
|
+
3. 若未提供,则明确告知当前将回退使用 skill 自带 `docs/`
|
|
86
|
+
|
|
87
|
+
### 未配置时的行为
|
|
88
|
+
|
|
89
|
+
- 若已配置外部目录,则默认检索外部目录
|
|
90
|
+
- 若外部目录不存在、无权限或为空,则提示用户并回退到 skill 自带 `docs/`
|
|
91
|
+
- 若用户明确要求“只查外部目录”,则外部目录不可用时应直接报出原因,不自动回退
|
|
92
|
+
|
|
93
|
+
### 推荐提示语
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
为保证智能客服回答基于你们自己的产品文档,建议在安装或首次启用时配置知识目录绝对路径。若未配置,将暂时使用 skill 自带 docs/ 目录作为默认知识源。
|
|
97
|
+
```
|
|
98
|
+
|
|
15
99
|
## 核心工作流
|
|
16
100
|
|
|
17
101
|
```
|
|
18
|
-
1.问题归类 ──→ 2.Query改写 ──→ 3.检索(grep) ──→ 4.后处理(去重+扩展) ──→ 5.判定与分级(判定强/弱/无匹配) ──→ 6.自检与生成(校验原文引用并按模板输出)
|
|
19
|
-
│ ↑
|
|
20
|
-
│ 非技术 │
|
|
21
|
-
▼ │
|
|
22
|
-
引导话术结束
|
|
102
|
+
1.问题归类 ──→ 2.Query改写 ──→ 3.检索(auto: vector 或 grep) ──→ 4.后处理(去重+扩展) ──→ 5.判定与分级(判定强/弱/无匹配) ──→ 6.自检与生成(校验原文引用并按模板输出)
|
|
103
|
+
│ ↑ │
|
|
104
|
+
│ 非技术 │ │ 强/弱匹配
|
|
105
|
+
▼ │ ▼
|
|
106
|
+
引导话术结束 └─────────────────────────────────────────────── 无匹配:ReAct 回步骤2,最多3轮;仍无匹配 → 无法回答话术
|
|
23
107
|
```
|
|
24
108
|
|
|
25
109
|
## 标准工作流步骤说明(内部执行,不对外暴露)
|
|
@@ -28,7 +112,7 @@ version: 1.0.0
|
|
|
28
112
|
|------|----------|------|
|
|
29
113
|
| 1 | **问题归类**:判断是否属于平台技术/使用/配置范畴等技术类问题;**非技术** → 使用引导话术结束(不进入检索);否则进入步骤 2 | [response-templates 四](templates/response-templates.md#四非技术问题引导)、[指南 步骤1](references/retrieval-guide.md#步骤-1-问题归类) |
|
|
30
114
|
| 2 | **Query 改写**:口语→文档表述、同义词;按需 HyDE / Step-Back / 意图分解 等 | [指南 步骤2](references/retrieval-guide.md#步骤-2-query-改写) |
|
|
31
|
-
| 3 |
|
|
115
|
+
| 3 | **检索**:用户明确指定检索目录时,按指定目录检索;否则按“当前对话目录 → 安装时配置目录 → 内置 docs/”优先级检索。若已配置向量模型,优先走向量召回;失败时回退 grep | [指南 步骤3](references/retrieval-guide.md#步骤-3-检索) |
|
|
32
116
|
| 4 | **后处理**:去重 + 上下文扩展 | [指南 步骤4](references/retrieval-guide.md#步骤-4-后处理) |
|
|
33
117
|
| 5 | **判定与分级**:已匹配材料与原问题强/弱/无匹配;**无匹配 → ReAct 回步骤2**(最多3轮),仍无匹配→无法回答 | [指南 步骤5](references/retrieval-guide.md#步骤-5-判定与分级)、下表 |
|
|
34
118
|
| 6 | **自检与生成**:严格校验是否有原文引用,按强/弱匹配 模板输出| [response-templates](templates/response-templates.md) |
|
|
@@ -56,7 +140,10 @@ version: 1.0.0
|
|
|
56
140
|
|
|
57
141
|
## 附加资源
|
|
58
142
|
|
|
59
|
-
-
|
|
143
|
+
- **默认文档路径**:`smart-customer-service/docs/`
|
|
144
|
+
- **推荐外部知识目录配置项**:`SMART_CS_DOCS_DIR`
|
|
145
|
+
- **推荐向量配置项**:`SMART_CS_VECTOR_BASE_URL`、`SMART_CS_VECTOR_API_KEY`、`SMART_CS_VECTOR_MODEL`
|
|
146
|
+
- **安装器配置文件**:`~/.openclaw/codewave-toolkit.json`
|
|
60
147
|
- 文档目录:[docs/README.md](docs/README.md)
|
|
61
148
|
- 应答模板:[templates/response-templates.md](templates/response-templates.md)
|
|
62
149
|
- 检索指南:[references/retrieval-guide.md](references/retrieval-guide.md)
|