@kernelonpanic/kitcode 1.2.9 → 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 +329 -183
- 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) {
|
|
@@ -1867,20 +1973,20 @@ function finitePositive(value) {
|
|
|
1867
1973
|
}
|
|
1868
1974
|
|
|
1869
1975
|
// src/core/checkpoint.ts
|
|
1870
|
-
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
1976
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
1871
1977
|
import { createReadStream } from "fs";
|
|
1872
1978
|
import {
|
|
1873
1979
|
chmod as chmod3,
|
|
1874
1980
|
lstat,
|
|
1875
1981
|
mkdir as mkdir3,
|
|
1876
|
-
readFile as
|
|
1982
|
+
readFile as readFile5,
|
|
1877
1983
|
readdir as readdir2,
|
|
1878
1984
|
rename as rename4,
|
|
1879
1985
|
stat as stat3,
|
|
1880
1986
|
unlink as unlink2,
|
|
1881
|
-
writeFile as
|
|
1987
|
+
writeFile as writeFile5
|
|
1882
1988
|
} from "fs/promises";
|
|
1883
|
-
import
|
|
1989
|
+
import path6 from "path";
|
|
1884
1990
|
|
|
1885
1991
|
// src/tools/safepath.ts
|
|
1886
1992
|
import { realpathSync } from "fs";
|
|
@@ -1975,7 +2081,7 @@ function beginCheckpoint(options) {
|
|
|
1975
2081
|
},
|
|
1976
2082
|
markChanged(absolutePath) {
|
|
1977
2083
|
if (finished) return;
|
|
1978
|
-
const resolved =
|
|
2084
|
+
const resolved = path6.resolve(absolutePath);
|
|
1979
2085
|
let capturedEntry = [...captured.values()].find((entry) => entry.absolutePath === resolved);
|
|
1980
2086
|
if (!capturedEntry) {
|
|
1981
2087
|
const safe = resolveInside(root, absolutePath);
|
|
@@ -2006,12 +2112,12 @@ function beginCheckpoint(options) {
|
|
|
2006
2112
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2007
2113
|
entries
|
|
2008
2114
|
};
|
|
2009
|
-
const sessionDir =
|
|
2115
|
+
const sessionDir = path6.join(storageDir, options.sessionId);
|
|
2010
2116
|
await ensureDir(storageDir);
|
|
2011
2117
|
await ensureDir(sessionDir);
|
|
2012
2118
|
lastCheckpointTimestamp = Math.max(Date.now(), lastCheckpointTimestamp + 1);
|
|
2013
2119
|
const id = `${String(lastCheckpointTimestamp).padStart(13, "0")}-${randomBytes2(4).toString("hex")}`;
|
|
2014
|
-
await writeCheckpoint(
|
|
2120
|
+
await writeCheckpoint(path6.join(sessionDir, `${id}.json`), stored);
|
|
2015
2121
|
pruneCheckpoints(sessionDir).catch(() => void 0);
|
|
2016
2122
|
return { id, paths: entries.map((entry) => entry.path) };
|
|
2017
2123
|
}
|
|
@@ -2020,7 +2126,7 @@ function beginCheckpoint(options) {
|
|
|
2020
2126
|
async function undoLatestCheckpoint(options) {
|
|
2021
2127
|
assertSessionId2(options.sessionId);
|
|
2022
2128
|
const root = workspaceRoot(options.cwd);
|
|
2023
|
-
const sessionDir =
|
|
2129
|
+
const sessionDir = path6.join(options.storageDir ?? checkpointsDir, options.sessionId);
|
|
2024
2130
|
const file = await newestCheckpoint(sessionDir);
|
|
2025
2131
|
const empty = { found: false, restored: [], removed: [], conflicts: [], failed: [] };
|
|
2026
2132
|
if (!file) return empty;
|
|
@@ -2076,7 +2182,7 @@ async function snapshotFile(file) {
|
|
|
2076
2182
|
if (info.size > MAX_FILE_BYTES) {
|
|
2077
2183
|
throw new Error(`Cannot checkpoint ${file}: it exceeds the ${MAX_FILE_BYTES / 1e6} MB limit.`);
|
|
2078
2184
|
}
|
|
2079
|
-
const data = await
|
|
2185
|
+
const data = await readFile5(file);
|
|
2080
2186
|
if (data.length > MAX_FILE_BYTES) {
|
|
2081
2187
|
throw new Error(`Cannot checkpoint ${file}: it changed beyond the file-size limit while reading.`);
|
|
2082
2188
|
}
|
|
@@ -2093,7 +2199,7 @@ async function fingerprint(file) {
|
|
|
2093
2199
|
if (error.code === "ENOENT") return { existed: false };
|
|
2094
2200
|
throw error;
|
|
2095
2201
|
}
|
|
2096
|
-
const hash =
|
|
2202
|
+
const hash = createHash2("sha256");
|
|
2097
2203
|
if (!info.isFile()) {
|
|
2098
2204
|
hash.update(`kitcode:${info.isDirectory() ? "directory" : "non-file"}`);
|
|
2099
2205
|
return { existed: true, sha256: hash.digest("hex") };
|
|
@@ -2105,7 +2211,7 @@ function matchesSnapshot(before, after) {
|
|
|
2105
2211
|
if (before.existed !== after.existed) return false;
|
|
2106
2212
|
if (!before.existed) return true;
|
|
2107
2213
|
const data = Buffer.from(before.data ?? "", "base64");
|
|
2108
|
-
return after.sha256 ===
|
|
2214
|
+
return after.sha256 === createHash2("sha256").update(data).digest("hex");
|
|
2109
2215
|
}
|
|
2110
2216
|
function sameFingerprint(a, b) {
|
|
2111
2217
|
return a.existed === b.existed && (!a.existed || a.sha256 === b.sha256);
|
|
@@ -2114,14 +2220,14 @@ async function restoreFile(root, relativePath, snapshot, expected) {
|
|
|
2114
2220
|
const safe = resolveInside(root, relativePath);
|
|
2115
2221
|
if (!safe.ok || safe.relative !== relativePath) throw new Error("The restore path changed.");
|
|
2116
2222
|
const data = decodeSnapshot(snapshot);
|
|
2117
|
-
await mkdir3(
|
|
2223
|
+
await mkdir3(path6.dirname(safe.path), { recursive: true });
|
|
2118
2224
|
const rechecked = resolveInside(root, relativePath);
|
|
2119
2225
|
if (!rechecked.ok || rechecked.path !== safe.path || rechecked.relative !== relativePath) {
|
|
2120
2226
|
throw new Error("The restore path changed while preparing its parent directory.");
|
|
2121
2227
|
}
|
|
2122
2228
|
const temp = `${rechecked.path}.${process.pid}.${randomBytes2(4).toString("hex")}.undo`;
|
|
2123
2229
|
try {
|
|
2124
|
-
await
|
|
2230
|
+
await writeFile5(temp, data, { mode: snapshot.mode ?? 384 });
|
|
2125
2231
|
await chmod3(temp, snapshot.mode ?? 384);
|
|
2126
2232
|
const finalPath = resolveInside(root, relativePath);
|
|
2127
2233
|
if (!finalPath.ok || finalPath.path !== rechecked.path || finalPath.relative !== relativePath) {
|
|
@@ -2144,7 +2250,7 @@ async function writeCheckpoint(file, checkpoint) {
|
|
|
2144
2250
|
}
|
|
2145
2251
|
const temp = `${file}.${process.pid}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
2146
2252
|
try {
|
|
2147
|
-
await
|
|
2253
|
+
await writeFile5(temp, body, { encoding: "utf8", mode: 384 });
|
|
2148
2254
|
await chmod3(temp, 384);
|
|
2149
2255
|
await rename4(temp, file);
|
|
2150
2256
|
} catch (error) {
|
|
@@ -2157,7 +2263,7 @@ async function readCheckpoint(file) {
|
|
|
2157
2263
|
if (info.size > MAX_CHECKPOINT_JSON_BYTES) throw new Error("Undo checkpoint is too large to read.");
|
|
2158
2264
|
let parsed;
|
|
2159
2265
|
try {
|
|
2160
|
-
parsed = JSON.parse(await
|
|
2266
|
+
parsed = JSON.parse(await readFile5(file, "utf8"));
|
|
2161
2267
|
} catch (error) {
|
|
2162
2268
|
throw new Error(`Undo checkpoint is not valid JSON: ${error.message}`);
|
|
2163
2269
|
}
|
|
@@ -2173,7 +2279,7 @@ function isCheckpoint(value) {
|
|
|
2173
2279
|
return item.entries.every((entry) => {
|
|
2174
2280
|
if (typeof entry !== "object" || entry === null) return false;
|
|
2175
2281
|
const candidate = entry;
|
|
2176
|
-
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") {
|
|
2177
2283
|
return false;
|
|
2178
2284
|
}
|
|
2179
2285
|
if (candidate.before.existed) {
|
|
@@ -2205,12 +2311,12 @@ async function newestCheckpoint(sessionDir) {
|
|
|
2205
2311
|
throw error;
|
|
2206
2312
|
}
|
|
2207
2313
|
const name = names.filter((candidate) => CHECKPOINT_NAME.test(candidate)).sort().at(-1);
|
|
2208
|
-
return name ?
|
|
2314
|
+
return name ? path6.join(sessionDir, name) : null;
|
|
2209
2315
|
}
|
|
2210
2316
|
async function pruneCheckpoints(sessionDir) {
|
|
2211
2317
|
const names = (await readdir2(sessionDir)).filter((candidate) => CHECKPOINT_NAME.test(candidate)).sort().reverse();
|
|
2212
2318
|
await Promise.all(
|
|
2213
|
-
names.slice(MAX_CHECKPOINTS_PER_SESSION).map((name) => unlink2(
|
|
2319
|
+
names.slice(MAX_CHECKPOINTS_PER_SESSION).map((name) => unlink2(path6.join(sessionDir, name)))
|
|
2214
2320
|
);
|
|
2215
2321
|
}
|
|
2216
2322
|
function workspaceRoot(cwd) {
|
|
@@ -2225,19 +2331,11 @@ function assertSessionId2(id) {
|
|
|
2225
2331
|
}
|
|
2226
2332
|
|
|
2227
2333
|
// src/core/diagnostics.ts
|
|
2228
|
-
import { readFile as
|
|
2229
|
-
import
|
|
2334
|
+
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
2335
|
+
import path7 from "path";
|
|
2230
2336
|
|
|
2231
2337
|
// src/tools/bash.ts
|
|
2232
2338
|
import { spawn } from "child_process";
|
|
2233
|
-
|
|
2234
|
-
// src/tools/summary.ts
|
|
2235
|
-
function brief(value, max = 60) {
|
|
2236
|
-
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2237
|
-
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
// src/tools/bash.ts
|
|
2241
2339
|
var DEFAULT_TIMEOUT = 12e4;
|
|
2242
2340
|
var MAX_TIMEOUT = 6e5;
|
|
2243
2341
|
var MAX_OUTPUT = 1e5;
|
|
@@ -2377,7 +2475,7 @@ async function detectDiagnosticCommands(cwd, configured = []) {
|
|
|
2377
2475
|
const custom = configured.map((command) => command.trim()).filter(Boolean);
|
|
2378
2476
|
if (custom.length > 0) return custom.slice(0, 8);
|
|
2379
2477
|
const commands = [];
|
|
2380
|
-
const packageJson = await readPackageJson(
|
|
2478
|
+
const packageJson = await readPackageJson(path7.join(cwd, "package.json"));
|
|
2381
2479
|
if (packageJson) {
|
|
2382
2480
|
const manager = await packageManager(cwd, packageJson.packageManager);
|
|
2383
2481
|
for (const name of ["lint", "typecheck", "check", "test"]) {
|
|
@@ -2386,11 +2484,11 @@ async function detectDiagnosticCommands(cwd, configured = []) {
|
|
|
2386
2484
|
}
|
|
2387
2485
|
}
|
|
2388
2486
|
}
|
|
2389
|
-
if (await isFile2(
|
|
2487
|
+
if (await isFile2(path7.join(cwd, "Cargo.toml"))) {
|
|
2390
2488
|
commands.push("cargo check", "cargo test");
|
|
2391
2489
|
}
|
|
2392
|
-
if (await isFile2(
|
|
2393
|
-
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"))) {
|
|
2394
2492
|
commands.push("python -m pytest");
|
|
2395
2493
|
}
|
|
2396
2494
|
return [...new Set(commands)].slice(0, 8);
|
|
@@ -2480,7 +2578,7 @@ async function readPackageJson(file) {
|
|
|
2480
2578
|
try {
|
|
2481
2579
|
const info = await stat4(file);
|
|
2482
2580
|
if (!info.isFile() || info.size > MAX_PACKAGE_BYTES) return null;
|
|
2483
|
-
const value = JSON.parse(await
|
|
2581
|
+
const value = JSON.parse(await readFile6(file, "utf8"));
|
|
2484
2582
|
if (typeof value !== "object" || value === null) return null;
|
|
2485
2583
|
const candidate = value;
|
|
2486
2584
|
return {
|
|
@@ -2494,9 +2592,9 @@ async function readPackageJson(file) {
|
|
|
2494
2592
|
async function packageManager(cwd, declared) {
|
|
2495
2593
|
const name = declared?.split("@")[0];
|
|
2496
2594
|
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") return name;
|
|
2497
|
-
if (await isFile2(
|
|
2498
|
-
if (await isFile2(
|
|
2499
|
-
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"))) {
|
|
2500
2598
|
return "bun";
|
|
2501
2599
|
}
|
|
2502
2600
|
return "npm";
|
|
@@ -2514,9 +2612,9 @@ async function isFile2(file) {
|
|
|
2514
2612
|
|
|
2515
2613
|
// src/core/attachments.ts
|
|
2516
2614
|
import { execFile } from "child_process";
|
|
2517
|
-
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";
|
|
2518
2616
|
import os2 from "os";
|
|
2519
|
-
import
|
|
2617
|
+
import path8 from "path";
|
|
2520
2618
|
import { fileURLToPath } from "url";
|
|
2521
2619
|
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
2522
2620
|
var MAX_TEXT_BYTES = 256 * 1024;
|
|
@@ -2547,7 +2645,7 @@ async function loadAttachment(cwd, requestedPath) {
|
|
|
2547
2645
|
const resolved = resolveAttachmentPath(cwd, requestedPath);
|
|
2548
2646
|
const linkInfo = await lstat2(resolved).catch(() => null);
|
|
2549
2647
|
if (linkInfo?.isSymbolicLink()) {
|
|
2550
|
-
throw new Error(`Cannot attach symbolic links: ${
|
|
2648
|
+
throw new Error(`Cannot attach symbolic links: ${path8.basename(resolved)}`);
|
|
2551
2649
|
}
|
|
2552
2650
|
const info = await stat5(resolved).catch((error) => {
|
|
2553
2651
|
if (error.code === "ENOENT") throw new Error(`Attachment not found: ${resolved}`);
|
|
@@ -2556,7 +2654,7 @@ async function loadAttachment(cwd, requestedPath) {
|
|
|
2556
2654
|
if (info.isDirectory()) {
|
|
2557
2655
|
const attachment = await loadFirstImageFromDirectory(resolved);
|
|
2558
2656
|
if (attachment) return attachment;
|
|
2559
|
-
throw new Error(`No supported images found in directory: ${
|
|
2657
|
+
throw new Error(`No supported images found in directory: ${path8.basename(resolved)}`);
|
|
2560
2658
|
}
|
|
2561
2659
|
if (!info.isFile()) throw new Error(`Attachment is not a regular file: ${resolved}`);
|
|
2562
2660
|
return loadResolvedAttachment(resolved, info.size);
|
|
@@ -2566,7 +2664,7 @@ async function loadAutomaticAttachment(cwd, requestedPath) {
|
|
|
2566
2664
|
const resolved = resolveAttachmentPath(cwd, requestedPath);
|
|
2567
2665
|
if (isSensitiveAutomaticPath(resolved)) {
|
|
2568
2666
|
throw new Error(
|
|
2569
|
-
`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)}`
|
|
2570
2668
|
);
|
|
2571
2669
|
}
|
|
2572
2670
|
const linkInfo = await lstat2(resolved).catch(() => null);
|
|
@@ -2586,14 +2684,14 @@ function looksLikeAttachmentPath(value) {
|
|
|
2586
2684
|
if (trimmed.length > MAX_PATH_CHARS) return false;
|
|
2587
2685
|
const candidate = normalizeInputPath(trimmed);
|
|
2588
2686
|
if (/^file:\/\//i.test(candidate)) return true;
|
|
2589
|
-
if (
|
|
2687
|
+
if (path8.isAbsolute(candidate)) return true;
|
|
2590
2688
|
if (/^(?:~|\.{1,2})[\\/]/.test(candidate)) return true;
|
|
2591
2689
|
if (candidate.includes("/") || candidate.includes("\\")) {
|
|
2592
2690
|
if (trimmed.length > 200) return false;
|
|
2593
2691
|
return true;
|
|
2594
2692
|
}
|
|
2595
|
-
const basename3 =
|
|
2596
|
-
return
|
|
2693
|
+
const basename3 = path8.basename(candidate).toLowerCase();
|
|
2694
|
+
return path8.extname(basename3) !== "" || AUTO_PATH_NAMES.has(basename3);
|
|
2597
2695
|
}
|
|
2598
2696
|
async function loadClipboardImage(platform = process.platform, runner = runClipboardCommand) {
|
|
2599
2697
|
const commands = clipboardCommands(platform);
|
|
@@ -2625,12 +2723,12 @@ async function loadClipboardImage(platform = process.platform, runner = runClipb
|
|
|
2625
2723
|
throw new Error(`No supported image found in the clipboard. ${hint}`);
|
|
2626
2724
|
}
|
|
2627
2725
|
async function loadResolvedAttachment(resolved, size) {
|
|
2628
|
-
const name = safeName(
|
|
2726
|
+
const name = safeName(path8.basename(resolved));
|
|
2629
2727
|
const imageLimitCandidate = size <= MAX_IMAGE_BYTES;
|
|
2630
2728
|
if (!imageLimitCandidate) {
|
|
2631
2729
|
throw new Error(`Attachment is larger than ${formatBytes(MAX_IMAGE_BYTES)}: ${name}`);
|
|
2632
2730
|
}
|
|
2633
|
-
const data = await
|
|
2731
|
+
const data = await readFile7(resolved);
|
|
2634
2732
|
const mediaType = detectImage(data);
|
|
2635
2733
|
if (mediaType) {
|
|
2636
2734
|
return {
|
|
@@ -2659,10 +2757,10 @@ async function loadFirstImageFromDirectory(dirPath) {
|
|
|
2659
2757
|
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
2660
2758
|
const entries = await readdir3(dirPath, { withFileTypes: true }).catch(() => null);
|
|
2661
2759
|
if (!entries) return null;
|
|
2662
|
-
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));
|
|
2663
2761
|
if (sorted.length === 0) return null;
|
|
2664
2762
|
const first2 = sorted[0];
|
|
2665
|
-
const filePath =
|
|
2763
|
+
const filePath = path8.join(dirPath, first2.name);
|
|
2666
2764
|
const info = await stat5(filePath).catch(() => null);
|
|
2667
2765
|
if (!info?.isFile()) return null;
|
|
2668
2766
|
return loadResolvedAttachment(filePath, info.size);
|
|
@@ -2672,17 +2770,17 @@ function resolveAttachmentPath(cwd, requestedPath) {
|
|
|
2672
2770
|
if (!cleaned) throw new Error("Give a file path: /attach <path>");
|
|
2673
2771
|
if (/^file:\/\//i.test(cleaned)) {
|
|
2674
2772
|
try {
|
|
2675
|
-
return
|
|
2773
|
+
return path8.normalize(fileURLToPath(cleaned));
|
|
2676
2774
|
} catch {
|
|
2677
2775
|
throw new Error(`Invalid local file URL: ${cleaned}`);
|
|
2678
2776
|
}
|
|
2679
2777
|
}
|
|
2680
|
-
const expanded = cleaned === "~" ? os2.homedir() : cleaned.startsWith("~/") ?
|
|
2681
|
-
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);
|
|
2682
2780
|
}
|
|
2683
2781
|
function isSensitiveAutomaticPath(file) {
|
|
2684
|
-
const basename3 =
|
|
2685
|
-
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));
|
|
2686
2784
|
}
|
|
2687
2785
|
function attachmentLabel(block) {
|
|
2688
2786
|
if (block.type === "image") return `image: ${block.name}`;
|
|
@@ -2827,7 +2925,7 @@ try {
|
|
|
2827
2925
|
}
|
|
2828
2926
|
`;
|
|
2829
2927
|
function textMime(file) {
|
|
2830
|
-
const extension =
|
|
2928
|
+
const extension = path8.extname(file).toLowerCase();
|
|
2831
2929
|
const known = {
|
|
2832
2930
|
".json": "application/json",
|
|
2833
2931
|
".md": "text/markdown",
|
|
@@ -2880,8 +2978,8 @@ var SINGLE_MAX_TOKENS = 768;
|
|
|
2880
2978
|
var CHUNK_MAX_TOKENS = 512;
|
|
2881
2979
|
var MERGE_MAX_TOKENS = 1024;
|
|
2882
2980
|
var CHUNK_CONCURRENCY = 3;
|
|
2883
|
-
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.";
|
|
2884
|
-
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.";
|
|
2885
2983
|
async function compactHistory(options) {
|
|
2886
2984
|
const cut = compactCutIndex(options.history);
|
|
2887
2985
|
if (cut <= 0) {
|
|
@@ -3283,6 +3381,12 @@ function buildSystemPrompt(args) {
|
|
|
3283
3381
|
if (args.skills && args.skills.trim() !== "") {
|
|
3284
3382
|
lines.push("", args.skills);
|
|
3285
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
|
+
}
|
|
3286
3390
|
return lines.join("\n");
|
|
3287
3391
|
}
|
|
3288
3392
|
|
|
@@ -3533,7 +3637,7 @@ function clip(value) {
|
|
|
3533
3637
|
// package.json
|
|
3534
3638
|
var package_default = {
|
|
3535
3639
|
name: "@kernelonpanic/kitcode",
|
|
3536
|
-
version: "1.
|
|
3640
|
+
version: "1.3.0",
|
|
3537
3641
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3538
3642
|
type: "module",
|
|
3539
3643
|
license: "MIT",
|
|
@@ -3601,7 +3705,7 @@ var package_default = {
|
|
|
3601
3705
|
|
|
3602
3706
|
// src/version.ts
|
|
3603
3707
|
var KITCODE_VERSION = package_default.version;
|
|
3604
|
-
var KITCODE_COMMIT = true ? "
|
|
3708
|
+
var KITCODE_COMMIT = true ? "7cd983ca3dc89406326e5e513870686036c3ab9c" : "development";
|
|
3605
3709
|
|
|
3606
3710
|
// src/mcp/client.ts
|
|
3607
3711
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3892,8 +3996,8 @@ function balanceEndpoints(baseUrl) {
|
|
|
3892
3996
|
endpoint(url.origin, "/v1/balance", genericBalance)
|
|
3893
3997
|
];
|
|
3894
3998
|
}
|
|
3895
|
-
function endpoint(origin,
|
|
3896
|
-
return { url: `${origin}${
|
|
3999
|
+
function endpoint(origin, path15, parse2) {
|
|
4000
|
+
return { url: `${origin}${path15}`, parse: parse2 };
|
|
3897
4001
|
}
|
|
3898
4002
|
function deepSeekBalance(body) {
|
|
3899
4003
|
const infos = record(body)?.["balance_infos"];
|
|
@@ -3986,8 +4090,8 @@ async function limitedResponseText(response, limit) {
|
|
|
3986
4090
|
}
|
|
3987
4091
|
|
|
3988
4092
|
// src/providers/catalog.ts
|
|
3989
|
-
import { chmod as chmod4, readFile as
|
|
3990
|
-
import
|
|
4093
|
+
import { chmod as chmod4, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
4094
|
+
import path9 from "path";
|
|
3991
4095
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3992
4096
|
var CACHE_VERSION = 3;
|
|
3993
4097
|
async function loadModels(provider, refresh = false) {
|
|
@@ -4009,11 +4113,11 @@ async function loadModels(provider, refresh = false) {
|
|
|
4009
4113
|
return models;
|
|
4010
4114
|
}
|
|
4011
4115
|
function cacheFile(providerId) {
|
|
4012
|
-
return
|
|
4116
|
+
return path9.join(cacheDir, "models", `${providerId.replace(/[^\w.-]/g, "_")}.json`);
|
|
4013
4117
|
}
|
|
4014
4118
|
async function readCache(file) {
|
|
4015
4119
|
try {
|
|
4016
|
-
const parsed = JSON.parse(await
|
|
4120
|
+
const parsed = JSON.parse(await readFile8(file, "utf8"));
|
|
4017
4121
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4018
4122
|
const candidate = parsed;
|
|
4019
4123
|
if (typeof candidate.fetchedAt !== "number" || !Number.isFinite(candidate.fetchedAt) || !Array.isArray(candidate.models) || candidate.models.length > 1e4 || !candidate.models.every(validModel)) {
|
|
@@ -4044,8 +4148,8 @@ function validModel(value) {
|
|
|
4044
4148
|
async function writeCache(file, models) {
|
|
4045
4149
|
const payload = { version: CACHE_VERSION, fetchedAt: Date.now(), models };
|
|
4046
4150
|
try {
|
|
4047
|
-
await ensureDir(
|
|
4048
|
-
await
|
|
4151
|
+
await ensureDir(path9.dirname(file));
|
|
4152
|
+
await writeFile6(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
|
|
4049
4153
|
await chmod4(file, 384);
|
|
4050
4154
|
} catch {
|
|
4051
4155
|
return;
|
|
@@ -4194,7 +4298,8 @@ async function* streamTurn(client, providerId, apiKey2, req) {
|
|
|
4194
4298
|
tools: toToolParams(req.tools)
|
|
4195
4299
|
};
|
|
4196
4300
|
if (req.thinking) params.thinking = { type: "adaptive", display: "summarized" };
|
|
4197
|
-
|
|
4301
|
+
const effort = resolveEffort(req.effort, req.messages);
|
|
4302
|
+
if (effort) params.output_config = { effort };
|
|
4198
4303
|
let thinking = "";
|
|
4199
4304
|
let text = "";
|
|
4200
4305
|
let events = 0;
|
|
@@ -4382,11 +4487,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4382
4487
|
let recognised = 0;
|
|
4383
4488
|
const calls = /* @__PURE__ */ new Map();
|
|
4384
4489
|
const capture = captureResponseHead();
|
|
4490
|
+
const effort = openAiEffort(req.model, resolveEffort(req.effort, req.messages));
|
|
4385
4491
|
try {
|
|
4386
4492
|
const stream = await client.withOptions({ fetch: capture.fetch }).chat.completions.create(
|
|
4387
4493
|
{
|
|
4388
4494
|
model: req.model,
|
|
4389
4495
|
max_tokens: req.maxTokens,
|
|
4496
|
+
...effort ? { reasoning_effort: effort } : {},
|
|
4390
4497
|
messages: toChatMessages(req.system, req.messages),
|
|
4391
4498
|
...req.tools.length > 0 ? { tools: toChatTools(req.tools) } : {},
|
|
4392
4499
|
stream: true,
|
|
@@ -4683,11 +4790,11 @@ function isFileEdit(tool) {
|
|
|
4683
4790
|
}
|
|
4684
4791
|
|
|
4685
4792
|
// src/tools/edit.ts
|
|
4686
|
-
import { readFile as
|
|
4793
|
+
import { readFile as readFile9, stat as stat6 } from "fs/promises";
|
|
4687
4794
|
|
|
4688
4795
|
// src/tools/safe-write.ts
|
|
4689
4796
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4690
|
-
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";
|
|
4691
4798
|
import { basename as basename2, dirname as dirname2, join } from "path";
|
|
4692
4799
|
var UnsafeFileChangeError = class extends Error {
|
|
4693
4800
|
constructor(message) {
|
|
@@ -4750,7 +4857,7 @@ async function atomicWriteSafeFile(file, data, snapshot) {
|
|
|
4750
4857
|
await rename5(temp, file);
|
|
4751
4858
|
} catch (error) {
|
|
4752
4859
|
await handle?.close().catch(() => void 0);
|
|
4753
|
-
await
|
|
4860
|
+
await rm4(temp, { force: true }).catch(() => void 0);
|
|
4754
4861
|
throw error;
|
|
4755
4862
|
}
|
|
4756
4863
|
}
|
|
@@ -4845,56 +4952,56 @@ var editTool = {
|
|
|
4845
4952
|
return `edit(${brief(input.path)})`;
|
|
4846
4953
|
},
|
|
4847
4954
|
async preview(input, ctx) {
|
|
4848
|
-
const { path:
|
|
4849
|
-
const safe = resolveInside(ctx.cwd,
|
|
4955
|
+
const { path: path15, oldString, newString, replaceAll = false } = input;
|
|
4956
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4850
4957
|
if (!safe.ok) return { kind: "text", text: safe.reason };
|
|
4851
4958
|
const info = await stat6(safe.path).catch(() => null);
|
|
4852
4959
|
if (info && info.size > MAX_FILE_BYTES2) {
|
|
4853
|
-
return { kind: "text", text: `Cannot preview ${
|
|
4960
|
+
return { kind: "text", text: `Cannot preview ${path15}: the file is too large.` };
|
|
4854
4961
|
}
|
|
4855
|
-
const beforeBuffer = await
|
|
4856
|
-
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}.` };
|
|
4857
4964
|
if (beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
4858
|
-
return { kind: "text", text: `Cannot preview binary file ${
|
|
4965
|
+
return { kind: "text", text: `Cannot preview binary file ${path15}` };
|
|
4859
4966
|
}
|
|
4860
4967
|
const before = beforeBuffer.toString("utf8");
|
|
4861
4968
|
const after = replacement(before, oldString, newString, replaceAll);
|
|
4862
|
-
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 };
|
|
4863
4970
|
},
|
|
4864
4971
|
async execute(input, ctx) {
|
|
4865
|
-
const { path:
|
|
4866
|
-
const safe = resolveInside(ctx.cwd,
|
|
4972
|
+
const { path: path15, oldString, newString, replaceAll = false } = input;
|
|
4973
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4867
4974
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
4868
4975
|
let snapshot;
|
|
4869
4976
|
try {
|
|
4870
4977
|
snapshot = await readSafeFileSnapshot(safe.path, MAX_FILE_BYTES2);
|
|
4871
4978
|
} catch (error) {
|
|
4872
|
-
return { content: `Cannot edit ${
|
|
4979
|
+
return { content: `Cannot edit ${path15}: ${error.message}.`, isError: true };
|
|
4873
4980
|
}
|
|
4874
|
-
if (!snapshot.exists) return { content: `Cannot read ${
|
|
4981
|
+
if (!snapshot.exists) return { content: `Cannot read ${path15}.`, isError: true };
|
|
4875
4982
|
const beforeBuffer = snapshot.data;
|
|
4876
4983
|
if (beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
4877
|
-
return { content: `Cannot edit ${
|
|
4984
|
+
return { content: `Cannot edit ${path15}: it is a binary file, not text.`, isError: true };
|
|
4878
4985
|
}
|
|
4879
4986
|
const before = beforeBuffer.toString("utf8");
|
|
4880
4987
|
const count = countOccurrences(before, oldString);
|
|
4881
4988
|
if (count === 0) {
|
|
4882
4989
|
return {
|
|
4883
|
-
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.`,
|
|
4884
4991
|
isError: true
|
|
4885
4992
|
};
|
|
4886
4993
|
}
|
|
4887
4994
|
if (count > 1 && !replaceAll) {
|
|
4888
4995
|
return {
|
|
4889
|
-
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.`,
|
|
4890
4997
|
isError: true
|
|
4891
4998
|
};
|
|
4892
4999
|
}
|
|
4893
5000
|
const after = replacement(before, oldString, newString, replaceAll);
|
|
4894
|
-
if (after === null) return { content: `Could not prepare edit for ${
|
|
5001
|
+
if (after === null) return { content: `Could not prepare edit for ${path15}.`, isError: true };
|
|
4895
5002
|
if (Buffer.byteLength(after, "utf8") > MAX_FILE_BYTES2) {
|
|
4896
5003
|
return {
|
|
4897
|
-
content: `Cannot edit ${
|
|
5004
|
+
content: `Cannot edit ${path15}: the result exceeds the ${MAX_FILE_BYTES2 / 1e6} MB limit.`,
|
|
4898
5005
|
isError: true
|
|
4899
5006
|
};
|
|
4900
5007
|
}
|
|
@@ -4907,11 +5014,11 @@ var editTool = {
|
|
|
4907
5014
|
await atomicWriteSafeFile(safe.path, after, snapshot);
|
|
4908
5015
|
ctx.checkpoint?.markChanged(safe.path);
|
|
4909
5016
|
} catch (error) {
|
|
4910
|
-
return { content: `Failed to write ${
|
|
5017
|
+
return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
|
|
4911
5018
|
}
|
|
4912
5019
|
return {
|
|
4913
|
-
content: `Edited ${
|
|
4914
|
-
display: { kind: "diff", path:
|
|
5020
|
+
content: `Edited ${path15} (${count} ${count === 1 ? "replacement" : "replacements"})`,
|
|
5021
|
+
display: { kind: "diff", path: path15, before, after }
|
|
4915
5022
|
};
|
|
4916
5023
|
}
|
|
4917
5024
|
};
|
|
@@ -4946,11 +5053,11 @@ var globTool = {
|
|
|
4946
5053
|
return `glob(${brief(input.pattern)})`;
|
|
4947
5054
|
},
|
|
4948
5055
|
async execute(input, ctx) {
|
|
4949
|
-
const { pattern, path:
|
|
5056
|
+
const { pattern, path: path15 = "." } = input;
|
|
4950
5057
|
if (patternEscapes(pattern)) {
|
|
4951
5058
|
return { content: `Pattern ${pattern} must be relative to the workspace root.`, isError: true };
|
|
4952
5059
|
}
|
|
4953
|
-
const safe = resolveInside(ctx.cwd,
|
|
5060
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
4954
5061
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
4955
5062
|
const entries = await fg(pattern, {
|
|
4956
5063
|
cwd: safe.path,
|
|
@@ -4972,12 +5079,12 @@ var globTool = {
|
|
|
4972
5079
|
};
|
|
4973
5080
|
|
|
4974
5081
|
// src/tools/grep.ts
|
|
4975
|
-
import { readFile as
|
|
5082
|
+
import { readFile as readFile10, stat as stat7 } from "fs/promises";
|
|
4976
5083
|
import { join as join3 } from "path";
|
|
4977
5084
|
import fg2 from "fast-glob";
|
|
4978
5085
|
|
|
4979
5086
|
// src/tools/sensitive.ts
|
|
4980
|
-
import
|
|
5087
|
+
import path10 from "path";
|
|
4981
5088
|
var sensitiveNames = /* @__PURE__ */ new Set([
|
|
4982
5089
|
".npmrc",
|
|
4983
5090
|
".pypirc",
|
|
@@ -5004,9 +5111,9 @@ var sensitiveExtensions = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx
|
|
|
5004
5111
|
function isSensitivePath(value) {
|
|
5005
5112
|
if (typeof value !== "string") return false;
|
|
5006
5113
|
const normalized = value.replaceAll("\\", "/").toLowerCase();
|
|
5007
|
-
const name =
|
|
5114
|
+
const name = path10.posix.basename(normalized);
|
|
5008
5115
|
if (name === ".env" || name.startsWith(".env.")) return true;
|
|
5009
|
-
if (sensitiveNames.has(name) || sensitiveExtensions.has(
|
|
5116
|
+
if (sensitiveNames.has(name) || sensitiveExtensions.has(path10.posix.extname(name))) return true;
|
|
5010
5117
|
return normalized.split("/").some((part) => part === ".ssh" || part === ".aws");
|
|
5011
5118
|
}
|
|
5012
5119
|
function mentionsSensitivePattern(value) {
|
|
@@ -5124,16 +5231,16 @@ var grepTool = {
|
|
|
5124
5231
|
defaultPermission: "allow",
|
|
5125
5232
|
readOnly: true,
|
|
5126
5233
|
permission(input, ctx) {
|
|
5127
|
-
const { path:
|
|
5128
|
-
if (isSensitivePath(
|
|
5129
|
-
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;
|
|
5130
5237
|
return safe?.ok && isSensitivePath(safe.relative) ? "ask" : void 0;
|
|
5131
5238
|
},
|
|
5132
5239
|
summarize(input) {
|
|
5133
5240
|
return `grep(${brief(input.pattern)})`;
|
|
5134
5241
|
},
|
|
5135
5242
|
async execute(input, ctx) {
|
|
5136
|
-
const { pattern, path:
|
|
5243
|
+
const { pattern, path: path15 = ".", glob = "**/*", maxMatches } = input;
|
|
5137
5244
|
if (pattern.length > MAX_PATTERN_CHARS) {
|
|
5138
5245
|
return {
|
|
5139
5246
|
content: `Regular expression is limited to ${MAX_PATTERN_CHARS} characters.`,
|
|
@@ -5148,10 +5255,10 @@ var grepTool = {
|
|
|
5148
5255
|
if (patternEscapes(glob)) {
|
|
5149
5256
|
return { content: `Glob ${glob} must be relative to the workspace root.`, isError: true };
|
|
5150
5257
|
}
|
|
5151
|
-
const safe = resolveInside(ctx.cwd,
|
|
5258
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5152
5259
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5153
5260
|
const target = await stat7(safe.path).catch(() => null);
|
|
5154
|
-
if (!target) return { content: `Path not found: ${
|
|
5261
|
+
if (!target) return { content: `Path not found: ${path15}`, isError: true };
|
|
5155
5262
|
const discovered = target.isDirectory() ? (await fg2(glob, {
|
|
5156
5263
|
cwd: safe.path,
|
|
5157
5264
|
dot: false,
|
|
@@ -5182,7 +5289,7 @@ var grepTool = {
|
|
|
5182
5289
|
break;
|
|
5183
5290
|
}
|
|
5184
5291
|
scannedBytes += file.size;
|
|
5185
|
-
const buffer = await
|
|
5292
|
+
const buffer = await readFile10(file.absolute).catch(() => null);
|
|
5186
5293
|
if (!buffer || buffer.subarray(0, 4096).includes(0)) continue;
|
|
5187
5294
|
const lines = buffer.toString("utf8").split("\n");
|
|
5188
5295
|
const remaining = limit - hits.length;
|
|
@@ -5212,7 +5319,7 @@ var grepTool = {
|
|
|
5212
5319
|
};
|
|
5213
5320
|
|
|
5214
5321
|
// src/tools/read.ts
|
|
5215
|
-
import { readFile as
|
|
5322
|
+
import { readFile as readFile11, stat as stat8 } from "fs/promises";
|
|
5216
5323
|
var MAX_LINES = 2e3;
|
|
5217
5324
|
var MAX_CHARS = 2e5;
|
|
5218
5325
|
var MAX_FILE_BYTES4 = 5e6;
|
|
@@ -5241,30 +5348,30 @@ var readTool = {
|
|
|
5241
5348
|
return `read(${brief(input.path)})`;
|
|
5242
5349
|
},
|
|
5243
5350
|
async execute(input, ctx) {
|
|
5244
|
-
const { path:
|
|
5245
|
-
const safe = resolveInside(ctx.cwd,
|
|
5351
|
+
const { path: path15, offset = 1, limit } = input;
|
|
5352
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5246
5353
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5247
5354
|
const info = await stat8(safe.path).catch(() => null);
|
|
5248
|
-
if (!info) return { content: `File not found: ${
|
|
5355
|
+
if (!info) return { content: `File not found: ${path15}`, isError: true };
|
|
5249
5356
|
if (info.isDirectory()) {
|
|
5250
|
-
return { content: `${
|
|
5357
|
+
return { content: `${path15} is a directory, not a file. Use glob to list its contents.`, isError: true };
|
|
5251
5358
|
}
|
|
5252
5359
|
if (info.size > MAX_FILE_BYTES4) {
|
|
5253
5360
|
return {
|
|
5254
|
-
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.`,
|
|
5255
5362
|
isError: true
|
|
5256
5363
|
};
|
|
5257
5364
|
}
|
|
5258
|
-
const buffer = await
|
|
5365
|
+
const buffer = await readFile11(safe.path);
|
|
5259
5366
|
if (buffer.subarray(0, 4096).includes(0)) {
|
|
5260
|
-
return { content: `${
|
|
5367
|
+
return { content: `${path15} looks like a binary file and cannot be read as text.`, isError: true };
|
|
5261
5368
|
}
|
|
5262
5369
|
const lines = buffer.toString("utf8").split("\n");
|
|
5263
5370
|
if (lines.at(-1) === "") lines.pop();
|
|
5264
|
-
if (lines.length === 0) return { content: `${
|
|
5371
|
+
if (lines.length === 0) return { content: `${path15} is empty.` };
|
|
5265
5372
|
const start = Math.max(1, offset) - 1;
|
|
5266
5373
|
if (start >= lines.length) {
|
|
5267
|
-
return { content: `${
|
|
5374
|
+
return { content: `${path15} has ${lines.length} lines, so offset ${offset} is past the end.`, isError: true };
|
|
5268
5375
|
}
|
|
5269
5376
|
const window = lines.slice(start, start + Math.min(limit ?? MAX_LINES, MAX_LINES));
|
|
5270
5377
|
const rendered = [];
|
|
@@ -5291,7 +5398,7 @@ function toToolSchema(tool) {
|
|
|
5291
5398
|
}
|
|
5292
5399
|
|
|
5293
5400
|
// src/tools/write.ts
|
|
5294
|
-
import { mkdir as mkdir4, readFile as
|
|
5401
|
+
import { mkdir as mkdir4, readFile as readFile12, stat as stat9 } from "fs/promises";
|
|
5295
5402
|
import { dirname as dirname3 } from "path";
|
|
5296
5403
|
var MAX_FILE_BYTES5 = 5e6;
|
|
5297
5404
|
var writeTool = {
|
|
@@ -5311,64 +5418,64 @@ var writeTool = {
|
|
|
5311
5418
|
return `write(${brief(input.path)})`;
|
|
5312
5419
|
},
|
|
5313
5420
|
async preview(input, ctx) {
|
|
5314
|
-
const { path:
|
|
5421
|
+
const { path: path15, content } = input;
|
|
5315
5422
|
if (Buffer.byteLength(content, "utf8") > MAX_FILE_BYTES5) {
|
|
5316
5423
|
return { kind: "text", text: `Write is limited to ${MAX_FILE_BYTES5 / 1e6} MB.` };
|
|
5317
5424
|
}
|
|
5318
|
-
const safe = resolveInside(ctx.cwd,
|
|
5425
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5319
5426
|
if (!safe.ok) return { kind: "text", text: safe.reason };
|
|
5320
5427
|
const beforeInfo = await stat9(safe.path).catch(() => null);
|
|
5321
5428
|
if (beforeInfo && beforeInfo.size > MAX_FILE_BYTES5) {
|
|
5322
|
-
return { kind: "text", text: `Cannot preview ${
|
|
5429
|
+
return { kind: "text", text: `Cannot preview ${path15}: the existing file is too large.` };
|
|
5323
5430
|
}
|
|
5324
|
-
const beforeBuffer = await
|
|
5431
|
+
const beforeBuffer = await readFile12(safe.path).catch(() => null);
|
|
5325
5432
|
if (beforeBuffer?.subarray(0, 8192).includes(0)) {
|
|
5326
|
-
return { kind: "text", text: `Cannot preview binary file ${
|
|
5433
|
+
return { kind: "text", text: `Cannot preview binary file ${path15}` };
|
|
5327
5434
|
}
|
|
5328
5435
|
return {
|
|
5329
5436
|
kind: "diff",
|
|
5330
|
-
path:
|
|
5437
|
+
path: path15,
|
|
5331
5438
|
before: beforeBuffer?.toString("utf8") ?? "",
|
|
5332
5439
|
after: content
|
|
5333
5440
|
};
|
|
5334
5441
|
},
|
|
5335
5442
|
async execute(input, ctx) {
|
|
5336
|
-
const { path:
|
|
5443
|
+
const { path: path15, content } = input;
|
|
5337
5444
|
if (Buffer.byteLength(content, "utf8") > MAX_FILE_BYTES5) {
|
|
5338
5445
|
return {
|
|
5339
|
-
content: `Cannot write ${
|
|
5446
|
+
content: `Cannot write ${path15}: content exceeds the ${MAX_FILE_BYTES5 / 1e6} MB limit.`,
|
|
5340
5447
|
isError: true
|
|
5341
5448
|
};
|
|
5342
5449
|
}
|
|
5343
|
-
const safe = resolveInside(ctx.cwd,
|
|
5450
|
+
const safe = resolveInside(ctx.cwd, path15);
|
|
5344
5451
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5345
5452
|
let target = safe.path;
|
|
5346
5453
|
let snapshot;
|
|
5347
5454
|
try {
|
|
5348
5455
|
await mkdir4(dirname3(target), { recursive: true });
|
|
5349
|
-
const rechecked = resolveInside(ctx.cwd,
|
|
5456
|
+
const rechecked = resolveInside(ctx.cwd, path15);
|
|
5350
5457
|
if (!rechecked.ok || rechecked.path !== target) {
|
|
5351
5458
|
return {
|
|
5352
|
-
content: `Cannot write ${
|
|
5459
|
+
content: `Cannot write ${path15}: the path changed while its parent was created.`,
|
|
5353
5460
|
isError: true
|
|
5354
5461
|
};
|
|
5355
5462
|
}
|
|
5356
5463
|
target = rechecked.path;
|
|
5357
5464
|
snapshot = await readSafeFileSnapshot(target, MAX_FILE_BYTES5);
|
|
5358
5465
|
if (snapshot.exists && snapshot.data.subarray(0, 8192).includes(0)) {
|
|
5359
|
-
return { content: `Cannot write ${
|
|
5466
|
+
return { content: `Cannot write ${path15}: the existing file is binary, not text.`, isError: true };
|
|
5360
5467
|
}
|
|
5361
5468
|
await ctx.checkpoint?.capture(target);
|
|
5362
5469
|
await atomicWriteSafeFile(target, content, snapshot);
|
|
5363
5470
|
ctx.checkpoint?.markChanged(target);
|
|
5364
5471
|
} catch (error) {
|
|
5365
|
-
return { content: `Failed to write ${
|
|
5472
|
+
return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
|
|
5366
5473
|
}
|
|
5367
5474
|
const before = snapshot.exists ? snapshot.data.toString("utf8") : "";
|
|
5368
5475
|
const lines = content === "" ? 0 : content.replace(/\n$/, "").split("\n").length;
|
|
5369
5476
|
return {
|
|
5370
|
-
content: `${before === "" ? "Created" : "Updated"} ${
|
|
5371
|
-
display: { kind: "diff", path:
|
|
5477
|
+
content: `${before === "" ? "Created" : "Updated"} ${path15} (${lines} ${lines === 1 ? "line" : "lines"})`,
|
|
5478
|
+
display: { kind: "diff", path: path15, before, after: content }
|
|
5372
5479
|
};
|
|
5373
5480
|
}
|
|
5374
5481
|
};
|
|
@@ -5397,8 +5504,8 @@ function createToolRegistry(tools) {
|
|
|
5397
5504
|
}
|
|
5398
5505
|
|
|
5399
5506
|
// src/prompts/library.ts
|
|
5400
|
-
import { chmod as chmod6, readFile as
|
|
5401
|
-
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";
|
|
5402
5509
|
async function savePrompt(input) {
|
|
5403
5510
|
const slug = slugify(input.name);
|
|
5404
5511
|
if (!slug) throw new Error(`Prompt name has no letters or digits to name a file after: ${input.name}`);
|
|
@@ -5410,8 +5517,8 @@ async function savePrompt(input) {
|
|
|
5410
5517
|
body: input.body.trim()
|
|
5411
5518
|
};
|
|
5412
5519
|
await ensureDir(promptsDir);
|
|
5413
|
-
const file =
|
|
5414
|
-
await
|
|
5520
|
+
const file = path11.join(promptsDir, `${slug}.md`);
|
|
5521
|
+
await writeFile7(file, serialize(prompt), {
|
|
5415
5522
|
encoding: "utf8",
|
|
5416
5523
|
mode: 384
|
|
5417
5524
|
});
|
|
@@ -5421,7 +5528,7 @@ async function savePrompt(input) {
|
|
|
5421
5528
|
async function getPrompt(slug) {
|
|
5422
5529
|
assertSlug(slug);
|
|
5423
5530
|
try {
|
|
5424
|
-
return parse(slug, await
|
|
5531
|
+
return parse(slug, await readFile13(path11.join(promptsDir, `${slug}.md`), "utf8"));
|
|
5425
5532
|
} catch (error) {
|
|
5426
5533
|
if (isMissing(error)) return null;
|
|
5427
5534
|
throw error;
|
|
@@ -5437,7 +5544,7 @@ async function listPrompts() {
|
|
|
5437
5544
|
}
|
|
5438
5545
|
const prompts2 = await Promise.all(
|
|
5439
5546
|
names.filter((name) => name.endsWith(".md")).map(
|
|
5440
|
-
async (name) => parse(name.slice(0, -3), await
|
|
5547
|
+
async (name) => parse(name.slice(0, -3), await readFile13(path11.join(promptsDir, name), "utf8"))
|
|
5441
5548
|
)
|
|
5442
5549
|
);
|
|
5443
5550
|
return prompts2.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -5445,7 +5552,7 @@ async function listPrompts() {
|
|
|
5445
5552
|
async function deletePrompt(slug) {
|
|
5446
5553
|
assertSlug(slug);
|
|
5447
5554
|
try {
|
|
5448
|
-
await unlink3(
|
|
5555
|
+
await unlink3(path11.join(promptsDir, `${slug}.md`));
|
|
5449
5556
|
return true;
|
|
5450
5557
|
} catch (error) {
|
|
5451
5558
|
if (isMissing(error)) return false;
|
|
@@ -5503,7 +5610,7 @@ function parse(slug, text) {
|
|
|
5503
5610
|
|
|
5504
5611
|
// src/skills/library.ts
|
|
5505
5612
|
import { lstat as lstat4, open as open2, readdir as readdir5 } from "fs/promises";
|
|
5506
|
-
import
|
|
5613
|
+
import path12 from "path";
|
|
5507
5614
|
var SKILL_FILE = "SKILL.md";
|
|
5508
5615
|
var FRONTMATTER_BYTES = 8192;
|
|
5509
5616
|
var MAX_SKILL_BYTES = 5e6;
|
|
@@ -5516,7 +5623,7 @@ async function discoverSkills(dirs) {
|
|
|
5516
5623
|
if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink()) continue;
|
|
5517
5624
|
const entries = await readdir5(root, { withFileTypes: true }).catch(() => []);
|
|
5518
5625
|
const found = await Promise.all(
|
|
5519
|
-
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)))
|
|
5520
5627
|
);
|
|
5521
5628
|
for (const meta of found) {
|
|
5522
5629
|
if (meta && !byName.has(meta.name)) byName.set(meta.name, meta);
|
|
@@ -5556,11 +5663,11 @@ function formatSkillCatalogue(skills) {
|
|
|
5556
5663
|
async function readMeta(root, rootInfo, dir) {
|
|
5557
5664
|
const dirInfo = await lstat4(dir).catch(() => null);
|
|
5558
5665
|
if (!dirInfo?.isDirectory() || dirInfo.isSymbolicLink()) return null;
|
|
5559
|
-
const file =
|
|
5666
|
+
const file = path12.join(dir, SKILL_FILE);
|
|
5560
5667
|
const head = await readFrontmatterBytes(file);
|
|
5561
5668
|
if (!head) return null;
|
|
5562
5669
|
const { fields } = parseFrontmatter(head.text);
|
|
5563
|
-
const meta = { name: fields.name ||
|
|
5670
|
+
const meta = { name: fields.name || path12.basename(dir), description: fields.description ?? "", dir, file };
|
|
5564
5671
|
guards.set(meta, {
|
|
5565
5672
|
root,
|
|
5566
5673
|
rootIdentity: identity(rootInfo),
|
|
@@ -5656,8 +5763,8 @@ import {
|
|
|
5656
5763
|
writeFileSync
|
|
5657
5764
|
} from "fs";
|
|
5658
5765
|
import { chmod as chmod7, mkdir as mkdir5 } from "fs/promises";
|
|
5659
|
-
import
|
|
5660
|
-
var TMP_DIR =
|
|
5766
|
+
import path13 from "path";
|
|
5767
|
+
var TMP_DIR = path13.join(skillsDir, ".tmp");
|
|
5661
5768
|
var MAX_SKILL_BYTES2 = 5e6;
|
|
5662
5769
|
async function installSkill(source) {
|
|
5663
5770
|
await ensureDir(skillsDir);
|
|
@@ -5682,7 +5789,7 @@ function isNpmPackage(input) {
|
|
|
5682
5789
|
}
|
|
5683
5790
|
async function installFromGitHub(url) {
|
|
5684
5791
|
const { owner, repo, subdir, branch } = parseGitHubUrl(url);
|
|
5685
|
-
const name = safeSkillName(subdir ?
|
|
5792
|
+
const name = safeSkillName(subdir ? path13.posix.basename(subdir) : repo);
|
|
5686
5793
|
const skillDir = safeChildPath(skillsDir, name);
|
|
5687
5794
|
const tmpDir = safeChildPath(TMP_DIR, `${name}-${randomUUID4()}`);
|
|
5688
5795
|
try {
|
|
@@ -5717,16 +5824,16 @@ async function installFromNpm(packageName) {
|
|
|
5717
5824
|
if (!tarball) {
|
|
5718
5825
|
throw new Error(`Could not download npm package "${packageName}"`);
|
|
5719
5826
|
}
|
|
5720
|
-
const tarballPath =
|
|
5827
|
+
const tarballPath = path13.join(tmpDir, tarball);
|
|
5721
5828
|
validateTarball(tarballPath);
|
|
5722
|
-
const extractDir =
|
|
5829
|
+
const extractDir = path13.join(tmpDir, "extracted");
|
|
5723
5830
|
mkdirSync(extractDir, { recursive: true });
|
|
5724
5831
|
try {
|
|
5725
5832
|
execFileSync("tar", ["-xzf", tarballPath, "-C", extractDir], { stdio: "ignore" });
|
|
5726
5833
|
} catch {
|
|
5727
|
-
const packageDir =
|
|
5834
|
+
const packageDir = path13.join(tmpDir, "node_modules", packageName);
|
|
5728
5835
|
if (existsSync2(packageDir)) {
|
|
5729
|
-
const skillFile2 =
|
|
5836
|
+
const skillFile2 = path13.join(packageDir, "SKILL.md");
|
|
5730
5837
|
if (existsSync2(skillFile2)) {
|
|
5731
5838
|
const body2 = readSkillFile(skillFile2, packageDir, packageName);
|
|
5732
5839
|
await writeSkillFile(skillDir, body2);
|
|
@@ -5738,7 +5845,7 @@ async function installFromNpm(packageName) {
|
|
|
5738
5845
|
const extractedFiles = readdirSync(extractDir);
|
|
5739
5846
|
const packageRoot = extractedFiles.find((f) => f === "package");
|
|
5740
5847
|
if (!packageRoot) throw new Error("Could not find package root in extracted files");
|
|
5741
|
-
const skillFile =
|
|
5848
|
+
const skillFile = path13.join(extractDir, "package", "SKILL.md");
|
|
5742
5849
|
if (!existsSync2(skillFile)) {
|
|
5743
5850
|
throw new Error(`No SKILL.md found in npm package "${packageName}"`);
|
|
5744
5851
|
}
|
|
@@ -5772,26 +5879,26 @@ function validateTarball(file) {
|
|
|
5772
5879
|
}
|
|
5773
5880
|
for (const entry of entries) {
|
|
5774
5881
|
const normalized = entry.replace(/^\.\//, "");
|
|
5775
|
-
if (normalized.includes("\\") ||
|
|
5882
|
+
if (normalized.includes("\\") || path13.posix.isAbsolute(normalized) || normalized.split("/").some((segment) => segment === "..")) {
|
|
5776
5883
|
throw new Error(`Unsafe path in npm skill archive: ${entry}`);
|
|
5777
5884
|
}
|
|
5778
5885
|
}
|
|
5779
5886
|
}
|
|
5780
5887
|
async function installFromLocal(srcPath) {
|
|
5781
|
-
const resolved =
|
|
5888
|
+
const resolved = path13.resolve(srcPath);
|
|
5782
5889
|
let skillDir;
|
|
5783
5890
|
let skillFile;
|
|
5784
|
-
const directSkill =
|
|
5891
|
+
const directSkill = path13.join(resolved, "SKILL.md");
|
|
5785
5892
|
if (existsSync2(directSkill)) {
|
|
5786
5893
|
skillDir = resolved;
|
|
5787
5894
|
skillFile = directSkill;
|
|
5788
5895
|
} else if (resolved.endsWith("SKILL.md") && existsSync2(resolved)) {
|
|
5789
|
-
skillDir =
|
|
5896
|
+
skillDir = path13.dirname(resolved);
|
|
5790
5897
|
skillFile = resolved;
|
|
5791
5898
|
} else {
|
|
5792
5899
|
throw new Error(`No SKILL.md found at "${srcPath}"`);
|
|
5793
5900
|
}
|
|
5794
|
-
const name =
|
|
5901
|
+
const name = path13.basename(skillDir);
|
|
5795
5902
|
const destDir = safeChildPath(skillsDir, safeSkillName(name));
|
|
5796
5903
|
const body = readSkillFile(skillFile, skillDir, srcPath);
|
|
5797
5904
|
await writeSkillFile(destDir, body);
|
|
@@ -5804,7 +5911,7 @@ async function writeSkillFile(dir, body) {
|
|
|
5804
5911
|
throw new Error(`Refusing to replace symlinked skill directory: ${dir}`);
|
|
5805
5912
|
}
|
|
5806
5913
|
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
5807
|
-
const file =
|
|
5914
|
+
const file = path13.join(dir, "SKILL.md");
|
|
5808
5915
|
const existingFile = lstatSync(file, { throwIfNoEntry: false });
|
|
5809
5916
|
if (existingFile?.isSymbolicLink()) {
|
|
5810
5917
|
throw new Error(`Refusing to replace symlinked skill file: ${file}`);
|
|
@@ -5857,13 +5964,13 @@ function safeSkillName(value) {
|
|
|
5857
5964
|
return value;
|
|
5858
5965
|
}
|
|
5859
5966
|
function safeChildPath(root, ...segments) {
|
|
5860
|
-
const target =
|
|
5967
|
+
const target = path13.resolve(root, ...segments);
|
|
5861
5968
|
assertInside(root, target);
|
|
5862
5969
|
return target;
|
|
5863
5970
|
}
|
|
5864
5971
|
function assertInside(root, target) {
|
|
5865
|
-
const relative2 =
|
|
5866
|
-
if (relative2 === "" || !
|
|
5972
|
+
const relative2 = path13.relative(path13.resolve(root), path13.resolve(target));
|
|
5973
|
+
if (relative2 === "" || !path13.isAbsolute(relative2) && relative2 !== ".." && !relative2.startsWith(`..${path13.sep}`)) {
|
|
5867
5974
|
return;
|
|
5868
5975
|
}
|
|
5869
5976
|
throw new Error(`Path escapes the skills directory: ${target}`);
|
|
@@ -6189,10 +6296,17 @@ async function boot(options) {
|
|
|
6189
6296
|
}
|
|
6190
6297
|
const usage = createUsageTracker(session.usage, resolvePricing);
|
|
6191
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 })]);
|
|
6192
6305
|
const mainSystemPrompt = () => buildSystemPrompt({
|
|
6193
6306
|
cwd: options.cwd,
|
|
6194
6307
|
toolNames: tools.list().map((tool) => tool.name),
|
|
6195
|
-
skills: skillCatalogue
|
|
6308
|
+
skills: skillCatalogue,
|
|
6309
|
+
memory: projectMemory
|
|
6196
6310
|
});
|
|
6197
6311
|
let modelRef = session.model || config.model || "";
|
|
6198
6312
|
if (options.modelRef) modelRef = options.modelRef;
|
|
@@ -6538,7 +6652,7 @@ async function boot(options) {
|
|
|
6538
6652
|
},
|
|
6539
6653
|
async exportSession(id, destination) {
|
|
6540
6654
|
await persistQueue.catch(() => void 0);
|
|
6541
|
-
const target = destination ?
|
|
6655
|
+
const target = destination ? path14.resolve(options.cwd, destination) : path14.join(options.cwd, ".kitcode-exports");
|
|
6542
6656
|
if (!destination) await mkdir6(target, { recursive: true, mode: 448 });
|
|
6543
6657
|
return (await exportSession(id, target)).path;
|
|
6544
6658
|
},
|
|
@@ -6553,6 +6667,21 @@ async function boot(options) {
|
|
|
6553
6667
|
});
|
|
6554
6668
|
void refreshModelContextWindow();
|
|
6555
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
|
+
},
|
|
6556
6685
|
getEffort: () => config.effort,
|
|
6557
6686
|
async setEffort(effort) {
|
|
6558
6687
|
await persistConfig((draft) => {
|
|
@@ -7195,6 +7324,7 @@ var COMMANDS = [
|
|
|
7195
7324
|
{ name: "usage" },
|
|
7196
7325
|
{ name: "mcp", args: "[add|list|delete|enable|disable]" },
|
|
7197
7326
|
{ name: "attach", args: "<path|clipboard|clear>" },
|
|
7327
|
+
{ name: "memory", args: "[show|set <text>|clear]" },
|
|
7198
7328
|
{ name: "compact" },
|
|
7199
7329
|
{ name: "update" },
|
|
7200
7330
|
{ name: "checker" },
|
|
@@ -7388,7 +7518,7 @@ var en = {
|
|
|
7388
7518
|
escHelp: "esc \u2014 cancel the running turn",
|
|
7389
7519
|
cancelled: "Cancelled.",
|
|
7390
7520
|
queued: (count) => `queued: ${count}`,
|
|
7391
|
-
configAt: (
|
|
7521
|
+
configAt: (path15) => `config: ${path15}`,
|
|
7392
7522
|
skillsEmpty: "No skills installed. Drop a folder with a SKILL.md into ~/.kitcode/skills or ./.kitcode/skills",
|
|
7393
7523
|
skillsInstallUsage: "Usage: /skills install <github-url|npm-package|local-path>",
|
|
7394
7524
|
skillsInstalling: (source) => `Installing skill from "${source}"\u2026`,
|
|
@@ -7434,7 +7564,7 @@ var en = {
|
|
|
7434
7564
|
sessionDeleteAllFinalBody: "This cannot be undone. Press y only if you want to erase the complete session history.",
|
|
7435
7565
|
sessionsDeletedAll: (count) => `All saved chats deleted \xB7 ${count} sessions removed.`,
|
|
7436
7566
|
sessionsDeleteAllFailed: (deleted, failed) => `Session cleanup was incomplete \xB7 ${deleted} removed, ${failed} failed.`,
|
|
7437
|
-
sessionExported: (
|
|
7567
|
+
sessionExported: (path15) => `Session exported to ${path15}`,
|
|
7438
7568
|
sessionActionResume: "resume",
|
|
7439
7569
|
sessionActionRename: "rename",
|
|
7440
7570
|
sessionActionDelete: "delete",
|
|
@@ -7504,6 +7634,7 @@ ${url}`,
|
|
|
7504
7634
|
budget: "set token budget per turn",
|
|
7505
7635
|
theme: "change the accent colour",
|
|
7506
7636
|
lang: "change the interface language",
|
|
7637
|
+
memory: "view, replace or clear project memory",
|
|
7507
7638
|
prompt: "insert or save a prompt",
|
|
7508
7639
|
"prompt save": "save the current or recent text as a prompt",
|
|
7509
7640
|
"prompt delete": "delete a saved prompt",
|
|
@@ -7576,7 +7707,7 @@ var ru = {
|
|
|
7576
7707
|
escHelp: "esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0445\u043E\u0434",
|
|
7577
7708
|
cancelled: "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E.",
|
|
7578
7709
|
queued: (count) => `\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438: ${count}`,
|
|
7579
|
-
configAt: (
|
|
7710
|
+
configAt: (path15) => `\u043A\u043E\u043D\u0444\u0438\u0433: ${path15}`,
|
|
7580
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",
|
|
7581
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>",
|
|
7582
7713
|
skillsInstalling: (source) => `\u0423\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u044E \u0441\u043A\u0438\u043B\u043B \u0438\u0437 "${source}"\u2026`,
|
|
@@ -7622,7 +7753,7 @@ var ru = {
|
|
|
7622
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.",
|
|
7623
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}.`,
|
|
7624
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}.`,
|
|
7625
|
-
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}`,
|
|
7626
7757
|
sessionActionResume: "\u043E\u0442\u043A\u0440\u044B\u0442\u044C",
|
|
7627
7758
|
sessionActionRename: "\u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C",
|
|
7628
7759
|
sessionActionDelete: "\u0443\u0434\u0430\u043B\u0438\u0442\u044C",
|
|
@@ -7692,6 +7823,7 @@ ${url}`,
|
|
|
7692
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)",
|
|
7693
7824
|
theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
|
|
7694
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",
|
|
7695
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",
|
|
7696
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",
|
|
7697
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",
|
|
@@ -8545,6 +8677,7 @@ function ModelBadge({ ref: modelRef }) {
|
|
|
8545
8677
|
return /* @__PURE__ */ jsx9(Box9, { borderColor: theme.accent, borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: name }) });
|
|
8546
8678
|
}
|
|
8547
8679
|
var EFFORT_STYLES = {
|
|
8680
|
+
auto: { label: "auto", color: "cyan" },
|
|
8548
8681
|
max: { label: "max", color: "red", bold: true },
|
|
8549
8682
|
xhigh: { label: "xhigh", color: "yellow", bold: true },
|
|
8550
8683
|
high: { label: "high", color: "cyan" },
|
|
@@ -9624,7 +9757,7 @@ function sanitizeDisplay(display) {
|
|
|
9624
9757
|
|
|
9625
9758
|
// src/ui/App.tsx
|
|
9626
9759
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
9627
|
-
var EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
9760
|
+
var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
|
|
9628
9761
|
var STREAM_FRAME_MS = 50;
|
|
9629
9762
|
var MAX_ATTACHMENTS = 8;
|
|
9630
9763
|
function App({
|
|
@@ -10476,14 +10609,27 @@ ${strings.mcpAddUsage}`
|
|
|
10476
10609
|
forceRender((n) => n + 1);
|
|
10477
10610
|
return;
|
|
10478
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
|
+
}
|
|
10479
10624
|
case "effort": {
|
|
10480
|
-
|
|
10625
|
+
notice("info", runtime.effortDescription(history.current));
|
|
10626
|
+
const choice = EFFORTS.includes(rawRest) ? rawRest : await pick(
|
|
10481
10627
|
strings.titleEffort,
|
|
10482
10628
|
EFFORTS.map((effort) => ({ key: effort, label: effort }))
|
|
10483
10629
|
);
|
|
10484
10630
|
if (!choice) return;
|
|
10485
10631
|
await runtime.setEffort(choice);
|
|
10486
|
-
notice("info", strings.effortSet(choice));
|
|
10632
|
+
notice("info", `${strings.effortSet(choice)} \xB7 ${runtime.effortDescription(history.current)}`);
|
|
10487
10633
|
forceRender((n) => n + 1);
|
|
10488
10634
|
return;
|
|
10489
10635
|
}
|