refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity

Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes,
cline, codebuddy and pi end to end: capability descriptors, installer branches
and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters,
hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi
migrations, Kimi payload normalization in the hook guards, dead hostBehaviors
vocabulary, launcher home probes, fixtures, runtime-specific tests and the
prose that presented them as supported.

Installer output for the six kept runtimes is byte-identical to before the
prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and
read-injection-scanner are left in place pending a decision.
This commit is contained in:
Jakub Zych
2026-10-06 20:02:40 +02:00
parent 12ee75a509
commit 6cfa0c55d2
509 changed files with 1869 additions and 47737 deletions

View File

@@ -87,7 +87,7 @@ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
// migrated to hooks/lib/hook-exit.js's deny(), using its `stderrPayload`
// param (added for exactly this site): fd 1 still gets the full JSON
// `output`, fd 2 gets ONLY the plain-text `output.reason` string — because
// Kimi's native hook bus reads stderr verbatim back to the model, and a raw
// a native hook bus may read stderr verbatim back to the model, and a raw
// JSON-stringified object on stderr is not the same reason text a model
// should read. Byte-identical to the pre-migration emitBlock.
@@ -227,54 +227,6 @@ function emitBlock(output) {
deny(output, output.reason);
}
// #2304: Kimi's native hook bus delivers Kimi's tool vocabulary in the payload
// (Write → WriteFile, Edit/MultiEdit → StrReplaceFile) while the [[hooks]]
// matcher is registered pre-translated (runtime-hooks-surface.cts
// buildKimiHooksTomlBlock) — so without normalizing the payload too, the
// matcher fires but the tool_name check below exits 0 and the guard is dormant
// on Kimi. The tool_input field names differ as well (kimi-cli
// src/kimi_cli/tools/file/write.py): WriteFile takes `path`/`content`, and
// kimi-cli's hooks/events.py forwards tool_input verbatim, so both layers need
// mapping. Only WriteFile is mapped: this guard exits 0 for any tool but
// Write, so an Edit-class mapping here would be dead code. Accepts bare and
// module-qualified ('kimi_cli.tools.file:WriteFile') names; unknown names fall
// through untouched. Inlined per guard (not hooks/lib/): hook scripts are
// staged as standalone files, and a sibling require is a staging dependency
// that can fail silently.
// A Map, not an object literal: bare bracket lookup resolves prototype keys
// ('constructor', '__proto__', 'toString') to truthy functions/objects, so the
// !mapped fall-through never fires for them; Map.get returns undefined (same
// shape as canonicalizeRuntimeName in src/runtime-name-policy.cts).
const KIMI_TOOL_NAMES = new Map([['WriteFile', 'Write']]);
function normalizeKimiPayload(data) {
// #2595: total over everything JSON can express — JSON.parse('null') is
// null, and reading .tool_name off a primitive would throw into the outer
// fail-open catch. A null payload has nothing to guard; pass it through
// deliberately rather than by crash.
if (data === null || typeof data !== 'object') return data;
const raw = data.tool_name;
if (typeof raw !== 'string') return data;
const mapped = KIMI_TOOL_NAMES.get(raw.slice(raw.lastIndexOf(':') + 1));
if (!mapped) return data;
data.tool_name = mapped;
const input = data.tool_input;
if (input && typeof input === 'object') {
// #2595 (review): Kimi's `path` is AUTHORITATIVE — it must win outright,
// not merely fill in when `file_path` happens to be absent. kimi-cli's
// WriteFile schema carries no `file_path` at all (src/kimi_cli/tools/
// file/write.py), so a `file_path` in a Kimi payload is ALWAYS
// model-supplied; under the old `=== undefined` condition a payload
// pairing a curated `path` with a spurious `file_path: ""` left this
// guard reading '' and exiting 0 while kimi-cli wrote to `path` — a
// one-key bypass needing no crash. Overwriting can only narrow what the
// guard inspects to the path that will actually be written.
if (typeof input.path === 'string') {
input.file_path = input.path;
}
}
return data;
}
let input = '';
const stdinTimeout = setTimeout(() => allow(undefined), 3000);
process.stdin.setEncoding('utf8');
@@ -282,7 +234,7 @@ process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
clearTimeout(stdinTimeout);
try {
const data = normalizeKimiPayload(JSON.parse(input));
const data = JSON.parse(input);
// A null/primitive payload has nothing to guard — exit deliberately
// rather than throwing into the fail-open catch below (#2595 class).