kc-beta 0.6.2 → 0.7.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.
- package/LICENSE +81 -0
- package/LICENSE-COMMERCIAL.md +125 -0
- package/README.md +21 -3
- package/package.json +14 -5
- package/src/agent/context-window.js +9 -12
- package/src/agent/context.js +14 -1
- package/src/agent/document-parser.js +169 -0
- package/src/agent/engine.js +382 -19
- package/src/agent/history/event-history.js +222 -0
- package/src/agent/llm-client.js +55 -0
- package/src/agent/message-utils.js +63 -0
- package/src/agent/pipelines/_milestone-derive.js +566 -0
- package/src/agent/pipelines/base.js +21 -0
- package/src/agent/pipelines/distillation.js +28 -15
- package/src/agent/pipelines/extraction.js +130 -36
- package/src/agent/pipelines/finalization.js +178 -11
- package/src/agent/pipelines/index.js +6 -1
- package/src/agent/pipelines/initializer.js +74 -8
- package/src/agent/pipelines/production-qc.js +31 -44
- package/src/agent/pipelines/skill-authoring.js +97 -80
- package/src/agent/pipelines/skill-testing.js +106 -23
- package/src/agent/retry.js +10 -2
- package/src/agent/scheduler.js +14 -2
- package/src/agent/session-state.js +18 -1
- package/src/agent/skill-loader.js +13 -7
- package/src/agent/skill-validator.js +19 -5
- package/src/agent/task-manager.js +61 -5
- package/src/agent/tools/document-chunk.js +21 -9
- package/src/agent/tools/phase-advance.js +37 -5
- package/src/agent/tools/release.js +51 -9
- package/src/agent/tools/rule-catalog.js +11 -1
- package/src/agent/tools/workspace-file.js +32 -0
- package/src/agent/workspace.js +39 -1
- package/src/cli/components.js +64 -14
- package/src/cli/index.js +62 -3
- package/src/cli/meme.js +26 -25
- package/src/config.js +65 -22
- package/src/model-tiers.json +24 -8
- package/src/providers.js +42 -0
- package/template/release/v1/README.md.tmpl +108 -0
- package/template/release/v1/catalog.json.tmpl +4 -0
- package/template/release/v1/kc_runtime/__init__.py +11 -0
- package/template/release/v1/kc_runtime/confidence.py +63 -0
- package/template/release/v1/kc_runtime/doc_parser.py +127 -0
- package/template/release/v1/manifest.json.tmpl +11 -0
- package/template/release/v1/render_dashboard.py +117 -0
- package/template/release/v1/run.py +212 -0
- package/template/release/v1/serve.sh +17 -0
- package/template/skills/en/meta-meta/work-decomposition/SKILL.md +326 -0
- package/template/skills/en/skill-creator/SKILL.md +1 -1
- package/template/skills/zh/meta-meta/work-decomposition/SKILL.md +321 -0
- package/template/skills/zh/skill-creator/SKILL.md +1 -1
package/src/cli/index.js
CHANGED
|
@@ -76,6 +76,13 @@ function App({ engine, config }) {
|
|
|
76
76
|
}, []);
|
|
77
77
|
|
|
78
78
|
const addMessage = useCallback((msg) => {
|
|
79
|
+
// v0.7.0 H6: dismiss welcome banner once any real message lands.
|
|
80
|
+
// The banner state was initialized true and never set false — the
|
|
81
|
+
// banner stayed on every frame for the entire session, eating
|
|
82
|
+
// permanent screen real estate. Conditionally clear on first
|
|
83
|
+
// user/agent/tool-result message; system-only messages don't
|
|
84
|
+
// dismiss (they're often just the banner-side info itself).
|
|
85
|
+
if (msg && msg.role !== "system") setShowWelcome(false);
|
|
79
86
|
setMessages((prev) => {
|
|
80
87
|
if (prev.length < MAX_RETAINED_MESSAGES) return [...prev, msg];
|
|
81
88
|
// Cap hit: drop the oldest non-system entry. If everything is system
|
|
@@ -385,7 +392,16 @@ function App({ engine, config }) {
|
|
|
385
392
|
const sched = new Scheduler(engineRef.current.workspace);
|
|
386
393
|
const jobs = sched.list();
|
|
387
394
|
if (jobs.length === 0) {
|
|
388
|
-
|
|
395
|
+
// v0.6.3.1: also surface pending input files. The welcome banner
|
|
396
|
+
// tells the user "run /schedule for details" when input/ has
|
|
397
|
+
// unseen files, but the no-jobs branch used to ignore those —
|
|
398
|
+
// user got a dead-end "no jobs" reply with the files invisible.
|
|
399
|
+
const pending = sched.pendingInputCount();
|
|
400
|
+
const tail = sched.tailLog(8);
|
|
401
|
+
let body = "No scheduled ingestion jobs. Ask KC to set one up via the schedule_fetch tool.";
|
|
402
|
+
if (pending > 0) body += `\n\nPending in input/: ${pending} file(s) (drop into workspace input/ to be picked up).`;
|
|
403
|
+
if (tail) body += `\n\nlogs/ingest.log (last 8):\n${tail}`;
|
|
404
|
+
addMessage({ role: "system", content: body });
|
|
389
405
|
} else {
|
|
390
406
|
const lines = jobs.map((j) => {
|
|
391
407
|
const status = j.enabled ? "✓ enabled" : "· disabled";
|
|
@@ -423,6 +439,11 @@ function App({ engine, config }) {
|
|
|
423
439
|
streamingRef.current = true;
|
|
424
440
|
setStreaming(true);
|
|
425
441
|
setSpinnerStatus("Compacting...");
|
|
442
|
+
// v0.7.0 H7: top-level .catch on the IIFE — the inner try/catch
|
|
443
|
+
// handles the compact() failure path; this tail .catch silences
|
|
444
|
+
// any secondary rejection from the catch handler or finally
|
|
445
|
+
// block (e.g., addMessage throw). Without it, those would be
|
|
446
|
+
// UnhandledPromiseRejection in strict-mode Node.
|
|
426
447
|
(async () => {
|
|
427
448
|
try {
|
|
428
449
|
const result = await engineRef.current.compact();
|
|
@@ -469,7 +490,7 @@ function App({ engine, config }) {
|
|
|
469
490
|
runTurn(next);
|
|
470
491
|
}
|
|
471
492
|
}
|
|
472
|
-
})();
|
|
493
|
+
})().catch(() => { /* H7 defensive tail */ });
|
|
473
494
|
return true;
|
|
474
495
|
}
|
|
475
496
|
|
|
@@ -528,6 +549,10 @@ function App({ engine, config }) {
|
|
|
528
549
|
}
|
|
529
550
|
} else {
|
|
530
551
|
// Resume a previous session
|
|
552
|
+
// v0.7.0 H8: top-level .catch on the IIFE so a throw inside
|
|
553
|
+
// addMessage()/setMessages() (e.g., during the catch handler
|
|
554
|
+
// itself, or in Ink reconciler) doesn't surface as an
|
|
555
|
+
// UnhandledPromiseRejection that crashes Node strict-mode.
|
|
531
556
|
(async () => {
|
|
532
557
|
try {
|
|
533
558
|
const client = new LLMClient({
|
|
@@ -541,6 +566,17 @@ function App({ engine, config }) {
|
|
|
541
566
|
setSessionId(resumed.workspace.sessionId);
|
|
542
567
|
setPhase(resumed.currentPhase);
|
|
543
568
|
setMessages([]);
|
|
569
|
+
// v0.7.0 F2: re-populate TaskBoard state from the resumed
|
|
570
|
+
// engine's TaskManager. Without this, the TUI showed an
|
|
571
|
+
// empty task list after /resume even when tasks.json on
|
|
572
|
+
// disk had pending work. The setTaskList path mirrors what
|
|
573
|
+
// the per-event tasks_progress handler does for live
|
|
574
|
+
// sessions.
|
|
575
|
+
try {
|
|
576
|
+
const tasks = resumed.taskManager.getAllTasks();
|
|
577
|
+
setTaskList(tasks);
|
|
578
|
+
setTaskProgress(resumed.taskManager.progress);
|
|
579
|
+
} catch { /* taskManager unavailable on very old session-state */ }
|
|
544
580
|
addMessage({
|
|
545
581
|
role: "system",
|
|
546
582
|
content:
|
|
@@ -552,7 +588,8 @@ function App({ engine, config }) {
|
|
|
552
588
|
} catch (err) {
|
|
553
589
|
addMessage({ role: "system", content: `Resume failed: ${err.message}` });
|
|
554
590
|
}
|
|
555
|
-
})();
|
|
591
|
+
})().catch(() => { /* defended above; tail catch silences any
|
|
592
|
+
secondary rejection from the catch handler itself */ });
|
|
556
593
|
}
|
|
557
594
|
return true;
|
|
558
595
|
|
|
@@ -562,6 +599,13 @@ function App({ engine, config }) {
|
|
|
562
599
|
try { engineRef.current.saveState(); } catch { /* ignore */ }
|
|
563
600
|
try { engineRef.current.stop(); } catch { /* ignore */ }
|
|
564
601
|
exit();
|
|
602
|
+
// v0.6.3.1: force-exit after a brief grace window. Ink's exit()
|
|
603
|
+
// unmounts the TUI but in-flight LLM streams / subagent fetches
|
|
604
|
+
// / unflushed appendFileSync handles can keep the Node event loop
|
|
605
|
+
// alive indefinitely on long sessions. The 500ms gives saveState
|
|
606
|
+
// and any synchronous flushes time to complete; after that we
|
|
607
|
+
// hard-exit so the user's terminal returns to the shell promptly.
|
|
608
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
565
609
|
return true;
|
|
566
610
|
|
|
567
611
|
default:
|
|
@@ -756,9 +800,24 @@ export async function main({ languageOverride } = {}) {
|
|
|
756
800
|
|
|
757
801
|
// Save state on process exit + stop background diagnostics (B0.1 heap
|
|
758
802
|
// sampler). saveState is idempotent; stop() is safe to call twice.
|
|
803
|
+
//
|
|
804
|
+
// v0.6.3.1: handler must terminate. Pre-fix it only saved + returned, which
|
|
805
|
+
// overrides Node's default SIGINT behavior — the process kept running with
|
|
806
|
+
// active LLM streams / subagent fetches keeping the event loop alive, and
|
|
807
|
+
// mashing ^C did nothing visible. Now: first ^C saves and tries clean exit
|
|
808
|
+
// after 500ms; second ^C hard-kills with no further saves.
|
|
809
|
+
let interruptCount = 0;
|
|
759
810
|
const saveOnExit = () => {
|
|
811
|
+
interruptCount++;
|
|
812
|
+
if (interruptCount >= 2) {
|
|
813
|
+
// Second interrupt — user wants out NOW
|
|
814
|
+
process.stderr.write("\nForce-exiting (second interrupt).\n");
|
|
815
|
+
process.exit(130); // 128 + SIGINT
|
|
816
|
+
}
|
|
760
817
|
try { engine.saveState(); } catch { /* ignore */ }
|
|
761
818
|
try { engine.stop(); } catch { /* ignore */ }
|
|
819
|
+
process.stderr.write("\nReceived interrupt — saving state, then exiting in 500ms (press again to force).\n");
|
|
820
|
+
setTimeout(() => process.exit(130), 500).unref();
|
|
762
821
|
};
|
|
763
822
|
process.on("SIGINT", saveOnExit);
|
|
764
823
|
process.on("SIGTERM", saveOnExit);
|
package/src/cli/meme.js
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
// AUTO-GENERATED by scripts/build-meme.js — DO NOT EDIT BY HAND.
|
|
2
|
+
// Source of truth lives in src/cli/meme.source.js.
|
|
3
|
+
//
|
|
4
|
+
// v0.7.0 Group K2: the textual easter-egg payload (lyrics, team
|
|
5
|
+
// handles, watermark) is XOR-encoded + base64 in BLOB below. The
|
|
6
|
+
// decoder is intentionally simple — the goal is to defeat grep-based
|
|
7
|
+
// plagiarism scraping, not to provide cryptographic protection.
|
|
8
|
+
// Determined reverse-engineering can recover the data; the watermark
|
|
9
|
+
// survives and identifies origin in any copies. Please leave it in.
|
|
10
|
+
|
|
11
|
+
import React from "react";
|
|
2
12
|
import { Box, Text, useInput } from "ink";
|
|
3
13
|
|
|
4
14
|
const h = React.createElement;
|
|
5
15
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
const _K = Buffer.from([
|
|
17
|
+
0x4b, 0x43, 0x37, 0x31, 0x2a, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2a, 0x66, 0x61, 0x74,
|
|
18
|
+
]);
|
|
19
|
+
const _BLOB = "MGFbSFgBAgFMXyhRY0ENGGs0VlheSAAcCkUAHEUIQ1hpFFIWWA1BARoXEh1OAwVUJC0XRUINQRALBBAbCEpDPSVjWERYSAUACwQeUQZENhFrM1ZDXkgVHQFFABxFCENYaQFCRQoBD1IBEAFTRg8EB2lvFWVCDRMXSRZTEgoSEwE/KxdFRUgHGwABUV8IMgkRayZZVQoBElIAAARRBkQgVD8sWl5YGg4FThIWU0cTEgBrMVJQSQBBFAEXUV8IMg5UKSYXWU8JExZMOF9RXgMAGWl5bBNqWFA0BxYbUQZEITUmJltYS0pNUC4kHQBPCiobKCZZEwZKITMaFxYGWURNVgsAVkNFBAgcCyYhPwhKQzQPKkFYWQEOHCoMARZJEg4GEzYVHQgoJgAPBhY0XwlDWGkDcENPDQ89HAQdFE9ETVYLC1ZdTCVDXkwlGBpeBQkRJW5SX00BDxcLF0dBCEpDNAcqW0hiHQAcCUdfUWorAAY+KFgTBkohIwcEHR9DCghWZ2F3QkUGBh8PClFfCCYVHC4sWRMGSiEqBwIGEghKQzQTGseupvdDXkwlChtCC0NYaQNNXk8SDhdMSVEzUB8ZACMmQ0NFBA1QM0lRBEsSBAYmIkVaCFJDOS1FscQKNg4YMgVYQ0dILx0ABhweRwMTFyIiWxEbRlFcXkWxxAqkyFQGJlpYXwVBXU4OGgdJDgQaZiZZVkMGBBccUUFT6NFBHD83R0IQR04VBxEbBkhIAhsmbFxYXgsJFwBIFh1NDw8RLjEDAwUDAl8NCRpRVw==";
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"There's a truth to find",
|
|
17
|
-
"The end is new",
|
|
18
|
-
"A tomorrow we must reach for",
|
|
19
|
-
"To be heard",
|
|
20
|
-
];
|
|
21
|
+
function _decode() {
|
|
22
|
+
const buf = Buffer.from(_BLOB, "base64");
|
|
23
|
+
const out = Buffer.alloc(buf.length);
|
|
24
|
+
for (let i = 0; i < buf.length; i++) out[i] = buf[i] ^ _K[i % _K.length];
|
|
25
|
+
return JSON.parse(out.toString("utf-8"));
|
|
26
|
+
}
|
|
21
27
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"@XY🌟", "@HalfM", "@GreenOrange",
|
|
27
|
-
"@LilyHuang", "@Qianlili", "@songmao",
|
|
28
|
-
"@zoezoe", "@yhhm",
|
|
29
|
-
];
|
|
28
|
+
const _payload = _decode();
|
|
29
|
+
const LYRICS = _payload.lyrics;
|
|
30
|
+
const TEAM = _payload.team;
|
|
31
|
+
const __KC_MEME_WATERMARK__ = _payload.watermark;
|
|
30
32
|
|
|
31
33
|
export function MemeOverlay({ onDismiss }) {
|
|
32
34
|
useInput((input, key) => {
|
|
@@ -34,7 +36,6 @@ export function MemeOverlay({ onDismiss }) {
|
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
return h(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, marginTop: 1, marginBottom: 1 },
|
|
37
|
-
// Lyrics block
|
|
38
39
|
h(Box, { flexDirection: "column" },
|
|
39
40
|
...LYRICS.map((line, i) =>
|
|
40
41
|
h(Text, { key: `l-${i}`, color: "cyan", italic: true }, line),
|
|
@@ -43,7 +44,6 @@ export function MemeOverlay({ onDismiss }) {
|
|
|
43
44
|
h(Text, null, ""),
|
|
44
45
|
h(Text, { dimColor: true }, "─".repeat(60)),
|
|
45
46
|
h(Text, null, ""),
|
|
46
|
-
// Team credit
|
|
47
47
|
h(Text, { color: "yellow", bold: true },
|
|
48
48
|
"Here's to all the smart minds that are/were part of our team:"),
|
|
49
49
|
h(Text, null, ""),
|
|
@@ -54,5 +54,6 @@ export function MemeOverlay({ onDismiss }) {
|
|
|
54
54
|
),
|
|
55
55
|
h(Text, null, ""),
|
|
56
56
|
h(Text, { dimColor: true }, "Press ESC or Enter to dismiss."),
|
|
57
|
+
h(Text, { dimColor: true }, __KC_MEME_WATERMARK__),
|
|
57
58
|
);
|
|
58
59
|
}
|
package/src/config.js
CHANGED
|
@@ -23,8 +23,20 @@ function loadGlobalConfig() {
|
|
|
23
23
|
*/
|
|
24
24
|
function loadEnvFile(envPath) {
|
|
25
25
|
if (!fs.existsSync(envPath)) return {};
|
|
26
|
+
// v0.7.0 H9: defend bootstrap against a .env that exists but isn't
|
|
27
|
+
// readable (permission denied, unexpected directory, encoding error,
|
|
28
|
+
// race with concurrent write). Old code threw and crashed config
|
|
29
|
+
// bootstrap before the CLI was even up — return empty {} on any
|
|
30
|
+
// read failure so the user sees the more actionable
|
|
31
|
+
// "no API key configured" error from loadSettings instead.
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = fs.readFileSync(envPath, "utf-8");
|
|
35
|
+
} catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
26
38
|
const env = {};
|
|
27
|
-
const lines =
|
|
39
|
+
const lines = raw.split("\n");
|
|
28
40
|
for (const line of lines) {
|
|
29
41
|
const trimmed = line.trim();
|
|
30
42
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -51,8 +63,13 @@ export function loadSettings(workspacePath) {
|
|
|
51
63
|
const gc = loadGlobalConfig();
|
|
52
64
|
const env = workspacePath ? loadEnvFile(path.join(workspacePath, ".env")) : {};
|
|
53
65
|
|
|
66
|
+
// Session-scoped overrides (process.env). Internal knob for benchmarking
|
|
67
|
+
// — lets a single launch swap conductor/workspace/context without touching
|
|
68
|
+
// ~/.kc_agent/config.json. Not exposed in --help or onboard.
|
|
69
|
+
const penv = process.env;
|
|
70
|
+
|
|
54
71
|
// Resolve provider metadata for authType/apiFormat defaults
|
|
55
|
-
const provider = gc.provider || "siliconflow";
|
|
72
|
+
const provider = penv.KC_PROVIDER || gc.provider || "siliconflow";
|
|
56
73
|
const providerDef = getProviderById(provider);
|
|
57
74
|
|
|
58
75
|
const settings = {
|
|
@@ -61,10 +78,10 @@ export function loadSettings(workspacePath) {
|
|
|
61
78
|
authType: gc.auth_type || providerDef?.authType || "bearer",
|
|
62
79
|
apiFormat: gc.api_format || providerDef?.apiFormat || "openai",
|
|
63
80
|
|
|
64
|
-
// Conductor LLM (
|
|
65
|
-
llmApiKey: env.LLM_API_KEY || env.SILICONFLOW_API_KEY || gc.api_key || "",
|
|
66
|
-
llmBaseUrl: env.LLM_BASE_URL || env.SILICONFLOW_BASE_URL || gc.base_url || "https://api.siliconflow.cn/v1",
|
|
67
|
-
kcModel: gc.conductor_model || "glm-5",
|
|
81
|
+
// Conductor LLM (process.env wins → workspace .env → global config)
|
|
82
|
+
llmApiKey: penv.KC_LLM_API_KEY || env.LLM_API_KEY || env.SILICONFLOW_API_KEY || gc.api_key || "",
|
|
83
|
+
llmBaseUrl: penv.KC_LLM_BASE_URL || env.LLM_BASE_URL || env.SILICONFLOW_BASE_URL || gc.base_url || "https://api.siliconflow.cn/v1",
|
|
84
|
+
kcModel: penv.KC_CONDUCTOR_MODEL || gc.conductor_model || "glm-5",
|
|
68
85
|
kcMaxTokens: parseInt(env.KC_MAX_TOKENS || gc.kc_max_tokens?.toString() || "65536", 10),
|
|
69
86
|
|
|
70
87
|
// Tier models (from .env or global config tiers)
|
|
@@ -78,10 +95,10 @@ export function loadSettings(workspacePath) {
|
|
|
78
95
|
vlmTier2: env.VLM_TIER2 || gc.vlm_tiers?.tier2 || "",
|
|
79
96
|
vlmTier3: env.VLM_TIER3 || gc.vlm_tiers?.tier3 || "",
|
|
80
97
|
|
|
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 || "",
|
|
98
|
+
// Worker LLM — optional, defaults to conductor config (process.env wins)
|
|
99
|
+
workerProvider: penv.KC_WORKER_PROVIDER || gc.worker_provider || "",
|
|
100
|
+
workerApiKey: penv.KC_WORKER_API_KEY || env.WORKER_API_KEY || gc.worker_api_key || "",
|
|
101
|
+
workerBaseUrl: penv.KC_WORKER_BASE_URL || env.WORKER_BASE_URL || gc.worker_base_url || "",
|
|
85
102
|
workerAuthType: gc.worker_auth_type || "",
|
|
86
103
|
workerApiFormat: gc.worker_api_format || "",
|
|
87
104
|
|
|
@@ -89,8 +106,8 @@ export function loadSettings(workspacePath) {
|
|
|
89
106
|
mineruApiUrl: env.MINERU_API_URL || "",
|
|
90
107
|
mineruApiKey: env.MINERU_API_KEY || "",
|
|
91
108
|
|
|
92
|
-
// Workspace
|
|
93
|
-
kcWorkspaceRoot: gc.workspace_root || path.join(os.homedir(), ".kc_agent", "workspaces"),
|
|
109
|
+
// Workspace (process.env wins — for parallel benchmark runs)
|
|
110
|
+
kcWorkspaceRoot: penv.KC_WORKSPACE_ROOT || gc.workspace_root || path.join(os.homedir(), ".kc_agent", "workspaces"),
|
|
94
111
|
kcExecTimeout: parseInt(env.KC_EXEC_TIMEOUT || "30", 10),
|
|
95
112
|
|
|
96
113
|
// Accuracy thresholds
|
|
@@ -110,16 +127,42 @@ export function loadSettings(workspacePath) {
|
|
|
110
127
|
tavilyApiKey: env.TAVILY_API_KEY || gc.tavily_api_key || "",
|
|
111
128
|
|
|
112
129
|
// Context management — A2: prefer per-provider cap from providers.js
|
|
113
|
-
// over the generic 200000 default. KC_CONTEXT_LIMIT
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
// over the generic 200000 default. process.env.KC_CONTEXT_LIMIT wins
|
|
131
|
+
// (session-scoped override for benchmarking long-context models without
|
|
132
|
+
// editing global config), then workspace .env, then global config, then
|
|
133
|
+
// provider.contextLimit, then a safe 200000 fallback.
|
|
134
|
+
//
|
|
135
|
+
// v0.7.0 E3 (#96): providerContextCap is the deployment hard ceiling
|
|
136
|
+
// (e.g., SiliconFlow's GLM-5.1 caps at 202_752 despite the model's
|
|
137
|
+
// native 1M). Effective contextLimit = min(user-requested,
|
|
138
|
+
// providerContextCap). E2E #5 GLM hit HTTP 413 because user set
|
|
139
|
+
// KC_CONTEXT_LIMIT=400000 but the deployment refused at ~203k.
|
|
140
|
+
// The cap is applied AFTER user-priority resolution so the user
|
|
141
|
+
// can't accidentally bypass it.
|
|
142
|
+
kcContextLimit: (() => {
|
|
143
|
+
const requested = parseInt(
|
|
144
|
+
penv.KC_CONTEXT_LIMIT ||
|
|
145
|
+
env.KC_CONTEXT_LIMIT ||
|
|
146
|
+
gc.kc_context_limit?.toString() ||
|
|
147
|
+
providerDef?.contextLimit?.toString() ||
|
|
148
|
+
"200000",
|
|
149
|
+
10,
|
|
150
|
+
);
|
|
151
|
+
const cap = providerDef?.providerContextCap;
|
|
152
|
+
if (typeof cap === "number" && cap > 0 && requested > cap) {
|
|
153
|
+
// Surface a one-time warning so users notice the clamp without
|
|
154
|
+
// burying it in events.jsonl.
|
|
155
|
+
// eslint-disable-next-line no-console
|
|
156
|
+
console.warn(
|
|
157
|
+
`[config] KC_CONTEXT_LIMIT=${requested} clamped to ${cap} ` +
|
|
158
|
+
`(provider ${providerDef.id} hardCap). E2E #5 hit HTTP 413 at ` +
|
|
159
|
+
`~203k on SiliconFlow GLM-5.1; cap protects against deployment ` +
|
|
160
|
+
`hard-ceiling rejections.`,
|
|
161
|
+
);
|
|
162
|
+
return cap;
|
|
163
|
+
}
|
|
164
|
+
return requested;
|
|
165
|
+
})(),
|
|
123
166
|
toolOutputOffloadTokens: parseInt(env.TOOL_OUTPUT_OFFLOAD_TOKENS || gc.tool_output_offload_tokens?.toString() || "2000", 10),
|
|
124
167
|
toolOutputOffloadErrorTokens: parseInt(env.TOOL_OUTPUT_OFFLOAD_ERROR_TOKENS || gc.tool_output_offload_error_tokens?.toString() || "500", 10),
|
|
125
168
|
maxMessageTokens: parseInt(env.MAX_MESSAGE_TOKENS || gc.max_message_tokens?.toString() || "60000", 10),
|
package/src/model-tiers.json
CHANGED
|
@@ -139,18 +139,34 @@
|
|
|
139
139
|
}
|
|
140
140
|
},
|
|
141
141
|
|
|
142
|
+
"tencent": {
|
|
143
|
+
"_comment": "Tencent Hunyuan via Lkeap plan endpoint. hy3-preview is the hidden flagship (not in /models listing but accepts requests). hunyuan-t1 is a thinking model — if used as conductor, ensure v0.6.3.1 reasoning_content roundtrip is in place.",
|
|
144
|
+
"conductor": "hy3-preview",
|
|
145
|
+
"llm": {
|
|
146
|
+
"tier1": "hy3-preview, hunyuan-t1",
|
|
147
|
+
"tier2": "hunyuan-turbos, hunyuan-2.0-thinking",
|
|
148
|
+
"tier3": "hunyuan-2.0-instruct, tc-code-latest",
|
|
149
|
+
"tier4": "tc-code-latest"
|
|
150
|
+
},
|
|
151
|
+
"vlm": {
|
|
152
|
+
"tier1": "",
|
|
153
|
+
"tier2": "",
|
|
154
|
+
"tier3": ""
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
142
158
|
"xiaomi": {
|
|
143
|
-
"_comment": "Xiaomi MiMo coding plan — flagship Pro + standard + multimodal Omni. Native 1M context but KC caps to 200K. TTS variants excluded (no KC use case).",
|
|
144
|
-
"conductor": "
|
|
159
|
+
"_comment": "Xiaomi MiMo coding plan — flagship Pro + standard + multimodal Omni. Native 1M context but KC caps to 200K. TTS variants excluded (no KC use case). Endpoint normalizes IDs to lowercase — must match exactly.",
|
|
160
|
+
"conductor": "mimo-v2.5-pro",
|
|
145
161
|
"llm": {
|
|
146
|
-
"tier1": "
|
|
147
|
-
"tier2": "
|
|
148
|
-
"tier3": "
|
|
149
|
-
"tier4": "
|
|
162
|
+
"tier1": "mimo-v2.5-pro",
|
|
163
|
+
"tier2": "mimo-v2.5",
|
|
164
|
+
"tier3": "mimo-v2-pro",
|
|
165
|
+
"tier4": "mimo-v2-pro"
|
|
150
166
|
},
|
|
151
167
|
"vlm": {
|
|
152
|
-
"tier1": "
|
|
153
|
-
"tier2": "
|
|
168
|
+
"tier1": "mimo-v2-omni",
|
|
169
|
+
"tier2": "mimo-v2-omni",
|
|
154
170
|
"tier3": ""
|
|
155
171
|
}
|
|
156
172
|
},
|
package/src/providers.js
CHANGED
|
@@ -47,6 +47,14 @@ const PROVIDERS = [
|
|
|
47
47
|
apiFormat: "openai",
|
|
48
48
|
modelsEndpoint: "/models",
|
|
49
49
|
contextLimit: 200000, // GLM-5.1, Kimi-K2.5 — 200K native
|
|
50
|
+
// v0.7.0 E3 (#96): provider hardCap. SiliconFlow's GLM-5.1
|
|
51
|
+
// deployment caps prompts at ~202,752 tokens despite the model's
|
|
52
|
+
// native 1M — E2E #5 GLM hit HTTP 413 at 203,363 tokens with
|
|
53
|
+
// KC_CONTEXT_LIMIT=400000 set. providerContextCap protects against
|
|
54
|
+
// user-set context limits exceeding the deployment hard ceiling.
|
|
55
|
+
// Effective limit becomes min(providerContextCap, modelContextLimit,
|
|
56
|
+
// KC_CONTEXT_LIMIT). When undefined, no provider cap applied.
|
|
57
|
+
providerContextCap: 200000,
|
|
50
58
|
defaultModel: getTierConfig("siliconflow").conductor || "glm-5",
|
|
51
59
|
defaultTiers: getTierConfig("siliconflow").llm,
|
|
52
60
|
defaultVlm: getTierConfig("siliconflow").vlm,
|
|
@@ -256,6 +264,40 @@ const PROVIDERS = [
|
|
|
256
264
|
zh: "小米 MiMo(V2.5 系列,编程计划)",
|
|
257
265
|
},
|
|
258
266
|
},
|
|
267
|
+
{
|
|
268
|
+
// Tencent Hunyuan via the Lkeap "plan" coding-token endpoint. The /models
|
|
269
|
+
// endpoint exposes a multi-vendor menu (glm-5.x, kimi-k2.5, minimax,
|
|
270
|
+
// hunyuan-*, tc-code-latest); hy3-preview is a hidden flagship that
|
|
271
|
+
// accepts requests but doesn't appear in /models. Curated list reflects
|
|
272
|
+
// what was advertised + the preview model the user has access to.
|
|
273
|
+
id: "tencent",
|
|
274
|
+
name: "Tencent Hunyuan",
|
|
275
|
+
baseUrl: "https://api.lkeap.cloud.tencent.com/plan/v3",
|
|
276
|
+
authType: "bearer",
|
|
277
|
+
apiFormat: "openai",
|
|
278
|
+
modelsEndpoint: "/models",
|
|
279
|
+
supportsCodingPlanKey: true,
|
|
280
|
+
contextLimit: 200000, // hy3-preview is officially 256K; keep below cap with margin
|
|
281
|
+
defaultModel: getTierConfig("tencent").conductor || "hy3-preview",
|
|
282
|
+
defaultTiers: getTierConfig("tencent").llm,
|
|
283
|
+
defaultVlm: getTierConfig("tencent").vlm,
|
|
284
|
+
curatedModels: [
|
|
285
|
+
{ id: "hy3-preview", ownedBy: "tencent" }, // hidden flagship
|
|
286
|
+
{ id: "hunyuan-t1", ownedBy: "tencent" }, // thinking model
|
|
287
|
+
{ id: "hunyuan-turbos", ownedBy: "tencent" },
|
|
288
|
+
{ id: "hunyuan-2.0-thinking", ownedBy: "tencent" },
|
|
289
|
+
{ id: "hunyuan-2.0-instruct", ownedBy: "tencent" },
|
|
290
|
+
{ id: "tc-code-latest", ownedBy: "tencent" },
|
|
291
|
+
// Multi-vendor pass-throughs on the same plan key:
|
|
292
|
+
{ id: "glm-5.1", ownedBy: "system" },
|
|
293
|
+
{ id: "kimi-k2.5", ownedBy: "system" },
|
|
294
|
+
{ id: "minimax-m2.7", ownedBy: "system" },
|
|
295
|
+
],
|
|
296
|
+
labels: {
|
|
297
|
+
en: "Tencent Hunyuan (Lkeap plan)",
|
|
298
|
+
zh: "腾讯混元(Lkeap 编程计划)",
|
|
299
|
+
},
|
|
300
|
+
},
|
|
259
301
|
{
|
|
260
302
|
id: "openrouter",
|
|
261
303
|
name: "OpenRouter",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# KC Verification Release — v1
|
|
2
|
+
|
|
3
|
+
This bundle is a self-contained verification system produced by KC's
|
|
4
|
+
finalization phase. It runs without KC's CLI installed.
|
|
5
|
+
|
|
6
|
+
## Project
|
|
7
|
+
|
|
8
|
+
- **Generated by**: KC v{{kc_version}}
|
|
9
|
+
- **Session**: `{{session_id}}`
|
|
10
|
+
- **Generated at**: {{generated_at}}
|
|
11
|
+
- **Rules**: {{rule_count}}
|
|
12
|
+
- **Workflows**: {{workflow_count}}
|
|
13
|
+
|
|
14
|
+
## What this does
|
|
15
|
+
|
|
16
|
+
{{project_description}}
|
|
17
|
+
|
|
18
|
+
## How to run
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
python3 >= 3.9
|
|
24
|
+
# Optional native parsers (recommended; falls back to LibreOffice if missing):
|
|
25
|
+
pip install pypdf python-docx
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Single-document smoke test
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python3 run.py --doc /path/to/document.pdf
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Full batch
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python3 run.py /path/to/input_dir/
|
|
38
|
+
# results land in output/results/<doc_stem>.json
|
|
39
|
+
# summary in output/results/summary.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Filter by rule
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python3 run.py /path/to/input_dir/ --rules R001,R005,R012
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Render dashboard
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python3 render_dashboard.py output/results/ > dashboard.html
|
|
52
|
+
./serve.sh # http://localhost:8765/dashboard.html
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Layout
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
release/v1/
|
|
59
|
+
├── run.py # entry point
|
|
60
|
+
├── render_dashboard.py # HTML dashboard renderer
|
|
61
|
+
├── serve.sh # local http server shim
|
|
62
|
+
├── manifest.json # populated bundle manifest
|
|
63
|
+
├── catalog.json # populated rule catalog
|
|
64
|
+
├── confidence_calibration.json # historical accuracy per rule (for confidence calibration)
|
|
65
|
+
├── README.md # this file
|
|
66
|
+
├── kc_runtime/
|
|
67
|
+
│ ├── __init__.py
|
|
68
|
+
│ ├── doc_parser.py # PDF/DOCX/TXT → text
|
|
69
|
+
│ └── confidence.py # calibration helpers
|
|
70
|
+
└── workflows/
|
|
71
|
+
└── <rule_id>/workflow_v1.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Workflow contract
|
|
75
|
+
|
|
76
|
+
Each `workflows/<rule_id>/workflow_v1.py` is a standalone Python script:
|
|
77
|
+
|
|
78
|
+
- Takes a document path on `sys.argv[1]`
|
|
79
|
+
- Emits a single JSON line on stdout containing the verdict
|
|
80
|
+
- Exit code 0 on success, non-zero on workflow-internal error
|
|
81
|
+
|
|
82
|
+
Verdict shape:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"rule_id": "R001",
|
|
87
|
+
"verdict": "PASS|FAIL|PARTIAL|NOT_APPLICABLE|UNDETERMINED|ERROR",
|
|
88
|
+
"confidence": 0.0,
|
|
89
|
+
"reason": "human-readable explanation",
|
|
90
|
+
"evidence": ["snippet 1", "snippet 2"]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Known limitations
|
|
95
|
+
|
|
96
|
+
{{known_limitations}}
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
This bundle is licensed under the same terms as KC itself
|
|
101
|
+
(PolyForm Noncommercial 1.0.0). For commercial use, see KC's
|
|
102
|
+
LICENSE-COMMERCIAL.md.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
*Re-running this bundle on a new document set is the recommended path.
|
|
107
|
+
For methodology changes (new rules, threshold tuning), re-run KC's
|
|
108
|
+
distillation + production_qc phases and re-emit a fresh release.*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""KC release runtime — v1.
|
|
2
|
+
|
|
3
|
+
Minimal Python helpers used by run.py to dispatch verification
|
|
4
|
+
workflows. Designed to be drop-in self-contained: stdlib + a handful
|
|
5
|
+
of optional native parsers (pypdf, python-docx) for document
|
|
6
|
+
parsing. Falls back to plaintext + LibreOffice CLI if natives
|
|
7
|
+
unavailable — never crashes the run on a missing dep.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
__all__ = ["doc_parser", "confidence"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Confidence calibration helpers for the release runtime.
|
|
3
|
+
|
|
4
|
+
Workflows return raw verdicts with a self-reported confidence score.
|
|
5
|
+
This module re-weights that score against the historical accuracy
|
|
6
|
+
captured during KC's distillation phase, so users see calibrated
|
|
7
|
+
confidence rather than the agent's prior. Falls back to identity
|
|
8
|
+
when no calibration data is available.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def calibrate(verdict: dict, historical: dict) -> dict:
|
|
15
|
+
"""
|
|
16
|
+
Adjust verdict["confidence"] using historical accuracy for the rule.
|
|
17
|
+
|
|
18
|
+
Schema for `historical`:
|
|
19
|
+
{
|
|
20
|
+
"historical_accuracy": {
|
|
21
|
+
"<rule_id>": {"accuracy": float in [0, 1], "n_samples": int},
|
|
22
|
+
...
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
If the rule has no calibration data, the verdict is returned
|
|
27
|
+
unchanged. If the rule's accuracy is < 0.5 (worse than coin flip),
|
|
28
|
+
confidence is dampened by the calibration ratio. If accuracy is
|
|
29
|
+
high but n_samples is small, calibration trusts the raw score
|
|
30
|
+
more (avoid over-correcting on weak prior).
|
|
31
|
+
"""
|
|
32
|
+
rule_id = verdict.get("rule_id")
|
|
33
|
+
if not rule_id:
|
|
34
|
+
return verdict
|
|
35
|
+
|
|
36
|
+
hist = historical.get("historical_accuracy", {}).get(rule_id)
|
|
37
|
+
if not hist:
|
|
38
|
+
return verdict
|
|
39
|
+
|
|
40
|
+
accuracy = float(hist.get("accuracy", 1.0))
|
|
41
|
+
n_samples = int(hist.get("n_samples", 0))
|
|
42
|
+
|
|
43
|
+
raw = float(verdict.get("confidence", 0.5))
|
|
44
|
+
|
|
45
|
+
# Bayesian-ish blend: weight raw confidence vs accuracy by n_samples.
|
|
46
|
+
# Small n → trust the raw score; large n → trust the prior more.
|
|
47
|
+
weight = min(0.5, n_samples / 100.0)
|
|
48
|
+
calibrated = raw * (1 - weight) + raw * accuracy * weight
|
|
49
|
+
|
|
50
|
+
out = dict(verdict)
|
|
51
|
+
out["confidence"] = round(calibrated, 4)
|
|
52
|
+
out["confidence_raw"] = raw
|
|
53
|
+
out["confidence_calibrated"] = True
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def confidence_band(score: float) -> str:
|
|
58
|
+
"""Map numeric score to a verbal band: high / medium / low."""
|
|
59
|
+
if score >= 0.8:
|
|
60
|
+
return "high"
|
|
61
|
+
if score >= 0.5:
|
|
62
|
+
return "medium"
|
|
63
|
+
return "low"
|