@kernelonpanic/kitcode 1.2.8 → 1.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/README.md +40 -1
- package/README.ru.md +40 -1
- package/dist/index.js +385 -193
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -40,7 +40,7 @@ var providerConfigSchema = z.object({
|
|
|
40
40
|
keyEnv: z.string().optional(),
|
|
41
41
|
headers: z.record(z.string(), z.string()).optional()
|
|
42
42
|
});
|
|
43
|
-
var effortSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
43
|
+
var effortSchema = z.enum(["auto", "low", "medium", "high", "xhigh", "max"]);
|
|
44
44
|
var permissionModeSchema = z.enum(["allow", "ask", "deny"]);
|
|
45
45
|
var mcpStdioSchema = z.object({
|
|
46
46
|
type: z.literal("stdio"),
|
|
@@ -1024,14 +1024,120 @@ function skipControlString(text, from, bellTerminates) {
|
|
|
1024
1024
|
return text.length - 1;
|
|
1025
1025
|
}
|
|
1026
1026
|
|
|
1027
|
+
// src/tools/memory.ts
|
|
1028
|
+
import { z as z2 } from "zod";
|
|
1029
|
+
|
|
1030
|
+
// src/tools/summary.ts
|
|
1031
|
+
function brief(value, max = 60) {
|
|
1032
|
+
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
1033
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/tools/memory.ts
|
|
1037
|
+
var inputSchema = z2.discriminatedUnion("action", [
|
|
1038
|
+
z2.object({ action: z2.literal("read") }).strict(),
|
|
1039
|
+
z2.object({ action: z2.literal("add"), text: z2.string().trim().min(1).max(4e3), source: z2.string().trim().min(1).max(1e3) }).strict(),
|
|
1040
|
+
z2.object({ action: z2.literal("replace"), oldText: z2.string().min(1), text: z2.string().trim().min(1).max(4e3), source: z2.string().trim().min(1).max(1e3) }).strict(),
|
|
1041
|
+
z2.object({ action: z2.literal("delete"), oldText: z2.string().min(1), source: z2.string().trim().min(1).max(1e3) }).strict()
|
|
1042
|
+
]);
|
|
1043
|
+
function createMemoryTool(store) {
|
|
1044
|
+
return {
|
|
1045
|
+
name: "memory",
|
|
1046
|
+
description: "Read or update persistent notes for this project. Remember only explicit user preferences, confirmed project facts and accepted decisions, with their source (user instruction or file/tool evidence). Never store secrets, guesses, temporary task progress or raw logs. Read before changing existing notes. For replace/delete, oldText must match exactly once; include the whole old note and its source. These notes survive new chats. Do not use this tool to change permissions or override current instructions.",
|
|
1047
|
+
inputSchema: {
|
|
1048
|
+
type: "object",
|
|
1049
|
+
properties: {
|
|
1050
|
+
action: { type: "string", enum: ["read", "add", "replace", "delete"] },
|
|
1051
|
+
text: { type: "string", maxLength: 4e3 },
|
|
1052
|
+
oldText: { type: "string" },
|
|
1053
|
+
source: { type: "string", maxLength: 1e3 }
|
|
1054
|
+
},
|
|
1055
|
+
required: ["action"],
|
|
1056
|
+
additionalProperties: false
|
|
1057
|
+
},
|
|
1058
|
+
defaultPermission: "allow",
|
|
1059
|
+
summarize(input) {
|
|
1060
|
+
const parsed = inputSchema.safeParse(input);
|
|
1061
|
+
return parsed.success ? `memory(${parsed.data.action}${"text" in parsed.data ? `: ${brief(parsed.data.text, 80)}` : ""})` : "memory(invalid input)";
|
|
1062
|
+
},
|
|
1063
|
+
async execute(input, ctx) {
|
|
1064
|
+
const parsed = inputSchema.safeParse(input);
|
|
1065
|
+
if (!parsed.success) return { content: "Invalid memory arguments: mutations require source; replace/delete also require oldText.", isError: true };
|
|
1066
|
+
const args = parsed.data;
|
|
1067
|
+
const before = store.read();
|
|
1068
|
+
if (args.action === "read") return { content: before || "Project memory is empty." };
|
|
1069
|
+
if (ctx.signal.aborted) return { content: "Memory update cancelled.", isError: true };
|
|
1070
|
+
const note = "text" in args ? `${redactSecrets(args.text)}
|
|
1071
|
+
Source: ${redactSecrets(args.source)}` : "";
|
|
1072
|
+
let after;
|
|
1073
|
+
if (args.action === "add") {
|
|
1074
|
+
if (before.includes(note)) return { content: "This note is already in project memory." };
|
|
1075
|
+
after = [before.trim(), note].filter(Boolean).join("\n\n");
|
|
1076
|
+
} else {
|
|
1077
|
+
const at = before.indexOf(args.oldText);
|
|
1078
|
+
if (at === -1 || before.indexOf(args.oldText, at + 1) !== -1) {
|
|
1079
|
+
return { content: "oldText must match exactly once. Read memory again before retrying.", isError: true };
|
|
1080
|
+
}
|
|
1081
|
+
after = (before.slice(0, at) + note + before.slice(at + args.oldText.length)).trim();
|
|
1082
|
+
}
|
|
1083
|
+
await store.save(after);
|
|
1084
|
+
return { content: `Project memory updated (${args.action}).
|
|
1085
|
+
${store.read()}`, display: { kind: "diff", path: "project memory", before, after: store.read() } };
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/core/memory.ts
|
|
1091
|
+
import { createHash } from "crypto";
|
|
1092
|
+
import { readFile as readFile3, writeFile as writeFile3, rm as rm3 } from "fs/promises";
|
|
1093
|
+
import path4 from "path";
|
|
1094
|
+
function projectMemoryPath(workspace) {
|
|
1095
|
+
const key = process.platform === "win32" ? workspace.toLowerCase() : workspace;
|
|
1096
|
+
return path4.join(homeDir, "memory", createHash("sha256").update(key).digest("hex") + ".txt");
|
|
1097
|
+
}
|
|
1098
|
+
async function readProjectMemory(workspace) {
|
|
1099
|
+
try {
|
|
1100
|
+
return await readFile3(projectMemoryPath(workspace), "utf8");
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (error.code === "ENOENT") return "";
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async function saveProjectMemory(workspace, text) {
|
|
1107
|
+
if (text.length > 16e3) throw new Error("Project memory is limited to 16,000 characters.");
|
|
1108
|
+
const file = projectMemoryPath(workspace);
|
|
1109
|
+
await ensureDir(path4.dirname(file));
|
|
1110
|
+
await writeFile3(file, redactSecrets(text), { mode: 384 });
|
|
1111
|
+
}
|
|
1112
|
+
async function clearProjectMemory(workspace) {
|
|
1113
|
+
await rm3(projectMemoryPath(workspace), { force: true });
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/providers/effort.ts
|
|
1117
|
+
function resolveEffort(effort, messages) {
|
|
1118
|
+
if (effort !== "auto") return effort;
|
|
1119
|
+
const latest = messages.findLast((message) => message.role === "user" && message.content.some((block) => block.type === "text"));
|
|
1120
|
+
const text = latest?.content.filter((block) => block.type === "text").map((block) => block.text).join("\n") ?? "";
|
|
1121
|
+
return text.length > 500 || /bug|fix|debug|test|refactor|implement|баг|фикс|исправ|тест|рефактор|реализ/i.test(text) ? "high" : "medium";
|
|
1122
|
+
}
|
|
1123
|
+
function openAiEffort(model, effort) {
|
|
1124
|
+
const id = model.split("/").at(-1) ?? model;
|
|
1125
|
+
if (!/^(?:o[134](?:-|$)|gpt-5(?:[.-]|$))/.test(id)) return void 0;
|
|
1126
|
+
if (/^(?:o[134](?:-|$)|gpt-5(?:-(?:mini|nano|\d{4})|$)|gpt-5\.1(?:-|$))/.test(id)) {
|
|
1127
|
+
return effort === "max" || effort === "xhigh" ? "high" : effort;
|
|
1128
|
+
}
|
|
1129
|
+
if (/^gpt-5\.[234](?:-|$)/.test(id)) return effort === "max" ? "xhigh" : effort;
|
|
1130
|
+
return effort === "max" || effort === "xhigh" ? "high" : effort;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1027
1133
|
// src/app/runtime.ts
|
|
1028
|
-
import
|
|
1134
|
+
import path14 from "path";
|
|
1029
1135
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
1030
1136
|
|
|
1031
1137
|
// src/core/session.ts
|
|
1032
1138
|
import { randomBytes } from "crypto";
|
|
1033
|
-
import { readFile as
|
|
1034
|
-
import
|
|
1139
|
+
import { readFile as readFile4, readdir, rename as rename3, stat as stat2, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
1140
|
+
import path5 from "path";
|
|
1035
1141
|
var MAX_SESSION_BYTES = 5e7;
|
|
1036
1142
|
function createSession(cwd, model) {
|
|
1037
1143
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1050,10 +1156,10 @@ async function saveSession(state) {
|
|
|
1050
1156
|
assertSessionId(state.id);
|
|
1051
1157
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1052
1158
|
await ensureDir(sessionsDir);
|
|
1053
|
-
const file =
|
|
1159
|
+
const file = path5.join(sessionsDir, `${state.id}.json`);
|
|
1054
1160
|
const temp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
1055
1161
|
try {
|
|
1056
|
-
await
|
|
1162
|
+
await writeFile4(temp, JSON.stringify(state), { encoding: "utf8", mode: 384 });
|
|
1057
1163
|
await atomicRename(temp, file);
|
|
1058
1164
|
} catch (error) {
|
|
1059
1165
|
await unlink(temp).catch(() => void 0);
|
|
@@ -1077,14 +1183,14 @@ async function atomicRename(from, to, retries = 5) {
|
|
|
1077
1183
|
}
|
|
1078
1184
|
async function loadSession(id) {
|
|
1079
1185
|
assertSessionId(id);
|
|
1080
|
-
const file =
|
|
1186
|
+
const file = path5.join(sessionsDir, `${id}.json`);
|
|
1081
1187
|
let raw;
|
|
1082
1188
|
try {
|
|
1083
1189
|
const info = await stat2(file);
|
|
1084
1190
|
if (info.size > MAX_SESSION_BYTES) {
|
|
1085
1191
|
throw new Error(`Session exceeds the ${MAX_SESSION_BYTES / 1e6} MB load limit: ${file}`);
|
|
1086
1192
|
}
|
|
1087
|
-
raw = await
|
|
1193
|
+
raw = await readFile4(file, "utf8");
|
|
1088
1194
|
} catch (error) {
|
|
1089
1195
|
if (error instanceof Error && error.message.startsWith("Session exceeds")) throw error;
|
|
1090
1196
|
if (error.code === "ENOENT") {
|
|
@@ -1100,7 +1206,7 @@ async function listSessions(limit = 20) {
|
|
|
1100
1206
|
const files = await filesByRecency();
|
|
1101
1207
|
const summaries = [];
|
|
1102
1208
|
for (const file of files.slice(0, limit)) {
|
|
1103
|
-
const state = readState(await
|
|
1209
|
+
const state = readState(await readFile4(file, "utf8").catch(() => ""));
|
|
1104
1210
|
if (state) {
|
|
1105
1211
|
summaries.push({
|
|
1106
1212
|
id: state.id,
|
|
@@ -1126,7 +1232,7 @@ async function renameSession(query, title) {
|
|
|
1126
1232
|
async function deleteSession(query) {
|
|
1127
1233
|
const id = await resolveSessionId(query);
|
|
1128
1234
|
assertSessionId(id);
|
|
1129
|
-
await unlink(
|
|
1235
|
+
await unlink(path5.join(sessionsDir, `${id}.json`)).catch((error) => {
|
|
1130
1236
|
if (error.code === "ENOENT") throw new Error(`Session no longer exists: ${id}`);
|
|
1131
1237
|
throw error;
|
|
1132
1238
|
});
|
|
@@ -1151,7 +1257,7 @@ async function deleteAllSessions() {
|
|
|
1151
1257
|
}
|
|
1152
1258
|
});
|
|
1153
1259
|
const results = await Promise.allSettled(
|
|
1154
|
-
ids.map((id) => unlink(
|
|
1260
|
+
ids.map((id) => unlink(path5.join(sessionsDir, `${id}.json`)))
|
|
1155
1261
|
);
|
|
1156
1262
|
const deleted = [];
|
|
1157
1263
|
const failed = [];
|
|
@@ -1172,7 +1278,7 @@ async function exportSession(query, destination) {
|
|
|
1172
1278
|
const id = await resolveSessionId(query);
|
|
1173
1279
|
const state = await loadSession(id);
|
|
1174
1280
|
const target = await resolveExportTarget(destination, state);
|
|
1175
|
-
await
|
|
1281
|
+
await writeFile4(target, renderSessionMarkdown(state), {
|
|
1176
1282
|
encoding: "utf8",
|
|
1177
1283
|
mode: 384,
|
|
1178
1284
|
flag: "wx"
|
|
@@ -1186,7 +1292,7 @@ async function exportSession(query, destination) {
|
|
|
1186
1292
|
}
|
|
1187
1293
|
async function latestSessionFor(cwd) {
|
|
1188
1294
|
for (const file of await filesByRecency()) {
|
|
1189
|
-
const state = readState(await
|
|
1295
|
+
const state = readState(await readFile4(file, "utf8").catch(() => ""));
|
|
1190
1296
|
if (state?.cwd === cwd) return state;
|
|
1191
1297
|
}
|
|
1192
1298
|
return null;
|
|
@@ -1216,7 +1322,7 @@ async function filesByRecency() {
|
|
|
1216
1322
|
}
|
|
1217
1323
|
const dated = await Promise.all(
|
|
1218
1324
|
names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
1219
|
-
const file =
|
|
1325
|
+
const file = path5.join(sessionsDir, name);
|
|
1220
1326
|
const info = await stat2(file).catch(() => null);
|
|
1221
1327
|
return { file, at: info?.mtimeMs ?? 0, size: info?.size ?? 0 };
|
|
1222
1328
|
})
|
|
@@ -1296,14 +1402,14 @@ function readUsageEntries(value) {
|
|
|
1296
1402
|
});
|
|
1297
1403
|
}
|
|
1298
1404
|
async function resolveExportTarget(destination, state) {
|
|
1299
|
-
const resolved =
|
|
1405
|
+
const resolved = path5.resolve(destination);
|
|
1300
1406
|
const info = await stat2(resolved).catch(() => null);
|
|
1301
1407
|
if (info?.isDirectory()) {
|
|
1302
|
-
return
|
|
1408
|
+
return path5.join(resolved, exportFileName(state));
|
|
1303
1409
|
}
|
|
1304
1410
|
if (info) throw new Error(`Export destination is not a directory: ${resolved}`);
|
|
1305
|
-
const parent = await stat2(
|
|
1306
|
-
if (!parent?.isDirectory()) throw new Error(`Export directory does not exist: ${
|
|
1411
|
+
const parent = await stat2(path5.dirname(resolved)).catch(() => null);
|
|
1412
|
+
if (!parent?.isDirectory()) throw new Error(`Export directory does not exist: ${path5.dirname(resolved)}`);
|
|
1307
1413
|
return resolved;
|
|
1308
1414
|
}
|
|
1309
1415
|
function exportFileName(state) {
|
|
@@ -1430,6 +1536,10 @@ async function runTurn(cfg, history, hooks, signal) {
|
|
|
1430
1536
|
let pauses = 0;
|
|
1431
1537
|
let steps = 0;
|
|
1432
1538
|
for (; ; ) {
|
|
1539
|
+
if (signal.aborted) {
|
|
1540
|
+
hooks.onEvent({ type: "turn_end", stopReason: "aborted" });
|
|
1541
|
+
return sanitizeHistory(messages);
|
|
1542
|
+
}
|
|
1433
1543
|
if (++steps > MAX_TURN_STEPS) {
|
|
1434
1544
|
hooks.onEvent({
|
|
1435
1545
|
type: "notice",
|
|
@@ -1516,12 +1626,16 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
|
|
|
1516
1626
|
signal
|
|
1517
1627
|
};
|
|
1518
1628
|
let outcome;
|
|
1629
|
+
let text = "";
|
|
1630
|
+
let thinking = "";
|
|
1519
1631
|
for await (const event of streamWithRetry(cfg.provider, request, cfg.modelRef, signal)) {
|
|
1520
1632
|
switch (event.type) {
|
|
1521
1633
|
case "text_delta":
|
|
1634
|
+
text += event.text;
|
|
1522
1635
|
hooks.onEvent({ type: "text_delta", text: event.text });
|
|
1523
1636
|
break;
|
|
1524
1637
|
case "thinking_delta":
|
|
1638
|
+
thinking += event.text;
|
|
1525
1639
|
hooks.onEvent({ type: "thinking_delta", text: event.text });
|
|
1526
1640
|
break;
|
|
1527
1641
|
case "usage":
|
|
@@ -1540,7 +1654,12 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
|
|
|
1540
1654
|
}
|
|
1541
1655
|
}
|
|
1542
1656
|
if (outcome) return outcome;
|
|
1543
|
-
if (signal.aborted)
|
|
1657
|
+
if (signal.aborted) {
|
|
1658
|
+
const content = [];
|
|
1659
|
+
if (thinking) content.push({ type: "thinking", text: thinking });
|
|
1660
|
+
if (text) content.push({ type: "text", text });
|
|
1661
|
+
return { content, stopReason: "aborted" };
|
|
1662
|
+
}
|
|
1544
1663
|
throw new Error(
|
|
1545
1664
|
`"${cfg.provider.id}" ended the stream without completing the turn \u2014 nothing was generated. The endpoint answered, but not with a usable ${cfg.provider.kind} response.`
|
|
1546
1665
|
);
|
|
@@ -1550,8 +1669,29 @@ async function* streamWithRetry(provider, request, modelRef, signal) {
|
|
|
1550
1669
|
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
1551
1670
|
if (signal.aborted) return;
|
|
1552
1671
|
try {
|
|
1553
|
-
|
|
1554
|
-
|
|
1672
|
+
const stream = provider.stream(request)[Symbol.asyncIterator]();
|
|
1673
|
+
let cancel;
|
|
1674
|
+
let cancelTimer;
|
|
1675
|
+
const cancelled = new Promise((resolve3) => {
|
|
1676
|
+
cancel = () => {
|
|
1677
|
+
cancelTimer ??= setTimeout(() => resolve3({ done: true, value: void 0 }), 100);
|
|
1678
|
+
};
|
|
1679
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
1680
|
+
if (signal.aborted) cancel();
|
|
1681
|
+
});
|
|
1682
|
+
try {
|
|
1683
|
+
for (; ; ) {
|
|
1684
|
+
const next = await Promise.race([stream.next(), cancelled]);
|
|
1685
|
+
if (next.done) break;
|
|
1686
|
+
if (!signal.aborted || next.value.type === "usage" || next.value.type === "done") {
|
|
1687
|
+
yield next.value;
|
|
1688
|
+
}
|
|
1689
|
+
if (next.value.type === "done") break;
|
|
1690
|
+
}
|
|
1691
|
+
} finally {
|
|
1692
|
+
signal.removeEventListener("abort", cancel);
|
|
1693
|
+
if (cancelTimer !== void 0) clearTimeout(cancelTimer);
|
|
1694
|
+
void stream.return?.().catch(() => void 0);
|
|
1555
1695
|
}
|
|
1556
1696
|
return;
|
|
1557
1697
|
} catch (error) {
|
|
@@ -1833,20 +1973,20 @@ function finitePositive(value) {
|
|
|
1833
1973
|
}
|
|
1834
1974
|
|
|
1835
1975
|
// src/core/checkpoint.ts
|
|
1836
|
-
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
1976
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
1837
1977
|
import { createReadStream } from "fs";
|
|
1838
1978
|
import {
|
|
1839
1979
|
chmod as chmod3,
|
|
1840
1980
|
lstat,
|
|
1841
1981
|
mkdir as mkdir3,
|
|
1842
|
-
readFile as
|
|
1982
|
+
readFile as readFile5,
|
|
1843
1983
|
readdir as readdir2,
|
|
1844
1984
|
rename as rename4,
|
|
1845
1985
|
stat as stat3,
|
|
1846
1986
|
unlink as unlink2,
|
|
1847
|
-
writeFile as
|
|
1987
|
+
writeFile as writeFile5
|
|
1848
1988
|
} from "fs/promises";
|
|
1849
|
-
import
|
|
1989
|
+
import path6 from "path";
|
|
1850
1990
|
|
|
1851
1991
|
// src/tools/safepath.ts
|
|
1852
1992
|
import { realpathSync } from "fs";
|
|
@@ -1941,7 +2081,7 @@ function beginCheckpoint(options) {
|
|
|
1941
2081
|
},
|
|
1942
2082
|
markChanged(absolutePath) {
|
|
1943
2083
|
if (finished) return;
|
|
1944
|
-
const resolved =
|
|
2084
|
+
const resolved = path6.resolve(absolutePath);
|
|
1945
2085
|
let capturedEntry = [...captured.values()].find((entry) => entry.absolutePath === resolved);
|
|
1946
2086
|
if (!capturedEntry) {
|
|
1947
2087
|
const safe = resolveInside(root, absolutePath);
|
|
@@ -1972,12 +2112,12 @@ function beginCheckpoint(options) {
|
|
|
1972
2112
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1973
2113
|
entries
|
|
1974
2114
|
};
|
|
1975
|
-
const sessionDir =
|
|
2115
|
+
const sessionDir = path6.join(storageDir, options.sessionId);
|
|
1976
2116
|
await ensureDir(storageDir);
|
|
1977
2117
|
await ensureDir(sessionDir);
|
|
1978
2118
|
lastCheckpointTimestamp = Math.max(Date.now(), lastCheckpointTimestamp + 1);
|
|
1979
2119
|
const id = `${String(lastCheckpointTimestamp).padStart(13, "0")}-${randomBytes2(4).toString("hex")}`;
|
|
1980
|
-
await writeCheckpoint(
|
|
2120
|
+
await writeCheckpoint(path6.join(sessionDir, `${id}.json`), stored);
|
|
1981
2121
|
pruneCheckpoints(sessionDir).catch(() => void 0);
|
|
1982
2122
|
return { id, paths: entries.map((entry) => entry.path) };
|
|
1983
2123
|
}
|
|
@@ -1986,7 +2126,7 @@ function beginCheckpoint(options) {
|
|
|
1986
2126
|
async function undoLatestCheckpoint(options) {
|
|
1987
2127
|
assertSessionId2(options.sessionId);
|
|
1988
2128
|
const root = workspaceRoot(options.cwd);
|
|
1989
|
-
const sessionDir =
|
|
2129
|
+
const sessionDir = path6.join(options.storageDir ?? checkpointsDir, options.sessionId);
|
|
1990
2130
|
const file = await newestCheckpoint(sessionDir);
|
|
1991
2131
|
const empty = { found: false, restored: [], removed: [], conflicts: [], failed: [] };
|
|
1992
2132
|
if (!file) return empty;
|
|
@@ -2042,7 +2182,7 @@ async function snapshotFile(file) {
|
|
|
2042
2182
|
if (info.size > MAX_FILE_BYTES) {
|
|
2043
2183
|
throw new Error(`Cannot checkpoint ${file}: it exceeds the ${MAX_FILE_BYTES / 1e6} MB limit.`);
|
|
2044
2184
|
}
|
|
2045
|
-
const data = await
|
|
2185
|
+
const data = await readFile5(file);
|
|
2046
2186
|
if (data.length > MAX_FILE_BYTES) {
|
|
2047
2187
|
throw new Error(`Cannot checkpoint ${file}: it changed beyond the file-size limit while reading.`);
|
|
2048
2188
|
}
|
|
@@ -2059,7 +2199,7 @@ async function fingerprint(file) {
|
|
|
2059
2199
|
if (error.code === "ENOENT") return { existed: false };
|
|
2060
2200
|
throw error;
|
|
2061
2201
|
}
|
|
2062
|
-
const hash =
|
|
2202
|
+
const hash = createHash2("sha256");
|
|
2063
2203
|
if (!info.isFile()) {
|
|
2064
2204
|
hash.update(`kitcode:${info.isDirectory() ? "directory" : "non-file"}`);
|
|
2065
2205
|
return { existed: true, sha256: hash.digest("hex") };
|
|
@@ -2071,7 +2211,7 @@ function matchesSnapshot(before, after) {
|
|
|
2071
2211
|
if (before.existed !== after.existed) return false;
|
|
2072
2212
|
if (!before.existed) return true;
|
|
2073
2213
|
const data = Buffer.from(before.data ?? "", "base64");
|
|
2074
|
-
return after.sha256 ===
|
|
2214
|
+
return after.sha256 === createHash2("sha256").update(data).digest("hex");
|
|
2075
2215
|
}
|
|
2076
2216
|
function sameFingerprint(a, b) {
|
|
2077
2217
|
return a.existed === b.existed && (!a.existed || a.sha256 === b.sha256);
|
|
@@ -2080,14 +2220,14 @@ async function restoreFile(root, relativePath, snapshot, expected) {
|
|
|
2080
2220
|
const safe = resolveInside(root, relativePath);
|
|
2081
2221
|
if (!safe.ok || safe.relative !== relativePath) throw new Error("The restore path changed.");
|
|
2082
2222
|
const data = decodeSnapshot(snapshot);
|
|
2083
|
-
await mkdir3(
|
|
2223
|
+
await mkdir3(path6.dirname(safe.path), { recursive: true });
|
|
2084
2224
|
const rechecked = resolveInside(root, relativePath);
|
|
2085
2225
|
if (!rechecked.ok || rechecked.path !== safe.path || rechecked.relative !== relativePath) {
|
|
2086
2226
|
throw new Error("The restore path changed while preparing its parent directory.");
|
|
2087
2227
|
}
|
|
2088
2228
|
const temp = `${rechecked.path}.${process.pid}.${randomBytes2(4).toString("hex")}.undo`;
|
|
2089
2229
|
try {
|
|
2090
|
-
await
|
|
2230
|
+
await writeFile5(temp, data, { mode: snapshot.mode ?? 384 });
|
|
2091
2231
|
await chmod3(temp, snapshot.mode ?? 384);
|
|
2092
2232
|
const finalPath = resolveInside(root, relativePath);
|
|
2093
2233
|
if (!finalPath.ok || finalPath.path !== rechecked.path || finalPath.relative !== relativePath) {
|
|
@@ -2110,7 +2250,7 @@ async function writeCheckpoint(file, checkpoint) {
|
|
|
2110
2250
|
}
|
|
2111
2251
|
const temp = `${file}.${process.pid}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
2112
2252
|
try {
|
|
2113
|
-
await
|
|
2253
|
+
await writeFile5(temp, body, { encoding: "utf8", mode: 384 });
|
|
2114
2254
|
await chmod3(temp, 384);
|
|
2115
2255
|
await rename4(temp, file);
|
|
2116
2256
|
} catch (error) {
|
|
@@ -2123,7 +2263,7 @@ async function readCheckpoint(file) {
|
|
|
2123
2263
|
if (info.size > MAX_CHECKPOINT_JSON_BYTES) throw new Error("Undo checkpoint is too large to read.");
|
|
2124
2264
|
let parsed;
|
|
2125
2265
|
try {
|
|
2126
|
-
parsed = JSON.parse(await
|
|
2266
|
+
parsed = JSON.parse(await readFile5(file, "utf8"));
|
|
2127
2267
|
} catch (error) {
|
|
2128
2268
|
throw new Error(`Undo checkpoint is not valid JSON: ${error.message}`);
|
|
2129
2269
|
}
|
|
@@ -2139,7 +2279,7 @@ function isCheckpoint(value) {
|
|
|
2139
2279
|
return item.entries.every((entry) => {
|
|
2140
2280
|
if (typeof entry !== "object" || entry === null) return false;
|
|
2141
2281
|
const candidate = entry;
|
|
2142
|
-
if (typeof candidate.path !== "string" || candidate.path === "" ||
|
|
2282
|
+
if (typeof candidate.path !== "string" || candidate.path === "" || path6.isAbsolute(candidate.path) || typeof candidate.before !== "object" || candidate.before === null || typeof candidate.after !== "object" || candidate.after === null || typeof candidate.before.existed !== "boolean" || typeof candidate.after.existed !== "boolean") {
|
|
2143
2283
|
return false;
|
|
2144
2284
|
}
|
|
2145
2285
|
if (candidate.before.existed) {
|
|
@@ -2171,12 +2311,12 @@ async function newestCheckpoint(sessionDir) {
|
|
|
2171
2311
|
throw error;
|
|
2172
2312
|
}
|
|
2173
2313
|
const name = names.filter((candidate) => CHECKPOINT_NAME.test(candidate)).sort().at(-1);
|
|
2174
|
-
return name ?
|
|
2314
|
+
return name ? path6.join(sessionDir, name) : null;
|
|
2175
2315
|
}
|
|
2176
2316
|
async function pruneCheckpoints(sessionDir) {
|
|
2177
2317
|
const names = (await readdir2(sessionDir)).filter((candidate) => CHECKPOINT_NAME.test(candidate)).sort().reverse();
|
|
2178
2318
|
await Promise.all(
|
|
2179
|
-
names.slice(MAX_CHECKPOINTS_PER_SESSION).map((name) => unlink2(
|
|
2319
|
+
names.slice(MAX_CHECKPOINTS_PER_SESSION).map((name) => unlink2(path6.join(sessionDir, name)))
|
|
2180
2320
|
);
|
|
2181
2321
|
}
|
|
2182
2322
|
function workspaceRoot(cwd) {
|
|
@@ -2191,19 +2331,11 @@ function assertSessionId2(id) {
|
|
|
2191
2331
|
}
|
|
2192
2332
|
|
|
2193
2333
|
// src/core/diagnostics.ts
|
|
2194
|
-
import { readFile as
|
|
2195
|
-
import
|
|
2334
|
+
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
2335
|
+
import path7 from "path";
|
|
2196
2336
|
|
|
2197
2337
|
// src/tools/bash.ts
|
|
2198
2338
|
import { spawn } from "child_process";
|
|
2199
|
-
|
|
2200
|
-
// src/tools/summary.ts
|
|
2201
|
-
function brief(value, max = 60) {
|
|
2202
|
-
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2203
|
-
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
// src/tools/bash.ts
|
|
2207
2339
|
var DEFAULT_TIMEOUT = 12e4;
|
|
2208
2340
|
var MAX_TIMEOUT = 6e5;
|
|
2209
2341
|
var MAX_OUTPUT = 1e5;
|
|
@@ -2343,7 +2475,7 @@ async function detectDiagnosticCommands(cwd, configured = []) {
|
|
|
2343
2475
|
const custom = configured.map((command) => command.trim()).filter(Boolean);
|
|
2344
2476
|
if (custom.length > 0) return custom.slice(0, 8);
|
|
2345
2477
|
const commands = [];
|
|
2346
|
-
const packageJson = await readPackageJson(
|
|
2478
|
+
const packageJson = await readPackageJson(path7.join(cwd, "package.json"));
|
|
2347
2479
|
if (packageJson) {
|
|
2348
2480
|
const manager = await packageManager(cwd, packageJson.packageManager);
|
|
2349
2481
|
for (const name of ["lint", "typecheck", "check", "test"]) {
|
|
@@ -2352,11 +2484,11 @@ async function detectDiagnosticCommands(cwd, configured = []) {
|
|
|
2352
2484
|
}
|
|
2353
2485
|
}
|
|
2354
2486
|
}
|
|
2355
|
-
if (await isFile2(
|
|
2487
|
+
if (await isFile2(path7.join(cwd, "Cargo.toml"))) {
|
|
2356
2488
|
commands.push("cargo check", "cargo test");
|
|
2357
2489
|
}
|
|
2358
|
-
if (await isFile2(
|
|
2359
|
-
if (await isFile2(
|
|
2490
|
+
if (await isFile2(path7.join(cwd, "go.mod"))) commands.push("go test ./...");
|
|
2491
|
+
if (await isFile2(path7.join(cwd, "pyproject.toml")) || await isFile2(path7.join(cwd, "pytest.ini")) || await isFile2(path7.join(cwd, "tox.ini"))) {
|
|
2360
2492
|
commands.push("python -m pytest");
|
|
2361
2493
|
}
|
|
2362
2494
|
return [...new Set(commands)].slice(0, 8);
|
|
@@ -2446,7 +2578,7 @@ async function readPackageJson(file) {
|
|
|
2446
2578
|
try {
|
|
2447
2579
|
const info = await stat4(file);
|
|
2448
2580
|
if (!info.isFile() || info.size > MAX_PACKAGE_BYTES) return null;
|
|
2449
|
-
const value = JSON.parse(await
|
|
2581
|
+
const value = JSON.parse(await readFile6(file, "utf8"));
|
|
2450
2582
|
if (typeof value !== "object" || value === null) return null;
|
|
2451
2583
|
const candidate = value;
|
|
2452
2584
|
return {
|
|
@@ -2460,9 +2592,9 @@ async function readPackageJson(file) {
|
|
|
2460
2592
|
async function packageManager(cwd, declared) {
|
|
2461
2593
|
const name = declared?.split("@")[0];
|
|
2462
2594
|
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") return name;
|
|
2463
|
-
if (await isFile2(
|
|
2464
|
-
if (await isFile2(
|
|
2465
|
-
if (await isFile2(
|
|
2595
|
+
if (await isFile2(path7.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
2596
|
+
if (await isFile2(path7.join(cwd, "yarn.lock"))) return "yarn";
|
|
2597
|
+
if (await isFile2(path7.join(cwd, "bun.lock")) || await isFile2(path7.join(cwd, "bun.lockb"))) {
|
|
2466
2598
|
return "bun";
|
|
2467
2599
|
}
|
|
2468
2600
|
return "npm";
|
|
@@ -2480,9 +2612,9 @@ async function isFile2(file) {
|
|
|
2480
2612
|
|
|
2481
2613
|
// src/core/attachments.ts
|
|
2482
2614
|
import { execFile } from "child_process";
|
|
2483
|
-
import { lstat as lstat2, readdir as readdir3, readFile as
|
|
2615
|
+
import { lstat as lstat2, readdir as readdir3, readFile as readFile7, stat as stat5 } from "fs/promises";
|
|
2484
2616
|
import os2 from "os";
|
|
2485
|
-
import
|
|
2617
|
+
import path8 from "path";
|
|
2486
2618
|
import { fileURLToPath } from "url";
|
|
2487
2619
|
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
2488
2620
|
var MAX_TEXT_BYTES = 256 * 1024;
|
|
@@ -2513,7 +2645,7 @@ async function loadAttachment(cwd, requestedPath) {
|
|
|
2513
2645
|
const resolved = resolveAttachmentPath(cwd, requestedPath);
|
|
2514
2646
|
const linkInfo = await lstat2(resolved).catch(() => null);
|
|
2515
2647
|
if (linkInfo?.isSymbolicLink()) {
|
|
2516
|
-
throw new Error(`Cannot attach symbolic links: ${
|
|
2648
|
+
throw new Error(`Cannot attach symbolic links: ${path8.basename(resolved)}`);
|
|
2517
2649
|
}
|
|
2518
2650
|
const info = await stat5(resolved).catch((error) => {
|
|
2519
2651
|
if (error.code === "ENOENT") throw new Error(`Attachment not found: ${resolved}`);
|
|
@@ -2522,7 +2654,7 @@ async function loadAttachment(cwd, requestedPath) {
|
|
|
2522
2654
|
if (info.isDirectory()) {
|
|
2523
2655
|
const attachment = await loadFirstImageFromDirectory(resolved);
|
|
2524
2656
|
if (attachment) return attachment;
|
|
2525
|
-
throw new Error(`No supported images found in directory: ${
|
|
2657
|
+
throw new Error(`No supported images found in directory: ${path8.basename(resolved)}`);
|
|
2526
2658
|
}
|
|
2527
2659
|
if (!info.isFile()) throw new Error(`Attachment is not a regular file: ${resolved}`);
|
|
2528
2660
|
return loadResolvedAttachment(resolved, info.size);
|
|
@@ -2532,7 +2664,7 @@ async function loadAutomaticAttachment(cwd, requestedPath) {
|
|
|
2532
2664
|
const resolved = resolveAttachmentPath(cwd, requestedPath);
|
|
2533
2665
|
if (isSensitiveAutomaticPath(resolved)) {
|
|
2534
2666
|
throw new Error(
|
|
2535
|
-
`For safety, sensitive-looking files must be attached explicitly with /attach: ${
|
|
2667
|
+
`For safety, sensitive-looking files must be attached explicitly with /attach: ${path8.basename(resolved)}`
|
|
2536
2668
|
);
|
|
2537
2669
|
}
|
|
2538
2670
|
const linkInfo = await lstat2(resolved).catch(() => null);
|
|
@@ -2552,14 +2684,14 @@ function looksLikeAttachmentPath(value) {
|
|
|
2552
2684
|
if (trimmed.length > MAX_PATH_CHARS) return false;
|
|
2553
2685
|
const candidate = normalizeInputPath(trimmed);
|
|
2554
2686
|
if (/^file:\/\//i.test(candidate)) return true;
|
|
2555
|
-
if (
|
|
2687
|
+
if (path8.isAbsolute(candidate)) return true;
|
|
2556
2688
|
if (/^(?:~|\.{1,2})[\\/]/.test(candidate)) return true;
|
|
2557
2689
|
if (candidate.includes("/") || candidate.includes("\\")) {
|
|
2558
2690
|
if (trimmed.length > 200) return false;
|
|
2559
2691
|
return true;
|
|
2560
2692
|
}
|
|
2561
|
-
const basename3 =
|
|
2562
|
-
return
|
|
2693
|
+
const basename3 = path8.basename(candidate).toLowerCase();
|
|
2694
|
+
return path8.extname(basename3) !== "" || AUTO_PATH_NAMES.has(basename3);
|
|
2563
2695
|
}
|
|
2564
2696
|
async function loadClipboardImage(platform = process.platform, runner = runClipboardCommand) {
|
|
2565
2697
|
const commands = clipboardCommands(platform);
|
|
@@ -2591,12 +2723,12 @@ async function loadClipboardImage(platform = process.platform, runner = runClipb
|
|
|
2591
2723
|
throw new Error(`No supported image found in the clipboard. ${hint}`);
|
|
2592
2724
|
}
|
|
2593
2725
|
async function loadResolvedAttachment(resolved, size) {
|
|
2594
|
-
const name = safeName(
|
|
2726
|
+
const name = safeName(path8.basename(resolved));
|
|
2595
2727
|
const imageLimitCandidate = size <= MAX_IMAGE_BYTES;
|
|
2596
2728
|
if (!imageLimitCandidate) {
|
|
2597
2729
|
throw new Error(`Attachment is larger than ${formatBytes(MAX_IMAGE_BYTES)}: ${name}`);
|
|
2598
2730
|
}
|
|
2599
|
-
const data = await
|
|
2731
|
+
const data = await readFile7(resolved);
|
|
2600
2732
|
const mediaType = detectImage(data);
|
|
2601
2733
|
if (mediaType) {
|
|
2602
2734
|
return {
|
|
@@ -2625,10 +2757,10 @@ async function loadFirstImageFromDirectory(dirPath) {
|
|
|
2625
2757
|
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
2626
2758
|
const entries = await readdir3(dirPath, { withFileTypes: true }).catch(() => null);
|
|
2627
2759
|
if (!entries) return null;
|
|
2628
|
-
const sorted = entries.filter((entry) => entry.isFile() && IMAGE_EXTENSIONS.has(
|
|
2760
|
+
const sorted = entries.filter((entry) => entry.isFile() && IMAGE_EXTENSIONS.has(path8.extname(entry.name).toLowerCase())).sort((a, b) => a.name.localeCompare(b.name));
|
|
2629
2761
|
if (sorted.length === 0) return null;
|
|
2630
2762
|
const first2 = sorted[0];
|
|
2631
|
-
const filePath =
|
|
2763
|
+
const filePath = path8.join(dirPath, first2.name);
|
|
2632
2764
|
const info = await stat5(filePath).catch(() => null);
|
|
2633
2765
|
if (!info?.isFile()) return null;
|
|
2634
2766
|
return loadResolvedAttachment(filePath, info.size);
|
|
@@ -2638,17 +2770,17 @@ function resolveAttachmentPath(cwd, requestedPath) {
|
|
|
2638
2770
|
if (!cleaned) throw new Error("Give a file path: /attach <path>");
|
|
2639
2771
|
if (/^file:\/\//i.test(cleaned)) {
|
|
2640
2772
|
try {
|
|
2641
|
-
return
|
|
2773
|
+
return path8.normalize(fileURLToPath(cleaned));
|
|
2642
2774
|
} catch {
|
|
2643
2775
|
throw new Error(`Invalid local file URL: ${cleaned}`);
|
|
2644
2776
|
}
|
|
2645
2777
|
}
|
|
2646
|
-
const expanded = cleaned === "~" ? os2.homedir() : cleaned.startsWith("~/") ?
|
|
2647
|
-
return
|
|
2778
|
+
const expanded = cleaned === "~" ? os2.homedir() : cleaned.startsWith("~/") ? path8.join(os2.homedir(), cleaned.slice(2)) : cleaned;
|
|
2779
|
+
return path8.isAbsolute(expanded) ? path8.normalize(expanded) : path8.resolve(cwd, expanded);
|
|
2648
2780
|
}
|
|
2649
2781
|
function isSensitiveAutomaticPath(file) {
|
|
2650
|
-
const basename3 =
|
|
2651
|
-
return basename3.startsWith(".env.") || SENSITIVE_AUTO_NAMES.has(basename3) || SENSITIVE_AUTO_EXTENSIONS.has(
|
|
2782
|
+
const basename3 = path8.basename(file).toLowerCase();
|
|
2783
|
+
return basename3.startsWith(".env.") || SENSITIVE_AUTO_NAMES.has(basename3) || SENSITIVE_AUTO_EXTENSIONS.has(path8.extname(basename3));
|
|
2652
2784
|
}
|
|
2653
2785
|
function attachmentLabel(block) {
|
|
2654
2786
|
if (block.type === "image") return `image: ${block.name}`;
|
|
@@ -2793,7 +2925,7 @@ try {
|
|
|
2793
2925
|
}
|
|
2794
2926
|
`;
|
|
2795
2927
|
function textMime(file) {
|
|
2796
|
-
const extension =
|
|
2928
|
+
const extension = path8.extname(file).toLowerCase();
|
|
2797
2929
|
const known = {
|
|
2798
2930
|
".json": "application/json",
|
|
2799
2931
|
".md": "text/markdown",
|
|
@@ -2846,8 +2978,8 @@ var SINGLE_MAX_TOKENS = 768;
|
|
|
2846
2978
|
var CHUNK_MAX_TOKENS = 512;
|
|
2847
2979
|
var MERGE_MAX_TOKENS = 1024;
|
|
2848
2980
|
var CHUNK_CONCURRENCY = 3;
|
|
2849
|
-
var SYSTEM_SUMMARIZE = "You are summarizing a coding conversation so another agent can continue the work. Extract and preserve ONLY: concrete requirements, user preferences, files changed, commands run and their results, errors encountered, bugs found, architectural decisions, and unfinished work. Be terse and factual. Do NOT include greetings, acknowledgments, chain-of-thought, or step-by-step narration. Return only the summary.";
|
|
2850
|
-
var SYSTEM_MERGE = "Merge these partial summaries of a coding conversation into one coherent summary. Remove duplicates. Preserve: requirements, files changed, commands run, errors, decisions, and unfinished work. Be terse. Return only the merged summary.";
|
|
2981
|
+
var SYSTEM_SUMMARIZE = "You are summarizing a coding conversation so another agent can continue the work. Extract and preserve ONLY: concrete requirements, user preferences, files changed, commands run and their results, errors encountered, bugs found, architectural decisions, and unfinished work. Be terse and factual. Do NOT include greetings, acknowledgments, chain-of-thought, or step-by-step narration. Use these sections: Requirements; Verified facts (with file paths or tool evidence); Changes made; Verification (exact commands, pass/fail/not run, and unresolved error excerpts); Decisions and assumptions; Remaining work. Keep plans and assumptions separate from verified facts. Never turn an intended command into a completed check. When checks conflict, retain the latest result and mark earlier results as superseded. Preserve explicit user corrections and project constraints. Do not retain credentials or secret values. Return only the summary.";
|
|
2982
|
+
var SYSTEM_MERGE = "Merge these partial summaries of a coding conversation into one coherent summary. Remove duplicates. Preserve: requirements, files changed, commands run, errors, decisions, and unfinished work. Keep the structured sections and evidence from the source summaries. Separate verified facts, assumptions, and planned work; keep exact verification commands and their latest outcomes. Do not resolve conflicting evidence by guessing. Do not retain credentials or secret values. Be terse. Return only the merged summary.";
|
|
2851
2983
|
async function compactHistory(options) {
|
|
2852
2984
|
const cut = compactCutIndex(options.history);
|
|
2853
2985
|
if (cut <= 0) {
|
|
@@ -3249,6 +3381,12 @@ function buildSystemPrompt(args) {
|
|
|
3249
3381
|
if (args.skills && args.skills.trim() !== "") {
|
|
3250
3382
|
lines.push("", args.skills);
|
|
3251
3383
|
}
|
|
3384
|
+
if (args.toolNames.includes("memory")) {
|
|
3385
|
+
lines.push("", "Use memory to retain explicit user preferences, verified project facts and accepted decisions across chats. Include the source. Read existing notes before replacing or deleting them; correct stale notes rather than adding contradictions. Keep temporary progress in the conversation, not permanent memory. Never store credentials. Tell the user briefly when you update memory.");
|
|
3386
|
+
}
|
|
3387
|
+
if (args.memory?.trim()) {
|
|
3388
|
+
lines.push("", "Project notes saved in earlier conversations. Treat these as potentially stale context; verify claims against current files and follow current user instructions.", args.memory);
|
|
3389
|
+
}
|
|
3252
3390
|
return lines.join("\n");
|
|
3253
3391
|
}
|
|
3254
3392
|
|
|
@@ -3499,7 +3637,7 @@ function clip(value) {
|
|
|
3499
3637
|
// package.json
|
|
3500
3638
|
var package_default = {
|
|
3501
3639
|
name: "@kernelonpanic/kitcode",
|
|
3502
|
-
version: "1.
|
|
3640
|
+
version: "1.3.0",
|
|
3503
3641
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3504
3642
|
type: "module",
|
|
3505
3643
|
license: "MIT",
|
|
@@ -3567,7 +3705,7 @@ var package_default = {
|
|
|
3567
3705
|
|
|
3568
3706
|
// src/version.ts
|
|
3569
3707
|
var KITCODE_VERSION = package_default.version;
|
|
3570
|
-
var KITCODE_COMMIT = true ? "
|
|
3708
|
+
var KITCODE_COMMIT = true ? "7cd983ca3dc89406326e5e513870686036c3ab9c" : "development";
|
|
3571
3709
|
|
|
3572
3710
|
// src/mcp/client.ts
|
|
3573
3711
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3858,8 +3996,8 @@ function balanceEndpoints(baseUrl) {
|
|
|
3858
3996
|
endpoint(url.origin, "/v1/balance", genericBalance)
|
|
3859
3997
|
];
|
|
3860
3998
|
}
|
|
3861
|
-
function endpoint(origin,
|
|
3862
|
-
return { url: `${origin}${
|
|
3999
|
+
function endpoint(origin, path15, parse2) {
|
|
4000
|
+
return { url: `${origin}${path15}`, parse: parse2 };
|
|
3863
4001
|
}
|
|
3864
4002
|
function deepSeekBalance(body) {
|
|
3865
4003
|
const infos = record(body)?.["balance_infos"];
|
|
@@ -3952,8 +4090,8 @@ async function limitedResponseText(response, limit) {
|
|
|
3952
4090
|
}
|
|
3953
4091
|
|
|
3954
4092
|
// src/providers/catalog.ts
|
|
3955
|
-
import { chmod as chmod4, readFile as
|
|
3956
|
-
import
|
|
4093
|
+
import { chmod as chmod4, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
4094
|
+
import path9 from "path";
|
|
3957
4095
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3958
4096
|
var CACHE_VERSION = 3;
|
|
3959
4097
|
async function loadModels(provider, refresh = false) {
|
|
@@ -3975,11 +4113,11 @@ async function loadModels(provider, refresh = false) {
|
|
|
3975
4113
|
return models;
|
|
3976
4114
|
}
|
|
3977
4115
|
function cacheFile(providerId) {
|
|
3978
|
-
return
|
|
4116
|
+
return path9.join(cacheDir, "models", `${providerId.replace(/[^\w.-]/g, "_")}.json`);
|
|
3979
4117
|
}
|
|
3980
4118
|
async function readCache(file) {
|
|
3981
4119
|
try {
|
|
3982
|
-
const parsed = JSON.parse(await
|
|
4120
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
3983
4121
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
3984
4122
|
const candidate = parsed;
|
|
3985
4123
|
if (typeof candidate.fetchedAt !== "number" || !Number.isFinite(candidate.fetchedAt) || !Array.isArray(candidate.models) || candidate.models.length > 1e4 || !candidate.models.every(validModel)) {
|
|
@@ -4010,8 +4148,8 @@ function validModel(value) {
|
|
|
4010
4148
|
async function writeCache(file, models) {
|
|
4011
4149
|
const payload = { version: CACHE_VERSION, fetchedAt: Date.now(), models };
|
|
4012
4150
|
try {
|
|
4013
|
-
await ensureDir(
|
|
4014
|
-
await
|
|
4151
|
+
await ensureDir(path9.dirname(file));
|
|
4152
|
+
await writeFile6(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
|
|
4015
4153
|
await chmod4(file, 384);
|
|
4016
4154
|
} catch {
|
|
4017
4155
|
return;
|
|
@@ -4160,7 +4298,8 @@ async function* streamTurn(client, providerId, apiKey2, req) {
|
|
|
4160
4298
|
tools: toToolParams(req.tools)
|
|
4161
4299
|
};
|
|
4162
4300
|
if (req.thinking) params.thinking = { type: "adaptive", display: "summarized" };
|
|
4163
|
-
|
|
4301
|
+
const effort = resolveEffort(req.effort, req.messages);
|
|
4302
|
+
if (effort) params.output_config = { effort };
|
|
4164
4303
|
let thinking = "";
|
|
4165
4304
|
let text = "";
|
|
4166
4305
|
let events = 0;
|
|
@@ -4348,11 +4487,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4348
4487
|
let recognised = 0;
|
|
4349
4488
|
const calls = /* @__PURE__ */ new Map();
|
|
4350
4489
|
const capture = captureResponseHead();
|
|
4490
|
+
const effort = openAiEffort(req.model, resolveEffort(req.effort, req.messages));
|
|
4351
4491
|
try {
|
|
4352
4492
|
const stream = await client.withOptions({ fetch: capture.fetch }).chat.completions.create(
|
|
4353
4493
|
{
|
|
4354
4494
|
model: req.model,
|
|
4355
4495
|
max_tokens: req.maxTokens,
|
|
4496
|
+
...effort ? { reasoning_effort: effort } : {},
|
|
4356
4497
|
messages: toChatMessages(req.system, req.messages),
|
|
4357
4498
|
...req.tools.length > 0 ? { tools: toChatTools(req.tools) } : {},
|
|
4358
4499
|
stream: true,
|
|
@@ -4649,11 +4790,11 @@ function isFileEdit(tool) {
|
|
|
4649
4790
|
}
|
|
4650
4791
|
|
|
4651
4792
|
// src/tools/edit.ts
|
|
4652
|
-
import { readFile as
|
|
4793
|
+
import { readFile as readFile9, stat as stat6 } from "fs/promises";
|
|
4653
4794
|
|
|
4654
4795
|
// src/tools/safe-write.ts
|
|
4655
4796
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4656
|
-
import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as
|
|
4797
|
+
import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as rm4 } from "fs/promises";
|
|
4657
4798
|
import { basename as basename2, dirname as dirname2, join } from "path";
|
|
4658
4799
|
var UnsafeFileChangeError = class extends Error {
|
|
4659
4800
|
constructor(message) {
|
|
@@ -4716,7 +4857,7 @@ async function atomicWriteSafeFile(file, data, snapshot) {
|
|
|
4716
4857
|
await rename5(temp, file);
|
|
4717
4858
|
} catch (error) {
|
|
4718
4859
|
await handle?.close().catch(() => void 0);
|
|
4719
|
-
await
|
|
4860
|
+
await rm4(temp, { force: true }).catch(() => void 0);
|
|
4720
4861
|
throw error;
|
|
4721
4862
|
}
|
|
4722
4863
|
}
|
|
@@ -4811,56 +4952,56 @@ var editTool = {
|
|
|
4811
4952
|
return `edit(${brief(input.path)})`;
|
|
4812
4953
|
},
|
|
4813
4954
|
async preview(input, ctx) {
|
|
4814
|
-
const { path:
|
|
4815
|
-
const safe = resolveInside(ctx.cwd,
|
|
4955
|
+
const { path: path15, oldString, newString, replaceAll = false } = input;
|
|
4956
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4816
4957
|
if (!safe.ok) return { kind: "text", text: safe.reason };
|
|
4817
4958
|
const info = await stat6(safe.path).catch(() => null);
|
|
4818
4959
|
if (info && info.size > MAX_FILE_BYTES2) {
|
|
4819
|
-
return { kind: "text", text: `Cannot preview ${
|
|
4960
|
+
return { kind: "text", text: `Cannot preview ${path15}: the file is too large.` };
|
|
4820
4961
|
}
|
|
4821
|
-
const beforeBuffer = await
|
|
4822
|
-
if (!beforeBuffer) return { kind: "text", text: `Cannot read ${
|
|
4962
|
+
const beforeBuffer = await readFile9(safe.path).catch(() => null);
|
|
4963
|
+
if (!beforeBuffer) return { kind: "text", text: `Cannot read ${path15}.` };
|
|
4823
4964
|
if (beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
4824
|
-
return { kind: "text", text: `Cannot preview binary file ${
|
|
4965
|
+
return { kind: "text", text: `Cannot preview binary file ${path15}` };
|
|
4825
4966
|
}
|
|
4826
4967
|
const before = beforeBuffer.toString("utf8");
|
|
4827
4968
|
const after = replacement(before, oldString, newString, replaceAll);
|
|
4828
|
-
return after === null ? { kind: "text", text: `Cannot preview edit: oldString is not a valid match in ${
|
|
4969
|
+
return after === null ? { kind: "text", text: `Cannot preview edit: oldString is not a valid match in ${path15}.` } : { kind: "diff", path: path15, before, after };
|
|
4829
4970
|
},
|
|
4830
4971
|
async execute(input, ctx) {
|
|
4831
|
-
const { path:
|
|
4832
|
-
const safe = resolveInside(ctx.cwd,
|
|
4972
|
+
const { path: path15, oldString, newString, replaceAll = false } = input;
|
|
4973
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4833
4974
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
4834
4975
|
let snapshot;
|
|
4835
4976
|
try {
|
|
4836
4977
|
snapshot = await readSafeFileSnapshot(safe.path, MAX_FILE_BYTES2);
|
|
4837
4978
|
} catch (error) {
|
|
4838
|
-
return { content: `Cannot edit ${
|
|
4979
|
+
return { content: `Cannot edit ${path15}: ${error.message}.`, isError: true };
|
|
4839
4980
|
}
|
|
4840
|
-
if (!snapshot.exists) return { content: `Cannot read ${
|
|
4981
|
+
if (!snapshot.exists) return { content: `Cannot read ${path15}.`, isError: true };
|
|
4841
4982
|
const beforeBuffer = snapshot.data;
|
|
4842
4983
|
if (beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
4843
|
-
return { content: `Cannot edit ${
|
|
4984
|
+
return { content: `Cannot edit ${path15}: it is a binary file, not text.`, isError: true };
|
|
4844
4985
|
}
|
|
4845
4986
|
const before = beforeBuffer.toString("utf8");
|
|
4846
4987
|
const count = countOccurrences(before, oldString);
|
|
4847
4988
|
if (count === 0) {
|
|
4848
4989
|
return {
|
|
4849
|
-
content: `oldString was not found in ${
|
|
4990
|
+
content: `oldString was not found in ${path15}. Read the file again and match the text exactly, including whitespace.`,
|
|
4850
4991
|
isError: true
|
|
4851
4992
|
};
|
|
4852
4993
|
}
|
|
4853
4994
|
if (count > 1 && !replaceAll) {
|
|
4854
4995
|
return {
|
|
4855
|
-
content: `oldString matches ${count} places in ${
|
|
4996
|
+
content: `oldString matches ${count} places in ${path15}. Add more surrounding context to identify a single one, or set replaceAll to true.`,
|
|
4856
4997
|
isError: true
|
|
4857
4998
|
};
|
|
4858
4999
|
}
|
|
4859
5000
|
const after = replacement(before, oldString, newString, replaceAll);
|
|
4860
|
-
if (after === null) return { content: `Could not prepare edit for ${
|
|
5001
|
+
if (after === null) return { content: `Could not prepare edit for ${path15}.`, isError: true };
|
|
4861
5002
|
if (Buffer.byteLength(after, "utf8") > MAX_FILE_BYTES2) {
|
|
4862
5003
|
return {
|
|
4863
|
-
content: `Cannot edit ${
|
|
5004
|
+
content: `Cannot edit ${path15}: the result exceeds the ${MAX_FILE_BYTES2 / 1e6} MB limit.`,
|
|
4864
5005
|
isError: true
|
|
4865
5006
|
};
|
|
4866
5007
|
}
|
|
@@ -4873,11 +5014,11 @@ var editTool = {
|
|
|
4873
5014
|
await atomicWriteSafeFile(safe.path, after, snapshot);
|
|
4874
5015
|
ctx.checkpoint?.markChanged(safe.path);
|
|
4875
5016
|
} catch (error) {
|
|
4876
|
-
return { content: `Failed to write ${
|
|
5017
|
+
return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
|
|
4877
5018
|
}
|
|
4878
5019
|
return {
|
|
4879
|
-
content: `Edited ${
|
|
4880
|
-
display: { kind: "diff", path:
|
|
5020
|
+
content: `Edited ${path15} (${count} ${count === 1 ? "replacement" : "replacements"})`,
|
|
5021
|
+
display: { kind: "diff", path: path15, before, after }
|
|
4881
5022
|
};
|
|
4882
5023
|
}
|
|
4883
5024
|
};
|
|
@@ -4912,11 +5053,11 @@ var globTool = {
|
|
|
4912
5053
|
return `glob(${brief(input.pattern)})`;
|
|
4913
5054
|
},
|
|
4914
5055
|
async execute(input, ctx) {
|
|
4915
|
-
const { pattern, path:
|
|
5056
|
+
const { pattern, path: path15 = "." } = input;
|
|
4916
5057
|
if (patternEscapes(pattern)) {
|
|
4917
5058
|
return { content: `Pattern ${pattern} must be relative to the workspace root.`, isError: true };
|
|
4918
5059
|
}
|
|
4919
|
-
const safe = resolveInside(ctx.cwd,
|
|
5060
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4920
5061
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
4921
5062
|
const entries = await fg(pattern, {
|
|
4922
5063
|
cwd: safe.path,
|
|
@@ -4938,12 +5079,12 @@ var globTool = {
|
|
|
4938
5079
|
};
|
|
4939
5080
|
|
|
4940
5081
|
// src/tools/grep.ts
|
|
4941
|
-
import { readFile as
|
|
5082
|
+
import { readFile as readFile10, stat as stat7 } from "fs/promises";
|
|
4942
5083
|
import { join as join3 } from "path";
|
|
4943
5084
|
import fg2 from "fast-glob";
|
|
4944
5085
|
|
|
4945
5086
|
// src/tools/sensitive.ts
|
|
4946
|
-
import
|
|
5087
|
+
import path10 from "path";
|
|
4947
5088
|
var sensitiveNames = /* @__PURE__ */ new Set([
|
|
4948
5089
|
".npmrc",
|
|
4949
5090
|
".pypirc",
|
|
@@ -4970,9 +5111,9 @@ var sensitiveExtensions = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx
|
|
|
4970
5111
|
function isSensitivePath(value) {
|
|
4971
5112
|
if (typeof value !== "string") return false;
|
|
4972
5113
|
const normalized = value.replaceAll("\\", "/").toLowerCase();
|
|
4973
|
-
const name =
|
|
5114
|
+
const name = path10.posix.basename(normalized);
|
|
4974
5115
|
if (name === ".env" || name.startsWith(".env.")) return true;
|
|
4975
|
-
if (sensitiveNames.has(name) || sensitiveExtensions.has(
|
|
5116
|
+
if (sensitiveNames.has(name) || sensitiveExtensions.has(path10.posix.extname(name))) return true;
|
|
4976
5117
|
return normalized.split("/").some((part) => part === ".ssh" || part === ".aws");
|
|
4977
5118
|
}
|
|
4978
5119
|
function mentionsSensitivePattern(value) {
|
|
@@ -5090,16 +5231,16 @@ var grepTool = {
|
|
|
5090
5231
|
defaultPermission: "allow",
|
|
5091
5232
|
readOnly: true,
|
|
5092
5233
|
permission(input, ctx) {
|
|
5093
|
-
const { path:
|
|
5094
|
-
if (isSensitivePath(
|
|
5095
|
-
const safe = ctx &&
|
|
5234
|
+
const { path: path15, glob } = input ?? {};
|
|
5235
|
+
if (isSensitivePath(path15) || mentionsSensitivePattern(glob)) return "ask";
|
|
5236
|
+
const safe = ctx && path15 ? resolveInside(ctx.cwd, path15) : void 0;
|
|
5096
5237
|
return safe?.ok && isSensitivePath(safe.relative) ? "ask" : void 0;
|
|
5097
5238
|
},
|
|
5098
5239
|
summarize(input) {
|
|
5099
5240
|
return `grep(${brief(input.pattern)})`;
|
|
5100
5241
|
},
|
|
5101
5242
|
async execute(input, ctx) {
|
|
5102
|
-
const { pattern, path:
|
|
5243
|
+
const { pattern, path: path15 = ".", glob = "**/*", maxMatches } = input;
|
|
5103
5244
|
if (pattern.length > MAX_PATTERN_CHARS) {
|
|
5104
5245
|
return {
|
|
5105
5246
|
content: `Regular expression is limited to ${MAX_PATTERN_CHARS} characters.`,
|
|
@@ -5114,10 +5255,10 @@ var grepTool = {
|
|
|
5114
5255
|
if (patternEscapes(glob)) {
|
|
5115
5256
|
return { content: `Glob ${glob} must be relative to the workspace root.`, isError: true };
|
|
5116
5257
|
}
|
|
5117
|
-
const safe = resolveInside(ctx.cwd,
|
|
5258
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5118
5259
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5119
5260
|
const target = await stat7(safe.path).catch(() => null);
|
|
5120
|
-
if (!target) return { content: `Path not found: ${
|
|
5261
|
+
if (!target) return { content: `Path not found: ${path15}`, isError: true };
|
|
5121
5262
|
const discovered = target.isDirectory() ? (await fg2(glob, {
|
|
5122
5263
|
cwd: safe.path,
|
|
5123
5264
|
dot: false,
|
|
@@ -5148,7 +5289,7 @@ var grepTool = {
|
|
|
5148
5289
|
break;
|
|
5149
5290
|
}
|
|
5150
5291
|
scannedBytes += file.size;
|
|
5151
|
-
const buffer = await
|
|
5292
|
+
const buffer = await readFile10(file.absolute).catch(() => null);
|
|
5152
5293
|
if (!buffer || buffer.subarray(0, 4096).includes(0)) continue;
|
|
5153
5294
|
const lines = buffer.toString("utf8").split("\n");
|
|
5154
5295
|
const remaining = limit - hits.length;
|
|
@@ -5178,7 +5319,7 @@ var grepTool = {
|
|
|
5178
5319
|
};
|
|
5179
5320
|
|
|
5180
5321
|
// src/tools/read.ts
|
|
5181
|
-
import { readFile as
|
|
5322
|
+
import { readFile as readFile11, stat as stat8 } from "fs/promises";
|
|
5182
5323
|
var MAX_LINES = 2e3;
|
|
5183
5324
|
var MAX_CHARS = 2e5;
|
|
5184
5325
|
var MAX_FILE_BYTES4 = 5e6;
|
|
@@ -5207,30 +5348,30 @@ var readTool = {
|
|
|
5207
5348
|
return `read(${brief(input.path)})`;
|
|
5208
5349
|
},
|
|
5209
5350
|
async execute(input, ctx) {
|
|
5210
|
-
const { path:
|
|
5211
|
-
const safe = resolveInside(ctx.cwd,
|
|
5351
|
+
const { path: path15, offset = 1, limit } = input;
|
|
5352
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5212
5353
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5213
5354
|
const info = await stat8(safe.path).catch(() => null);
|
|
5214
|
-
if (!info) return { content: `File not found: ${
|
|
5355
|
+
if (!info) return { content: `File not found: ${path15}`, isError: true };
|
|
5215
5356
|
if (info.isDirectory()) {
|
|
5216
|
-
return { content: `${
|
|
5357
|
+
return { content: `${path15} is a directory, not a file. Use glob to list its contents.`, isError: true };
|
|
5217
5358
|
}
|
|
5218
5359
|
if (info.size > MAX_FILE_BYTES4) {
|
|
5219
5360
|
return {
|
|
5220
|
-
content: `${
|
|
5361
|
+
content: `${path15} is ${(info.size / 1e6).toFixed(1)} MB; read is limited to ${MAX_FILE_BYTES4 / 1e6} MB per file. Use grep or another targeted tool instead.`,
|
|
5221
5362
|
isError: true
|
|
5222
5363
|
};
|
|
5223
5364
|
}
|
|
5224
|
-
const buffer = await
|
|
5365
|
+
const buffer = await readFile11(safe.path);
|
|
5225
5366
|
if (buffer.subarray(0, 4096).includes(0)) {
|
|
5226
|
-
return { content: `${
|
|
5367
|
+
return { content: `${path15} looks like a binary file and cannot be read as text.`, isError: true };
|
|
5227
5368
|
}
|
|
5228
5369
|
const lines = buffer.toString("utf8").split("\n");
|
|
5229
5370
|
if (lines.at(-1) === "") lines.pop();
|
|
5230
|
-
if (lines.length === 0) return { content: `${
|
|
5371
|
+
if (lines.length === 0) return { content: `${path15} is empty.` };
|
|
5231
5372
|
const start = Math.max(1, offset) - 1;
|
|
5232
5373
|
if (start >= lines.length) {
|
|
5233
|
-
return { content: `${
|
|
5374
|
+
return { content: `${path15} has ${lines.length} lines, so offset ${offset} is past the end.`, isError: true };
|
|
5234
5375
|
}
|
|
5235
5376
|
const window = lines.slice(start, start + Math.min(limit ?? MAX_LINES, MAX_LINES));
|
|
5236
5377
|
const rendered = [];
|
|
@@ -5257,7 +5398,7 @@ function toToolSchema(tool) {
|
|
|
5257
5398
|
}
|
|
5258
5399
|
|
|
5259
5400
|
// src/tools/write.ts
|
|
5260
|
-
import { mkdir as mkdir4, readFile as
|
|
5401
|
+
import { mkdir as mkdir4, readFile as readFile12, stat as stat9 } from "fs/promises";
|
|
5261
5402
|
import { dirname as dirname3 } from "path";
|
|
5262
5403
|
var MAX_FILE_BYTES5 = 5e6;
|
|
5263
5404
|
var writeTool = {
|
|
@@ -5277,64 +5418,64 @@ var writeTool = {
|
|
|
5277
5418
|
return `write(${brief(input.path)})`;
|
|
5278
5419
|
},
|
|
5279
5420
|
async preview(input, ctx) {
|
|
5280
|
-
const { path:
|
|
5421
|
+
const { path: path15, content } = input;
|
|
5281
5422
|
if (Buffer.byteLength(content, "utf8") > MAX_FILE_BYTES5) {
|
|
5282
5423
|
return { kind: "text", text: `Write is limited to ${MAX_FILE_BYTES5 / 1e6} MB.` };
|
|
5283
5424
|
}
|
|
5284
|
-
const safe = resolveInside(ctx.cwd,
|
|
5425
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5285
5426
|
if (!safe.ok) return { kind: "text", text: safe.reason };
|
|
5286
5427
|
const beforeInfo = await stat9(safe.path).catch(() => null);
|
|
5287
5428
|
if (beforeInfo && beforeInfo.size > MAX_FILE_BYTES5) {
|
|
5288
|
-
return { kind: "text", text: `Cannot preview ${
|
|
5429
|
+
return { kind: "text", text: `Cannot preview ${path15}: the existing file is too large.` };
|
|
5289
5430
|
}
|
|
5290
|
-
const beforeBuffer = await
|
|
5431
|
+
const beforeBuffer = await readFile12(safe.path).catch(() => null);
|
|
5291
5432
|
if (beforeBuffer?.subarray(0, 8192).includes(0)) {
|
|
5292
|
-
return { kind: "text", text: `Cannot preview binary file ${
|
|
5433
|
+
return { kind: "text", text: `Cannot preview binary file ${path15}` };
|
|
5293
5434
|
}
|
|
5294
5435
|
return {
|
|
5295
5436
|
kind: "diff",
|
|
5296
|
-
path:
|
|
5437
|
+
path: path15,
|
|
5297
5438
|
before: beforeBuffer?.toString("utf8") ?? "",
|
|
5298
5439
|
after: content
|
|
5299
5440
|
};
|
|
5300
5441
|
},
|
|
5301
5442
|
async execute(input, ctx) {
|
|
5302
|
-
const { path:
|
|
5443
|
+
const { path: path15, content } = input;
|
|
5303
5444
|
if (Buffer.byteLength(content, "utf8") > MAX_FILE_BYTES5) {
|
|
5304
5445
|
return {
|
|
5305
|
-
content: `Cannot write ${
|
|
5446
|
+
content: `Cannot write ${path15}: content exceeds the ${MAX_FILE_BYTES5 / 1e6} MB limit.`,
|
|
5306
5447
|
isError: true
|
|
5307
5448
|
};
|
|
5308
5449
|
}
|
|
5309
|
-
const safe = resolveInside(ctx.cwd,
|
|
5450
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5310
5451
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5311
5452
|
let target = safe.path;
|
|
5312
5453
|
let snapshot;
|
|
5313
5454
|
try {
|
|
5314
5455
|
await mkdir4(dirname3(target), { recursive: true });
|
|
5315
|
-
const rechecked = resolveInside(ctx.cwd,
|
|
5456
|
+
const rechecked = resolveInside(ctx.cwd, path15);
|
|
5316
5457
|
if (!rechecked.ok || rechecked.path !== target) {
|
|
5317
5458
|
return {
|
|
5318
|
-
content: `Cannot write ${
|
|
5459
|
+
content: `Cannot write ${path15}: the path changed while its parent was created.`,
|
|
5319
5460
|
isError: true
|
|
5320
5461
|
};
|
|
5321
5462
|
}
|
|
5322
5463
|
target = rechecked.path;
|
|
5323
5464
|
snapshot = await readSafeFileSnapshot(target, MAX_FILE_BYTES5);
|
|
5324
5465
|
if (snapshot.exists && snapshot.data.subarray(0, 8192).includes(0)) {
|
|
5325
|
-
return { content: `Cannot write ${
|
|
5466
|
+
return { content: `Cannot write ${path15}: the existing file is binary, not text.`, isError: true };
|
|
5326
5467
|
}
|
|
5327
5468
|
await ctx.checkpoint?.capture(target);
|
|
5328
5469
|
await atomicWriteSafeFile(target, content, snapshot);
|
|
5329
5470
|
ctx.checkpoint?.markChanged(target);
|
|
5330
5471
|
} catch (error) {
|
|
5331
|
-
return { content: `Failed to write ${
|
|
5472
|
+
return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
|
|
5332
5473
|
}
|
|
5333
5474
|
const before = snapshot.exists ? snapshot.data.toString("utf8") : "";
|
|
5334
5475
|
const lines = content === "" ? 0 : content.replace(/\n$/, "").split("\n").length;
|
|
5335
5476
|
return {
|
|
5336
|
-
content: `${before === "" ? "Created" : "Updated"} ${
|
|
5337
|
-
display: { kind: "diff", path:
|
|
5477
|
+
content: `${before === "" ? "Created" : "Updated"} ${path15} (${lines} ${lines === 1 ? "line" : "lines"})`,
|
|
5478
|
+
display: { kind: "diff", path: path15, before, after: content }
|
|
5338
5479
|
};
|
|
5339
5480
|
}
|
|
5340
5481
|
};
|
|
@@ -5363,8 +5504,8 @@ function createToolRegistry(tools) {
|
|
|
5363
5504
|
}
|
|
5364
5505
|
|
|
5365
5506
|
// src/prompts/library.ts
|
|
5366
|
-
import { chmod as chmod6, readFile as
|
|
5367
|
-
import
|
|
5507
|
+
import { chmod as chmod6, readFile as readFile13, readdir as readdir4, unlink as unlink3, writeFile as writeFile7 } from "fs/promises";
|
|
5508
|
+
import path11 from "path";
|
|
5368
5509
|
async function savePrompt(input) {
|
|
5369
5510
|
const slug = slugify(input.name);
|
|
5370
5511
|
if (!slug) throw new Error(`Prompt name has no letters or digits to name a file after: ${input.name}`);
|
|
@@ -5376,8 +5517,8 @@ async function savePrompt(input) {
|
|
|
5376
5517
|
body: input.body.trim()
|
|
5377
5518
|
};
|
|
5378
5519
|
await ensureDir(promptsDir);
|
|
5379
|
-
const file =
|
|
5380
|
-
await
|
|
5520
|
+
const file = path11.join(promptsDir, `${slug}.md`);
|
|
5521
|
+
await writeFile7(file, serialize(prompt), {
|
|
5381
5522
|
encoding: "utf8",
|
|
5382
5523
|
mode: 384
|
|
5383
5524
|
});
|
|
@@ -5387,7 +5528,7 @@ async function savePrompt(input) {
|
|
|
5387
5528
|
async function getPrompt(slug) {
|
|
5388
5529
|
assertSlug(slug);
|
|
5389
5530
|
try {
|
|
5390
|
-
return parse(slug, await
|
|
5531
|
+
return parse(slug, await readFile13(path11.join(promptsDir, `${slug}.md`), "utf8"));
|
|
5391
5532
|
} catch (error) {
|
|
5392
5533
|
if (isMissing(error)) return null;
|
|
5393
5534
|
throw error;
|
|
@@ -5403,7 +5544,7 @@ async function listPrompts() {
|
|
|
5403
5544
|
}
|
|
5404
5545
|
const prompts2 = await Promise.all(
|
|
5405
5546
|
names.filter((name) => name.endsWith(".md")).map(
|
|
5406
|
-
async (name) => parse(name.slice(0, -3), await
|
|
5547
|
+
async (name) => parse(name.slice(0, -3), await readFile13(path11.join(promptsDir, name), "utf8"))
|
|
5407
5548
|
)
|
|
5408
5549
|
);
|
|
5409
5550
|
return prompts2.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -5411,7 +5552,7 @@ async function listPrompts() {
|
|
|
5411
5552
|
async function deletePrompt(slug) {
|
|
5412
5553
|
assertSlug(slug);
|
|
5413
5554
|
try {
|
|
5414
|
-
await unlink3(
|
|
5555
|
+
await unlink3(path11.join(promptsDir, `${slug}.md`));
|
|
5415
5556
|
return true;
|
|
5416
5557
|
} catch (error) {
|
|
5417
5558
|
if (isMissing(error)) return false;
|
|
@@ -5469,7 +5610,7 @@ function parse(slug, text) {
|
|
|
5469
5610
|
|
|
5470
5611
|
// src/skills/library.ts
|
|
5471
5612
|
import { lstat as lstat4, open as open2, readdir as readdir5 } from "fs/promises";
|
|
5472
|
-
import
|
|
5613
|
+
import path12 from "path";
|
|
5473
5614
|
var SKILL_FILE = "SKILL.md";
|
|
5474
5615
|
var FRONTMATTER_BYTES = 8192;
|
|
5475
5616
|
var MAX_SKILL_BYTES = 5e6;
|
|
@@ -5482,7 +5623,7 @@ async function discoverSkills(dirs) {
|
|
|
5482
5623
|
if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink()) continue;
|
|
5483
5624
|
const entries = await readdir5(root, { withFileTypes: true }).catch(() => []);
|
|
5484
5625
|
const found = await Promise.all(
|
|
5485
|
-
entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(root, rootInfo,
|
|
5626
|
+
entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(root, rootInfo, path12.join(root, entry.name)))
|
|
5486
5627
|
);
|
|
5487
5628
|
for (const meta of found) {
|
|
5488
5629
|
if (meta && !byName.has(meta.name)) byName.set(meta.name, meta);
|
|
@@ -5522,11 +5663,11 @@ function formatSkillCatalogue(skills) {
|
|
|
5522
5663
|
async function readMeta(root, rootInfo, dir) {
|
|
5523
5664
|
const dirInfo = await lstat4(dir).catch(() => null);
|
|
5524
5665
|
if (!dirInfo?.isDirectory() || dirInfo.isSymbolicLink()) return null;
|
|
5525
|
-
const file =
|
|
5666
|
+
const file = path12.join(dir, SKILL_FILE);
|
|
5526
5667
|
const head = await readFrontmatterBytes(file);
|
|
5527
5668
|
if (!head) return null;
|
|
5528
5669
|
const { fields } = parseFrontmatter(head.text);
|
|
5529
|
-
const meta = { name: fields.name ||
|
|
5670
|
+
const meta = { name: fields.name || path12.basename(dir), description: fields.description ?? "", dir, file };
|
|
5530
5671
|
guards.set(meta, {
|
|
5531
5672
|
root,
|
|
5532
5673
|
rootIdentity: identity(rootInfo),
|
|
@@ -5622,8 +5763,8 @@ import {
|
|
|
5622
5763
|
writeFileSync
|
|
5623
5764
|
} from "fs";
|
|
5624
5765
|
import { chmod as chmod7, mkdir as mkdir5 } from "fs/promises";
|
|
5625
|
-
import
|
|
5626
|
-
var TMP_DIR =
|
|
5766
|
+
import path13 from "path";
|
|
5767
|
+
var TMP_DIR = path13.join(skillsDir, ".tmp");
|
|
5627
5768
|
var MAX_SKILL_BYTES2 = 5e6;
|
|
5628
5769
|
async function installSkill(source) {
|
|
5629
5770
|
await ensureDir(skillsDir);
|
|
@@ -5648,7 +5789,7 @@ function isNpmPackage(input) {
|
|
|
5648
5789
|
}
|
|
5649
5790
|
async function installFromGitHub(url) {
|
|
5650
5791
|
const { owner, repo, subdir, branch } = parseGitHubUrl(url);
|
|
5651
|
-
const name = safeSkillName(subdir ?
|
|
5792
|
+
const name = safeSkillName(subdir ? path13.posix.basename(subdir) : repo);
|
|
5652
5793
|
const skillDir = safeChildPath(skillsDir, name);
|
|
5653
5794
|
const tmpDir = safeChildPath(TMP_DIR, `${name}-${randomUUID4()}`);
|
|
5654
5795
|
try {
|
|
@@ -5683,16 +5824,16 @@ async function installFromNpm(packageName) {
|
|
|
5683
5824
|
if (!tarball) {
|
|
5684
5825
|
throw new Error(`Could not download npm package "${packageName}"`);
|
|
5685
5826
|
}
|
|
5686
|
-
const tarballPath =
|
|
5827
|
+
const tarballPath = path13.join(tmpDir, tarball);
|
|
5687
5828
|
validateTarball(tarballPath);
|
|
5688
|
-
const extractDir =
|
|
5829
|
+
const extractDir = path13.join(tmpDir, "extracted");
|
|
5689
5830
|
mkdirSync(extractDir, { recursive: true });
|
|
5690
5831
|
try {
|
|
5691
5832
|
execFileSync("tar", ["-xzf", tarballPath, "-C", extractDir], { stdio: "ignore" });
|
|
5692
5833
|
} catch {
|
|
5693
|
-
const packageDir =
|
|
5834
|
+
const packageDir = path13.join(tmpDir, "node_modules", packageName);
|
|
5694
5835
|
if (existsSync2(packageDir)) {
|
|
5695
|
-
const skillFile2 =
|
|
5836
|
+
const skillFile2 = path13.join(packageDir, "SKILL.md");
|
|
5696
5837
|
if (existsSync2(skillFile2)) {
|
|
5697
5838
|
const body2 = readSkillFile(skillFile2, packageDir, packageName);
|
|
5698
5839
|
await writeSkillFile(skillDir, body2);
|
|
@@ -5704,7 +5845,7 @@ async function installFromNpm(packageName) {
|
|
|
5704
5845
|
const extractedFiles = readdirSync(extractDir);
|
|
5705
5846
|
const packageRoot = extractedFiles.find((f) => f === "package");
|
|
5706
5847
|
if (!packageRoot) throw new Error("Could not find package root in extracted files");
|
|
5707
|
-
const skillFile =
|
|
5848
|
+
const skillFile = path13.join(extractDir, "package", "SKILL.md");
|
|
5708
5849
|
if (!existsSync2(skillFile)) {
|
|
5709
5850
|
throw new Error(`No SKILL.md found in npm package "${packageName}"`);
|
|
5710
5851
|
}
|
|
@@ -5738,26 +5879,26 @@ function validateTarball(file) {
|
|
|
5738
5879
|
}
|
|
5739
5880
|
for (const entry of entries) {
|
|
5740
5881
|
const normalized = entry.replace(/^\.\//, "");
|
|
5741
|
-
if (normalized.includes("\\") ||
|
|
5882
|
+
if (normalized.includes("\\") || path13.posix.isAbsolute(normalized) || normalized.split("/").some((segment) => segment === "..")) {
|
|
5742
5883
|
throw new Error(`Unsafe path in npm skill archive: ${entry}`);
|
|
5743
5884
|
}
|
|
5744
5885
|
}
|
|
5745
5886
|
}
|
|
5746
5887
|
async function installFromLocal(srcPath) {
|
|
5747
|
-
const resolved =
|
|
5888
|
+
const resolved = path13.resolve(srcPath);
|
|
5748
5889
|
let skillDir;
|
|
5749
5890
|
let skillFile;
|
|
5750
|
-
const directSkill =
|
|
5891
|
+
const directSkill = path13.join(resolved, "SKILL.md");
|
|
5751
5892
|
if (existsSync2(directSkill)) {
|
|
5752
5893
|
skillDir = resolved;
|
|
5753
5894
|
skillFile = directSkill;
|
|
5754
5895
|
} else if (resolved.endsWith("SKILL.md") && existsSync2(resolved)) {
|
|
5755
|
-
skillDir =
|
|
5896
|
+
skillDir = path13.dirname(resolved);
|
|
5756
5897
|
skillFile = resolved;
|
|
5757
5898
|
} else {
|
|
5758
5899
|
throw new Error(`No SKILL.md found at "${srcPath}"`);
|
|
5759
5900
|
}
|
|
5760
|
-
const name =
|
|
5901
|
+
const name = path13.basename(skillDir);
|
|
5761
5902
|
const destDir = safeChildPath(skillsDir, safeSkillName(name));
|
|
5762
5903
|
const body = readSkillFile(skillFile, skillDir, srcPath);
|
|
5763
5904
|
await writeSkillFile(destDir, body);
|
|
@@ -5770,7 +5911,7 @@ async function writeSkillFile(dir, body) {
|
|
|
5770
5911
|
throw new Error(`Refusing to replace symlinked skill directory: ${dir}`);
|
|
5771
5912
|
}
|
|
5772
5913
|
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
5773
|
-
const file =
|
|
5914
|
+
const file = path13.join(dir, "SKILL.md");
|
|
5774
5915
|
const existingFile = lstatSync(file, { throwIfNoEntry: false });
|
|
5775
5916
|
if (existingFile?.isSymbolicLink()) {
|
|
5776
5917
|
throw new Error(`Refusing to replace symlinked skill file: ${file}`);
|
|
@@ -5823,13 +5964,13 @@ function safeSkillName(value) {
|
|
|
5823
5964
|
return value;
|
|
5824
5965
|
}
|
|
5825
5966
|
function safeChildPath(root, ...segments) {
|
|
5826
|
-
const target =
|
|
5967
|
+
const target = path13.resolve(root, ...segments);
|
|
5827
5968
|
assertInside(root, target);
|
|
5828
5969
|
return target;
|
|
5829
5970
|
}
|
|
5830
5971
|
function assertInside(root, target) {
|
|
5831
|
-
const relative2 =
|
|
5832
|
-
if (relative2 === "" || !
|
|
5972
|
+
const relative2 = path13.relative(path13.resolve(root), path13.resolve(target));
|
|
5973
|
+
if (relative2 === "" || !path13.isAbsolute(relative2) && relative2 !== ".." && !relative2.startsWith(`..${path13.sep}`)) {
|
|
5833
5974
|
return;
|
|
5834
5975
|
}
|
|
5835
5976
|
throw new Error(`Path escapes the skills directory: ${target}`);
|
|
@@ -6155,10 +6296,17 @@ async function boot(options) {
|
|
|
6155
6296
|
}
|
|
6156
6297
|
const usage = createUsageTracker(session.usage, resolvePricing);
|
|
6157
6298
|
let skillCatalogue = formatSkillCatalogue(skills);
|
|
6299
|
+
let projectMemory = await readProjectMemory(workspaceRoot2);
|
|
6300
|
+
const saveMemory = async (text) => {
|
|
6301
|
+
await saveProjectMemory(workspaceRoot2, text);
|
|
6302
|
+
projectMemory = await readProjectMemory(workspaceRoot2);
|
|
6303
|
+
};
|
|
6304
|
+
tools.register([createMemoryTool({ read: () => projectMemory, save: saveMemory })]);
|
|
6158
6305
|
const mainSystemPrompt = () => buildSystemPrompt({
|
|
6159
6306
|
cwd: options.cwd,
|
|
6160
6307
|
toolNames: tools.list().map((tool) => tool.name),
|
|
6161
|
-
skills: skillCatalogue
|
|
6308
|
+
skills: skillCatalogue,
|
|
6309
|
+
memory: projectMemory
|
|
6162
6310
|
});
|
|
6163
6311
|
let modelRef = session.model || config.model || "";
|
|
6164
6312
|
if (options.modelRef) modelRef = options.modelRef;
|
|
@@ -6504,7 +6652,7 @@ async function boot(options) {
|
|
|
6504
6652
|
},
|
|
6505
6653
|
async exportSession(id, destination) {
|
|
6506
6654
|
await persistQueue.catch(() => void 0);
|
|
6507
|
-
const target = destination ?
|
|
6655
|
+
const target = destination ? path14.resolve(options.cwd, destination) : path14.join(options.cwd, ".kitcode-exports");
|
|
6508
6656
|
if (!destination) await mkdir6(target, { recursive: true, mode: 448 });
|
|
6509
6657
|
return (await exportSession(id, target)).path;
|
|
6510
6658
|
},
|
|
@@ -6519,6 +6667,21 @@ async function boot(options) {
|
|
|
6519
6667
|
});
|
|
6520
6668
|
void refreshModelContextWindow();
|
|
6521
6669
|
},
|
|
6670
|
+
readMemory: () => projectMemory,
|
|
6671
|
+
async saveMemory(text) {
|
|
6672
|
+
await saveMemory(text);
|
|
6673
|
+
},
|
|
6674
|
+
async clearMemory() {
|
|
6675
|
+
await clearProjectMemory(workspaceRoot2);
|
|
6676
|
+
projectMemory = "";
|
|
6677
|
+
},
|
|
6678
|
+
effortDescription(history) {
|
|
6679
|
+
if (!modelRef) return "unavailable";
|
|
6680
|
+
const { provider, modelId } = registry.resolve(modelRef);
|
|
6681
|
+
const requested = resolveEffort(config.effort, history);
|
|
6682
|
+
const effective = provider.kind === "openai" ? openAiEffort(modelId, requested) : requested;
|
|
6683
|
+
return effective ? `${config.effort} \u2192 ${effective} (API request)` : `${config.effort} \u2192 not sent: unknown model support`;
|
|
6684
|
+
},
|
|
6522
6685
|
getEffort: () => config.effort,
|
|
6523
6686
|
async setEffort(effort) {
|
|
6524
6687
|
await persistConfig((draft) => {
|
|
@@ -7067,6 +7230,10 @@ function validateKey(value) {
|
|
|
7067
7230
|
// src/app/tui.tsx
|
|
7068
7231
|
import { render } from "ink";
|
|
7069
7232
|
|
|
7233
|
+
// src/ui/App.tsx
|
|
7234
|
+
import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useStdout as useStdout2 } from "ink";
|
|
7235
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef4, useState as useState5 } from "react";
|
|
7236
|
+
|
|
7070
7237
|
// src/ui/terminal-size.ts
|
|
7071
7238
|
import { useStdout } from "ink";
|
|
7072
7239
|
import { useMemo, useSyncExternalStore } from "react";
|
|
@@ -7107,10 +7274,6 @@ function useTerminalSize() {
|
|
|
7107
7274
|
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
7108
7275
|
}
|
|
7109
7276
|
|
|
7110
|
-
// src/ui/App.tsx
|
|
7111
|
-
import { Box as Box13, Text as Text12, useApp, useInput as useInput2 } from "ink";
|
|
7112
|
-
import { useCallback, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef4, useState as useState5 } from "react";
|
|
7113
|
-
|
|
7114
7277
|
// src/mcp/add.ts
|
|
7115
7278
|
var SERVER_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
7116
7279
|
function parseMcpAddArgs(args) {
|
|
@@ -7161,6 +7324,7 @@ var COMMANDS = [
|
|
|
7161
7324
|
{ name: "usage" },
|
|
7162
7325
|
{ name: "mcp", args: "[add|list|delete|enable|disable]" },
|
|
7163
7326
|
{ name: "attach", args: "<path|clipboard|clear>" },
|
|
7327
|
+
{ name: "memory", args: "[show|set <text>|clear]" },
|
|
7164
7328
|
{ name: "compact" },
|
|
7165
7329
|
{ name: "update" },
|
|
7166
7330
|
{ name: "checker" },
|
|
@@ -7354,7 +7518,7 @@ var en = {
|
|
|
7354
7518
|
escHelp: "esc \u2014 cancel the running turn",
|
|
7355
7519
|
cancelled: "Cancelled.",
|
|
7356
7520
|
queued: (count) => `queued: ${count}`,
|
|
7357
|
-
configAt: (
|
|
7521
|
+
configAt: (path15) => `config: ${path15}`,
|
|
7358
7522
|
skillsEmpty: "No skills installed. Drop a folder with a SKILL.md into ~/.kitcode/skills or ./.kitcode/skills",
|
|
7359
7523
|
skillsInstallUsage: "Usage: /skills install <github-url|npm-package|local-path>",
|
|
7360
7524
|
skillsInstalling: (source) => `Installing skill from "${source}"\u2026`,
|
|
@@ -7400,7 +7564,7 @@ var en = {
|
|
|
7400
7564
|
sessionDeleteAllFinalBody: "This cannot be undone. Press y only if you want to erase the complete session history.",
|
|
7401
7565
|
sessionsDeletedAll: (count) => `All saved chats deleted \xB7 ${count} sessions removed.`,
|
|
7402
7566
|
sessionsDeleteAllFailed: (deleted, failed) => `Session cleanup was incomplete \xB7 ${deleted} removed, ${failed} failed.`,
|
|
7403
|
-
sessionExported: (
|
|
7567
|
+
sessionExported: (path15) => `Session exported to ${path15}`,
|
|
7404
7568
|
sessionActionResume: "resume",
|
|
7405
7569
|
sessionActionRename: "rename",
|
|
7406
7570
|
sessionActionDelete: "delete",
|
|
@@ -7470,6 +7634,7 @@ ${url}`,
|
|
|
7470
7634
|
budget: "set token budget per turn",
|
|
7471
7635
|
theme: "change the accent colour",
|
|
7472
7636
|
lang: "change the interface language",
|
|
7637
|
+
memory: "view, replace or clear project memory",
|
|
7473
7638
|
prompt: "insert or save a prompt",
|
|
7474
7639
|
"prompt save": "save the current or recent text as a prompt",
|
|
7475
7640
|
"prompt delete": "delete a saved prompt",
|
|
@@ -7542,7 +7707,7 @@ var ru = {
|
|
|
7542
7707
|
escHelp: "esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0445\u043E\u0434",
|
|
7543
7708
|
cancelled: "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E.",
|
|
7544
7709
|
queued: (count) => `\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438: ${count}`,
|
|
7545
|
-
configAt: (
|
|
7710
|
+
configAt: (path15) => `\u043A\u043E\u043D\u0444\u0438\u0433: ${path15}`,
|
|
7546
7711
|
skillsEmpty: "\u0421\u043A\u0438\u043B\u043B\u043E\u0432 \u043D\u0435\u0442. \u041F\u043E\u043B\u043E\u0436\u0438 \u043F\u0430\u043F\u043A\u0443 \u0441 \u0444\u0430\u0439\u043B\u043E\u043C SKILL.md \u0432 ~/.kitcode/skills \u0438\u043B\u0438 ./.kitcode/skills",
|
|
7547
7712
|
skillsInstallUsage: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /skills install <github-url|npm-\u043F\u0430\u043A\u0435\u0442|\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439-\u043F\u0443\u0442\u044C>",
|
|
7548
7713
|
skillsInstalling: (source) => `\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u044E \u0441\u043A\u0438\u043B\u043B \u0438\u0437 "${source}"\u2026`,
|
|
@@ -7588,7 +7753,7 @@ var ru = {
|
|
|
7588
7753
|
sessionDeleteAllFinalBody: "\u042D\u0442\u043E \u043D\u0435\u043B\u044C\u0437\u044F \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C. \u041D\u0430\u0436\u0438\u043C\u0430\u0439 y \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0445\u043E\u0447\u0435\u0448\u044C \u0441\u0442\u0435\u0440\u0435\u0442\u044C \u0432\u0441\u044E \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0435\u0441\u0441\u0438\u0439.",
|
|
7589
7754
|
sessionsDeletedAll: (count) => `\u0412\u0441\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u0447\u0430\u0442\u044B \u0443\u0434\u0430\u043B\u0435\u043D\u044B \xB7 \u0441\u0435\u0441\u0441\u0438\u0439: ${count}.`,
|
|
7590
7755
|
sessionsDeleteAllFailed: (deleted, failed) => `\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u043D\u0435 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \xB7 \u0443\u0434\u0430\u043B\u0435\u043D\u043E: ${deleted}, \u043E\u0448\u0438\u0431\u043E\u043A: ${failed}.`,
|
|
7591
|
-
sessionExported: (
|
|
7756
|
+
sessionExported: (path15) => `\u0421\u0435\u0441\u0441\u0438\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0430: ${path15}`,
|
|
7592
7757
|
sessionActionResume: "\u043E\u0442\u043A\u0440\u044B\u0442\u044C",
|
|
7593
7758
|
sessionActionRename: "\u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C",
|
|
7594
7759
|
sessionActionDelete: "\u0443\u0434\u0430\u043B\u0438\u0442\u044C",
|
|
@@ -7658,6 +7823,7 @@ ${url}`,
|
|
|
7658
7823
|
budget: "\u043B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0437\u0430 \u0445\u043E\u0434 (0 = \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442)",
|
|
7659
7824
|
theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
|
|
7660
7825
|
lang: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430",
|
|
7826
|
+
memory: "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C, \u0437\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u0438\u043B\u0438 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430",
|
|
7661
7827
|
prompt: "\u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0438\u043B\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u043C\u0442",
|
|
7662
7828
|
skills: "\u0441\u043F\u0438\u0441\u043E\u043A \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0441\u043A\u0438\u043B\u043B\u043E\u0432",
|
|
7663
7829
|
"prompt delete": "\u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u043C\u0442",
|
|
@@ -8511,6 +8677,7 @@ function ModelBadge({ ref: modelRef }) {
|
|
|
8511
8677
|
return /* @__PURE__ */ jsx9(Box9, { borderColor: theme.accent, borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: name }) });
|
|
8512
8678
|
}
|
|
8513
8679
|
var EFFORT_STYLES = {
|
|
8680
|
+
auto: { label: "auto", color: "cyan" },
|
|
8514
8681
|
max: { label: "max", color: "red", bold: true },
|
|
8515
8682
|
xhigh: { label: "xhigh", color: "yellow", bold: true },
|
|
8516
8683
|
high: { label: "high", color: "cyan" },
|
|
@@ -9590,7 +9757,7 @@ function sanitizeDisplay(display) {
|
|
|
9590
9757
|
|
|
9591
9758
|
// src/ui/App.tsx
|
|
9592
9759
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
9593
|
-
var EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
9760
|
+
var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
|
|
9594
9761
|
var STREAM_FRAME_MS = 50;
|
|
9595
9762
|
var MAX_ATTACHMENTS = 8;
|
|
9596
9763
|
function App({
|
|
@@ -9598,7 +9765,8 @@ function App({
|
|
|
9598
9765
|
initialHistory,
|
|
9599
9766
|
warnings = []
|
|
9600
9767
|
}) {
|
|
9601
|
-
const { exit } = useApp();
|
|
9768
|
+
const { exit, suspendTerminal } = useApp();
|
|
9769
|
+
const { stdout } = useStdout2();
|
|
9602
9770
|
const { rows } = useTerminalSize();
|
|
9603
9771
|
const [transcript, setTranscript] = useState5(
|
|
9604
9772
|
() => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
|
|
@@ -9634,6 +9802,9 @@ function App({
|
|
|
9634
9802
|
const [context, setContext] = useState5(() => runtime.modelContext());
|
|
9635
9803
|
const transcriptEvents = useRef4([]);
|
|
9636
9804
|
const transcriptTimer = useRef4(null);
|
|
9805
|
+
const clearScreen = useCallback(() => {
|
|
9806
|
+
if (stdout.isTTY) stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
9807
|
+
}, [stdout]);
|
|
9637
9808
|
const replaceAttachments = useCallback((next) => {
|
|
9638
9809
|
attachmentsRef.current = next;
|
|
9639
9810
|
setAttachments(next);
|
|
@@ -9801,12 +9972,20 @@ function App({
|
|
|
9801
9972
|
exit();
|
|
9802
9973
|
return;
|
|
9803
9974
|
case "clear":
|
|
9975
|
+
if (busyRef.current && abort.current) {
|
|
9976
|
+
abort.current.abort();
|
|
9977
|
+
}
|
|
9978
|
+
queueRef.current = [];
|
|
9979
|
+
setPendingCount(0);
|
|
9804
9980
|
await runtime.newSession();
|
|
9805
9981
|
history.current = [];
|
|
9806
9982
|
replaceAttachments([]);
|
|
9807
9983
|
setPromptHistory([]);
|
|
9808
|
-
|
|
9809
|
-
|
|
9984
|
+
await suspendTerminal(async () => {
|
|
9985
|
+
setTranscript(emptyTranscript());
|
|
9986
|
+
setTranscriptRevision((revision) => revision + 1);
|
|
9987
|
+
clearScreen();
|
|
9988
|
+
});
|
|
9810
9989
|
sessionStart.current = Date.now();
|
|
9811
9990
|
turns.current = 0;
|
|
9812
9991
|
void runtime.persist([]).catch(
|
|
@@ -10430,14 +10609,27 @@ ${strings.mcpAddUsage}`
|
|
|
10430
10609
|
forceRender((n) => n + 1);
|
|
10431
10610
|
return;
|
|
10432
10611
|
}
|
|
10612
|
+
case "memory": {
|
|
10613
|
+
if (rawRest === "clear") {
|
|
10614
|
+
if (await ask2(lang === "ru" ? "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430?" : "Clear project memory?")) await runtime.clearMemory();
|
|
10615
|
+
} else if (rawRest.startsWith("set ")) {
|
|
10616
|
+
await runtime.saveMemory(rawRest.slice(4).trim());
|
|
10617
|
+
} else if (rawRest !== "" && rawRest !== "show") {
|
|
10618
|
+
notice("warn", "/memory show | /memory set <text> | /memory clear");
|
|
10619
|
+
return;
|
|
10620
|
+
}
|
|
10621
|
+
notice("info", runtime.readMemory() || (lang === "ru" ? "\u041F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0443\u0441\u0442\u0430." : "Project memory is empty."));
|
|
10622
|
+
return;
|
|
10623
|
+
}
|
|
10433
10624
|
case "effort": {
|
|
10434
|
-
|
|
10625
|
+
notice("info", runtime.effortDescription(history.current));
|
|
10626
|
+
const choice = EFFORTS.includes(rawRest) ? rawRest : await pick(
|
|
10435
10627
|
strings.titleEffort,
|
|
10436
10628
|
EFFORTS.map((effort) => ({ key: effort, label: effort }))
|
|
10437
10629
|
);
|
|
10438
10630
|
if (!choice) return;
|
|
10439
10631
|
await runtime.setEffort(choice);
|
|
10440
|
-
notice("info", strings.effortSet(choice));
|
|
10632
|
+
notice("info", `${strings.effortSet(choice)} \xB7 ${runtime.effortDescription(history.current)}`);
|
|
10441
10633
|
forceRender((n) => n + 1);
|
|
10442
10634
|
return;
|
|
10443
10635
|
}
|