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.
Files changed (52) hide show
  1. package/LICENSE +81 -0
  2. package/LICENSE-COMMERCIAL.md +125 -0
  3. package/README.md +21 -3
  4. package/package.json +14 -5
  5. package/src/agent/context-window.js +9 -12
  6. package/src/agent/context.js +14 -1
  7. package/src/agent/document-parser.js +169 -0
  8. package/src/agent/engine.js +382 -19
  9. package/src/agent/history/event-history.js +222 -0
  10. package/src/agent/llm-client.js +55 -0
  11. package/src/agent/message-utils.js +63 -0
  12. package/src/agent/pipelines/_milestone-derive.js +566 -0
  13. package/src/agent/pipelines/base.js +21 -0
  14. package/src/agent/pipelines/distillation.js +28 -15
  15. package/src/agent/pipelines/extraction.js +130 -36
  16. package/src/agent/pipelines/finalization.js +178 -11
  17. package/src/agent/pipelines/index.js +6 -1
  18. package/src/agent/pipelines/initializer.js +74 -8
  19. package/src/agent/pipelines/production-qc.js +31 -44
  20. package/src/agent/pipelines/skill-authoring.js +97 -80
  21. package/src/agent/pipelines/skill-testing.js +106 -23
  22. package/src/agent/retry.js +10 -2
  23. package/src/agent/scheduler.js +14 -2
  24. package/src/agent/session-state.js +18 -1
  25. package/src/agent/skill-loader.js +13 -7
  26. package/src/agent/skill-validator.js +19 -5
  27. package/src/agent/task-manager.js +61 -5
  28. package/src/agent/tools/document-chunk.js +21 -9
  29. package/src/agent/tools/phase-advance.js +37 -5
  30. package/src/agent/tools/release.js +51 -9
  31. package/src/agent/tools/rule-catalog.js +11 -1
  32. package/src/agent/tools/workspace-file.js +32 -0
  33. package/src/agent/workspace.js +39 -1
  34. package/src/cli/components.js +64 -14
  35. package/src/cli/index.js +62 -3
  36. package/src/cli/meme.js +26 -25
  37. package/src/config.js +65 -22
  38. package/src/model-tiers.json +24 -8
  39. package/src/providers.js +42 -0
  40. package/template/release/v1/README.md.tmpl +108 -0
  41. package/template/release/v1/catalog.json.tmpl +4 -0
  42. package/template/release/v1/kc_runtime/__init__.py +11 -0
  43. package/template/release/v1/kc_runtime/confidence.py +63 -0
  44. package/template/release/v1/kc_runtime/doc_parser.py +127 -0
  45. package/template/release/v1/manifest.json.tmpl +11 -0
  46. package/template/release/v1/render_dashboard.py +117 -0
  47. package/template/release/v1/run.py +212 -0
  48. package/template/release/v1/serve.sh +17 -0
  49. package/template/skills/en/meta-meta/work-decomposition/SKILL.md +326 -0
  50. package/template/skills/en/skill-creator/SKILL.md +1 -1
  51. package/template/skills/zh/meta-meta/work-decomposition/SKILL.md +321 -0
  52. 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
- addMessage({ role: "system", content: "No scheduled ingestion jobs. Ask KC to set one up via the schedule_fetch tool." });
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
- import React, { useState } from "react";
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
- // F6: /meme easter egg. Intentionally not listed in /help — discovery
7
- // is the point. Press ESC or Enter to dismiss. Content per the v0.6.0
8
- // plan (item 15) — lyrics + team credit.
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
- const LYRICS = [
11
- "I'll wait and soon",
12
- "We're stranded on the beach",
13
- "In our dream",
14
- "We part too soon",
15
- "But in our lies",
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 TEAM = [
23
- "@kitchen-engineer42", "@Xigua", "@Amelia", "@01Fish",
24
- "@zyxthetroll", "@theon", "@DivisionDirectorXu",
25
- "@AnselKocen", "@CarolineCRL", "@GraceGuo",
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 = fs.readFileSync(envPath, "utf-8").split("\n");
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 (generic keys with legacy fallback)
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 env still wins.
114
- // gc.kc_context_limit (global config) is next. Then provider.contextLimit.
115
- // Then a safe 200000 fallback for unknown/custom providers.
116
- kcContextLimit: parseInt(
117
- env.KC_CONTEXT_LIMIT ||
118
- gc.kc_context_limit?.toString() ||
119
- providerDef?.contextLimit?.toString() ||
120
- "200000",
121
- 10,
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),
@@ -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": "MiMo-V2.5-Pro",
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": "MiMo-V2.5-Pro",
147
- "tier2": "MiMo-V2.5",
148
- "tier3": "MiMo-V2-Pro",
149
- "tier4": "MiMo-V2-Pro"
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": "MiMo-V2-Omni",
153
- "tier2": "MiMo-V2-Omni",
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,4 @@
1
+ [
2
+ /* Populated by KC finalization from rules/catalog.json. Each entry: */
3
+ /* { "id": "R001", "title": "...", "description": "...", "source_ref": "..." } */
4
+ ]
@@ -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"