kc-beta 0.2.1 → 0.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/package.json +1 -1
- package/src/agent/context.js +8 -4
- package/src/agent/engine.js +65 -9
- package/src/agent/pipelines/initializer.js +53 -8
- package/src/agent/session-state.js +1 -0
- package/src/agent/skill-loader.js +13 -1
- package/src/agent/tools/document-parse.js +104 -21
- package/src/agent/tools/document-search.js +24 -8
- package/src/agent/tools/sandbox-exec.js +16 -5
- package/src/agent/tools/workspace-file.js +47 -20
- package/src/agent/workspace.js +24 -1
- package/src/cli/components.js +8 -1
- package/src/cli/config.js +100 -6
- package/src/cli/index.js +14 -1
- package/src/cli/onboard.js +70 -1
- package/src/config.js +43 -3
- package/src/model-tiers.json +153 -0
- package/src/providers.js +63 -66
- package/template/AGENT.md +20 -0
- package/template/skills/en/meta/compliance-judgment/SKILL.md +10 -42
- package/template/skills/en/meta/document-chunking/SKILL.md +32 -0
- package/template/skills/en/meta/document-parsing/SKILL.md +11 -18
- package/template/skills/en/meta/entity-extraction/SKILL.md +13 -28
- package/template/skills/en/meta/tree-processing/SKILL.md +19 -1
- package/template/skills/en/meta-meta/auto-model-selection/SKILL.md +53 -0
- package/template/skills/en/meta-meta/pdf-review-dashboard/SKILL.md +57 -0
- package/template/skills/en/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
- package/template/skills/en/meta-meta/rule-extraction/SKILL.md +24 -1
- package/template/skills/en/meta-meta/skill-authoring/SKILL.md +6 -0
- package/template/skills/en/meta-meta/skill-to-workflow/SKILL.md +4 -0
- package/template/skills/zh/meta/compliance-judgment/SKILL.md +41 -262
- package/template/skills/zh/meta/document-chunking/SKILL.md +32 -0
- package/template/skills/zh/meta/document-parsing/SKILL.md +65 -132
- package/template/skills/zh/meta/entity-extraction/SKILL.md +68 -230
- package/template/skills/zh/meta/tree-processing/SKILL.md +82 -194
- package/template/skills/zh/meta-meta/auto-model-selection/SKILL.md +51 -0
- package/template/skills/zh/meta-meta/pdf-review-dashboard/SKILL.md +55 -0
- package/template/skills/zh/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
- package/template/skills/zh/meta-meta/rule-extraction/SKILL.md +79 -164
- package/template/skills/zh/meta-meta/skill-authoring/SKILL.md +64 -185
- package/template/skills/zh/meta-meta/skill-to-workflow/SKILL.md +95 -216
|
@@ -5,9 +5,9 @@ import { BaseTool, ToolResult } from "./base.js";
|
|
|
5
5
|
const MAX_READ = 50_000;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Read, write, or list files in the workspace directory.
|
|
9
|
-
* All paths are resolved relative to the
|
|
10
|
-
* traversal protection. VersionManager hooks into writes for automatic versioning.
|
|
8
|
+
* Read, write, or list files in the workspace or project directory.
|
|
9
|
+
* All paths are resolved relative to the chosen scope with
|
|
10
|
+
* traversal protection. VersionManager hooks into workspace writes for automatic versioning.
|
|
11
11
|
*/
|
|
12
12
|
export class WorkspaceFileTool extends BaseTool {
|
|
13
13
|
/**
|
|
@@ -24,9 +24,10 @@ export class WorkspaceFileTool extends BaseTool {
|
|
|
24
24
|
|
|
25
25
|
get description() {
|
|
26
26
|
return (
|
|
27
|
-
"Read, write, or list files
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"Read, write, or list files. " +
|
|
28
|
+
"scope='workspace' (default): KC's working directory for rules, skills, workflows, results. " +
|
|
29
|
+
"scope='project': the user's project folder where KC was launched — source regulations and samples live here. " +
|
|
30
|
+
"Operations: read (returns file content), write (creates/overwrites a file), list (shows directory contents)."
|
|
30
31
|
);
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -41,34 +42,58 @@ export class WorkspaceFileTool extends BaseTool {
|
|
|
41
42
|
},
|
|
42
43
|
path: {
|
|
43
44
|
type: "string",
|
|
44
|
-
description: "Relative path within the
|
|
45
|
+
description: "Relative path within the chosen scope. Defaults to '.' for list.",
|
|
45
46
|
},
|
|
46
47
|
content: {
|
|
47
48
|
type: "string",
|
|
48
49
|
description: "File content to write (required for write operation)",
|
|
49
50
|
},
|
|
51
|
+
scope: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["workspace", "project"],
|
|
54
|
+
description: "Which directory to operate in. 'workspace' (default) = KC's workspace. 'project' = user's project directory.",
|
|
55
|
+
},
|
|
50
56
|
},
|
|
51
57
|
required: ["operation"],
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
_resolveForScope(filePath, scope) {
|
|
62
|
+
if (scope === "project") {
|
|
63
|
+
return this._workspace.resolveProjectPath(filePath);
|
|
64
|
+
}
|
|
65
|
+
return this._workspace.resolvePath(filePath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_baseForScope(scope) {
|
|
69
|
+
if (scope === "project") {
|
|
70
|
+
return this._workspace.projectDir;
|
|
71
|
+
}
|
|
72
|
+
return this._workspace.cwd;
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
async execute(input) {
|
|
56
76
|
const op = input.operation || "";
|
|
57
77
|
const filePath = input.path || ".";
|
|
58
78
|
const content = input.content || "";
|
|
79
|
+
const scope = input.scope || "workspace";
|
|
80
|
+
|
|
81
|
+
if (scope === "project" && !this._workspace.projectDir) {
|
|
82
|
+
return new ToolResult("No project directory available. KC was launched without a project context.", true);
|
|
83
|
+
}
|
|
59
84
|
|
|
60
85
|
try {
|
|
61
|
-
if (op === "read") return this._read(filePath);
|
|
62
|
-
if (op === "write") return this._write(filePath, content);
|
|
63
|
-
if (op === "list") return this._list(filePath);
|
|
86
|
+
if (op === "read") return this._read(filePath, scope);
|
|
87
|
+
if (op === "write") return this._write(filePath, content, scope);
|
|
88
|
+
if (op === "list") return this._list(filePath, scope);
|
|
64
89
|
return new ToolResult(`Unknown operation: ${op}`, true);
|
|
65
90
|
} catch (err) {
|
|
66
91
|
return new ToolResult(`File error: ${err.message}`, true);
|
|
67
92
|
}
|
|
68
93
|
}
|
|
69
94
|
|
|
70
|
-
_read(filePath) {
|
|
71
|
-
const resolved = this.
|
|
95
|
+
_read(filePath, scope) {
|
|
96
|
+
const resolved = this._resolveForScope(filePath, scope);
|
|
72
97
|
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
|
|
73
98
|
return new ToolResult(`File not found: ${filePath}`, true);
|
|
74
99
|
}
|
|
@@ -79,27 +104,28 @@ export class WorkspaceFileTool extends BaseTool {
|
|
|
79
104
|
return new ToolResult(text);
|
|
80
105
|
}
|
|
81
106
|
|
|
82
|
-
_write(filePath, content) {
|
|
107
|
+
_write(filePath, content, scope) {
|
|
83
108
|
if (!filePath || filePath === ".") {
|
|
84
109
|
return new ToolResult("Path required for write operation", true);
|
|
85
110
|
}
|
|
86
|
-
const resolved = this.
|
|
111
|
+
const resolved = this._resolveForScope(filePath, scope);
|
|
87
112
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
88
113
|
fs.writeFileSync(resolved, content, "utf-8");
|
|
89
114
|
|
|
90
|
-
// Version tracking
|
|
115
|
+
// Version tracking only for workspace writes
|
|
91
116
|
let traceId = null;
|
|
92
|
-
if (this._versionManager) {
|
|
117
|
+
if (scope === "workspace" && this._versionManager) {
|
|
93
118
|
traceId = this._versionManager.onWrite(filePath, content);
|
|
94
119
|
}
|
|
95
120
|
|
|
96
|
-
|
|
121
|
+
const label = scope === "project" ? `[project] ${filePath}` : filePath;
|
|
122
|
+
let msg = `Wrote ${content.length} chars to ${label}`;
|
|
97
123
|
if (traceId) msg += ` [trace: ${traceId}]`;
|
|
98
124
|
return new ToolResult(msg);
|
|
99
125
|
}
|
|
100
126
|
|
|
101
|
-
_list(filePath) {
|
|
102
|
-
const resolved = this.
|
|
127
|
+
_list(filePath, scope) {
|
|
128
|
+
const resolved = this._resolveForScope(filePath, scope);
|
|
103
129
|
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
|
|
104
130
|
return new ToolResult(`Not a directory: ${filePath}`, true);
|
|
105
131
|
}
|
|
@@ -112,8 +138,9 @@ export class WorkspaceFileTool extends BaseTool {
|
|
|
112
138
|
if (entries.length === 0) {
|
|
113
139
|
return new ToolResult("(empty directory)");
|
|
114
140
|
}
|
|
141
|
+
const base = this._baseForScope(scope);
|
|
115
142
|
const lines = entries.map((e) => {
|
|
116
|
-
const rel = path.relative(
|
|
143
|
+
const rel = path.relative(base, path.join(resolved, e.name));
|
|
117
144
|
const marker = e.isDirectory() ? "[dir] " : " ";
|
|
118
145
|
return `${marker}${rel}`;
|
|
119
146
|
});
|
package/src/agent/workspace.js
CHANGED
|
@@ -11,11 +11,13 @@ export class Workspace {
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {string} root - Workspace root directory
|
|
13
13
|
* @param {string} [sessionId] - Session identifier (auto-generated if omitted)
|
|
14
|
+
* @param {string} [projectDir] - User's project directory (CWD at launch)
|
|
14
15
|
*/
|
|
15
|
-
constructor(root, sessionId) {
|
|
16
|
+
constructor(root, sessionId, projectDir) {
|
|
16
17
|
this.root = path.resolve(root);
|
|
17
18
|
this.sessionId = sessionId || crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
18
19
|
this.path = path.resolve(this.root, this.sessionId);
|
|
20
|
+
this.projectDir = projectDir ? path.resolve(projectDir) : null;
|
|
19
21
|
fs.mkdirSync(this.path, { recursive: true });
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -42,6 +44,27 @@ export class Workspace {
|
|
|
42
44
|
return resolved;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a user-supplied relative path against the project directory.
|
|
49
|
+
* Same traversal protection as resolvePath() but for the project folder.
|
|
50
|
+
* @param {string} userPath
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
resolveProjectPath(userPath) {
|
|
54
|
+
if (!this.projectDir) {
|
|
55
|
+
throw new Error("No project directory available");
|
|
56
|
+
}
|
|
57
|
+
if (path.isAbsolute(userPath)) {
|
|
58
|
+
throw new Error(`Absolute paths not allowed: ${userPath}`);
|
|
59
|
+
}
|
|
60
|
+
const resolved = path.resolve(this.projectDir, userPath);
|
|
61
|
+
const base = path.resolve(this.projectDir);
|
|
62
|
+
if (resolved !== base && !resolved.startsWith(base + path.sep)) {
|
|
63
|
+
throw new Error(`Path escapes project directory: ${userPath}`);
|
|
64
|
+
}
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
/**
|
|
46
69
|
* Rename the workspace folder. Returns the new sessionId.
|
|
47
70
|
* @param {string} newName
|
package/src/cli/components.js
CHANGED
|
@@ -54,7 +54,7 @@ export function StatusBar({ sessionId, phase, contextTokens, contextLimit }) {
|
|
|
54
54
|
|
|
55
55
|
// --- Welcome banner ---
|
|
56
56
|
|
|
57
|
-
export function WelcomeBanner() {
|
|
57
|
+
export function WelcomeBanner({ projectDir } = {}) {
|
|
58
58
|
return h(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "gray", paddingLeft: 1, paddingRight: 1 },
|
|
59
59
|
h(Box, null,
|
|
60
60
|
h(Text, { bold: true }, "KC AGENT CLI"),
|
|
@@ -62,6 +62,13 @@ export function WelcomeBanner() {
|
|
|
62
62
|
),
|
|
63
63
|
h(Text, { dimColor: true }, "Hope you never know what KC was."),
|
|
64
64
|
h(Text, null, ""),
|
|
65
|
+
projectDir
|
|
66
|
+
? h(Box, { flexDirection: "column" },
|
|
67
|
+
h(Text, { dimColor: true }, `Project: ${projectDir}`),
|
|
68
|
+
h(Text, { color: "yellow", dimColor: true }, "KC has full read/write access to this directory. We recommend backing up important files."),
|
|
69
|
+
)
|
|
70
|
+
: null,
|
|
71
|
+
h(Text, null, ""),
|
|
65
72
|
h(Text, { dimColor: true }, "Product of Memium / kitchen-engineer42"),
|
|
66
73
|
);
|
|
67
74
|
}
|
package/src/cli/config.js
CHANGED
|
@@ -23,7 +23,7 @@ const L = {
|
|
|
23
23
|
noConfig: "No config found. Run 'kc-beta onboard' first.",
|
|
24
24
|
menu: "Configuration Categories",
|
|
25
25
|
choose: "Choose category (q to quit)",
|
|
26
|
-
categories: ["LLM Provider & API Key", "Model Tiers", "Quality Thresholds", "Language"],
|
|
26
|
+
categories: ["LLM Provider & API Key", "Model Tiers", "VLM Tiers (Vision/OCR)", "Worker LLM Provider", "Quality Thresholds", "Language"],
|
|
27
27
|
saved: "Saved.",
|
|
28
28
|
back: "← Back to menu",
|
|
29
29
|
enterKeep: "Press Enter to keep",
|
|
@@ -41,7 +41,7 @@ const L = {
|
|
|
41
41
|
noConfig: "未找到配置。请先运行 'kc-beta onboard'。",
|
|
42
42
|
menu: "配置类别",
|
|
43
43
|
choose: "选择类别(q 退出)",
|
|
44
|
-
categories: ["大模型服务商 & API 密钥", "模型分层", "质量阈值", "语言"],
|
|
44
|
+
categories: ["大模型服务商 & API 密钥", "模型分层", "VLM 视觉模型分层", "Worker LLM 服务商", "质量阈值", "语言"],
|
|
45
45
|
saved: "已保存。",
|
|
46
46
|
back: "← 返回菜单",
|
|
47
47
|
enterKeep: "回车保留当前值",
|
|
@@ -118,11 +118,26 @@ async function editProvider(rl, config, t) {
|
|
|
118
118
|
// Base URL
|
|
119
119
|
if (provider.id === "custom") {
|
|
120
120
|
config.base_url = await ask(rl, ` ${CYAN}${t.baseUrl}${RESET}`, config.base_url || "");
|
|
121
|
+
} else if (provider.supportsCodingPlanKey) {
|
|
122
|
+
// Providers with coding plan support — ask which key type
|
|
123
|
+
const keyTypeLabel = config.language === "zh" ? "密钥类型" : "Key Type";
|
|
124
|
+
const opt1 = config.language === "zh" ? "API Key(按量付费)" : "API Key (pay-per-use)";
|
|
125
|
+
const opt2 = config.language === "zh" ? "Coding Plan Key(包年包月)" : "Coding Plan Key (subscription)";
|
|
126
|
+
console.log(` ${CYAN}${keyTypeLabel}:${RESET}`);
|
|
127
|
+
const isCodingPlan = config.base_url === provider.codingPlanUrl;
|
|
128
|
+
console.log(` 1. ${opt1}${!isCodingPlan ? ` ${GREEN}(${t.currentValue})${RESET}` : ""}`);
|
|
129
|
+
console.log(` 2. ${opt2}${isCodingPlan ? ` ${GREEN}(${t.currentValue})${RESET}` : ""}`);
|
|
130
|
+
const keyTypeDefault = isCodingPlan ? "2" : "1";
|
|
131
|
+
const keyTypeChoice = await ask(rl, ` ${GRAY}>${RESET}`, keyTypeDefault);
|
|
132
|
+
config.base_url = keyTypeChoice === "2" ? provider.codingPlanUrl : provider.baseUrl;
|
|
133
|
+
console.log(` ${CYAN}${t.baseUrl}${RESET}: ${DIM}${config.base_url}${RESET}`);
|
|
121
134
|
} else {
|
|
135
|
+
// Keep existing base_url if it matches this provider, otherwise use default
|
|
122
136
|
const defaultUrl = provider.baseUrl;
|
|
123
137
|
console.log(` ${CYAN}${t.baseUrl}${RESET}: ${DIM}${defaultUrl}${RESET}`);
|
|
124
138
|
config.base_url = defaultUrl;
|
|
125
139
|
}
|
|
140
|
+
console.log();
|
|
126
141
|
|
|
127
142
|
// API Key
|
|
128
143
|
const masked = maskKey(config.api_key);
|
|
@@ -161,14 +176,93 @@ async function editTiers(rl, config, t) {
|
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
/**
|
|
164
|
-
* Category 3:
|
|
179
|
+
* Category 3: VLM Tiers (Vision/OCR)
|
|
165
180
|
*/
|
|
166
|
-
async function
|
|
181
|
+
async function editVlmTiers(rl, config, t) {
|
|
167
182
|
console.log();
|
|
168
183
|
console.log(` ${BOLD}${t.categories[2]}${RESET}`);
|
|
169
184
|
console.log(` ${GRAY}${"─".repeat(35)}${RESET}`);
|
|
170
185
|
console.log();
|
|
171
186
|
|
|
187
|
+
const vlmTiers = config.vlm_tiers || {};
|
|
188
|
+
const provider = getProviderById(config.provider);
|
|
189
|
+
const defaults = provider?.defaultVlm || {};
|
|
190
|
+
|
|
191
|
+
for (const tier of ["tier1", "tier2", "tier3"]) {
|
|
192
|
+
const current = vlmTiers[tier] || defaults[tier] || "";
|
|
193
|
+
vlmTiers[tier] = await ask(rl, ` ${CYAN}${tier.toUpperCase()}${RESET}`, current, t.enterKeep);
|
|
194
|
+
}
|
|
195
|
+
config.vlm_tiers = vlmTiers;
|
|
196
|
+
console.log();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Category 4: Worker LLM Provider
|
|
201
|
+
*/
|
|
202
|
+
async function editWorkerProvider(rl, config, t) {
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(` ${BOLD}${t.categories[3]}${RESET}`);
|
|
205
|
+
console.log(` ${GRAY}${"─".repeat(35)}${RESET}`);
|
|
206
|
+
console.log();
|
|
207
|
+
|
|
208
|
+
const currentWorker = config.worker_provider || "";
|
|
209
|
+
const statusLabel = currentWorker
|
|
210
|
+
? `${currentWorker} (${getProviderById(currentWorker)?.name || "unknown"})`
|
|
211
|
+
: config.language === "zh" ? "(与主服务商相同)" : "(same as conductor)";
|
|
212
|
+
console.log(` ${DIM}${t.currentValue}: ${statusLabel}${RESET}`);
|
|
213
|
+
console.log();
|
|
214
|
+
|
|
215
|
+
const sameLabel = config.language === "zh" ? "使用与主服务商相同配置?" : "Use same provider as conductor?";
|
|
216
|
+
const sameChoice = await ask(rl, ` ${sameLabel}`, "Y", "Y/n");
|
|
217
|
+
|
|
218
|
+
if (sameChoice.toLowerCase() === "n" || sameChoice.toLowerCase() === "no") {
|
|
219
|
+
const providers = getProviders();
|
|
220
|
+
const labels = getProviderLabels(config.language || "en");
|
|
221
|
+
console.log();
|
|
222
|
+
console.log(` ${CYAN}${t.categories[3]}:${RESET}`);
|
|
223
|
+
for (let i = 0; i < labels.length; i++) {
|
|
224
|
+
const marker = providers[i].id === config.worker_provider ? ` ${GREEN}(${t.currentValue})${RESET}` : "";
|
|
225
|
+
console.log(` ${i + 1}. ${labels[i].label}${marker}`);
|
|
226
|
+
}
|
|
227
|
+
const wIdx = parseInt(await ask(rl, ` ${GRAY}>${RESET}`, "1"), 10) - 1;
|
|
228
|
+
const wp = providers[Math.max(0, Math.min(wIdx, providers.length - 1))];
|
|
229
|
+
config.worker_provider = wp.id;
|
|
230
|
+
config.worker_auth_type = wp.authType;
|
|
231
|
+
config.worker_api_format = wp.apiFormat;
|
|
232
|
+
|
|
233
|
+
if (wp.id === "custom") {
|
|
234
|
+
config.worker_base_url = await ask(rl, ` ${CYAN}Base URL${RESET}`, config.worker_base_url || "");
|
|
235
|
+
} else {
|
|
236
|
+
config.worker_base_url = wp.baseUrl;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Worker API Key
|
|
240
|
+
const masked = maskKey(config.worker_api_key);
|
|
241
|
+
const keyPrompt = masked
|
|
242
|
+
? ` ${CYAN}API Key (Worker)${RESET} ${DIM}(${masked})${RESET}`
|
|
243
|
+
: ` ${CYAN}API Key (Worker)${RESET}`;
|
|
244
|
+
const newKey = await ask(rl, keyPrompt, "", masked ? t.enterKeep : "");
|
|
245
|
+
if (newKey) config.worker_api_key = newKey;
|
|
246
|
+
} else {
|
|
247
|
+
// Clear worker-specific config (use conductor)
|
|
248
|
+
config.worker_provider = "";
|
|
249
|
+
config.worker_api_key = "";
|
|
250
|
+
config.worker_base_url = "";
|
|
251
|
+
config.worker_auth_type = "";
|
|
252
|
+
config.worker_api_format = "";
|
|
253
|
+
}
|
|
254
|
+
console.log();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Category 5: Quality Thresholds
|
|
259
|
+
*/
|
|
260
|
+
async function editThresholds(rl, config, t) {
|
|
261
|
+
console.log();
|
|
262
|
+
console.log(` ${BOLD}${t.categories[4]}${RESET}`);
|
|
263
|
+
console.log(` ${GRAY}${"─".repeat(35)}${RESET}`);
|
|
264
|
+
console.log();
|
|
265
|
+
|
|
172
266
|
config.accuracy_threshold = parseFloat(
|
|
173
267
|
await ask(rl, ` ${CYAN}Accuracy threshold${RESET}`, String(config.accuracy_threshold ?? 0.9), t.enterKeep)
|
|
174
268
|
);
|
|
@@ -189,7 +283,7 @@ async function editThresholds(rl, config, t) {
|
|
|
189
283
|
*/
|
|
190
284
|
async function editLanguage(rl, config, t) {
|
|
191
285
|
console.log();
|
|
192
|
-
console.log(` ${BOLD}${t.categories[
|
|
286
|
+
console.log(` ${BOLD}${t.categories[5]}${RESET}`);
|
|
193
287
|
console.log(` ${GRAY}${"─".repeat(35)}${RESET}`);
|
|
194
288
|
console.log();
|
|
195
289
|
|
|
@@ -202,7 +296,7 @@ async function editLanguage(rl, config, t) {
|
|
|
202
296
|
console.log();
|
|
203
297
|
}
|
|
204
298
|
|
|
205
|
-
const CATEGORY_HANDLERS = [editProvider, editTiers, editThresholds, editLanguage];
|
|
299
|
+
const CATEGORY_HANDLERS = [editProvider, editTiers, editVlmTiers, editWorkerProvider, editThresholds, editLanguage];
|
|
206
300
|
|
|
207
301
|
/**
|
|
208
302
|
* Main config editor loop.
|
package/src/cli/index.js
CHANGED
|
@@ -163,6 +163,7 @@ function App({ engine, config }) {
|
|
|
163
163
|
`Model: ${config.kcModel}\n` +
|
|
164
164
|
`Provider: ${config.provider || "unknown"}\n` +
|
|
165
165
|
`LLM URL: ${config.llmBaseUrl}\n` +
|
|
166
|
+
`Project: ${engineRef.current.workspace.projectDir || "(none)"}\n` +
|
|
166
167
|
`Workspace: ${engineRef.current.workspace.cwd}\n` +
|
|
167
168
|
`Tools: ${engineRef.current.toolRegistry.size} registered\n` +
|
|
168
169
|
`History: ${engineRef.current.history.messages.length} messages\n` +
|
|
@@ -320,7 +321,7 @@ function App({ engine, config }) {
|
|
|
320
321
|
|
|
321
322
|
return h(Box, { flexDirection: "column" },
|
|
322
323
|
// Welcome banner
|
|
323
|
-
showWelcome ? h(WelcomeBanner) : null,
|
|
324
|
+
showWelcome ? h(WelcomeBanner, { projectDir: config.projectDir }) : null,
|
|
324
325
|
|
|
325
326
|
// Message history
|
|
326
327
|
...messages.map((msg, i) => {
|
|
@@ -389,6 +390,9 @@ function App({ engine, config }) {
|
|
|
389
390
|
export async function main({ languageOverride } = {}) {
|
|
390
391
|
const config = loadSettings();
|
|
391
392
|
|
|
393
|
+
// Capture user's project directory (CWD at launch)
|
|
394
|
+
config.projectDir = process.cwd();
|
|
395
|
+
|
|
392
396
|
// Session-only language override (does NOT persist to config)
|
|
393
397
|
if (languageOverride) {
|
|
394
398
|
config.language = languageOverride;
|
|
@@ -399,6 +403,15 @@ export async function main({ languageOverride } = {}) {
|
|
|
399
403
|
process.exit(1);
|
|
400
404
|
}
|
|
401
405
|
|
|
406
|
+
// Warn if all worker LLM tiers are blank
|
|
407
|
+
const allTiersBlank = !config.tier1 && !config.tier2 && !config.tier3 && !config.tier4;
|
|
408
|
+
if (allTiersBlank) {
|
|
409
|
+
const msg = config.language === "zh"
|
|
410
|
+
? " ⚠ 所有 Worker LLM 分层为空。DISTILL 模式将不可用。运行 'kc-beta config' 或 'kc-beta onboard' 配置模型分层。"
|
|
411
|
+
: " ⚠ All worker LLM tiers are blank. DISTILL mode will not work. Run 'kc-beta config' or 'kc-beta onboard' to configure model tiers.";
|
|
412
|
+
console.log(`\x1b[33m${msg}\x1b[0m\n`);
|
|
413
|
+
}
|
|
414
|
+
|
|
402
415
|
const client = new LLMClient({
|
|
403
416
|
apiKey: config.llmApiKey,
|
|
404
417
|
baseUrl: config.llmBaseUrl,
|
package/src/cli/onboard.js
CHANGED
|
@@ -37,7 +37,11 @@ const L = {
|
|
|
37
37
|
keyTypeOptions: ["API Key (pay-per-use)", "Coding Plan Key (subscription)"],
|
|
38
38
|
conductorModel: "Conductor Model",
|
|
39
39
|
workerTiers: "Worker LLM Tiers",
|
|
40
|
+
vlmTiers: "VLM Tiers (Vision/OCR)",
|
|
40
41
|
tierHint: "Press Enter to accept defaults",
|
|
42
|
+
workerConfig: "Worker LLM Provider",
|
|
43
|
+
workerSameProvider: "Use same provider for worker LLMs?",
|
|
44
|
+
yesNo: "Y/n",
|
|
41
45
|
accuracy: "Accuracy Threshold",
|
|
42
46
|
saved: "Saved to",
|
|
43
47
|
runHint: "Run {cmd} to start the agent.",
|
|
@@ -67,7 +71,11 @@ const L = {
|
|
|
67
71
|
keyTypeOptions: ["API Key(按量付费)", "Coding Plan Key(包年包月)"],
|
|
68
72
|
conductorModel: "主模型",
|
|
69
73
|
workerTiers: "Worker 模型分层",
|
|
74
|
+
vlmTiers: "VLM 视觉模型分层(OCR)",
|
|
70
75
|
tierHint: "回车接受默认值",
|
|
76
|
+
workerConfig: "Worker LLM 服务商",
|
|
77
|
+
workerSameProvider: "Worker LLM 使用同一服务商?",
|
|
78
|
+
yesNo: "Y/n",
|
|
71
79
|
accuracy: "准确率阈值",
|
|
72
80
|
saved: "已保存至",
|
|
73
81
|
runHint: "运行 {cmd} 启动 Agent。",
|
|
@@ -237,7 +245,7 @@ export async function onboard() {
|
|
|
237
245
|
);
|
|
238
246
|
console.log();
|
|
239
247
|
|
|
240
|
-
// --- Worker tiers ---
|
|
248
|
+
// --- Worker LLM tiers ---
|
|
241
249
|
console.log(` ${CYAN}${t.workerTiers}${RESET} ${DIM}(${t.tierHint})${RESET}`);
|
|
242
250
|
const tiers = {};
|
|
243
251
|
for (const tier of ["tier1", "tier2", "tier3", "tier4"]) {
|
|
@@ -251,6 +259,59 @@ export async function onboard() {
|
|
|
251
259
|
}
|
|
252
260
|
console.log();
|
|
253
261
|
|
|
262
|
+
// --- VLM tiers (vision/OCR) ---
|
|
263
|
+
console.log(` ${CYAN}${t.vlmTiers}${RESET} ${DIM}(${t.tierHint})${RESET}`);
|
|
264
|
+
const vlmTiers = {};
|
|
265
|
+
for (const tier of ["tier1", "tier2", "tier3"]) {
|
|
266
|
+
const def = provider.defaultVlm?.[tier] || existing?.vlm_tiers?.[tier] || "";
|
|
267
|
+
vlmTiers[tier] = await ask(
|
|
268
|
+
rl,
|
|
269
|
+
` ${tier.toUpperCase()}`,
|
|
270
|
+
def,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
console.log();
|
|
274
|
+
|
|
275
|
+
// --- Worker LLM provider (optional) ---
|
|
276
|
+
console.log(` ${CYAN}${t.workerConfig}${RESET}`);
|
|
277
|
+
const sameProvider = await ask(rl, ` ${t.workerSameProvider}`, "Y", t.yesNo);
|
|
278
|
+
let workerProvider = "";
|
|
279
|
+
let workerApiKey = "";
|
|
280
|
+
let workerBaseUrl = "";
|
|
281
|
+
let workerAuthType = "";
|
|
282
|
+
let workerApiFormat = "";
|
|
283
|
+
|
|
284
|
+
if (sameProvider.toLowerCase() === "n" || sameProvider.toLowerCase() === "no") {
|
|
285
|
+
// Pick a different provider for workers
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(` ${CYAN}${t.providerPrompt} (Worker):${RESET}`);
|
|
288
|
+
for (let i = 0; i < labels.length; i++) {
|
|
289
|
+
console.log(` ${i + 1}. ${labels[i].label}`);
|
|
290
|
+
}
|
|
291
|
+
const wIdx = parseInt(await ask(rl, ` ${GRAY}>${RESET} ${t.choose}`, "1"), 10) - 1;
|
|
292
|
+
const wp = providers[Math.max(0, Math.min(wIdx, providers.length - 1))];
|
|
293
|
+
workerProvider = wp.id;
|
|
294
|
+
workerAuthType = wp.authType;
|
|
295
|
+
workerApiFormat = wp.apiFormat;
|
|
296
|
+
workerBaseUrl = wp.baseUrl;
|
|
297
|
+
|
|
298
|
+
if (wp.id === "custom") {
|
|
299
|
+
workerBaseUrl = await ask(rl, ` ${t.baseUrl}`, existing.worker_base_url || "");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Worker API key
|
|
303
|
+
const wMasked = existing.worker_api_key ? existing.worker_api_key.slice(0, 6) + "..." + existing.worker_api_key.slice(-4) : "";
|
|
304
|
+
const wKeyHint = wMasked ? t.apiKeyKeep : t.apiKeyRequired;
|
|
305
|
+
workerApiKey = await ask(
|
|
306
|
+
rl,
|
|
307
|
+
` ${CYAN}${t.apiKey} (Worker)${RESET}`,
|
|
308
|
+
"",
|
|
309
|
+
wKeyHint,
|
|
310
|
+
);
|
|
311
|
+
workerApiKey = workerApiKey || existing.worker_api_key || "";
|
|
312
|
+
}
|
|
313
|
+
console.log();
|
|
314
|
+
|
|
254
315
|
rl.close();
|
|
255
316
|
|
|
256
317
|
// Preserve existing thresholds or set defaults (editable via 'kc-beta config')
|
|
@@ -268,6 +329,14 @@ export async function onboard() {
|
|
|
268
329
|
api_format: provider.apiFormat,
|
|
269
330
|
conductor_model: model,
|
|
270
331
|
tiers,
|
|
332
|
+
vlm_tiers: vlmTiers,
|
|
333
|
+
// Worker LLM (optional — empty means use conductor config)
|
|
334
|
+
worker_provider: workerProvider,
|
|
335
|
+
worker_api_key: workerApiKey,
|
|
336
|
+
worker_base_url: workerBaseUrl,
|
|
337
|
+
worker_auth_type: workerAuthType,
|
|
338
|
+
worker_api_format: workerApiFormat,
|
|
339
|
+
// Thresholds
|
|
271
340
|
accuracy_threshold: accuracy,
|
|
272
341
|
systemic_threshold: systemicThreshold,
|
|
273
342
|
spot_check_rate: spotCheckRate,
|
package/src/config.js
CHANGED
|
@@ -55,7 +55,7 @@ export function loadSettings(workspacePath) {
|
|
|
55
55
|
const provider = gc.provider || "siliconflow";
|
|
56
56
|
const providerDef = getProviderById(provider);
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const settings = {
|
|
59
59
|
// Provider identity
|
|
60
60
|
provider,
|
|
61
61
|
authType: gc.auth_type || providerDef?.authType || "bearer",
|
|
@@ -73,8 +73,17 @@ export function loadSettings(workspacePath) {
|
|
|
73
73
|
tier3: env.TIER3 || gc.tiers?.tier3 || "",
|
|
74
74
|
tier4: env.TIER4 || gc.tiers?.tier4 || "",
|
|
75
75
|
|
|
76
|
-
// OCR models
|
|
77
|
-
|
|
76
|
+
// VLM tiers (vision/OCR models)
|
|
77
|
+
vlmTier1: env.VLM_TIER1 || gc.vlm_tiers?.tier1 || "",
|
|
78
|
+
vlmTier2: env.VLM_TIER2 || gc.vlm_tiers?.tier2 || "",
|
|
79
|
+
vlmTier3: env.VLM_TIER3 || gc.vlm_tiers?.tier3 || "",
|
|
80
|
+
|
|
81
|
+
// Worker LLM — optional, defaults to conductor config
|
|
82
|
+
workerProvider: gc.worker_provider || "",
|
|
83
|
+
workerApiKey: env.WORKER_API_KEY || gc.worker_api_key || "",
|
|
84
|
+
workerBaseUrl: env.WORKER_BASE_URL || gc.worker_base_url || "",
|
|
85
|
+
workerAuthType: gc.worker_auth_type || "",
|
|
86
|
+
workerApiFormat: gc.worker_api_format || "",
|
|
78
87
|
|
|
79
88
|
// Document parsing
|
|
80
89
|
mineruApiUrl: env.MINERU_API_URL || "",
|
|
@@ -106,6 +115,37 @@ export function loadSettings(workspacePath) {
|
|
|
106
115
|
// Language
|
|
107
116
|
language: env.LANGUAGE || gc.language || "en",
|
|
108
117
|
};
|
|
118
|
+
|
|
119
|
+
// Effective worker config (falls back to conductor config)
|
|
120
|
+
settings.effectiveWorkerProvider = () => settings.workerProvider || settings.provider;
|
|
121
|
+
settings.effectiveWorkerApiKey = () => settings.workerApiKey || settings.llmApiKey;
|
|
122
|
+
settings.effectiveWorkerBaseUrl = () => {
|
|
123
|
+
if (settings.workerBaseUrl) return settings.workerBaseUrl;
|
|
124
|
+
// If worker uses a different provider, use that provider's default base URL
|
|
125
|
+
if (settings.workerProvider && settings.workerProvider !== settings.provider) {
|
|
126
|
+
const wp = getProviderById(settings.workerProvider);
|
|
127
|
+
return wp?.baseUrl || settings.llmBaseUrl;
|
|
128
|
+
}
|
|
129
|
+
return settings.llmBaseUrl;
|
|
130
|
+
};
|
|
131
|
+
settings.effectiveWorkerAuthType = () => {
|
|
132
|
+
if (settings.workerAuthType) return settings.workerAuthType;
|
|
133
|
+
if (settings.workerProvider && settings.workerProvider !== settings.provider) {
|
|
134
|
+
const wp = getProviderById(settings.workerProvider);
|
|
135
|
+
return wp?.authType || settings.authType;
|
|
136
|
+
}
|
|
137
|
+
return settings.authType;
|
|
138
|
+
};
|
|
139
|
+
settings.effectiveWorkerApiFormat = () => {
|
|
140
|
+
if (settings.workerApiFormat) return settings.workerApiFormat;
|
|
141
|
+
if (settings.workerProvider && settings.workerProvider !== settings.provider) {
|
|
142
|
+
const wp = getProviderById(settings.workerProvider);
|
|
143
|
+
return wp?.apiFormat || settings.apiFormat;
|
|
144
|
+
}
|
|
145
|
+
return settings.apiFormat;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return settings;
|
|
109
149
|
}
|
|
110
150
|
|
|
111
151
|
export { GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_PATH };
|