@kernelonpanic/kitcode 1.3.1 → 1.3.2
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/dist/index.js +105 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,8 +62,7 @@ var themeSchema = z.object({
|
|
|
62
62
|
var langSchema = z.enum(["en", "ru"]);
|
|
63
63
|
var budgetSchema = z.object({
|
|
64
64
|
maxTokensPerTurn: z.number().int().min(0).max(1e7).default(1e7),
|
|
65
|
-
maxCostUsdPerTurn: z.number().nonnegative().max(1e3).default(0)
|
|
66
|
-
maxSubagentsPerTurn: z.number().int().min(0).max(16).default(3)
|
|
65
|
+
maxCostUsdPerTurn: z.number().nonnegative().max(1e3).default(0)
|
|
67
66
|
});
|
|
68
67
|
var diagnosticsSchema = z.object({
|
|
69
68
|
autoRun: z.boolean().default(true),
|
|
@@ -79,8 +78,7 @@ var configSchema = z.object({
|
|
|
79
78
|
maxTokens: z.number().int().positive().max(2e5).default(64e3),
|
|
80
79
|
budget: budgetSchema.default({
|
|
81
80
|
maxTokensPerTurn: 1e7,
|
|
82
|
-
maxCostUsdPerTurn: 0
|
|
83
|
-
maxSubagentsPerTurn: 3
|
|
81
|
+
maxCostUsdPerTurn: 0
|
|
84
82
|
}),
|
|
85
83
|
diagnostics: diagnosticsSchema.default({ autoRun: true, commands: [] }),
|
|
86
84
|
providers: z.record(providerIdSchema, providerConfigSchema).default({}),
|
|
@@ -1814,8 +1812,23 @@ var MAX_PAUSE_RESUMES = 5;
|
|
|
1814
1812
|
var MAX_RETRIES2 = 3;
|
|
1815
1813
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
1816
1814
|
var MAX_TURN_STEPS = 64;
|
|
1817
|
-
|
|
1815
|
+
function createExecutionLane() {
|
|
1816
|
+
let tail2 = Promise.resolve();
|
|
1817
|
+
return {
|
|
1818
|
+
run(operation) {
|
|
1819
|
+
const result = tail2.then(operation);
|
|
1820
|
+
tail2 = result.then(() => void 0, () => void 0);
|
|
1821
|
+
return result;
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1818
1825
|
async function runTurn(cfg, history, hooks, signal) {
|
|
1826
|
+
const permissionLane = cfg.permissionLane ?? createExecutionLane();
|
|
1827
|
+
const originalHooks = hooks;
|
|
1828
|
+
hooks = {
|
|
1829
|
+
...hooks,
|
|
1830
|
+
requestPermission: (request) => permissionLane.run(async () => signal.aborted ? "deny" : originalHooks.requestPermission(request))
|
|
1831
|
+
};
|
|
1819
1832
|
let messages = [...history];
|
|
1820
1833
|
let pauses = 0;
|
|
1821
1834
|
let steps = 0;
|
|
@@ -1833,40 +1846,35 @@ async function runTurn(cfg, history, hooks, signal) {
|
|
|
1833
1846
|
hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
|
|
1834
1847
|
return messages;
|
|
1835
1848
|
}
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1849
|
+
const request = async () => {
|
|
1850
|
+
if (signal.aborted) return { content: [], stopReason: "aborted" };
|
|
1851
|
+
const budgetDecision = cfg.budget?.beforeRequest({
|
|
1852
|
+
modelRef: cfg.modelRef,
|
|
1853
|
+
maxOutputTokens: cfg.maxTokens,
|
|
1854
|
+
estimatedInputTokens: estimateRequestTokens(cfg, messages)
|
|
1855
|
+
});
|
|
1856
|
+
if (budgetDecision && !budgetDecision.allowed) {
|
|
1857
|
+
hooks.onEvent({ type: "notice", level: "warn", text: budgetDecision.reason });
|
|
1858
|
+
hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
hooks.onEvent({ type: "turn_start" });
|
|
1862
|
+
return consumeStream(
|
|
1863
|
+
cfg,
|
|
1864
|
+
messages,
|
|
1865
|
+
hooks,
|
|
1866
|
+
signal,
|
|
1867
|
+
budgetDecision?.maxOutputTokens ?? cfg.maxTokens
|
|
1868
|
+
);
|
|
1869
|
+
};
|
|
1870
|
+
const outcome = await (cfg.modelRequestLane?.run(request) ?? request());
|
|
1871
|
+
if (!outcome) return messages;
|
|
1854
1872
|
messages.push({ role: "assistant", content: outcome.content });
|
|
1855
1873
|
if (outcome.stopReason === "aborted") {
|
|
1856
1874
|
hooks.onEvent({ type: "turn_end", stopReason: "aborted" });
|
|
1857
1875
|
return sanitizeHistory(messages);
|
|
1858
1876
|
}
|
|
1859
1877
|
if (outcome.stopReason === "tool_use") {
|
|
1860
|
-
const callCount = outcome.content.filter((block) => block.type === "tool_use").length;
|
|
1861
|
-
if (callCount > MAX_TOOL_CALLS_PER_STEP) {
|
|
1862
|
-
hooks.onEvent({
|
|
1863
|
-
type: "notice",
|
|
1864
|
-
level: "warn",
|
|
1865
|
-
text: `The model returned ${callCount} tool calls at once; the limit is ${MAX_TOOL_CALLS_PER_STEP}. None were run. Ask it to split the work into smaller batches.`
|
|
1866
|
-
});
|
|
1867
|
-
hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
|
|
1868
|
-
return sanitizeHistory(messages);
|
|
1869
|
-
}
|
|
1870
1878
|
const results = await runToolCalls(cfg, outcome.content, hooks, signal);
|
|
1871
1879
|
if (results.length > 0) messages.push({ role: "user", content: results });
|
|
1872
1880
|
if (signal.aborted) {
|
|
@@ -2054,10 +2062,32 @@ function estimateRequestTokens(cfg, messages) {
|
|
|
2054
2062
|
async function runToolCalls(cfg, content, hooks, signal) {
|
|
2055
2063
|
const calls = content.filter((block) => block.type === "tool_use");
|
|
2056
2064
|
const results = [];
|
|
2057
|
-
|
|
2065
|
+
const run = (call) => {
|
|
2066
|
+
const operation = () => runOneCall(cfg, call, hooks, signal);
|
|
2067
|
+
return call.name !== "task" && cfg.executionLane ? cfg.executionLane.run(operation) : operation();
|
|
2068
|
+
};
|
|
2069
|
+
for (let index = 0; index < calls.length; ) {
|
|
2070
|
+
const call = calls[index];
|
|
2071
|
+
if (call.name !== "task") {
|
|
2072
|
+
results.push(await run(call));
|
|
2073
|
+
index += 1;
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
const batch = [];
|
|
2077
|
+
while (calls[index]?.name === "task") {
|
|
2078
|
+
batch.push(run(calls[index]));
|
|
2079
|
+
index += 1;
|
|
2080
|
+
}
|
|
2081
|
+
const settled = await Promise.allSettled(batch);
|
|
2082
|
+
for (const result of settled) {
|
|
2083
|
+
if (result.status === "rejected") throw result.reason;
|
|
2084
|
+
results.push(result.value);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2058
2087
|
return results;
|
|
2059
2088
|
}
|
|
2060
2089
|
async function runOneCall(cfg, call, hooks, signal) {
|
|
2090
|
+
if (signal.aborted) return toolError(call.id, "Interrupted by the user before this tool ran.");
|
|
2061
2091
|
const tool = cfg.tools.get(call.name);
|
|
2062
2092
|
if (!tool) {
|
|
2063
2093
|
return toolError(call.id, `Unknown tool "${call.name}". Use only the tools provided.`);
|
|
@@ -2076,26 +2106,31 @@ async function runOneCall(cfg, call, hooks, signal) {
|
|
|
2076
2106
|
}) !== "deny"
|
|
2077
2107
|
};
|
|
2078
2108
|
const requestedPermission = requestedPermissionFor(tool, call.input, ctx);
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
if (gate === "ask") {
|
|
2087
|
-
const decision = await hooks.requestPermission({
|
|
2088
|
-
toolName: tool.name,
|
|
2089
|
-
summary: summary2,
|
|
2090
|
-
input: call.input,
|
|
2091
|
-
display: await previewCall(tool, call.input, ctx),
|
|
2092
|
-
allowAlways: canAlwaysApprove(tool)
|
|
2093
|
-
});
|
|
2094
|
-
if (decision === "deny") {
|
|
2095
|
-
return toolError(call.id, "The user declined this call. Do not retry it; ask how to proceed.");
|
|
2109
|
+
const permission = async () => {
|
|
2110
|
+
const gate = cfg.permissions.decide(tool, requestedPermission);
|
|
2111
|
+
if (gate === "deny") {
|
|
2112
|
+
return toolError(
|
|
2113
|
+
call.id,
|
|
2114
|
+
cfg.permissions.denyReason?.(tool, requestedPermission) ?? `Tool "${tool.name}" is disabled by the user's configuration. Do not retry it; find another way or ask.`
|
|
2115
|
+
);
|
|
2096
2116
|
}
|
|
2097
|
-
if (
|
|
2098
|
-
|
|
2117
|
+
if (gate === "ask") {
|
|
2118
|
+
const decision = await hooks.requestPermission({
|
|
2119
|
+
toolName: tool.name,
|
|
2120
|
+
summary: summary2,
|
|
2121
|
+
input: call.input,
|
|
2122
|
+
display: await previewCall(tool, call.input, ctx),
|
|
2123
|
+
allowAlways: canAlwaysApprove(tool)
|
|
2124
|
+
});
|
|
2125
|
+
if (decision === "deny") {
|
|
2126
|
+
return toolError(call.id, "The user declined this call. Do not retry it; ask how to proceed.");
|
|
2127
|
+
}
|
|
2128
|
+
if (decision === "always") cfg.permissions.grantForSession(tool.name);
|
|
2129
|
+
}
|
|
2130
|
+
return void 0;
|
|
2131
|
+
};
|
|
2132
|
+
const permissionResult = await permission();
|
|
2133
|
+
if (permissionResult) return permissionResult;
|
|
2099
2134
|
if (signal.aborted) {
|
|
2100
2135
|
return toolError(call.id, "Interrupted by the user before this tool ran.");
|
|
2101
2136
|
}
|
|
@@ -2191,6 +2226,7 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
|
|
|
2191
2226
|
costUsd
|
|
2192
2227
|
});
|
|
2193
2228
|
return {
|
|
2229
|
+
hasLimits: limits.maxTokensPerTurn > 0 || limits.maxCostUsdPerTurn > 0,
|
|
2194
2230
|
beforeRequest(request) {
|
|
2195
2231
|
const current = snapshot();
|
|
2196
2232
|
if (limits.maxTokensPerTurn > 0 && current.tokens >= limits.maxTokensPerTurn) {
|
|
@@ -3667,6 +3703,9 @@ var SUBAGENT_SYSTEM = [
|
|
|
3667
3703
|
'Your final message is the return value \u2014 it is the only thing the caller ever sees. End with the answer itself: the findings, the file paths, the conclusion. Never end with "I will now..." or a recap of the steps you took. Carry over every concrete detail the caller needs (paths, names, line numbers) and leave out the narration of how you found them.'
|
|
3668
3704
|
].join("\n");
|
|
3669
3705
|
function createSubagentRunner(makeConfig, tools) {
|
|
3706
|
+
const permissionLane = createExecutionLane();
|
|
3707
|
+
const executionLane = createExecutionLane();
|
|
3708
|
+
const modelRequestLane = createExecutionLane();
|
|
3670
3709
|
return {
|
|
3671
3710
|
async run(request) {
|
|
3672
3711
|
if (request.signal.aborted) return SUBAGENT_CANCELLED;
|
|
@@ -3674,6 +3713,9 @@ function createSubagentRunner(makeConfig, tools) {
|
|
|
3674
3713
|
const base = makeConfig(SUBAGENT_SYSTEM, tools);
|
|
3675
3714
|
const cfg = {
|
|
3676
3715
|
...base,
|
|
3716
|
+
permissionLane,
|
|
3717
|
+
executionLane,
|
|
3718
|
+
modelRequestLane: base.budget?.hasLimits ? modelRequestLane : void 0,
|
|
3677
3719
|
provider: {
|
|
3678
3720
|
id: base.provider.id,
|
|
3679
3721
|
kind: base.provider.kind,
|
|
@@ -3898,7 +3940,7 @@ function clip(value) {
|
|
|
3898
3940
|
// package.json
|
|
3899
3941
|
var package_default = {
|
|
3900
3942
|
name: "@kernelonpanic/kitcode",
|
|
3901
|
-
version: "1.3.
|
|
3943
|
+
version: "1.3.2",
|
|
3902
3944
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3903
3945
|
type: "module",
|
|
3904
3946
|
license: "MIT",
|
|
@@ -3966,7 +4008,7 @@ var package_default = {
|
|
|
3966
4008
|
|
|
3967
4009
|
// src/version.ts
|
|
3968
4010
|
var KITCODE_VERSION = package_default.version;
|
|
3969
|
-
var KITCODE_COMMIT = true ? "
|
|
4011
|
+
var KITCODE_COMMIT = true ? "993d55335487da4915fa49660911ba6e69aa3685" : "development";
|
|
3970
4012
|
|
|
3971
4013
|
// src/mcp/client.ts
|
|
3972
4014
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -6309,16 +6351,15 @@ function unknownSkill(name, available) {
|
|
|
6309
6351
|
// src/tools/task.ts
|
|
6310
6352
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
6311
6353
|
var MAX_PROGRESS_LINES = 10;
|
|
6312
|
-
var MAX_SUBAGENTS_PER_TURN = 3;
|
|
6313
6354
|
var DESCRIPTION = [
|
|
6314
6355
|
"Hand one self-contained sub-task to a subagent that works in its own fresh context and reports back a single summary.",
|
|
6315
6356
|
"Use it when the task is genuinely independent and its intermediate output would be long or noisy: a wide search across the repository, reading many files to answer one question, tracing where something is used.",
|
|
6316
6357
|
"Do not use it for work that is a couple of tool calls, and do not use it when you already have what you need \u2014 call the tools yourself instead.",
|
|
6317
6358
|
'The subagent shares this filesystem and working directory but has NO memory of this conversation and cannot ask you anything, so "prompt" must stand alone: the goal, the constraints, and exactly what to report back.',
|
|
6359
|
+
"For multiple independent tasks, issue consecutive task calls in the same response so they can run concurrently. Do not delegate overlapping file edits.",
|
|
6318
6360
|
"It runs to completion and you receive only its final message."
|
|
6319
6361
|
].join(" ");
|
|
6320
|
-
function createTaskTool(runner
|
|
6321
|
-
const spawnsPerTurn = /* @__PURE__ */ new WeakMap();
|
|
6362
|
+
function createTaskTool(runner) {
|
|
6322
6363
|
return {
|
|
6323
6364
|
name: TASK_TOOL_NAME,
|
|
6324
6365
|
description: DESCRIPTION,
|
|
@@ -6340,14 +6381,6 @@ function createTaskTool(runner, maxSubagents = MAX_SUBAGENTS_PER_TURN) {
|
|
|
6340
6381
|
if (instruction === "") {
|
|
6341
6382
|
return { content: "task needs a prompt: the full instruction for the subagent.", isError: true };
|
|
6342
6383
|
}
|
|
6343
|
-
const spawned = (spawnsPerTurn.get(ctx.signal) ?? 0) + 1;
|
|
6344
|
-
spawnsPerTurn.set(ctx.signal, spawned);
|
|
6345
|
-
if (spawned > maxSubagents) {
|
|
6346
|
-
return {
|
|
6347
|
-
content: `Already delegated ${maxSubagents} subagents while answering this message, which is the limit. Do the remaining work yourself with the tools you have.`,
|
|
6348
|
-
isError: true
|
|
6349
|
-
};
|
|
6350
|
-
}
|
|
6351
6384
|
const progress = [];
|
|
6352
6385
|
const subagentId = `sa${randomBytes3(4).toString("hex")}`;
|
|
6353
6386
|
const description = field(input, "description") || "subagent";
|
|
@@ -6675,7 +6708,7 @@ async function boot(options) {
|
|
|
6675
6708
|
}
|
|
6676
6709
|
};
|
|
6677
6710
|
tools.register([
|
|
6678
|
-
createTaskTool(wrappedRunner
|
|
6711
|
+
createTaskTool(wrappedRunner)
|
|
6679
6712
|
]);
|
|
6680
6713
|
let persistQueue = Promise.resolve();
|
|
6681
6714
|
let configQueue = Promise.resolve();
|
|
@@ -8815,9 +8848,6 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8815
8848
|
Box8,
|
|
8816
8849
|
{
|
|
8817
8850
|
width: "100%",
|
|
8818
|
-
borderStyle: "round",
|
|
8819
|
-
borderColor: disabled ? theme.warn : theme.accent,
|
|
8820
|
-
borderDimColor: disabled,
|
|
8821
8851
|
paddingX: 1,
|
|
8822
8852
|
children: [
|
|
8823
8853
|
/* @__PURE__ */ jsx8(Text8, { color: disabled ? "gray" : theme.accent, children: "\u203A " }),
|
|
@@ -9153,9 +9183,9 @@ import { createContext as createContext3, Fragment as Fragment4, useContext as u
|
|
|
9153
9183
|
import stringWidth from "string-width";
|
|
9154
9184
|
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
9155
9185
|
var DimContext = createContext3(false);
|
|
9156
|
-
function Text10({ dimColor, ...props }) {
|
|
9186
|
+
function Text10({ dimColor, color, ...props }) {
|
|
9157
9187
|
const dim = useContext3(DimContext);
|
|
9158
|
-
return /* @__PURE__ */ jsx11(InkText, { ...props, dimColor: dimColor ?? dim });
|
|
9188
|
+
return /* @__PURE__ */ jsx11(InkText, { ...props, color: dim ? void 0 : color, dimColor: dimColor ?? dim });
|
|
9159
9189
|
}
|
|
9160
9190
|
function Markdown({ children, dim = false }) {
|
|
9161
9191
|
const blocks = useMemo3(() => extractBlocks(children), [children]);
|
|
@@ -10344,7 +10374,6 @@ function App({
|
|
|
10344
10374
|
const command = (firstSpace === -1 ? body : body.slice(0, firstSpace)).toLowerCase();
|
|
10345
10375
|
const rawRest = firstSpace === -1 ? "" : body.slice(firstSpace).trim();
|
|
10346
10376
|
const rest = rawRest ? rawRest.split(/\s+/) : [];
|
|
10347
|
-
setInput("");
|
|
10348
10377
|
switch (command) {
|
|
10349
10378
|
case "help":
|
|
10350
10379
|
notice(
|
|
@@ -11302,7 +11331,9 @@ Rename the file if you want a different name.`);
|
|
|
11302
11331
|
const text = raw.trim();
|
|
11303
11332
|
const submitSlash = () => {
|
|
11304
11333
|
if (!detachedInput) setInput("");
|
|
11305
|
-
|
|
11334
|
+
const command = text.slice(1).trim().split(/\s+/)[0]?.toLowerCase();
|
|
11335
|
+
const liveCommand = ["help", "usage", "subagents", "config", "exit", "quit"].includes(command ?? "");
|
|
11336
|
+
if (busyRef.current && !liveCommand || slashPendingRef.current > 0) {
|
|
11306
11337
|
queueRef.current = [...queueRef.current, { kind: "command", line: text }];
|
|
11307
11338
|
setPendingCount(queueRef.current.length);
|
|
11308
11339
|
return;
|