kimiflare 0.15.0 → 0.17.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/dist/index.js +1138 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ async function loadConfig() {
|
|
|
37
37
|
const envCoauthor = readCoauthorEnv();
|
|
38
38
|
const envCacheStable = process.env.KIMIFLARE_CACHE_STABLE_PROMPTS;
|
|
39
39
|
const cacheStablePrompts = envCacheStable === "0" || envCacheStable === "false" ? false : true;
|
|
40
|
+
const envCompiled = process.env.KIMIFLARE_COMPILED_CONTEXT;
|
|
41
|
+
const compiledContext = envCompiled === "1" || envCompiled === "true" ? true : false;
|
|
40
42
|
if (envAccount && envToken) {
|
|
41
43
|
return {
|
|
42
44
|
accountId: envAccount,
|
|
@@ -47,7 +49,8 @@ async function loadConfig() {
|
|
|
47
49
|
coauthor: envCoauthor?.enabled ?? true,
|
|
48
50
|
coauthorName: envCoauthor?.name,
|
|
49
51
|
coauthorEmail: envCoauthor?.email,
|
|
50
|
-
cacheStablePrompts
|
|
52
|
+
cacheStablePrompts,
|
|
53
|
+
compiledContext
|
|
51
54
|
};
|
|
52
55
|
}
|
|
53
56
|
try {
|
|
@@ -64,7 +67,8 @@ async function loadConfig() {
|
|
|
64
67
|
coauthorName: envCoauthor?.name ?? parsed.coauthorName,
|
|
65
68
|
coauthorEmail: envCoauthor?.email ?? parsed.coauthorEmail,
|
|
66
69
|
mcpServers: parsed.mcpServers,
|
|
67
|
-
cacheStablePrompts: parsed.cacheStablePrompts ?? cacheStablePrompts
|
|
70
|
+
cacheStablePrompts: parsed.cacheStablePrompts ?? cacheStablePrompts,
|
|
71
|
+
compiledContext: parsed.compiledContext ?? compiledContext
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
} catch {
|
|
@@ -406,16 +410,107 @@ var init_registry = __esm({
|
|
|
406
410
|
}
|
|
407
411
|
});
|
|
408
412
|
|
|
413
|
+
// src/storage-limits.ts
|
|
414
|
+
import { readdir, stat, unlink } from "fs/promises";
|
|
415
|
+
import { join as join2 } from "path";
|
|
416
|
+
async function listFilesByMtime(dir, pattern = /.*/) {
|
|
417
|
+
let entries;
|
|
418
|
+
try {
|
|
419
|
+
entries = await readdir(dir);
|
|
420
|
+
} catch {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
const files = [];
|
|
424
|
+
for (const name of entries) {
|
|
425
|
+
if (!pattern.test(name)) continue;
|
|
426
|
+
const p = join2(dir, name);
|
|
427
|
+
try {
|
|
428
|
+
const s = await stat(p);
|
|
429
|
+
if (s.isFile()) files.push({ path: p, mtime: s.mtime });
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
files.sort((a, b) => b.mtime < a.mtime ? -1 : 1);
|
|
434
|
+
return files;
|
|
435
|
+
}
|
|
436
|
+
async function pruneFiles(files, maxAgeDays, maxCount) {
|
|
437
|
+
const cutoff = new Date(Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3);
|
|
438
|
+
let removed = 0;
|
|
439
|
+
for (const f of files) {
|
|
440
|
+
if (f.mtime < cutoff) {
|
|
441
|
+
try {
|
|
442
|
+
await unlink(f.path);
|
|
443
|
+
removed++;
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const remaining = files.filter((f) => {
|
|
449
|
+
return f.mtime >= cutoff;
|
|
450
|
+
});
|
|
451
|
+
if (remaining.length > maxCount) {
|
|
452
|
+
const toDelete = remaining.slice(maxCount);
|
|
453
|
+
for (const f of toDelete) {
|
|
454
|
+
try {
|
|
455
|
+
await unlink(f.path);
|
|
456
|
+
removed++;
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return removed;
|
|
462
|
+
}
|
|
463
|
+
async function rotateJsonl(path, maxBytes, rotations) {
|
|
464
|
+
const { rename } = await import("fs/promises");
|
|
465
|
+
let s;
|
|
466
|
+
try {
|
|
467
|
+
s = await stat(path);
|
|
468
|
+
} catch {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (s.size <= maxBytes) return;
|
|
472
|
+
for (let i = rotations - 1; i >= 1; i--) {
|
|
473
|
+
const src = i === 1 ? path : `${path}.${i - 1}`;
|
|
474
|
+
const dst = `${path}.${i}`;
|
|
475
|
+
try {
|
|
476
|
+
await rename(src, dst);
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
var RETENTION;
|
|
482
|
+
var init_storage_limits = __esm({
|
|
483
|
+
"src/storage-limits.ts"() {
|
|
484
|
+
"use strict";
|
|
485
|
+
RETENTION = {
|
|
486
|
+
/** Session files older than this (days) are pruned. */
|
|
487
|
+
sessionMaxAgeDays: 30,
|
|
488
|
+
/** Max number of session files to keep. */
|
|
489
|
+
sessionMaxCount: 100,
|
|
490
|
+
/** Usage log day entries older than this (days) are pruned. */
|
|
491
|
+
usageDayMaxAgeDays: 90,
|
|
492
|
+
/** Usage log session entries older than this (days) are pruned. */
|
|
493
|
+
usageSessionMaxAgeDays: 30,
|
|
494
|
+
/** Max number of session entries in usage log. */
|
|
495
|
+
usageSessionMaxCount: 200,
|
|
496
|
+
/** Max size of cost-debug JSONL before rotation (bytes). */
|
|
497
|
+
costDebugMaxBytes: 5 * 1024 * 1024,
|
|
498
|
+
/** Number of rotated cost-debug files to keep. */
|
|
499
|
+
costDebugRotations: 2
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
409
504
|
// src/cost-debug.ts
|
|
410
505
|
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
411
506
|
import { homedir as homedir2 } from "os";
|
|
412
|
-
import { join as
|
|
507
|
+
import { join as join3 } from "path";
|
|
413
508
|
function debugDir() {
|
|
414
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
415
|
-
return
|
|
509
|
+
const xdg = process.env.XDG_DATA_HOME || join3(homedir2(), ".local", "share");
|
|
510
|
+
return join3(xdg, "kimiflare");
|
|
416
511
|
}
|
|
417
512
|
function debugPath() {
|
|
418
|
-
return
|
|
513
|
+
return join3(debugDir(), "cost-debug.jsonl");
|
|
419
514
|
}
|
|
420
515
|
function now() {
|
|
421
516
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -471,6 +566,7 @@ function buildToolStats(results) {
|
|
|
471
566
|
}
|
|
472
567
|
async function logCostDebug(entry) {
|
|
473
568
|
await mkdir2(debugDir(), { recursive: true });
|
|
569
|
+
await rotateJsonl(debugPath(), RETENTION.costDebugMaxBytes, RETENTION.costDebugRotations);
|
|
474
570
|
await appendFile(debugPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
475
571
|
}
|
|
476
572
|
function serializePrefix(messages) {
|
|
@@ -556,13 +652,15 @@ async function logTurnDebug(ctx) {
|
|
|
556
652
|
toolTotalRawBytes: toolTotalRaw,
|
|
557
653
|
toolTotalReducedBytes: toolTotalReduced,
|
|
558
654
|
toolSavingsPct: toolTotalRaw > 0 ? Math.round((toolTotalRaw - toolTotalReduced) / toolTotalRaw * 100) : 0,
|
|
559
|
-
cacheDiagnostics
|
|
655
|
+
cacheDiagnostics,
|
|
656
|
+
compaction: ctx.compaction
|
|
560
657
|
});
|
|
561
658
|
}
|
|
562
659
|
var LOG_VERSION;
|
|
563
660
|
var init_cost_debug = __esm({
|
|
564
661
|
"src/cost-debug.ts"() {
|
|
565
662
|
"use strict";
|
|
663
|
+
init_storage_limits();
|
|
566
664
|
LOG_VERSION = 1;
|
|
567
665
|
}
|
|
568
666
|
});
|
|
@@ -774,22 +872,18 @@ function isReadOnlyBash(command) {
|
|
|
774
872
|
return false;
|
|
775
873
|
}
|
|
776
874
|
}
|
|
777
|
-
const argCheck = COMMANDS_NEEDING_ARG_CHECK[cmd];
|
|
778
|
-
if (argCheck) {
|
|
779
|
-
return argCheck(args);
|
|
780
|
-
}
|
|
781
875
|
return READONLY_COMMANDS.has(cmd);
|
|
782
876
|
}
|
|
783
877
|
function systemPromptForMode(m) {
|
|
784
878
|
if (m === "plan") {
|
|
785
|
-
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat) along with read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
879
|
+
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat, grep) along with read/glob/grep/web-fetch. Scripting interpreters (node, python3, ruby, perl, awk) and build/package tools (npm, cargo, go, tsc, jest, etc.) are blocked in plan mode. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
786
880
|
}
|
|
787
881
|
if (m === "auto") {
|
|
788
882
|
return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
|
|
789
883
|
}
|
|
790
884
|
return "";
|
|
791
885
|
}
|
|
792
|
-
var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS
|
|
886
|
+
var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS;
|
|
793
887
|
var init_mode = __esm({
|
|
794
888
|
"src/mode.ts"() {
|
|
795
889
|
"use strict";
|
|
@@ -860,16 +954,8 @@ var init_mode = __esm({
|
|
|
860
954
|
"id",
|
|
861
955
|
"whoami",
|
|
862
956
|
"groups",
|
|
863
|
-
// Dev tools (version/info only)
|
|
864
|
-
"node",
|
|
865
|
-
"npx",
|
|
866
|
-
"python3",
|
|
867
|
-
"ruby",
|
|
868
|
-
"perl",
|
|
869
957
|
// Utilities
|
|
870
958
|
"jq",
|
|
871
|
-
"yq",
|
|
872
|
-
"awk",
|
|
873
959
|
"cut",
|
|
874
960
|
"tr",
|
|
875
961
|
"base64",
|
|
@@ -892,42 +978,16 @@ var init_mode = __esm({
|
|
|
892
978
|
"ss",
|
|
893
979
|
"lsof"
|
|
894
980
|
]);
|
|
895
|
-
COMMANDS_NEEDING_ARG_CHECK = {
|
|
896
|
-
find: (args) => !args.some((a) => a === "-delete" || a === "-exec"),
|
|
897
|
-
sed: (args) => !args.some((a) => a === "-i" || a.startsWith("-i")),
|
|
898
|
-
tar: (args) => args[0] === "-tf" || args[0] === "--list",
|
|
899
|
-
unzip: (args) => args[0] === "-l",
|
|
900
|
-
curl: (args) => !args.some((a) => a === "-o" || a === "-O" || a === "-d" || a === "--data" || a.startsWith("-X")),
|
|
901
|
-
wget: (args) => !args.some((a) => a === "-O" || a === "--output-document" || a.startsWith("--post")),
|
|
902
|
-
npm: (args) => ["list", "view", "config"].includes(args[0] ?? "") && !(args[0] === "config" && args[1] && !args[1].startsWith("get") && args[1] !== "list"),
|
|
903
|
-
tsc: (args) => args.every(
|
|
904
|
-
(a) => ["--noEmit", "--version", "--showConfig", "--help", "-h", "--init"].includes(a)
|
|
905
|
-
),
|
|
906
|
-
eslint: (args) => args.every(
|
|
907
|
-
(a) => ["--version", "--print-config", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
908
|
-
),
|
|
909
|
-
prettier: (args) => args.every(
|
|
910
|
-
(a) => ["--version", "--check", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
911
|
-
),
|
|
912
|
-
jest: (args) => args.every(
|
|
913
|
-
(a) => ["--version", "--listTests", "--showConfig", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
914
|
-
),
|
|
915
|
-
vitest: (args) => args.every(
|
|
916
|
-
(a) => ["--version", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
917
|
-
),
|
|
918
|
-
go: (args) => ["version", "env", "list", "mod"].includes(args[0] ?? "") && !(args[0] === "mod" && args[1] && !["graph", "download", "why", "verify"].includes(args[1])),
|
|
919
|
-
cargo: (args) => ["--version", "-V", "check", "test", "metadata"].includes(args[0] ?? "") && !(args[0] === "test" && args.includes("--no-run") === false)
|
|
920
|
-
};
|
|
921
981
|
}
|
|
922
982
|
});
|
|
923
983
|
|
|
924
984
|
// src/agent/system-prompt.ts
|
|
925
985
|
import { platform, release, homedir as homedir3 } from "os";
|
|
926
|
-
import { basename, join as
|
|
986
|
+
import { basename, join as join4 } from "path";
|
|
927
987
|
import { readFileSync, statSync } from "fs";
|
|
928
988
|
function loadContextFile(cwd) {
|
|
929
989
|
for (const name of CONTEXT_FILENAMES) {
|
|
930
|
-
const path =
|
|
990
|
+
const path = join4(cwd, name);
|
|
931
991
|
try {
|
|
932
992
|
const s = statSync(path);
|
|
933
993
|
if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
|
|
@@ -951,7 +1011,12 @@ How to work:
|
|
|
951
1011
|
- You have a 262k-token context window. Read as much of a file as needed rather than guessing.
|
|
952
1012
|
- If a request is ambiguous, ask one focused question instead of making large assumptions.
|
|
953
1013
|
- When you finish a task, stop. Do not add a closing summary.
|
|
954
|
-
- When creating git commits, you must include \`Co-authored-by: kimiflare <kimiflare@proton.me>\` in the commit message so kimiflare is credited as a contributor. The bash tool will also auto-append this trailer when it detects git commit-creating commands
|
|
1014
|
+
- When creating git commits, you must include \`Co-authored-by: kimiflare <kimiflare@proton.me>\` in the commit message so kimiflare is credited as a contributor. The bash tool will also auto-append this trailer when it detects git commit-creating commands.
|
|
1015
|
+
|
|
1016
|
+
Tool output reduction:
|
|
1017
|
+
- Large tool outputs (grep, read, bash, web_fetch) are reduced to compact summaries by default to preserve context window.
|
|
1018
|
+
- When you see "[output reduced]" with an artifact ID, you can call \`expand_artifact\` with that ID to retrieve the full raw output if you need more detail.
|
|
1019
|
+
- You can also re-run the original tool with more targeted parameters (e.g. read with offset/limit, grep with output_mode="files") instead of expanding.`;
|
|
955
1020
|
}
|
|
956
1021
|
function buildSessionPrefix(opts2) {
|
|
957
1022
|
const now2 = opts2.now ?? /* @__PURE__ */ new Date();
|
|
@@ -1005,11 +1070,6 @@ function resolvePath(cwd, input) {
|
|
|
1005
1070
|
}
|
|
1006
1071
|
return isAbsolute(input) ? input : resolve(cwd, input);
|
|
1007
1072
|
}
|
|
1008
|
-
function truncate(s, n) {
|
|
1009
|
-
if (s.length <= n) return s;
|
|
1010
|
-
return s.slice(0, n) + `
|
|
1011
|
-
... [truncated, ${s.length - n} chars omitted]`;
|
|
1012
|
-
}
|
|
1013
1073
|
function collapsePath(input, cwd, maxLen = 40) {
|
|
1014
1074
|
if (!input) return input;
|
|
1015
1075
|
let abs;
|
|
@@ -1037,7 +1097,7 @@ var init_paths = __esm({
|
|
|
1037
1097
|
});
|
|
1038
1098
|
|
|
1039
1099
|
// src/tools/read.ts
|
|
1040
|
-
import { readFile as readFile2, stat } from "fs/promises";
|
|
1100
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1041
1101
|
var MAX_BYTES, readTool;
|
|
1042
1102
|
var init_read = __esm({
|
|
1043
1103
|
"src/tools/read.ts"() {
|
|
@@ -1046,7 +1106,7 @@ var init_read = __esm({
|
|
|
1046
1106
|
MAX_BYTES = 2 * 1024 * 1024;
|
|
1047
1107
|
readTool = {
|
|
1048
1108
|
name: "read",
|
|
1049
|
-
description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style.",
|
|
1109
|
+
description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style. When reading a full file without offset/limit, the output is reduced to a compact outline (imports, exports, signatures, preview) by default; use expand_artifact to retrieve the full content or specify offset/limit for a targeted slice.",
|
|
1050
1110
|
parameters: {
|
|
1051
1111
|
type: "object",
|
|
1052
1112
|
properties: {
|
|
@@ -1061,7 +1121,7 @@ var init_read = __esm({
|
|
|
1061
1121
|
render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
|
|
1062
1122
|
async run(args, ctx) {
|
|
1063
1123
|
const abs = resolvePath(ctx.cwd, args.path);
|
|
1064
|
-
const st = await
|
|
1124
|
+
const st = await stat2(abs);
|
|
1065
1125
|
if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
|
|
1066
1126
|
const text = await readFile2(abs, "utf8");
|
|
1067
1127
|
const lines = text.split("\n");
|
|
@@ -1173,7 +1233,7 @@ var init_edit = __esm({
|
|
|
1173
1233
|
// src/tools/bash.ts
|
|
1174
1234
|
import { spawn } from "child_process";
|
|
1175
1235
|
import { tmpdir } from "os";
|
|
1176
|
-
import { join as
|
|
1236
|
+
import { join as join5 } from "path";
|
|
1177
1237
|
function formatBashTitle(raw) {
|
|
1178
1238
|
let cmd = (raw ?? "").trim();
|
|
1179
1239
|
const m = cmd.match(/^cd\s+([^\s&;]+)\s*(?:&&|;)\s*(.*)$/);
|
|
@@ -1189,7 +1249,7 @@ function injectCoauthor(command, coauthor) {
|
|
|
1189
1249
|
const isRebaseContinue = /\bgit\s+rebase\b/.test(trimmed) && !/\b--abort\b|\b--skip\b/.test(trimmed);
|
|
1190
1250
|
const mentionsGit = /\bgit\b/.test(trimmed);
|
|
1191
1251
|
if (!createsCommit && !isRebaseContinue && !mentionsGit) return command;
|
|
1192
|
-
const tmpFile =
|
|
1252
|
+
const tmpFile = join5(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1193
1253
|
const amendBlock = `
|
|
1194
1254
|
if ! git log -1 --pretty=%B 2>/dev/null | grep -qF "${trailer}"; then
|
|
1195
1255
|
git log -1 --pretty=%B | git interpret-trailers --trailer "${trailer}" > "${tmpFile}" && git commit --amend -F "${tmpFile}" --no-edit && rm -f "${tmpFile}"
|
|
@@ -1242,26 +1302,23 @@ ${stdout.trimEnd()}`);
|
|
|
1242
1302
|
${stderr.trimEnd()}`);
|
|
1243
1303
|
if (!stdout && !stderr) parts.push("(no output)");
|
|
1244
1304
|
const raw = parts.join("\n");
|
|
1245
|
-
const reduced = truncate(raw, OUTPUT_CAP);
|
|
1246
1305
|
resolve2({
|
|
1247
|
-
content:
|
|
1306
|
+
content: raw,
|
|
1248
1307
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1249
|
-
reducedBytes: Buffer.byteLength(
|
|
1308
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1250
1309
|
});
|
|
1251
1310
|
});
|
|
1252
1311
|
});
|
|
1253
1312
|
}
|
|
1254
|
-
var DEFAULT_TIMEOUT, MAX_TIMEOUT,
|
|
1313
|
+
var DEFAULT_TIMEOUT, MAX_TIMEOUT, bashTool;
|
|
1255
1314
|
var init_bash = __esm({
|
|
1256
1315
|
"src/tools/bash.ts"() {
|
|
1257
1316
|
"use strict";
|
|
1258
|
-
init_paths();
|
|
1259
1317
|
DEFAULT_TIMEOUT = 12e4;
|
|
1260
1318
|
MAX_TIMEOUT = 6e5;
|
|
1261
|
-
OUTPUT_CAP = 3e4;
|
|
1262
1319
|
bashTool = {
|
|
1263
1320
|
name: "bash",
|
|
1264
|
-
description: "Run a shell command via `bash -lc`. Prompts the user for permission before executing. stdout and stderr are captured
|
|
1321
|
+
description: "Run a shell command via `bash -lc`. Prompts the user for permission before executing. stdout and stderr are captured and combined. Large outputs are reduced to a compact summary by default; use expand_artifact to retrieve the full log.",
|
|
1265
1322
|
parameters: {
|
|
1266
1323
|
type: "object",
|
|
1267
1324
|
properties: {
|
|
@@ -1346,11 +1403,10 @@ async function runRipgrep(args, root, mode) {
|
|
|
1346
1403
|
const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
|
|
1347
1404
|
const trimmed = stdout.trim();
|
|
1348
1405
|
if (!trimmed) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
|
|
1349
|
-
const reduced = truncate(trimmed, 3e4);
|
|
1350
1406
|
return {
|
|
1351
|
-
content:
|
|
1407
|
+
content: trimmed,
|
|
1352
1408
|
rawBytes: Buffer.byteLength(trimmed, "utf8"),
|
|
1353
|
-
reducedBytes: Buffer.byteLength(
|
|
1409
|
+
reducedBytes: Buffer.byteLength(trimmed, "utf8")
|
|
1354
1410
|
};
|
|
1355
1411
|
} catch (e) {
|
|
1356
1412
|
const err = e;
|
|
@@ -1389,11 +1445,10 @@ async function runJsFallback(args, root, mode) {
|
|
|
1389
1445
|
}
|
|
1390
1446
|
if (!out.length) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
|
|
1391
1447
|
const raw = out.join("\n");
|
|
1392
|
-
const reduced = truncate(raw, 3e4);
|
|
1393
1448
|
return {
|
|
1394
|
-
content:
|
|
1449
|
+
content: raw,
|
|
1395
1450
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1396
|
-
reducedBytes: Buffer.byteLength(
|
|
1451
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1397
1452
|
};
|
|
1398
1453
|
}
|
|
1399
1454
|
var pExecFile, cachedHasRg, grepTool;
|
|
@@ -1436,17 +1491,15 @@ var init_grep = __esm({
|
|
|
1436
1491
|
|
|
1437
1492
|
// src/tools/web-fetch.ts
|
|
1438
1493
|
import TurndownService from "turndown";
|
|
1439
|
-
var MAX_BYTES2,
|
|
1494
|
+
var MAX_BYTES2, TIMEOUT_MS, webFetchTool;
|
|
1440
1495
|
var init_web_fetch = __esm({
|
|
1441
1496
|
"src/tools/web-fetch.ts"() {
|
|
1442
1497
|
"use strict";
|
|
1443
|
-
init_paths();
|
|
1444
1498
|
MAX_BYTES2 = 1 * 1024 * 1024;
|
|
1445
|
-
MAX_OUTPUT = 1e5;
|
|
1446
1499
|
TIMEOUT_MS = 2e4;
|
|
1447
1500
|
webFetchTool = {
|
|
1448
1501
|
name: "web_fetch",
|
|
1449
|
-
description: "Fetch a URL over HTTPS and return its content. HTML pages are converted to markdown.
|
|
1502
|
+
description: "Fetch a URL over HTTPS and return its content. HTML pages are converted to markdown. Large pages are reduced to a summary by default; use expand_artifact to retrieve the full content.",
|
|
1450
1503
|
parameters: {
|
|
1451
1504
|
type: "object",
|
|
1452
1505
|
properties: {
|
|
@@ -1480,11 +1533,10 @@ ${td.turndown(bounded)}`;
|
|
|
1480
1533
|
|
|
1481
1534
|
${bounded}`;
|
|
1482
1535
|
}
|
|
1483
|
-
const reduced = truncate(raw, MAX_OUTPUT);
|
|
1484
1536
|
return {
|
|
1485
|
-
content:
|
|
1537
|
+
content: raw,
|
|
1486
1538
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1487
|
-
reducedBytes: Buffer.byteLength(
|
|
1539
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1488
1540
|
};
|
|
1489
1541
|
} finally {
|
|
1490
1542
|
clearTimeout(timer);
|
|
@@ -1576,6 +1628,423 @@ var init_tasks = __esm({
|
|
|
1576
1628
|
}
|
|
1577
1629
|
});
|
|
1578
1630
|
|
|
1631
|
+
// src/tools/artifact-store.ts
|
|
1632
|
+
var ToolArtifactStore;
|
|
1633
|
+
var init_artifact_store = __esm({
|
|
1634
|
+
"src/tools/artifact-store.ts"() {
|
|
1635
|
+
"use strict";
|
|
1636
|
+
ToolArtifactStore = class {
|
|
1637
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
1638
|
+
nextId = 0;
|
|
1639
|
+
maxArtifacts;
|
|
1640
|
+
maxTotalChars;
|
|
1641
|
+
constructor(opts2) {
|
|
1642
|
+
this.maxArtifacts = opts2?.maxArtifacts ?? 500;
|
|
1643
|
+
this.maxTotalChars = opts2?.maxTotalChars ?? 2e6;
|
|
1644
|
+
}
|
|
1645
|
+
/** Store raw content and return a stable artifact ID. */
|
|
1646
|
+
store(raw) {
|
|
1647
|
+
const id = `art_${++this.nextId}`;
|
|
1648
|
+
while (this.totalChars() + raw.length > this.maxTotalChars && this.artifacts.size > 0) {
|
|
1649
|
+
this.evictOldest();
|
|
1650
|
+
}
|
|
1651
|
+
while (this.artifacts.size >= this.maxArtifacts && this.artifacts.size > 0) {
|
|
1652
|
+
this.evictOldest();
|
|
1653
|
+
}
|
|
1654
|
+
this.artifacts.set(id, raw);
|
|
1655
|
+
return id;
|
|
1656
|
+
}
|
|
1657
|
+
retrieve(id) {
|
|
1658
|
+
return this.artifacts.get(id);
|
|
1659
|
+
}
|
|
1660
|
+
has(id) {
|
|
1661
|
+
return this.artifacts.has(id);
|
|
1662
|
+
}
|
|
1663
|
+
clear() {
|
|
1664
|
+
this.artifacts.clear();
|
|
1665
|
+
this.nextId = 0;
|
|
1666
|
+
}
|
|
1667
|
+
size() {
|
|
1668
|
+
return this.artifacts.size;
|
|
1669
|
+
}
|
|
1670
|
+
totalChars() {
|
|
1671
|
+
let sum = 0;
|
|
1672
|
+
for (const raw of this.artifacts.values()) {
|
|
1673
|
+
sum += raw.length;
|
|
1674
|
+
}
|
|
1675
|
+
return sum;
|
|
1676
|
+
}
|
|
1677
|
+
evictOldest() {
|
|
1678
|
+
const first = this.artifacts.keys().next().value;
|
|
1679
|
+
if (first !== void 0) {
|
|
1680
|
+
this.artifacts.delete(first);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// src/tools/reducer.ts
|
|
1688
|
+
function reduceToolOutput(toolName, raw, args, store, config = DEFAULT_REDUCER_CONFIG) {
|
|
1689
|
+
const rawBytes = Buffer.byteLength(raw, "utf8");
|
|
1690
|
+
const artifactId = store.store(raw);
|
|
1691
|
+
if (!config.enabled) {
|
|
1692
|
+
return { content: raw, rawBytes, reducedBytes: rawBytes, artifactId };
|
|
1693
|
+
}
|
|
1694
|
+
let reduced;
|
|
1695
|
+
let wasReduced = false;
|
|
1696
|
+
let hint;
|
|
1697
|
+
switch (toolName) {
|
|
1698
|
+
case "grep": {
|
|
1699
|
+
const r = reduceGrep(raw, args, config.grep);
|
|
1700
|
+
reduced = r.body;
|
|
1701
|
+
wasReduced = r.wasReduced;
|
|
1702
|
+
hint = r.hint;
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
case "read": {
|
|
1706
|
+
const r = reduceRead(raw, args, config.read);
|
|
1707
|
+
reduced = r.body;
|
|
1708
|
+
wasReduced = r.wasReduced;
|
|
1709
|
+
hint = r.hint;
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
case "bash": {
|
|
1713
|
+
const r = reduceBash(raw, args, config.bash);
|
|
1714
|
+
reduced = r.body;
|
|
1715
|
+
wasReduced = r.wasReduced;
|
|
1716
|
+
hint = r.hint;
|
|
1717
|
+
break;
|
|
1718
|
+
}
|
|
1719
|
+
case "web_fetch": {
|
|
1720
|
+
const r = reduceWebFetch(raw, args, config.webFetch);
|
|
1721
|
+
reduced = r.body;
|
|
1722
|
+
wasReduced = r.wasReduced;
|
|
1723
|
+
hint = r.hint;
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
default:
|
|
1727
|
+
reduced = raw;
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
if (!wasReduced) {
|
|
1731
|
+
return { content: reduced, rawBytes, reducedBytes: rawBytes, artifactId };
|
|
1732
|
+
}
|
|
1733
|
+
const footer = `[output reduced \u2014 full raw stored as artifact ${artifactId}]`;
|
|
1734
|
+
const content = hint ? `${reduced}
|
|
1735
|
+
${footer}
|
|
1736
|
+
${hint}` : `${reduced}
|
|
1737
|
+
${footer}`;
|
|
1738
|
+
const reducedBytes = Buffer.byteLength(content, "utf8");
|
|
1739
|
+
return { content, rawBytes, reducedBytes, artifactId };
|
|
1740
|
+
}
|
|
1741
|
+
function parseGrepLines(raw) {
|
|
1742
|
+
const matches = [];
|
|
1743
|
+
for (const line of raw.split("\n")) {
|
|
1744
|
+
const trimmed = line.trim();
|
|
1745
|
+
if (!trimmed) continue;
|
|
1746
|
+
const m = trimmed.match(/^(.+?):(\d+)?:(.*)$/);
|
|
1747
|
+
if (m) {
|
|
1748
|
+
matches.push({ file: m[1], line: m[2] ? parseInt(m[2], 10) : 0, text: m[3] });
|
|
1749
|
+
} else {
|
|
1750
|
+
matches.push({ file: trimmed, line: 0, text: "" });
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return matches;
|
|
1754
|
+
}
|
|
1755
|
+
function reduceGrep(raw, args, cfg) {
|
|
1756
|
+
const isFilesMode = args.output_mode === "files";
|
|
1757
|
+
const matches = parseGrepLines(raw);
|
|
1758
|
+
if (matches.length === 0) {
|
|
1759
|
+
return { body: raw, wasReduced: false };
|
|
1760
|
+
}
|
|
1761
|
+
if (isFilesMode) {
|
|
1762
|
+
const files = [...new Set(matches.map((m) => m.file))];
|
|
1763
|
+
const lines2 = [`${files.length} file(s) matched:`, ...files];
|
|
1764
|
+
return {
|
|
1765
|
+
body: lines2.join("\n"),
|
|
1766
|
+
wasReduced: true,
|
|
1767
|
+
hint: 'Re-run with output_mode="content" for match details.'
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1771
|
+
for (const m of matches) {
|
|
1772
|
+
const list = byFile.get(m.file) ?? [];
|
|
1773
|
+
list.push(m);
|
|
1774
|
+
byFile.set(m.file, list);
|
|
1775
|
+
}
|
|
1776
|
+
const lines = [];
|
|
1777
|
+
let totalShown = 0;
|
|
1778
|
+
const totalHits = matches.length;
|
|
1779
|
+
const fileCount = byFile.size;
|
|
1780
|
+
lines.push(`Matched ${fileCount} file(s) (${totalHits} total hits):`);
|
|
1781
|
+
for (const [file, hits] of byFile) {
|
|
1782
|
+
if (totalShown >= cfg.maxTotalLines) break;
|
|
1783
|
+
lines.push(` ${file}: ${hits.length} hit(s)`);
|
|
1784
|
+
const toShow = Math.min(hits.length, cfg.maxMatchesPerFile);
|
|
1785
|
+
for (let i = 0; i < toShow; i++) {
|
|
1786
|
+
const h = hits[i];
|
|
1787
|
+
const text = h.text.length > cfg.maxLineLength ? h.text.slice(0, cfg.maxLineLength) + "\u2026" : h.text;
|
|
1788
|
+
const prefix = h.line > 0 ? ` ${h.line}:` : " ";
|
|
1789
|
+
lines.push(`${prefix}${text}`);
|
|
1790
|
+
totalShown++;
|
|
1791
|
+
if (totalShown >= cfg.maxTotalLines) break;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (totalShown < totalHits) {
|
|
1795
|
+
lines.push(` \u2026 (${totalHits - totalShown} more hits omitted)`);
|
|
1796
|
+
}
|
|
1797
|
+
return {
|
|
1798
|
+
body: lines.join("\n"),
|
|
1799
|
+
wasReduced: totalHits > totalShown || fileCount > 1,
|
|
1800
|
+
hint: 'Use expand_artifact for full matches, or re-run with output_mode="files" for paths only.'
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function reduceRead(raw, args, cfg) {
|
|
1804
|
+
const hasSlice = typeof args.offset === "number" || typeof args.limit === "number";
|
|
1805
|
+
if (hasSlice) {
|
|
1806
|
+
const lines = raw.split("\n");
|
|
1807
|
+
if (lines.length > cfg.maxSliceLines) {
|
|
1808
|
+
const kept = lines.slice(0, cfg.maxSliceLines).join("\n");
|
|
1809
|
+
return {
|
|
1810
|
+
body: kept,
|
|
1811
|
+
wasReduced: true,
|
|
1812
|
+
hint: `\u2026 (${lines.length - cfg.maxSliceLines} more lines omitted)`
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return { body: raw, wasReduced: false };
|
|
1816
|
+
}
|
|
1817
|
+
const allLines = raw.split("\n");
|
|
1818
|
+
const totalLines = allLines.length;
|
|
1819
|
+
const cleanLines = allLines.map((l) => l.replace(/^\s*\d+\t/, ""));
|
|
1820
|
+
const imports = [];
|
|
1821
|
+
const exports = [];
|
|
1822
|
+
const functions = [];
|
|
1823
|
+
const classes = [];
|
|
1824
|
+
for (let i = 0; i < cleanLines.length; i++) {
|
|
1825
|
+
const line = cleanLines[i];
|
|
1826
|
+
const lineNum = i + 1;
|
|
1827
|
+
if (/^import\s+/.test(line)) {
|
|
1828
|
+
imports.push(`${lineNum}: ${line.trim()}`);
|
|
1829
|
+
} else if (/^(?:export\s+)?class\s+\w+/.test(line)) {
|
|
1830
|
+
classes.push(`${lineNum}: ${line.trim()}`);
|
|
1831
|
+
} else if (/^export\s+/.test(line)) {
|
|
1832
|
+
exports.push(`${lineNum}: ${line.trim()}`);
|
|
1833
|
+
} else if (/^(?:async\s+)?function\s+\w+/.test(line)) {
|
|
1834
|
+
functions.push(`${lineNum}: ${line.trim()}`);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const parts = [];
|
|
1838
|
+
parts.push(`File: ${totalLines} lines total`);
|
|
1839
|
+
if (imports.length > 0) {
|
|
1840
|
+
parts.push(`
|
|
1841
|
+
Imports (${imports.length}):`);
|
|
1842
|
+
parts.push(...imports.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1843
|
+
}
|
|
1844
|
+
if (exports.length > 0) {
|
|
1845
|
+
parts.push(`
|
|
1846
|
+
Exports (${exports.length}):`);
|
|
1847
|
+
parts.push(...exports.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1848
|
+
}
|
|
1849
|
+
if (functions.length > 0) {
|
|
1850
|
+
parts.push(`
|
|
1851
|
+
Functions (${functions.length}):`);
|
|
1852
|
+
parts.push(...functions.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1853
|
+
}
|
|
1854
|
+
if (classes.length > 0) {
|
|
1855
|
+
parts.push(`
|
|
1856
|
+
Classes (${classes.length}):`);
|
|
1857
|
+
parts.push(...classes.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1858
|
+
}
|
|
1859
|
+
const previewCount = Math.min(cfg.maxPreviewLines, totalLines);
|
|
1860
|
+
parts.push(`
|
|
1861
|
+
Preview (lines 1\u2013${previewCount}):`);
|
|
1862
|
+
parts.push(...allLines.slice(0, previewCount));
|
|
1863
|
+
return {
|
|
1864
|
+
body: parts.join("\n"),
|
|
1865
|
+
wasReduced: true,
|
|
1866
|
+
hint: "Use expand_artifact for full file, or read with offset/limit for a specific slice."
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
function reduceBash(raw, _args, cfg) {
|
|
1870
|
+
const lines = raw.split("\n");
|
|
1871
|
+
if (lines.length <= cfg.maxTotalLines) {
|
|
1872
|
+
return { body: raw, wasReduced: false };
|
|
1873
|
+
}
|
|
1874
|
+
let header = "";
|
|
1875
|
+
let bodyStart = 0;
|
|
1876
|
+
if (lines[0]?.startsWith("exit=") || lines[0]?.startsWith("(timed out")) {
|
|
1877
|
+
header = lines[0];
|
|
1878
|
+
bodyStart = 1;
|
|
1879
|
+
}
|
|
1880
|
+
const body = lines.slice(bodyStart);
|
|
1881
|
+
const isFailure = header.includes("exit=1") || raw.includes("Error:") || raw.includes("error:") || raw.includes("FAIL") || raw.includes("failed");
|
|
1882
|
+
const out = [header];
|
|
1883
|
+
if (isFailure) {
|
|
1884
|
+
const errorIndices = [];
|
|
1885
|
+
for (let i = 0; i < body.length; i++) {
|
|
1886
|
+
const line = body[i];
|
|
1887
|
+
if (/\bError\b/i.test(line) || /\berror\b/i.test(line) || /\bFAIL\b/i.test(line) || /\bfailed\b/i.test(line) || /^\s+at\s+/.test(line) || /\s+Error:\s+/.test(line)) {
|
|
1888
|
+
for (let j = Math.max(0, i - 2); j <= Math.min(body.length - 1, i + 2); j++) {
|
|
1889
|
+
if (!errorIndices.includes(j)) errorIndices.push(j);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
errorIndices.sort((a, b) => a - b);
|
|
1894
|
+
const cappedError = errorIndices.slice(0, cfg.maxErrorBlockLines);
|
|
1895
|
+
if (cappedError.length > 0) {
|
|
1896
|
+
out.push("--- error block ---");
|
|
1897
|
+
for (const idx of cappedError) {
|
|
1898
|
+
out.push(body[idx]);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
const testNames = [];
|
|
1902
|
+
for (const line of body) {
|
|
1903
|
+
const m = line.match(/(?:✗|✕|×|FAIL)\s+(.+)/) || line.match(/failing\s*\d*\s*:?\s*(.+)/i) || line.match(/Test\s+\w+\s+failed/i);
|
|
1904
|
+
if (m && m[1]) {
|
|
1905
|
+
const name = m[1].trim().slice(0, 120);
|
|
1906
|
+
if (!testNames.includes(name)) testNames.push(name);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (testNames.length > 0) {
|
|
1910
|
+
out.push("--- failing tests ---");
|
|
1911
|
+
out.push(...testNames.slice(0, 10));
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
const trailing = body.slice(-cfg.maxTrailingLines);
|
|
1915
|
+
out.push("--- last lines ---");
|
|
1916
|
+
out.push(...trailing);
|
|
1917
|
+
let result = out.join("\n");
|
|
1918
|
+
if (cfg.dedupeConsecutiveLines) {
|
|
1919
|
+
result = dedupeConsecutive(result);
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
body: result,
|
|
1923
|
+
wasReduced: true,
|
|
1924
|
+
hint: "Use expand_artifact for full output."
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function dedupeConsecutive(text) {
|
|
1928
|
+
const lines = text.split("\n");
|
|
1929
|
+
const out = [];
|
|
1930
|
+
let repeatCount = 1;
|
|
1931
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1932
|
+
const line = lines[i];
|
|
1933
|
+
const next = lines[i + 1];
|
|
1934
|
+
if (next !== void 0 && next === line) {
|
|
1935
|
+
repeatCount++;
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (repeatCount > 2) {
|
|
1939
|
+
out.push(line);
|
|
1940
|
+
out.push(`\u2026 (${repeatCount - 1} identical lines omitted)`);
|
|
1941
|
+
} else {
|
|
1942
|
+
for (let j = 0; j < repeatCount; j++) {
|
|
1943
|
+
out.push(line);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
repeatCount = 1;
|
|
1947
|
+
}
|
|
1948
|
+
return out.join("\n");
|
|
1949
|
+
}
|
|
1950
|
+
function reduceWebFetch(raw, args, cfg) {
|
|
1951
|
+
const url = typeof args.url === "string" ? args.url : "(unknown URL)";
|
|
1952
|
+
const titleMatch = raw.match(/^#\s+(.+)$/m);
|
|
1953
|
+
const title = titleMatch ? titleMatch[1].trim() : "(no title)";
|
|
1954
|
+
const parts = [];
|
|
1955
|
+
parts.push(`Title: ${title}`);
|
|
1956
|
+
parts.push(`URL: ${url}`);
|
|
1957
|
+
const headings = raw.match(/^#{1,3}\s+.+$/gm) ?? [];
|
|
1958
|
+
if (headings.length > 0) {
|
|
1959
|
+
parts.push("\nSections:");
|
|
1960
|
+
for (const h of headings.slice(0, 10)) {
|
|
1961
|
+
parts.push(` ${h}`);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
const bodyStart = raw.indexOf("\n\n");
|
|
1965
|
+
const body = bodyStart > 0 ? raw.slice(bodyStart + 2) : raw;
|
|
1966
|
+
const excerpt = body.slice(0, cfg.maxChars).trim();
|
|
1967
|
+
if (excerpt) {
|
|
1968
|
+
parts.push(`
|
|
1969
|
+
Excerpt (${excerpt.length} chars):`);
|
|
1970
|
+
parts.push(excerpt);
|
|
1971
|
+
}
|
|
1972
|
+
if (body.length > cfg.maxChars) {
|
|
1973
|
+
parts.push(`
|
|
1974
|
+
\u2026 (${body.length - cfg.maxChars} more chars omitted)`);
|
|
1975
|
+
}
|
|
1976
|
+
return {
|
|
1977
|
+
body: parts.join("\n"),
|
|
1978
|
+
wasReduced: true,
|
|
1979
|
+
hint: "Use expand_artifact for full page content."
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
var DEFAULT_REDUCER_CONFIG;
|
|
1983
|
+
var init_reducer = __esm({
|
|
1984
|
+
"src/tools/reducer.ts"() {
|
|
1985
|
+
"use strict";
|
|
1986
|
+
DEFAULT_REDUCER_CONFIG = {
|
|
1987
|
+
enabled: true,
|
|
1988
|
+
grep: {
|
|
1989
|
+
maxTotalLines: 50,
|
|
1990
|
+
maxMatchesPerFile: 3,
|
|
1991
|
+
maxLineLength: 200,
|
|
1992
|
+
maxOutputChars: 3e3
|
|
1993
|
+
},
|
|
1994
|
+
read: {
|
|
1995
|
+
maxOutlineLines: 60,
|
|
1996
|
+
maxSliceLines: 200,
|
|
1997
|
+
maxPreviewLines: 30,
|
|
1998
|
+
maxOutputChars: 4e3
|
|
1999
|
+
},
|
|
2000
|
+
bash: {
|
|
2001
|
+
maxTotalLines: 40,
|
|
2002
|
+
maxErrorBlockLines: 20,
|
|
2003
|
+
maxTrailingLines: 20,
|
|
2004
|
+
maxOutputChars: 4e3,
|
|
2005
|
+
dedupeConsecutiveLines: true
|
|
2006
|
+
},
|
|
2007
|
+
webFetch: {
|
|
2008
|
+
maxChars: 2e3,
|
|
2009
|
+
maxHeadingChars: 500
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
// src/tools/expand-artifact.ts
|
|
2016
|
+
function makeExpandArtifactTool(store) {
|
|
2017
|
+
return {
|
|
2018
|
+
name: "expand_artifact",
|
|
2019
|
+
description: "Retrieve the full raw content of a previously reduced tool output by its artifact ID. Use this when the compact summary is insufficient and you need the complete original output.",
|
|
2020
|
+
parameters: {
|
|
2021
|
+
type: "object",
|
|
2022
|
+
properties: {
|
|
2023
|
+
artifact_id: {
|
|
2024
|
+
type: "string",
|
|
2025
|
+
description: "The artifact ID from a reduced tool output footer, e.g. art_42."
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
required: ["artifact_id"],
|
|
2029
|
+
additionalProperties: false
|
|
2030
|
+
},
|
|
2031
|
+
needsPermission: false,
|
|
2032
|
+
render: (args) => ({ title: `expand ${args.artifact_id}` }),
|
|
2033
|
+
run: async (args) => {
|
|
2034
|
+
const raw = store.retrieve(args.artifact_id);
|
|
2035
|
+
if (!raw) {
|
|
2036
|
+
return `Artifact "${args.artifact_id}" not found. It may have been evicted from memory. Re-run the original tool to regenerate the output.`;
|
|
2037
|
+
}
|
|
2038
|
+
return raw;
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
var init_expand_artifact = __esm({
|
|
2043
|
+
"src/tools/expand-artifact.ts"() {
|
|
2044
|
+
"use strict";
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
|
|
1579
2048
|
// src/tools/executor.ts
|
|
1580
2049
|
function normalizeToolOutput(result) {
|
|
1581
2050
|
if (typeof result === "string") {
|
|
@@ -1599,6 +2068,9 @@ var init_executor = __esm({
|
|
|
1599
2068
|
init_grep();
|
|
1600
2069
|
init_web_fetch();
|
|
1601
2070
|
init_tasks();
|
|
2071
|
+
init_artifact_store();
|
|
2072
|
+
init_reducer();
|
|
2073
|
+
init_expand_artifact();
|
|
1602
2074
|
ALL_TOOLS = [
|
|
1603
2075
|
readTool,
|
|
1604
2076
|
writeTool,
|
|
@@ -1612,8 +2084,11 @@ var init_executor = __esm({
|
|
|
1612
2084
|
ToolExecutor = class {
|
|
1613
2085
|
sessionAllowed = /* @__PURE__ */ new Set();
|
|
1614
2086
|
tools;
|
|
2087
|
+
artifactStore;
|
|
1615
2088
|
constructor(tools = ALL_TOOLS) {
|
|
1616
2089
|
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
2090
|
+
this.artifactStore = new ToolArtifactStore();
|
|
2091
|
+
this.tools.set("expand_artifact", makeExpandArtifactTool(this.artifactStore));
|
|
1617
2092
|
}
|
|
1618
2093
|
list() {
|
|
1619
2094
|
return [...this.tools.values()];
|
|
@@ -1627,6 +2102,9 @@ var init_executor = __esm({
|
|
|
1627
2102
|
clearSessionPermissions() {
|
|
1628
2103
|
this.sessionAllowed.clear();
|
|
1629
2104
|
}
|
|
2105
|
+
clearArtifacts() {
|
|
2106
|
+
this.artifactStore.clear();
|
|
2107
|
+
}
|
|
1630
2108
|
async run(call, askPermission, ctx) {
|
|
1631
2109
|
const tool = this.tools.get(call.name);
|
|
1632
2110
|
if (!tool) {
|
|
@@ -1666,13 +2144,21 @@ var init_executor = __esm({
|
|
|
1666
2144
|
try {
|
|
1667
2145
|
const result = await tool.run(args, ctx);
|
|
1668
2146
|
const normalized = normalizeToolOutput(result);
|
|
2147
|
+
const reduced = reduceToolOutput(
|
|
2148
|
+
call.name,
|
|
2149
|
+
normalized.content,
|
|
2150
|
+
args,
|
|
2151
|
+
this.artifactStore,
|
|
2152
|
+
DEFAULT_REDUCER_CONFIG
|
|
2153
|
+
);
|
|
1669
2154
|
return {
|
|
1670
2155
|
tool_call_id: call.id,
|
|
1671
2156
|
name: call.name,
|
|
1672
|
-
content:
|
|
2157
|
+
content: reduced.content,
|
|
1673
2158
|
ok: true,
|
|
1674
|
-
rawBytes:
|
|
1675
|
-
reducedBytes:
|
|
2159
|
+
rawBytes: reduced.rawBytes,
|
|
2160
|
+
reducedBytes: reduced.reducedBytes,
|
|
2161
|
+
artifactId: reduced.artifactId
|
|
1676
2162
|
};
|
|
1677
2163
|
} catch (e) {
|
|
1678
2164
|
const msg = `Error running ${call.name}: ${e.message ?? String(e)}`;
|
|
@@ -1700,16 +2186,16 @@ var init_executor = __esm({
|
|
|
1700
2186
|
// src/util/update-check.ts
|
|
1701
2187
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1702
2188
|
import { homedir as homedir5 } from "os";
|
|
1703
|
-
import { join as
|
|
2189
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
1704
2190
|
import { fileURLToPath } from "url";
|
|
1705
2191
|
function cachePath() {
|
|
1706
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
1707
|
-
return
|
|
2192
|
+
const xdg = process.env.XDG_CONFIG_HOME || join6(homedir5(), ".config");
|
|
2193
|
+
return join6(xdg, "kimiflare", "update-check.json");
|
|
1708
2194
|
}
|
|
1709
2195
|
async function findPackageJson(startDir) {
|
|
1710
2196
|
let dir = startDir;
|
|
1711
2197
|
while (true) {
|
|
1712
|
-
const candidate =
|
|
2198
|
+
const candidate = join6(dir, "package.json");
|
|
1713
2199
|
try {
|
|
1714
2200
|
const raw = await readFile6(candidate, "utf8");
|
|
1715
2201
|
const parsed = JSON.parse(raw);
|
|
@@ -1882,6 +2368,413 @@ Do not include speculation. Do not include chat-style pleasantries. Use short bu
|
|
|
1882
2368
|
}
|
|
1883
2369
|
});
|
|
1884
2370
|
|
|
2371
|
+
// src/agent/session-state.ts
|
|
2372
|
+
function emptySessionState(task = "") {
|
|
2373
|
+
return {
|
|
2374
|
+
task,
|
|
2375
|
+
user_constraints: [],
|
|
2376
|
+
repo_facts: [],
|
|
2377
|
+
files_touched: [],
|
|
2378
|
+
files_modified: [],
|
|
2379
|
+
confirmed_findings: [],
|
|
2380
|
+
open_questions: [],
|
|
2381
|
+
recent_failures: [],
|
|
2382
|
+
decisions: [],
|
|
2383
|
+
next_actions: [],
|
|
2384
|
+
artifact_index: {}
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function formatRecalledArtifacts(recalled) {
|
|
2388
|
+
if (recalled.length === 0) return "";
|
|
2389
|
+
const lines = ["[recalled artifacts]"];
|
|
2390
|
+
for (const { id, artifact } of recalled) {
|
|
2391
|
+
lines.push(`--- artifact:${id} (${artifact.type} from ${artifact.source}) ---`);
|
|
2392
|
+
lines.push(artifact.raw);
|
|
2393
|
+
}
|
|
2394
|
+
return lines.join("\n");
|
|
2395
|
+
}
|
|
2396
|
+
function serializeSessionState(state) {
|
|
2397
|
+
const lines = [];
|
|
2398
|
+
lines.push(`task: ${state.task || "(none)"}`);
|
|
2399
|
+
if (state.user_constraints.length) lines.push(`constraints:
|
|
2400
|
+
${state.user_constraints.map((c) => " - " + c).join("\n")}`);
|
|
2401
|
+
if (state.repo_facts.length) lines.push(`repo_facts:
|
|
2402
|
+
${state.repo_facts.map((f) => " - " + f).join("\n")}`);
|
|
2403
|
+
if (state.files_touched.length) lines.push(`files_touched: ${state.files_touched.join(", ")}`);
|
|
2404
|
+
if (state.files_modified.length) lines.push(`files_modified: ${state.files_modified.join(", ")}`);
|
|
2405
|
+
if (state.confirmed_findings.length) lines.push(`findings:
|
|
2406
|
+
${state.confirmed_findings.map((f) => " - " + f).join("\n")}`);
|
|
2407
|
+
if (state.open_questions.length) lines.push(`open_questions:
|
|
2408
|
+
${state.open_questions.map((q) => " - " + q).join("\n")}`);
|
|
2409
|
+
if (state.recent_failures.length) lines.push(`recent_failures:
|
|
2410
|
+
${state.recent_failures.map((f) => " - " + f).join("\n")}`);
|
|
2411
|
+
if (state.decisions.length) lines.push(`decisions:
|
|
2412
|
+
${state.decisions.map((d) => " - " + d).join("\n")}`);
|
|
2413
|
+
if (state.next_actions.length) lines.push(`next_actions:
|
|
2414
|
+
${state.next_actions.map((a) => " - " + a).join("\n")}`);
|
|
2415
|
+
if (Object.keys(state.artifact_index).length) {
|
|
2416
|
+
lines.push("artifact_index:");
|
|
2417
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2418
|
+
lines.push(` ${id}: [${meta.type}] ${meta.summary}`);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
return lines.join("\n");
|
|
2422
|
+
}
|
|
2423
|
+
function buildSessionStateMessage(state) {
|
|
2424
|
+
return {
|
|
2425
|
+
role: "system",
|
|
2426
|
+
content: `[compiled session state]
|
|
2427
|
+
${serializeSessionState(state)}`
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
var ArtifactStore;
|
|
2431
|
+
var init_session_state = __esm({
|
|
2432
|
+
"src/agent/session-state.ts"() {
|
|
2433
|
+
"use strict";
|
|
2434
|
+
ArtifactStore = class {
|
|
2435
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
2436
|
+
maxArtifacts;
|
|
2437
|
+
maxTotalChars;
|
|
2438
|
+
constructor(opts2) {
|
|
2439
|
+
this.maxArtifacts = opts2?.maxArtifacts ?? 200;
|
|
2440
|
+
this.maxTotalChars = opts2?.maxTotalChars ?? 5e5;
|
|
2441
|
+
}
|
|
2442
|
+
add(a) {
|
|
2443
|
+
while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
|
|
2444
|
+
this.evictOldest();
|
|
2445
|
+
}
|
|
2446
|
+
while (this.artifacts.size >= this.maxArtifacts) {
|
|
2447
|
+
this.evictOldest();
|
|
2448
|
+
}
|
|
2449
|
+
this.artifacts.set(a.id, a);
|
|
2450
|
+
}
|
|
2451
|
+
get(id) {
|
|
2452
|
+
return this.artifacts.get(id);
|
|
2453
|
+
}
|
|
2454
|
+
has(id) {
|
|
2455
|
+
return this.artifacts.has(id);
|
|
2456
|
+
}
|
|
2457
|
+
list() {
|
|
2458
|
+
return [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
|
|
2459
|
+
}
|
|
2460
|
+
recall(ids) {
|
|
2461
|
+
const out = [];
|
|
2462
|
+
for (const id of ids) {
|
|
2463
|
+
const a = this.artifacts.get(id);
|
|
2464
|
+
if (a) out.push({ id, artifact: a });
|
|
2465
|
+
}
|
|
2466
|
+
return out;
|
|
2467
|
+
}
|
|
2468
|
+
size() {
|
|
2469
|
+
return this.artifacts.size;
|
|
2470
|
+
}
|
|
2471
|
+
totalChars() {
|
|
2472
|
+
let sum = 0;
|
|
2473
|
+
for (const a of this.artifacts.values()) {
|
|
2474
|
+
sum += a.raw.length;
|
|
2475
|
+
}
|
|
2476
|
+
return sum;
|
|
2477
|
+
}
|
|
2478
|
+
evictOldest() {
|
|
2479
|
+
let oldest;
|
|
2480
|
+
for (const a of this.artifacts.values()) {
|
|
2481
|
+
if (!oldest || a.ts < oldest.ts) oldest = a;
|
|
2482
|
+
}
|
|
2483
|
+
if (oldest) this.artifacts.delete(oldest.id);
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
|
|
2489
|
+
// src/agent/compaction.ts
|
|
2490
|
+
function approxTokens2(n) {
|
|
2491
|
+
return Math.round(n / 4);
|
|
2492
|
+
}
|
|
2493
|
+
function estimateMessageTokens(m) {
|
|
2494
|
+
let chars = 0;
|
|
2495
|
+
if (typeof m.content === "string") {
|
|
2496
|
+
chars = m.content.length;
|
|
2497
|
+
} else if (Array.isArray(m.content)) {
|
|
2498
|
+
chars = m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
|
|
2499
|
+
}
|
|
2500
|
+
if (m.reasoning_content) chars += m.reasoning_content.length;
|
|
2501
|
+
if (m.tool_calls) {
|
|
2502
|
+
for (const tc of m.tool_calls) {
|
|
2503
|
+
chars += tc.function.name.length + tc.function.arguments.length;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
return approxTokens2(chars);
|
|
2507
|
+
}
|
|
2508
|
+
function estimatePromptTokens(messages) {
|
|
2509
|
+
return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2510
|
+
}
|
|
2511
|
+
function groupIntoTurns(messages) {
|
|
2512
|
+
const prefix = [];
|
|
2513
|
+
let i = 0;
|
|
2514
|
+
while (i < messages.length && messages[i].role === "system") {
|
|
2515
|
+
prefix.push(messages[i]);
|
|
2516
|
+
i++;
|
|
2517
|
+
}
|
|
2518
|
+
const turns = [];
|
|
2519
|
+
while (i < messages.length) {
|
|
2520
|
+
if (messages[i].role !== "user") {
|
|
2521
|
+
i++;
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
const user = messages[i];
|
|
2525
|
+
i++;
|
|
2526
|
+
if (i >= messages.length || messages[i].role !== "assistant") {
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const assistant = messages[i];
|
|
2530
|
+
i++;
|
|
2531
|
+
const tools = [];
|
|
2532
|
+
while (i < messages.length && messages[i].role === "tool") {
|
|
2533
|
+
tools.push(messages[i]);
|
|
2534
|
+
i++;
|
|
2535
|
+
}
|
|
2536
|
+
turns.push({ user, assistant, tools });
|
|
2537
|
+
}
|
|
2538
|
+
return { prefix, turns };
|
|
2539
|
+
}
|
|
2540
|
+
function makeArtifactId(type, index) {
|
|
2541
|
+
return `${type}_${Date.now()}_${index}`;
|
|
2542
|
+
}
|
|
2543
|
+
function extractArtifactsFromTurn(turn, startIndex, store) {
|
|
2544
|
+
const artifacts = [];
|
|
2545
|
+
const stateDelta = {
|
|
2546
|
+
files_touched: [],
|
|
2547
|
+
files_modified: [],
|
|
2548
|
+
confirmed_findings: [],
|
|
2549
|
+
recent_failures: [],
|
|
2550
|
+
decisions: [],
|
|
2551
|
+
next_actions: []
|
|
2552
|
+
};
|
|
2553
|
+
const toolCalls = turn.assistant.tool_calls ?? [];
|
|
2554
|
+
for (let ti = 0; ti < turn.tools.length; ti++) {
|
|
2555
|
+
const tm = turn.tools[ti];
|
|
2556
|
+
const tc = toolCalls[ti];
|
|
2557
|
+
const name = tm.name ?? tc?.function.name ?? "unknown";
|
|
2558
|
+
const content = typeof tm.content === "string" ? tm.content : "";
|
|
2559
|
+
let type = "tool_result";
|
|
2560
|
+
let summary = `${name} result`;
|
|
2561
|
+
let path;
|
|
2562
|
+
if (name === "read") {
|
|
2563
|
+
type = "read_slice";
|
|
2564
|
+
try {
|
|
2565
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2566
|
+
path = args.path;
|
|
2567
|
+
summary = `read ${path ?? "file"}`;
|
|
2568
|
+
if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
|
|
2569
|
+
} catch {
|
|
2570
|
+
summary = "read file";
|
|
2571
|
+
}
|
|
2572
|
+
} else if (name === "bash") {
|
|
2573
|
+
type = "bash_log";
|
|
2574
|
+
try {
|
|
2575
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2576
|
+
const cmd = args.command ?? "";
|
|
2577
|
+
summary = `bash: ${cmd.slice(0, 60)}`;
|
|
2578
|
+
if (content.includes("Error") || content.includes("error") || content.includes("FAIL")) {
|
|
2579
|
+
stateDelta.recent_failures.push(`bash failed: ${cmd.slice(0, 80)}`);
|
|
2580
|
+
}
|
|
2581
|
+
} catch {
|
|
2582
|
+
summary = "bash command";
|
|
2583
|
+
}
|
|
2584
|
+
} else if (name === "grep") {
|
|
2585
|
+
type = "grep_result";
|
|
2586
|
+
summary = `grep results (${content.split("\n").length} lines)`;
|
|
2587
|
+
} else if (name === "web_fetch") {
|
|
2588
|
+
type = "web_fetch";
|
|
2589
|
+
try {
|
|
2590
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2591
|
+
summary = `web_fetch: ${args.url ?? "url"}`;
|
|
2592
|
+
} catch {
|
|
2593
|
+
summary = "web_fetch";
|
|
2594
|
+
}
|
|
2595
|
+
} else if (name === "write" || name === "edit") {
|
|
2596
|
+
try {
|
|
2597
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2598
|
+
path = args.path;
|
|
2599
|
+
if (path && !stateDelta.files_modified.includes(path)) stateDelta.files_modified.push(path);
|
|
2600
|
+
if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
|
|
2601
|
+
} catch {
|
|
2602
|
+
}
|
|
2603
|
+
continue;
|
|
2604
|
+
} else if (name === "glob") {
|
|
2605
|
+
try {
|
|
2606
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2607
|
+
summary = `glob: ${args.pattern ?? ""}`;
|
|
2608
|
+
} catch {
|
|
2609
|
+
summary = "glob";
|
|
2610
|
+
}
|
|
2611
|
+
} else if (name === "tasks_set") {
|
|
2612
|
+
try {
|
|
2613
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2614
|
+
const tasks = args.tasks ?? [];
|
|
2615
|
+
const inProgress = tasks.filter((t) => t.status === "in_progress").map((t) => t.title);
|
|
2616
|
+
const pending = tasks.filter((t) => t.status === "pending").map((t) => t.title);
|
|
2617
|
+
if (inProgress.length) stateDelta.next_actions.push(...inProgress);
|
|
2618
|
+
if (pending.length) stateDelta.next_actions.push(...pending);
|
|
2619
|
+
summary = `tasks_set: ${tasks.length} tasks`;
|
|
2620
|
+
} catch {
|
|
2621
|
+
summary = "tasks_set";
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
const maxRaw = 5e4;
|
|
2625
|
+
const raw = content.length > maxRaw ? content.slice(0, maxRaw) + `
|
|
2626
|
+
...[${content.length - maxRaw} chars truncated]` : content;
|
|
2627
|
+
const artifact = {
|
|
2628
|
+
id: makeArtifactId(type, startIndex + ti),
|
|
2629
|
+
type,
|
|
2630
|
+
summary,
|
|
2631
|
+
raw,
|
|
2632
|
+
source: name,
|
|
2633
|
+
path,
|
|
2634
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2635
|
+
};
|
|
2636
|
+
artifacts.push(artifact);
|
|
2637
|
+
if (!content.includes("Error") && !content.includes("error") && content.length > 0 && content.length < 2e3) {
|
|
2638
|
+
stateDelta.confirmed_findings.push(`${name}: ${content.slice(0, 200)}`);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
const assistantText = typeof turn.assistant.content === "string" ? turn.assistant.content : "";
|
|
2642
|
+
if (assistantText.length > 0) {
|
|
2643
|
+
const decisionPatterns = [
|
|
2644
|
+
/(?:decided?|will|plan to|going to|should|need to)\s+(.{10,200})/gi,
|
|
2645
|
+
/(?:let's|let us)\s+(.{10,200})/gi
|
|
2646
|
+
];
|
|
2647
|
+
for (const pattern of decisionPatterns) {
|
|
2648
|
+
let match;
|
|
2649
|
+
while ((match = pattern.exec(assistantText)) !== null) {
|
|
2650
|
+
const decision = match[1].trim().replace(/\.$/, "");
|
|
2651
|
+
if (decision.length > 10 && !stateDelta.decisions.includes(decision)) {
|
|
2652
|
+
stateDelta.decisions.push(decision);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return { artifacts, stateDelta };
|
|
2658
|
+
}
|
|
2659
|
+
function mergeState(state, delta) {
|
|
2660
|
+
const mergeArr = (a, b) => {
|
|
2661
|
+
if (!b) return a;
|
|
2662
|
+
const set = new Set(a);
|
|
2663
|
+
for (const item of b) set.add(item);
|
|
2664
|
+
return [...set];
|
|
2665
|
+
};
|
|
2666
|
+
return {
|
|
2667
|
+
...state,
|
|
2668
|
+
task: state.task || delta.task || "",
|
|
2669
|
+
user_constraints: mergeArr(state.user_constraints, delta.user_constraints),
|
|
2670
|
+
repo_facts: mergeArr(state.repo_facts, delta.repo_facts),
|
|
2671
|
+
files_touched: mergeArr(state.files_touched, delta.files_touched),
|
|
2672
|
+
files_modified: mergeArr(state.files_modified, delta.files_modified),
|
|
2673
|
+
confirmed_findings: mergeArr(state.confirmed_findings, delta.confirmed_findings),
|
|
2674
|
+
open_questions: mergeArr(state.open_questions, delta.open_questions),
|
|
2675
|
+
recent_failures: mergeArr(state.recent_failures, delta.recent_failures),
|
|
2676
|
+
decisions: mergeArr(state.decisions, delta.decisions),
|
|
2677
|
+
next_actions: mergeArr(state.next_actions, delta.next_actions),
|
|
2678
|
+
artifact_index: { ...state.artifact_index, ...delta.artifact_index }
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
function shouldCompact(opts2) {
|
|
2682
|
+
const tokenThreshold = opts2.tokenThreshold ?? 8e4;
|
|
2683
|
+
const turnThreshold = opts2.turnThreshold ?? 12;
|
|
2684
|
+
const tokens = estimatePromptTokens(opts2.messages);
|
|
2685
|
+
const { turns } = groupIntoTurns(opts2.messages);
|
|
2686
|
+
return tokens > tokenThreshold || turns.length > turnThreshold;
|
|
2687
|
+
}
|
|
2688
|
+
function compactMessages2(opts2) {
|
|
2689
|
+
const keepLastTurns = opts2.keepLastTurns ?? 4;
|
|
2690
|
+
const { prefix, turns } = groupIntoTurns(opts2.messages);
|
|
2691
|
+
const tokensBefore = estimatePromptTokens(opts2.messages);
|
|
2692
|
+
if (turns.length <= keepLastTurns) {
|
|
2693
|
+
return {
|
|
2694
|
+
newMessages: opts2.messages,
|
|
2695
|
+
newState: opts2.state,
|
|
2696
|
+
metrics: {
|
|
2697
|
+
estimatedTokensBefore: tokensBefore,
|
|
2698
|
+
estimatedTokensAfter: tokensBefore,
|
|
2699
|
+
archivedArtifacts: 0,
|
|
2700
|
+
recalledArtifacts: 0,
|
|
2701
|
+
rawTurnsRemoved: 0,
|
|
2702
|
+
rawTurnsKept: turns.length
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
const toCompact = turns.slice(0, turns.length - keepLastTurns);
|
|
2707
|
+
const toKeep = turns.slice(turns.length - keepLastTurns);
|
|
2708
|
+
let newState = { ...opts2.state };
|
|
2709
|
+
let archivedCount = 0;
|
|
2710
|
+
for (let i = 0; i < toCompact.length; i++) {
|
|
2711
|
+
const turn = toCompact[i];
|
|
2712
|
+
const { artifacts, stateDelta } = extractArtifactsFromTurn(turn, i, opts2.store);
|
|
2713
|
+
for (const artifact of artifacts) {
|
|
2714
|
+
opts2.store.add(artifact);
|
|
2715
|
+
archivedCount++;
|
|
2716
|
+
newState.artifact_index[artifact.id] = {
|
|
2717
|
+
type: artifact.type,
|
|
2718
|
+
summary: artifact.summary,
|
|
2719
|
+
source: artifact.source,
|
|
2720
|
+
path: artifact.path
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
newState = mergeState(newState, stateDelta);
|
|
2724
|
+
if (!newState.task && typeof turn.user.content === "string") {
|
|
2725
|
+
newState.task = turn.user.content.slice(0, 200);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
const workingMemory = [];
|
|
2729
|
+
for (const turn of toKeep) {
|
|
2730
|
+
workingMemory.push(turn.user);
|
|
2731
|
+
workingMemory.push(turn.assistant);
|
|
2732
|
+
for (const tm of turn.tools) {
|
|
2733
|
+
workingMemory.push(tm);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
const stateMsg = buildSessionStateMessage(newState);
|
|
2737
|
+
const newMessages = [...prefix, stateMsg, ...workingMemory];
|
|
2738
|
+
const tokensAfter = estimatePromptTokens(newMessages);
|
|
2739
|
+
const metrics = {
|
|
2740
|
+
estimatedTokensBefore: tokensBefore,
|
|
2741
|
+
estimatedTokensAfter: tokensAfter,
|
|
2742
|
+
archivedArtifacts: archivedCount,
|
|
2743
|
+
recalledArtifacts: 0,
|
|
2744
|
+
rawTurnsRemoved: toCompact.length,
|
|
2745
|
+
rawTurnsKept: toKeep.length
|
|
2746
|
+
};
|
|
2747
|
+
return { newMessages, newState, metrics };
|
|
2748
|
+
}
|
|
2749
|
+
function recallArtifacts(messages, store, state) {
|
|
2750
|
+
const text = messages.map((m) => typeof m.content === "string" ? m.content : "").join(" ");
|
|
2751
|
+
const ids = [];
|
|
2752
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2753
|
+
if (meta.path && text.includes(meta.path)) {
|
|
2754
|
+
ids.push(id);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
for (const failure of state.recent_failures) {
|
|
2758
|
+
const keyword = failure.split(":")[0];
|
|
2759
|
+
if (keyword && text.toLowerCase().includes(keyword.toLowerCase())) {
|
|
2760
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2761
|
+
if (meta.source === "bash" && !ids.includes(id)) {
|
|
2762
|
+
ids.push(id);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const uniqueIds = [...new Set(ids)].slice(0, 5);
|
|
2768
|
+
const recalled = store.recall(uniqueIds);
|
|
2769
|
+
return { ids: uniqueIds, recalled };
|
|
2770
|
+
}
|
|
2771
|
+
var init_compaction = __esm({
|
|
2772
|
+
"src/agent/compaction.ts"() {
|
|
2773
|
+
"use strict";
|
|
2774
|
+
init_session_state();
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
|
|
1885
2778
|
// src/mcp/adapter.ts
|
|
1886
2779
|
function mcpToolToSpec(serverName, mcpTool, client) {
|
|
1887
2780
|
const prefix = `mcp_${sanitizeName(serverName)}_`;
|
|
@@ -2416,7 +3309,7 @@ function buildRightParts(usage, contextLimit) {
|
|
|
2416
3309
|
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
2417
3310
|
`out ${usage.completion_tokens}`,
|
|
2418
3311
|
`ctx ${pct}%`,
|
|
2419
|
-
|
|
3312
|
+
`$${cost.total.toFixed(5)}`
|
|
2420
3313
|
];
|
|
2421
3314
|
}
|
|
2422
3315
|
function shortModel(m) {
|
|
@@ -3834,12 +4727,20 @@ var init_theme = __esm({
|
|
|
3834
4727
|
});
|
|
3835
4728
|
|
|
3836
4729
|
// src/sessions.ts
|
|
3837
|
-
|
|
4730
|
+
var sessions_exports = {};
|
|
4731
|
+
__export(sessions_exports, {
|
|
4732
|
+
listSessions: () => listSessions,
|
|
4733
|
+
loadSession: () => loadSession,
|
|
4734
|
+
makeSessionId: () => makeSessionId,
|
|
4735
|
+
pruneSessions: () => pruneSessions,
|
|
4736
|
+
saveSession: () => saveSession
|
|
4737
|
+
});
|
|
4738
|
+
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
3838
4739
|
import { homedir as homedir6 } from "os";
|
|
3839
|
-
import { join as
|
|
4740
|
+
import { join as join7 } from "path";
|
|
3840
4741
|
function sessionsDir() {
|
|
3841
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3842
|
-
return
|
|
4742
|
+
const xdg = process.env.XDG_DATA_HOME || join7(homedir6(), ".local", "share");
|
|
4743
|
+
return join7(xdg, "kimiflare", "sessions");
|
|
3843
4744
|
}
|
|
3844
4745
|
function sanitize(text) {
|
|
3845
4746
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -3852,24 +4753,29 @@ function makeSessionId(firstPrompt) {
|
|
|
3852
4753
|
async function saveSession(file) {
|
|
3853
4754
|
const dir = sessionsDir();
|
|
3854
4755
|
await mkdir5(dir, { recursive: true });
|
|
3855
|
-
const path =
|
|
4756
|
+
const path = join7(dir, `${file.id}.json`);
|
|
3856
4757
|
await writeFile5(path, JSON.stringify(file, null, 2), "utf8");
|
|
3857
4758
|
return path;
|
|
3858
4759
|
}
|
|
4760
|
+
async function pruneSessions() {
|
|
4761
|
+
const dir = sessionsDir();
|
|
4762
|
+
const files = await listFilesByMtime(dir, /\.json$/);
|
|
4763
|
+
return pruneFiles(files, RETENTION.sessionMaxAgeDays, RETENTION.sessionMaxCount);
|
|
4764
|
+
}
|
|
3859
4765
|
async function listSessions(limit = 30) {
|
|
3860
4766
|
const dir = sessionsDir();
|
|
3861
4767
|
let entries;
|
|
3862
4768
|
try {
|
|
3863
|
-
entries = await
|
|
4769
|
+
entries = await readdir2(dir);
|
|
3864
4770
|
} catch {
|
|
3865
4771
|
return [];
|
|
3866
4772
|
}
|
|
3867
4773
|
const summaries = [];
|
|
3868
4774
|
for (const name of entries) {
|
|
3869
4775
|
if (!name.endsWith(".json")) continue;
|
|
3870
|
-
const path =
|
|
4776
|
+
const path = join7(dir, name);
|
|
3871
4777
|
try {
|
|
3872
|
-
const [s, raw] = await Promise.all([
|
|
4778
|
+
const [s, raw] = await Promise.all([stat3(path), readFile7(path, "utf8")]);
|
|
3873
4779
|
const parsed = JSON.parse(raw);
|
|
3874
4780
|
const firstUser = parsed.messages.find((m) => m.role === "user");
|
|
3875
4781
|
const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : firstUser?.content ? firstUser.content.find((p) => p.type === "text")?.text ?? "(no prompt)" : "(no prompt)";
|
|
@@ -3894,6 +4800,7 @@ async function loadSession(filePath) {
|
|
|
3894
4800
|
var init_sessions = __esm({
|
|
3895
4801
|
"src/sessions.ts"() {
|
|
3896
4802
|
"use strict";
|
|
4803
|
+
init_storage_limits();
|
|
3897
4804
|
}
|
|
3898
4805
|
});
|
|
3899
4806
|
|
|
@@ -3939,17 +4846,21 @@ var init_image = __esm({
|
|
|
3939
4846
|
// src/usage-tracker.ts
|
|
3940
4847
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
3941
4848
|
import { homedir as homedir7 } from "os";
|
|
3942
|
-
import { join as
|
|
4849
|
+
import { join as join8 } from "path";
|
|
3943
4850
|
function usageDir() {
|
|
3944
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3945
|
-
return
|
|
4851
|
+
const xdg = process.env.XDG_DATA_HOME || join8(homedir7(), ".local", "share");
|
|
4852
|
+
return join8(xdg, "kimiflare");
|
|
3946
4853
|
}
|
|
3947
4854
|
function usagePath() {
|
|
3948
|
-
return
|
|
4855
|
+
return join8(usageDir(), "usage.json");
|
|
3949
4856
|
}
|
|
3950
4857
|
function today() {
|
|
3951
4858
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3952
4859
|
}
|
|
4860
|
+
function cutoffDate(daysBack) {
|
|
4861
|
+
const d = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1e3);
|
|
4862
|
+
return d.toISOString().slice(0, 10);
|
|
4863
|
+
}
|
|
3953
4864
|
async function loadLog() {
|
|
3954
4865
|
try {
|
|
3955
4866
|
const raw = await readFile9(usagePath(), "utf8");
|
|
@@ -3979,8 +4890,18 @@ function getOrCreateSession(log, sessionId, date) {
|
|
|
3979
4890
|
}
|
|
3980
4891
|
return session;
|
|
3981
4892
|
}
|
|
4893
|
+
function pruneUsageLog(log) {
|
|
4894
|
+
const dayCutoff = cutoffDate(RETENTION.usageDayMaxAgeDays);
|
|
4895
|
+
const sessionCutoff = cutoffDate(RETENTION.usageSessionMaxAgeDays);
|
|
4896
|
+
const days = log.days.filter((d) => d.date >= dayCutoff);
|
|
4897
|
+
let sessions = log.sessions.filter((s) => s.date >= sessionCutoff);
|
|
4898
|
+
if (sessions.length > RETENTION.usageSessionMaxCount) {
|
|
4899
|
+
sessions = sessions.sort((a, b) => b.date < a.date ? -1 : b.date > a.date ? 1 : 0).slice(0, RETENTION.usageSessionMaxCount);
|
|
4900
|
+
}
|
|
4901
|
+
return { ...log, days, sessions };
|
|
4902
|
+
}
|
|
3982
4903
|
async function recordUsage(sessionId, usage) {
|
|
3983
|
-
const log = await loadLog();
|
|
4904
|
+
const log = pruneUsageLog(await loadLog());
|
|
3984
4905
|
const date = today();
|
|
3985
4906
|
const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, usage.prompt_tokens_details?.cached_tokens ?? 0);
|
|
3986
4907
|
const day = getOrCreateDay(log, date);
|
|
@@ -3996,7 +4917,7 @@ async function recordUsage(sessionId, usage) {
|
|
|
3996
4917
|
await saveLog(log);
|
|
3997
4918
|
}
|
|
3998
4919
|
async function getCostReport(sessionId) {
|
|
3999
|
-
const log = await loadLog();
|
|
4920
|
+
const log = pruneUsageLog(await loadLog());
|
|
4000
4921
|
const date = today();
|
|
4001
4922
|
const currentMonth = date.slice(0, 7);
|
|
4002
4923
|
const session = log.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
|
|
@@ -4055,6 +4976,7 @@ var init_usage_tracker = __esm({
|
|
|
4055
4976
|
"src/usage-tracker.ts"() {
|
|
4056
4977
|
"use strict";
|
|
4057
4978
|
init_pricing();
|
|
4979
|
+
init_storage_limits();
|
|
4058
4980
|
LOG_VERSION2 = 1;
|
|
4059
4981
|
}
|
|
4060
4982
|
});
|
|
@@ -4067,8 +4989,8 @@ __export(app_exports, {
|
|
|
4067
4989
|
import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
|
|
4068
4990
|
import { Box as Box12, Text as Text13, useApp, useInput as useInput2, render } from "ink";
|
|
4069
4991
|
import { existsSync } from "fs";
|
|
4070
|
-
import { join as
|
|
4071
|
-
import { unlink } from "fs/promises";
|
|
4992
|
+
import { join as join9 } from "path";
|
|
4993
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
4072
4994
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4073
4995
|
function capEvents(prev) {
|
|
4074
4996
|
if (prev.length <= MAX_EVENTS) return prev;
|
|
@@ -4145,11 +5067,27 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4145
5067
|
const tasksRef = useRef3([]);
|
|
4146
5068
|
const usageRef = useRef3(null);
|
|
4147
5069
|
const updateCheckedRef = useRef3(false);
|
|
5070
|
+
const sessionStateRef = useRef3(emptySessionState());
|
|
5071
|
+
const artifactStoreRef = useRef3(new ArtifactStore());
|
|
5072
|
+
const compiledContextRef = useRef3(initialCfg?.compiledContext === true);
|
|
4148
5073
|
const updateNudgedRef = useRef3(false);
|
|
4149
5074
|
const compactSuggestedRef = useRef3(false);
|
|
4150
5075
|
const mcpManagerRef = useRef3(new McpManager());
|
|
4151
5076
|
const mcpToolsRef = useRef3([]);
|
|
4152
5077
|
const mcpInitRef = useRef3(false);
|
|
5078
|
+
useEffect4(() => {
|
|
5079
|
+
if (!cfg) return;
|
|
5080
|
+
void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
|
|
5081
|
+
({ pruneSessions: pruneSessions2 }) => pruneSessions2().then((removed) => {
|
|
5082
|
+
if (removed > 0) {
|
|
5083
|
+
setEvents((e) => [
|
|
5084
|
+
...e,
|
|
5085
|
+
{ kind: "info", key: mkKey(), text: `pruned ${removed} old session files` }
|
|
5086
|
+
]);
|
|
5087
|
+
}
|
|
5088
|
+
})
|
|
5089
|
+
);
|
|
5090
|
+
}, [cfg]);
|
|
4153
5091
|
useEffect4(() => {
|
|
4154
5092
|
if (!cfg || updateCheckedRef.current) return;
|
|
4155
5093
|
updateCheckedRef.current = true;
|
|
@@ -4351,7 +5289,8 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4351
5289
|
model: cfg.model,
|
|
4352
5290
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4353
5291
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4354
|
-
messages: messagesRef.current
|
|
5292
|
+
messages: messagesRef.current,
|
|
5293
|
+
sessionState: compiledContextRef.current ? sessionStateRef.current : void 0
|
|
4355
5294
|
});
|
|
4356
5295
|
} catch {
|
|
4357
5296
|
}
|
|
@@ -4416,29 +5355,55 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4416
5355
|
const controller = new AbortController();
|
|
4417
5356
|
activeControllerRef.current = controller;
|
|
4418
5357
|
try {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
5358
|
+
if (compiledContextRef.current) {
|
|
5359
|
+
const result = compactMessages2({
|
|
5360
|
+
messages: messagesRef.current,
|
|
5361
|
+
state: sessionStateRef.current,
|
|
5362
|
+
store: artifactStoreRef.current
|
|
5363
|
+
});
|
|
5364
|
+
if (result.metrics.rawTurnsRemoved === 0) {
|
|
5365
|
+
setEvents((e) => [
|
|
5366
|
+
...e,
|
|
5367
|
+
{ kind: "info", key: mkKey(), text: "nothing to compact yet" }
|
|
5368
|
+
]);
|
|
5369
|
+
} else {
|
|
5370
|
+
messagesRef.current = result.newMessages;
|
|
5371
|
+
sessionStateRef.current = result.newState;
|
|
5372
|
+
setEvents((e) => [
|
|
5373
|
+
...e,
|
|
5374
|
+
{
|
|
5375
|
+
kind: "info",
|
|
5376
|
+
key: mkKey(),
|
|
5377
|
+
text: `compacted ${result.metrics.rawTurnsRemoved} turns \u2192 ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens, ${result.metrics.archivedArtifacts} artifacts`
|
|
5378
|
+
}
|
|
5379
|
+
]);
|
|
5380
|
+
await saveSessionSafe();
|
|
5381
|
+
}
|
|
4431
5382
|
} else {
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
5383
|
+
const result = await compactMessages({
|
|
5384
|
+
accountId: cfg.accountId,
|
|
5385
|
+
apiToken: cfg.apiToken,
|
|
5386
|
+
model: cfg.model,
|
|
5387
|
+
messages: messagesRef.current,
|
|
5388
|
+
signal: controller.signal
|
|
5389
|
+
});
|
|
5390
|
+
if (result.replacedCount === 0) {
|
|
5391
|
+
setEvents((e) => [
|
|
5392
|
+
...e,
|
|
5393
|
+
{ kind: "info", key: mkKey(), text: "nothing to compact yet" }
|
|
5394
|
+
]);
|
|
5395
|
+
} else {
|
|
5396
|
+
messagesRef.current = result.newMessages;
|
|
5397
|
+
setEvents((e) => [
|
|
5398
|
+
...e,
|
|
5399
|
+
{
|
|
5400
|
+
kind: "info",
|
|
5401
|
+
key: mkKey(),
|
|
5402
|
+
text: `compacted ${result.replacedCount} messages into a summary`
|
|
5403
|
+
}
|
|
5404
|
+
]);
|
|
5405
|
+
await saveSessionSafe();
|
|
5406
|
+
}
|
|
4442
5407
|
}
|
|
4443
5408
|
} catch (e) {
|
|
4444
5409
|
if (e.name !== "AbortError") {
|
|
@@ -4465,13 +5430,13 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4465
5430
|
}
|
|
4466
5431
|
const cwd = process.cwd();
|
|
4467
5432
|
for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
|
|
4468
|
-
if (existsSync(
|
|
5433
|
+
if (existsSync(join9(cwd, name))) {
|
|
4469
5434
|
setEvents((e) => [
|
|
4470
5435
|
...e,
|
|
4471
5436
|
{
|
|
4472
5437
|
kind: "info",
|
|
4473
5438
|
key: mkKey(),
|
|
4474
|
-
text: `${name} already exists at ${
|
|
5439
|
+
text: `${name} already exists at ${join9(cwd, name)} \u2014 delete it first if you want to regenerate`
|
|
4475
5440
|
}
|
|
4476
5441
|
]);
|
|
4477
5442
|
return;
|
|
@@ -4588,7 +5553,7 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4588
5553
|
})
|
|
4589
5554
|
}
|
|
4590
5555
|
});
|
|
4591
|
-
if (existsSync(
|
|
5556
|
+
if (existsSync(join9(cwd, "KIMI.md"))) {
|
|
4592
5557
|
if (cacheStableRef.current) {
|
|
4593
5558
|
messagesRef.current[1] = {
|
|
4594
5559
|
role: "system",
|
|
@@ -4637,6 +5602,10 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4637
5602
|
const file = await loadSession(picked.filePath);
|
|
4638
5603
|
messagesRef.current = file.messages;
|
|
4639
5604
|
sessionIdRef.current = file.id;
|
|
5605
|
+
if (file.sessionState && compiledContextRef.current) {
|
|
5606
|
+
sessionStateRef.current = file.sessionState;
|
|
5607
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
5608
|
+
}
|
|
4640
5609
|
setEvents([
|
|
4641
5610
|
{
|
|
4642
5611
|
kind: "info",
|
|
@@ -4699,6 +5668,9 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4699
5668
|
messagesRef.current = [messagesRef.current[0]];
|
|
4700
5669
|
}
|
|
4701
5670
|
sessionIdRef.current = null;
|
|
5671
|
+
sessionStateRef.current = emptySessionState();
|
|
5672
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
5673
|
+
executorRef.current.clearArtifacts();
|
|
4702
5674
|
setEvents([]);
|
|
4703
5675
|
setUsage(null);
|
|
4704
5676
|
setTasks([]);
|
|
@@ -4926,7 +5898,7 @@ use: /thinking low | medium | high`
|
|
|
4926
5898
|
return true;
|
|
4927
5899
|
}
|
|
4928
5900
|
if (c === "/logout") {
|
|
4929
|
-
|
|
5901
|
+
unlink2(configPath()).catch(() => {
|
|
4930
5902
|
});
|
|
4931
5903
|
setEvents((e) => [
|
|
4932
5904
|
...e,
|
|
@@ -4987,6 +5959,17 @@ use: /thinking low | medium | high`
|
|
|
4987
5959
|
}
|
|
4988
5960
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
|
|
4989
5961
|
messagesRef.current.push({ role: "user", content });
|
|
5962
|
+
if (compiledContextRef.current) {
|
|
5963
|
+
const { ids, recalled } = recallArtifacts(messagesRef.current, artifactStoreRef.current, sessionStateRef.current);
|
|
5964
|
+
if (recalled.length > 0) {
|
|
5965
|
+
const recalledText = formatRecalledArtifacts(recalled);
|
|
5966
|
+
messagesRef.current.push({ role: "system", content: recalledText });
|
|
5967
|
+
sessionStateRef.current = {
|
|
5968
|
+
...sessionStateRef.current,
|
|
5969
|
+
artifact_index: { ...sessionStateRef.current.artifact_index }
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
4990
5973
|
setBusy(true);
|
|
4991
5974
|
setTurnStartedAt(Date.now());
|
|
4992
5975
|
const controller = new AbortController();
|
|
@@ -5096,6 +6079,26 @@ use: /thinking low | medium | high`
|
|
|
5096
6079
|
}
|
|
5097
6080
|
});
|
|
5098
6081
|
await saveSessionSafe();
|
|
6082
|
+
if (compiledContextRef.current && shouldCompact({ messages: messagesRef.current })) {
|
|
6083
|
+
const result = compactMessages2({
|
|
6084
|
+
messages: messagesRef.current,
|
|
6085
|
+
state: sessionStateRef.current,
|
|
6086
|
+
store: artifactStoreRef.current
|
|
6087
|
+
});
|
|
6088
|
+
if (result.metrics.rawTurnsRemoved > 0) {
|
|
6089
|
+
messagesRef.current = result.newMessages;
|
|
6090
|
+
sessionStateRef.current = result.newState;
|
|
6091
|
+
setEvents((e) => [
|
|
6092
|
+
...e,
|
|
6093
|
+
{
|
|
6094
|
+
kind: "info",
|
|
6095
|
+
key: mkKey(),
|
|
6096
|
+
text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
|
|
6097
|
+
}
|
|
6098
|
+
]);
|
|
6099
|
+
await saveSessionSafe();
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
5099
6102
|
} catch (e) {
|
|
5100
6103
|
if (e.name === "AbortError") {
|
|
5101
6104
|
setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
|
|
@@ -5292,6 +6295,8 @@ var init_app = __esm({
|
|
|
5292
6295
|
init_loop();
|
|
5293
6296
|
init_system_prompt();
|
|
5294
6297
|
init_compact();
|
|
6298
|
+
init_compaction();
|
|
6299
|
+
init_session_state();
|
|
5295
6300
|
init_executor();
|
|
5296
6301
|
init_manager();
|
|
5297
6302
|
init_messages();
|
|
@@ -5336,11 +6341,11 @@ init_update_check();
|
|
|
5336
6341
|
import { Command } from "commander";
|
|
5337
6342
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5338
6343
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5339
|
-
import { dirname as dirname3, join as
|
|
6344
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
5340
6345
|
function readPackageVersion() {
|
|
5341
6346
|
try {
|
|
5342
6347
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
5343
|
-
const pkg = JSON.parse(readFileSync2(
|
|
6348
|
+
const pkg = JSON.parse(readFileSync2(join10(here, "..", "package.json"), "utf8"));
|
|
5344
6349
|
return pkg.version ?? "0.0.0";
|
|
5345
6350
|
} catch {
|
|
5346
6351
|
return "0.0.0";
|