kimiflare 0.15.0 → 0.16.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 +678 -62
- 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
|
});
|
|
@@ -923,11 +1021,11 @@ var init_mode = __esm({
|
|
|
923
1021
|
|
|
924
1022
|
// src/agent/system-prompt.ts
|
|
925
1023
|
import { platform, release, homedir as homedir3 } from "os";
|
|
926
|
-
import { basename, join as
|
|
1024
|
+
import { basename, join as join4 } from "path";
|
|
927
1025
|
import { readFileSync, statSync } from "fs";
|
|
928
1026
|
function loadContextFile(cwd) {
|
|
929
1027
|
for (const name of CONTEXT_FILENAMES) {
|
|
930
|
-
const path =
|
|
1028
|
+
const path = join4(cwd, name);
|
|
931
1029
|
try {
|
|
932
1030
|
const s = statSync(path);
|
|
933
1031
|
if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
|
|
@@ -1037,7 +1135,7 @@ var init_paths = __esm({
|
|
|
1037
1135
|
});
|
|
1038
1136
|
|
|
1039
1137
|
// src/tools/read.ts
|
|
1040
|
-
import { readFile as readFile2, stat } from "fs/promises";
|
|
1138
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1041
1139
|
var MAX_BYTES, readTool;
|
|
1042
1140
|
var init_read = __esm({
|
|
1043
1141
|
"src/tools/read.ts"() {
|
|
@@ -1061,7 +1159,7 @@ var init_read = __esm({
|
|
|
1061
1159
|
render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
|
|
1062
1160
|
async run(args, ctx) {
|
|
1063
1161
|
const abs = resolvePath(ctx.cwd, args.path);
|
|
1064
|
-
const st = await
|
|
1162
|
+
const st = await stat2(abs);
|
|
1065
1163
|
if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
|
|
1066
1164
|
const text = await readFile2(abs, "utf8");
|
|
1067
1165
|
const lines = text.split("\n");
|
|
@@ -1173,7 +1271,7 @@ var init_edit = __esm({
|
|
|
1173
1271
|
// src/tools/bash.ts
|
|
1174
1272
|
import { spawn } from "child_process";
|
|
1175
1273
|
import { tmpdir } from "os";
|
|
1176
|
-
import { join as
|
|
1274
|
+
import { join as join5 } from "path";
|
|
1177
1275
|
function formatBashTitle(raw) {
|
|
1178
1276
|
let cmd = (raw ?? "").trim();
|
|
1179
1277
|
const m = cmd.match(/^cd\s+([^\s&;]+)\s*(?:&&|;)\s*(.*)$/);
|
|
@@ -1189,7 +1287,7 @@ function injectCoauthor(command, coauthor) {
|
|
|
1189
1287
|
const isRebaseContinue = /\bgit\s+rebase\b/.test(trimmed) && !/\b--abort\b|\b--skip\b/.test(trimmed);
|
|
1190
1288
|
const mentionsGit = /\bgit\b/.test(trimmed);
|
|
1191
1289
|
if (!createsCommit && !isRebaseContinue && !mentionsGit) return command;
|
|
1192
|
-
const tmpFile =
|
|
1290
|
+
const tmpFile = join5(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1193
1291
|
const amendBlock = `
|
|
1194
1292
|
if ! git log -1 --pretty=%B 2>/dev/null | grep -qF "${trailer}"; then
|
|
1195
1293
|
git log -1 --pretty=%B | git interpret-trailers --trailer "${trailer}" > "${tmpFile}" && git commit --amend -F "${tmpFile}" --no-edit && rm -f "${tmpFile}"
|
|
@@ -1700,16 +1798,16 @@ var init_executor = __esm({
|
|
|
1700
1798
|
// src/util/update-check.ts
|
|
1701
1799
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1702
1800
|
import { homedir as homedir5 } from "os";
|
|
1703
|
-
import { join as
|
|
1801
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
1704
1802
|
import { fileURLToPath } from "url";
|
|
1705
1803
|
function cachePath() {
|
|
1706
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
1707
|
-
return
|
|
1804
|
+
const xdg = process.env.XDG_CONFIG_HOME || join6(homedir5(), ".config");
|
|
1805
|
+
return join6(xdg, "kimiflare", "update-check.json");
|
|
1708
1806
|
}
|
|
1709
1807
|
async function findPackageJson(startDir) {
|
|
1710
1808
|
let dir = startDir;
|
|
1711
1809
|
while (true) {
|
|
1712
|
-
const candidate =
|
|
1810
|
+
const candidate = join6(dir, "package.json");
|
|
1713
1811
|
try {
|
|
1714
1812
|
const raw = await readFile6(candidate, "utf8");
|
|
1715
1813
|
const parsed = JSON.parse(raw);
|
|
@@ -1882,6 +1980,413 @@ Do not include speculation. Do not include chat-style pleasantries. Use short bu
|
|
|
1882
1980
|
}
|
|
1883
1981
|
});
|
|
1884
1982
|
|
|
1983
|
+
// src/agent/session-state.ts
|
|
1984
|
+
function emptySessionState(task = "") {
|
|
1985
|
+
return {
|
|
1986
|
+
task,
|
|
1987
|
+
user_constraints: [],
|
|
1988
|
+
repo_facts: [],
|
|
1989
|
+
files_touched: [],
|
|
1990
|
+
files_modified: [],
|
|
1991
|
+
confirmed_findings: [],
|
|
1992
|
+
open_questions: [],
|
|
1993
|
+
recent_failures: [],
|
|
1994
|
+
decisions: [],
|
|
1995
|
+
next_actions: [],
|
|
1996
|
+
artifact_index: {}
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function formatRecalledArtifacts(recalled) {
|
|
2000
|
+
if (recalled.length === 0) return "";
|
|
2001
|
+
const lines = ["[recalled artifacts]"];
|
|
2002
|
+
for (const { id, artifact } of recalled) {
|
|
2003
|
+
lines.push(`--- artifact:${id} (${artifact.type} from ${artifact.source}) ---`);
|
|
2004
|
+
lines.push(artifact.raw);
|
|
2005
|
+
}
|
|
2006
|
+
return lines.join("\n");
|
|
2007
|
+
}
|
|
2008
|
+
function serializeSessionState(state) {
|
|
2009
|
+
const lines = [];
|
|
2010
|
+
lines.push(`task: ${state.task || "(none)"}`);
|
|
2011
|
+
if (state.user_constraints.length) lines.push(`constraints:
|
|
2012
|
+
${state.user_constraints.map((c) => " - " + c).join("\n")}`);
|
|
2013
|
+
if (state.repo_facts.length) lines.push(`repo_facts:
|
|
2014
|
+
${state.repo_facts.map((f) => " - " + f).join("\n")}`);
|
|
2015
|
+
if (state.files_touched.length) lines.push(`files_touched: ${state.files_touched.join(", ")}`);
|
|
2016
|
+
if (state.files_modified.length) lines.push(`files_modified: ${state.files_modified.join(", ")}`);
|
|
2017
|
+
if (state.confirmed_findings.length) lines.push(`findings:
|
|
2018
|
+
${state.confirmed_findings.map((f) => " - " + f).join("\n")}`);
|
|
2019
|
+
if (state.open_questions.length) lines.push(`open_questions:
|
|
2020
|
+
${state.open_questions.map((q) => " - " + q).join("\n")}`);
|
|
2021
|
+
if (state.recent_failures.length) lines.push(`recent_failures:
|
|
2022
|
+
${state.recent_failures.map((f) => " - " + f).join("\n")}`);
|
|
2023
|
+
if (state.decisions.length) lines.push(`decisions:
|
|
2024
|
+
${state.decisions.map((d) => " - " + d).join("\n")}`);
|
|
2025
|
+
if (state.next_actions.length) lines.push(`next_actions:
|
|
2026
|
+
${state.next_actions.map((a) => " - " + a).join("\n")}`);
|
|
2027
|
+
if (Object.keys(state.artifact_index).length) {
|
|
2028
|
+
lines.push("artifact_index:");
|
|
2029
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2030
|
+
lines.push(` ${id}: [${meta.type}] ${meta.summary}`);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return lines.join("\n");
|
|
2034
|
+
}
|
|
2035
|
+
function buildSessionStateMessage(state) {
|
|
2036
|
+
return {
|
|
2037
|
+
role: "system",
|
|
2038
|
+
content: `[compiled session state]
|
|
2039
|
+
${serializeSessionState(state)}`
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
var ArtifactStore;
|
|
2043
|
+
var init_session_state = __esm({
|
|
2044
|
+
"src/agent/session-state.ts"() {
|
|
2045
|
+
"use strict";
|
|
2046
|
+
ArtifactStore = class {
|
|
2047
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
2048
|
+
maxArtifacts;
|
|
2049
|
+
maxTotalChars;
|
|
2050
|
+
constructor(opts2) {
|
|
2051
|
+
this.maxArtifacts = opts2?.maxArtifacts ?? 200;
|
|
2052
|
+
this.maxTotalChars = opts2?.maxTotalChars ?? 5e5;
|
|
2053
|
+
}
|
|
2054
|
+
add(a) {
|
|
2055
|
+
while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
|
|
2056
|
+
this.evictOldest();
|
|
2057
|
+
}
|
|
2058
|
+
while (this.artifacts.size >= this.maxArtifacts) {
|
|
2059
|
+
this.evictOldest();
|
|
2060
|
+
}
|
|
2061
|
+
this.artifacts.set(a.id, a);
|
|
2062
|
+
}
|
|
2063
|
+
get(id) {
|
|
2064
|
+
return this.artifacts.get(id);
|
|
2065
|
+
}
|
|
2066
|
+
has(id) {
|
|
2067
|
+
return this.artifacts.has(id);
|
|
2068
|
+
}
|
|
2069
|
+
list() {
|
|
2070
|
+
return [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
|
|
2071
|
+
}
|
|
2072
|
+
recall(ids) {
|
|
2073
|
+
const out = [];
|
|
2074
|
+
for (const id of ids) {
|
|
2075
|
+
const a = this.artifacts.get(id);
|
|
2076
|
+
if (a) out.push({ id, artifact: a });
|
|
2077
|
+
}
|
|
2078
|
+
return out;
|
|
2079
|
+
}
|
|
2080
|
+
size() {
|
|
2081
|
+
return this.artifacts.size;
|
|
2082
|
+
}
|
|
2083
|
+
totalChars() {
|
|
2084
|
+
let sum = 0;
|
|
2085
|
+
for (const a of this.artifacts.values()) {
|
|
2086
|
+
sum += a.raw.length;
|
|
2087
|
+
}
|
|
2088
|
+
return sum;
|
|
2089
|
+
}
|
|
2090
|
+
evictOldest() {
|
|
2091
|
+
let oldest;
|
|
2092
|
+
for (const a of this.artifacts.values()) {
|
|
2093
|
+
if (!oldest || a.ts < oldest.ts) oldest = a;
|
|
2094
|
+
}
|
|
2095
|
+
if (oldest) this.artifacts.delete(oldest.id);
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
|
|
2101
|
+
// src/agent/compaction.ts
|
|
2102
|
+
function approxTokens2(n) {
|
|
2103
|
+
return Math.round(n / 4);
|
|
2104
|
+
}
|
|
2105
|
+
function estimateMessageTokens(m) {
|
|
2106
|
+
let chars = 0;
|
|
2107
|
+
if (typeof m.content === "string") {
|
|
2108
|
+
chars = m.content.length;
|
|
2109
|
+
} else if (Array.isArray(m.content)) {
|
|
2110
|
+
chars = m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
|
|
2111
|
+
}
|
|
2112
|
+
if (m.reasoning_content) chars += m.reasoning_content.length;
|
|
2113
|
+
if (m.tool_calls) {
|
|
2114
|
+
for (const tc of m.tool_calls) {
|
|
2115
|
+
chars += tc.function.name.length + tc.function.arguments.length;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return approxTokens2(chars);
|
|
2119
|
+
}
|
|
2120
|
+
function estimatePromptTokens(messages) {
|
|
2121
|
+
return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2122
|
+
}
|
|
2123
|
+
function groupIntoTurns(messages) {
|
|
2124
|
+
const prefix = [];
|
|
2125
|
+
let i = 0;
|
|
2126
|
+
while (i < messages.length && messages[i].role === "system") {
|
|
2127
|
+
prefix.push(messages[i]);
|
|
2128
|
+
i++;
|
|
2129
|
+
}
|
|
2130
|
+
const turns = [];
|
|
2131
|
+
while (i < messages.length) {
|
|
2132
|
+
if (messages[i].role !== "user") {
|
|
2133
|
+
i++;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
const user = messages[i];
|
|
2137
|
+
i++;
|
|
2138
|
+
if (i >= messages.length || messages[i].role !== "assistant") {
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
const assistant = messages[i];
|
|
2142
|
+
i++;
|
|
2143
|
+
const tools = [];
|
|
2144
|
+
while (i < messages.length && messages[i].role === "tool") {
|
|
2145
|
+
tools.push(messages[i]);
|
|
2146
|
+
i++;
|
|
2147
|
+
}
|
|
2148
|
+
turns.push({ user, assistant, tools });
|
|
2149
|
+
}
|
|
2150
|
+
return { prefix, turns };
|
|
2151
|
+
}
|
|
2152
|
+
function makeArtifactId(type, index) {
|
|
2153
|
+
return `${type}_${Date.now()}_${index}`;
|
|
2154
|
+
}
|
|
2155
|
+
function extractArtifactsFromTurn(turn, startIndex, store) {
|
|
2156
|
+
const artifacts = [];
|
|
2157
|
+
const stateDelta = {
|
|
2158
|
+
files_touched: [],
|
|
2159
|
+
files_modified: [],
|
|
2160
|
+
confirmed_findings: [],
|
|
2161
|
+
recent_failures: [],
|
|
2162
|
+
decisions: [],
|
|
2163
|
+
next_actions: []
|
|
2164
|
+
};
|
|
2165
|
+
const toolCalls = turn.assistant.tool_calls ?? [];
|
|
2166
|
+
for (let ti = 0; ti < turn.tools.length; ti++) {
|
|
2167
|
+
const tm = turn.tools[ti];
|
|
2168
|
+
const tc = toolCalls[ti];
|
|
2169
|
+
const name = tm.name ?? tc?.function.name ?? "unknown";
|
|
2170
|
+
const content = typeof tm.content === "string" ? tm.content : "";
|
|
2171
|
+
let type = "tool_result";
|
|
2172
|
+
let summary = `${name} result`;
|
|
2173
|
+
let path;
|
|
2174
|
+
if (name === "read") {
|
|
2175
|
+
type = "read_slice";
|
|
2176
|
+
try {
|
|
2177
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2178
|
+
path = args.path;
|
|
2179
|
+
summary = `read ${path ?? "file"}`;
|
|
2180
|
+
if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
|
|
2181
|
+
} catch {
|
|
2182
|
+
summary = "read file";
|
|
2183
|
+
}
|
|
2184
|
+
} else if (name === "bash") {
|
|
2185
|
+
type = "bash_log";
|
|
2186
|
+
try {
|
|
2187
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2188
|
+
const cmd = args.command ?? "";
|
|
2189
|
+
summary = `bash: ${cmd.slice(0, 60)}`;
|
|
2190
|
+
if (content.includes("Error") || content.includes("error") || content.includes("FAIL")) {
|
|
2191
|
+
stateDelta.recent_failures.push(`bash failed: ${cmd.slice(0, 80)}`);
|
|
2192
|
+
}
|
|
2193
|
+
} catch {
|
|
2194
|
+
summary = "bash command";
|
|
2195
|
+
}
|
|
2196
|
+
} else if (name === "grep") {
|
|
2197
|
+
type = "grep_result";
|
|
2198
|
+
summary = `grep results (${content.split("\n").length} lines)`;
|
|
2199
|
+
} else if (name === "web_fetch") {
|
|
2200
|
+
type = "web_fetch";
|
|
2201
|
+
try {
|
|
2202
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2203
|
+
summary = `web_fetch: ${args.url ?? "url"}`;
|
|
2204
|
+
} catch {
|
|
2205
|
+
summary = "web_fetch";
|
|
2206
|
+
}
|
|
2207
|
+
} else if (name === "write" || name === "edit") {
|
|
2208
|
+
try {
|
|
2209
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2210
|
+
path = args.path;
|
|
2211
|
+
if (path && !stateDelta.files_modified.includes(path)) stateDelta.files_modified.push(path);
|
|
2212
|
+
if (path && !stateDelta.files_touched.includes(path)) stateDelta.files_touched.push(path);
|
|
2213
|
+
} catch {
|
|
2214
|
+
}
|
|
2215
|
+
continue;
|
|
2216
|
+
} else if (name === "glob") {
|
|
2217
|
+
try {
|
|
2218
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2219
|
+
summary = `glob: ${args.pattern ?? ""}`;
|
|
2220
|
+
} catch {
|
|
2221
|
+
summary = "glob";
|
|
2222
|
+
}
|
|
2223
|
+
} else if (name === "tasks_set") {
|
|
2224
|
+
try {
|
|
2225
|
+
const args = tc ? JSON.parse(tc.function.arguments) : {};
|
|
2226
|
+
const tasks = args.tasks ?? [];
|
|
2227
|
+
const inProgress = tasks.filter((t) => t.status === "in_progress").map((t) => t.title);
|
|
2228
|
+
const pending = tasks.filter((t) => t.status === "pending").map((t) => t.title);
|
|
2229
|
+
if (inProgress.length) stateDelta.next_actions.push(...inProgress);
|
|
2230
|
+
if (pending.length) stateDelta.next_actions.push(...pending);
|
|
2231
|
+
summary = `tasks_set: ${tasks.length} tasks`;
|
|
2232
|
+
} catch {
|
|
2233
|
+
summary = "tasks_set";
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
const maxRaw = 5e4;
|
|
2237
|
+
const raw = content.length > maxRaw ? content.slice(0, maxRaw) + `
|
|
2238
|
+
...[${content.length - maxRaw} chars truncated]` : content;
|
|
2239
|
+
const artifact = {
|
|
2240
|
+
id: makeArtifactId(type, startIndex + ti),
|
|
2241
|
+
type,
|
|
2242
|
+
summary,
|
|
2243
|
+
raw,
|
|
2244
|
+
source: name,
|
|
2245
|
+
path,
|
|
2246
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2247
|
+
};
|
|
2248
|
+
artifacts.push(artifact);
|
|
2249
|
+
if (!content.includes("Error") && !content.includes("error") && content.length > 0 && content.length < 2e3) {
|
|
2250
|
+
stateDelta.confirmed_findings.push(`${name}: ${content.slice(0, 200)}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const assistantText = typeof turn.assistant.content === "string" ? turn.assistant.content : "";
|
|
2254
|
+
if (assistantText.length > 0) {
|
|
2255
|
+
const decisionPatterns = [
|
|
2256
|
+
/(?:decided?|will|plan to|going to|should|need to)\s+(.{10,200})/gi,
|
|
2257
|
+
/(?:let's|let us)\s+(.{10,200})/gi
|
|
2258
|
+
];
|
|
2259
|
+
for (const pattern of decisionPatterns) {
|
|
2260
|
+
let match;
|
|
2261
|
+
while ((match = pattern.exec(assistantText)) !== null) {
|
|
2262
|
+
const decision = match[1].trim().replace(/\.$/, "");
|
|
2263
|
+
if (decision.length > 10 && !stateDelta.decisions.includes(decision)) {
|
|
2264
|
+
stateDelta.decisions.push(decision);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return { artifacts, stateDelta };
|
|
2270
|
+
}
|
|
2271
|
+
function mergeState(state, delta) {
|
|
2272
|
+
const mergeArr = (a, b) => {
|
|
2273
|
+
if (!b) return a;
|
|
2274
|
+
const set = new Set(a);
|
|
2275
|
+
for (const item of b) set.add(item);
|
|
2276
|
+
return [...set];
|
|
2277
|
+
};
|
|
2278
|
+
return {
|
|
2279
|
+
...state,
|
|
2280
|
+
task: state.task || delta.task || "",
|
|
2281
|
+
user_constraints: mergeArr(state.user_constraints, delta.user_constraints),
|
|
2282
|
+
repo_facts: mergeArr(state.repo_facts, delta.repo_facts),
|
|
2283
|
+
files_touched: mergeArr(state.files_touched, delta.files_touched),
|
|
2284
|
+
files_modified: mergeArr(state.files_modified, delta.files_modified),
|
|
2285
|
+
confirmed_findings: mergeArr(state.confirmed_findings, delta.confirmed_findings),
|
|
2286
|
+
open_questions: mergeArr(state.open_questions, delta.open_questions),
|
|
2287
|
+
recent_failures: mergeArr(state.recent_failures, delta.recent_failures),
|
|
2288
|
+
decisions: mergeArr(state.decisions, delta.decisions),
|
|
2289
|
+
next_actions: mergeArr(state.next_actions, delta.next_actions),
|
|
2290
|
+
artifact_index: { ...state.artifact_index, ...delta.artifact_index }
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function shouldCompact(opts2) {
|
|
2294
|
+
const tokenThreshold = opts2.tokenThreshold ?? 8e4;
|
|
2295
|
+
const turnThreshold = opts2.turnThreshold ?? 12;
|
|
2296
|
+
const tokens = estimatePromptTokens(opts2.messages);
|
|
2297
|
+
const { turns } = groupIntoTurns(opts2.messages);
|
|
2298
|
+
return tokens > tokenThreshold || turns.length > turnThreshold;
|
|
2299
|
+
}
|
|
2300
|
+
function compactMessages2(opts2) {
|
|
2301
|
+
const keepLastTurns = opts2.keepLastTurns ?? 4;
|
|
2302
|
+
const { prefix, turns } = groupIntoTurns(opts2.messages);
|
|
2303
|
+
const tokensBefore = estimatePromptTokens(opts2.messages);
|
|
2304
|
+
if (turns.length <= keepLastTurns) {
|
|
2305
|
+
return {
|
|
2306
|
+
newMessages: opts2.messages,
|
|
2307
|
+
newState: opts2.state,
|
|
2308
|
+
metrics: {
|
|
2309
|
+
estimatedTokensBefore: tokensBefore,
|
|
2310
|
+
estimatedTokensAfter: tokensBefore,
|
|
2311
|
+
archivedArtifacts: 0,
|
|
2312
|
+
recalledArtifacts: 0,
|
|
2313
|
+
rawTurnsRemoved: 0,
|
|
2314
|
+
rawTurnsKept: turns.length
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
const toCompact = turns.slice(0, turns.length - keepLastTurns);
|
|
2319
|
+
const toKeep = turns.slice(turns.length - keepLastTurns);
|
|
2320
|
+
let newState = { ...opts2.state };
|
|
2321
|
+
let archivedCount = 0;
|
|
2322
|
+
for (let i = 0; i < toCompact.length; i++) {
|
|
2323
|
+
const turn = toCompact[i];
|
|
2324
|
+
const { artifacts, stateDelta } = extractArtifactsFromTurn(turn, i, opts2.store);
|
|
2325
|
+
for (const artifact of artifacts) {
|
|
2326
|
+
opts2.store.add(artifact);
|
|
2327
|
+
archivedCount++;
|
|
2328
|
+
newState.artifact_index[artifact.id] = {
|
|
2329
|
+
type: artifact.type,
|
|
2330
|
+
summary: artifact.summary,
|
|
2331
|
+
source: artifact.source,
|
|
2332
|
+
path: artifact.path
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
newState = mergeState(newState, stateDelta);
|
|
2336
|
+
if (!newState.task && typeof turn.user.content === "string") {
|
|
2337
|
+
newState.task = turn.user.content.slice(0, 200);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const workingMemory = [];
|
|
2341
|
+
for (const turn of toKeep) {
|
|
2342
|
+
workingMemory.push(turn.user);
|
|
2343
|
+
workingMemory.push(turn.assistant);
|
|
2344
|
+
for (const tm of turn.tools) {
|
|
2345
|
+
workingMemory.push(tm);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
const stateMsg = buildSessionStateMessage(newState);
|
|
2349
|
+
const newMessages = [...prefix, stateMsg, ...workingMemory];
|
|
2350
|
+
const tokensAfter = estimatePromptTokens(newMessages);
|
|
2351
|
+
const metrics = {
|
|
2352
|
+
estimatedTokensBefore: tokensBefore,
|
|
2353
|
+
estimatedTokensAfter: tokensAfter,
|
|
2354
|
+
archivedArtifacts: archivedCount,
|
|
2355
|
+
recalledArtifacts: 0,
|
|
2356
|
+
rawTurnsRemoved: toCompact.length,
|
|
2357
|
+
rawTurnsKept: toKeep.length
|
|
2358
|
+
};
|
|
2359
|
+
return { newMessages, newState, metrics };
|
|
2360
|
+
}
|
|
2361
|
+
function recallArtifacts(messages, store, state) {
|
|
2362
|
+
const text = messages.map((m) => typeof m.content === "string" ? m.content : "").join(" ");
|
|
2363
|
+
const ids = [];
|
|
2364
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2365
|
+
if (meta.path && text.includes(meta.path)) {
|
|
2366
|
+
ids.push(id);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
for (const failure of state.recent_failures) {
|
|
2370
|
+
const keyword = failure.split(":")[0];
|
|
2371
|
+
if (keyword && text.toLowerCase().includes(keyword.toLowerCase())) {
|
|
2372
|
+
for (const [id, meta] of Object.entries(state.artifact_index)) {
|
|
2373
|
+
if (meta.source === "bash" && !ids.includes(id)) {
|
|
2374
|
+
ids.push(id);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
const uniqueIds = [...new Set(ids)].slice(0, 5);
|
|
2380
|
+
const recalled = store.recall(uniqueIds);
|
|
2381
|
+
return { ids: uniqueIds, recalled };
|
|
2382
|
+
}
|
|
2383
|
+
var init_compaction = __esm({
|
|
2384
|
+
"src/agent/compaction.ts"() {
|
|
2385
|
+
"use strict";
|
|
2386
|
+
init_session_state();
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
|
|
1885
2390
|
// src/mcp/adapter.ts
|
|
1886
2391
|
function mcpToolToSpec(serverName, mcpTool, client) {
|
|
1887
2392
|
const prefix = `mcp_${sanitizeName(serverName)}_`;
|
|
@@ -3834,12 +4339,20 @@ var init_theme = __esm({
|
|
|
3834
4339
|
});
|
|
3835
4340
|
|
|
3836
4341
|
// src/sessions.ts
|
|
3837
|
-
|
|
4342
|
+
var sessions_exports = {};
|
|
4343
|
+
__export(sessions_exports, {
|
|
4344
|
+
listSessions: () => listSessions,
|
|
4345
|
+
loadSession: () => loadSession,
|
|
4346
|
+
makeSessionId: () => makeSessionId,
|
|
4347
|
+
pruneSessions: () => pruneSessions,
|
|
4348
|
+
saveSession: () => saveSession
|
|
4349
|
+
});
|
|
4350
|
+
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
3838
4351
|
import { homedir as homedir6 } from "os";
|
|
3839
|
-
import { join as
|
|
4352
|
+
import { join as join7 } from "path";
|
|
3840
4353
|
function sessionsDir() {
|
|
3841
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3842
|
-
return
|
|
4354
|
+
const xdg = process.env.XDG_DATA_HOME || join7(homedir6(), ".local", "share");
|
|
4355
|
+
return join7(xdg, "kimiflare", "sessions");
|
|
3843
4356
|
}
|
|
3844
4357
|
function sanitize(text) {
|
|
3845
4358
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -3852,24 +4365,29 @@ function makeSessionId(firstPrompt) {
|
|
|
3852
4365
|
async function saveSession(file) {
|
|
3853
4366
|
const dir = sessionsDir();
|
|
3854
4367
|
await mkdir5(dir, { recursive: true });
|
|
3855
|
-
const path =
|
|
4368
|
+
const path = join7(dir, `${file.id}.json`);
|
|
3856
4369
|
await writeFile5(path, JSON.stringify(file, null, 2), "utf8");
|
|
3857
4370
|
return path;
|
|
3858
4371
|
}
|
|
4372
|
+
async function pruneSessions() {
|
|
4373
|
+
const dir = sessionsDir();
|
|
4374
|
+
const files = await listFilesByMtime(dir, /\.json$/);
|
|
4375
|
+
return pruneFiles(files, RETENTION.sessionMaxAgeDays, RETENTION.sessionMaxCount);
|
|
4376
|
+
}
|
|
3859
4377
|
async function listSessions(limit = 30) {
|
|
3860
4378
|
const dir = sessionsDir();
|
|
3861
4379
|
let entries;
|
|
3862
4380
|
try {
|
|
3863
|
-
entries = await
|
|
4381
|
+
entries = await readdir2(dir);
|
|
3864
4382
|
} catch {
|
|
3865
4383
|
return [];
|
|
3866
4384
|
}
|
|
3867
4385
|
const summaries = [];
|
|
3868
4386
|
for (const name of entries) {
|
|
3869
4387
|
if (!name.endsWith(".json")) continue;
|
|
3870
|
-
const path =
|
|
4388
|
+
const path = join7(dir, name);
|
|
3871
4389
|
try {
|
|
3872
|
-
const [s, raw] = await Promise.all([
|
|
4390
|
+
const [s, raw] = await Promise.all([stat3(path), readFile7(path, "utf8")]);
|
|
3873
4391
|
const parsed = JSON.parse(raw);
|
|
3874
4392
|
const firstUser = parsed.messages.find((m) => m.role === "user");
|
|
3875
4393
|
const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : firstUser?.content ? firstUser.content.find((p) => p.type === "text")?.text ?? "(no prompt)" : "(no prompt)";
|
|
@@ -3894,6 +4412,7 @@ async function loadSession(filePath) {
|
|
|
3894
4412
|
var init_sessions = __esm({
|
|
3895
4413
|
"src/sessions.ts"() {
|
|
3896
4414
|
"use strict";
|
|
4415
|
+
init_storage_limits();
|
|
3897
4416
|
}
|
|
3898
4417
|
});
|
|
3899
4418
|
|
|
@@ -3939,17 +4458,21 @@ var init_image = __esm({
|
|
|
3939
4458
|
// src/usage-tracker.ts
|
|
3940
4459
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
3941
4460
|
import { homedir as homedir7 } from "os";
|
|
3942
|
-
import { join as
|
|
4461
|
+
import { join as join8 } from "path";
|
|
3943
4462
|
function usageDir() {
|
|
3944
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3945
|
-
return
|
|
4463
|
+
const xdg = process.env.XDG_DATA_HOME || join8(homedir7(), ".local", "share");
|
|
4464
|
+
return join8(xdg, "kimiflare");
|
|
3946
4465
|
}
|
|
3947
4466
|
function usagePath() {
|
|
3948
|
-
return
|
|
4467
|
+
return join8(usageDir(), "usage.json");
|
|
3949
4468
|
}
|
|
3950
4469
|
function today() {
|
|
3951
4470
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3952
4471
|
}
|
|
4472
|
+
function cutoffDate(daysBack) {
|
|
4473
|
+
const d = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1e3);
|
|
4474
|
+
return d.toISOString().slice(0, 10);
|
|
4475
|
+
}
|
|
3953
4476
|
async function loadLog() {
|
|
3954
4477
|
try {
|
|
3955
4478
|
const raw = await readFile9(usagePath(), "utf8");
|
|
@@ -3979,8 +4502,18 @@ function getOrCreateSession(log, sessionId, date) {
|
|
|
3979
4502
|
}
|
|
3980
4503
|
return session;
|
|
3981
4504
|
}
|
|
4505
|
+
function pruneUsageLog(log) {
|
|
4506
|
+
const dayCutoff = cutoffDate(RETENTION.usageDayMaxAgeDays);
|
|
4507
|
+
const sessionCutoff = cutoffDate(RETENTION.usageSessionMaxAgeDays);
|
|
4508
|
+
const days = log.days.filter((d) => d.date >= dayCutoff);
|
|
4509
|
+
let sessions = log.sessions.filter((s) => s.date >= sessionCutoff);
|
|
4510
|
+
if (sessions.length > RETENTION.usageSessionMaxCount) {
|
|
4511
|
+
sessions = sessions.sort((a, b) => b.date < a.date ? -1 : b.date > a.date ? 1 : 0).slice(0, RETENTION.usageSessionMaxCount);
|
|
4512
|
+
}
|
|
4513
|
+
return { ...log, days, sessions };
|
|
4514
|
+
}
|
|
3982
4515
|
async function recordUsage(sessionId, usage) {
|
|
3983
|
-
const log = await loadLog();
|
|
4516
|
+
const log = pruneUsageLog(await loadLog());
|
|
3984
4517
|
const date = today();
|
|
3985
4518
|
const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, usage.prompt_tokens_details?.cached_tokens ?? 0);
|
|
3986
4519
|
const day = getOrCreateDay(log, date);
|
|
@@ -3996,7 +4529,7 @@ async function recordUsage(sessionId, usage) {
|
|
|
3996
4529
|
await saveLog(log);
|
|
3997
4530
|
}
|
|
3998
4531
|
async function getCostReport(sessionId) {
|
|
3999
|
-
const log = await loadLog();
|
|
4532
|
+
const log = pruneUsageLog(await loadLog());
|
|
4000
4533
|
const date = today();
|
|
4001
4534
|
const currentMonth = date.slice(0, 7);
|
|
4002
4535
|
const session = log.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
|
|
@@ -4055,6 +4588,7 @@ var init_usage_tracker = __esm({
|
|
|
4055
4588
|
"src/usage-tracker.ts"() {
|
|
4056
4589
|
"use strict";
|
|
4057
4590
|
init_pricing();
|
|
4591
|
+
init_storage_limits();
|
|
4058
4592
|
LOG_VERSION2 = 1;
|
|
4059
4593
|
}
|
|
4060
4594
|
});
|
|
@@ -4067,8 +4601,8 @@ __export(app_exports, {
|
|
|
4067
4601
|
import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
|
|
4068
4602
|
import { Box as Box12, Text as Text13, useApp, useInput as useInput2, render } from "ink";
|
|
4069
4603
|
import { existsSync } from "fs";
|
|
4070
|
-
import { join as
|
|
4071
|
-
import { unlink } from "fs/promises";
|
|
4604
|
+
import { join as join9 } from "path";
|
|
4605
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
4072
4606
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4073
4607
|
function capEvents(prev) {
|
|
4074
4608
|
if (prev.length <= MAX_EVENTS) return prev;
|
|
@@ -4145,11 +4679,27 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4145
4679
|
const tasksRef = useRef3([]);
|
|
4146
4680
|
const usageRef = useRef3(null);
|
|
4147
4681
|
const updateCheckedRef = useRef3(false);
|
|
4682
|
+
const sessionStateRef = useRef3(emptySessionState());
|
|
4683
|
+
const artifactStoreRef = useRef3(new ArtifactStore());
|
|
4684
|
+
const compiledContextRef = useRef3(initialCfg?.compiledContext === true);
|
|
4148
4685
|
const updateNudgedRef = useRef3(false);
|
|
4149
4686
|
const compactSuggestedRef = useRef3(false);
|
|
4150
4687
|
const mcpManagerRef = useRef3(new McpManager());
|
|
4151
4688
|
const mcpToolsRef = useRef3([]);
|
|
4152
4689
|
const mcpInitRef = useRef3(false);
|
|
4690
|
+
useEffect4(() => {
|
|
4691
|
+
if (!cfg) return;
|
|
4692
|
+
void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
|
|
4693
|
+
({ pruneSessions: pruneSessions2 }) => pruneSessions2().then((removed) => {
|
|
4694
|
+
if (removed > 0) {
|
|
4695
|
+
setEvents((e) => [
|
|
4696
|
+
...e,
|
|
4697
|
+
{ kind: "info", key: mkKey(), text: `pruned ${removed} old session files` }
|
|
4698
|
+
]);
|
|
4699
|
+
}
|
|
4700
|
+
})
|
|
4701
|
+
);
|
|
4702
|
+
}, [cfg]);
|
|
4153
4703
|
useEffect4(() => {
|
|
4154
4704
|
if (!cfg || updateCheckedRef.current) return;
|
|
4155
4705
|
updateCheckedRef.current = true;
|
|
@@ -4351,7 +4901,8 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4351
4901
|
model: cfg.model,
|
|
4352
4902
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4353
4903
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4354
|
-
messages: messagesRef.current
|
|
4904
|
+
messages: messagesRef.current,
|
|
4905
|
+
sessionState: compiledContextRef.current ? sessionStateRef.current : void 0
|
|
4355
4906
|
});
|
|
4356
4907
|
} catch {
|
|
4357
4908
|
}
|
|
@@ -4416,29 +4967,55 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4416
4967
|
const controller = new AbortController();
|
|
4417
4968
|
activeControllerRef.current = controller;
|
|
4418
4969
|
try {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4970
|
+
if (compiledContextRef.current) {
|
|
4971
|
+
const result = compactMessages2({
|
|
4972
|
+
messages: messagesRef.current,
|
|
4973
|
+
state: sessionStateRef.current,
|
|
4974
|
+
store: artifactStoreRef.current
|
|
4975
|
+
});
|
|
4976
|
+
if (result.metrics.rawTurnsRemoved === 0) {
|
|
4977
|
+
setEvents((e) => [
|
|
4978
|
+
...e,
|
|
4979
|
+
{ kind: "info", key: mkKey(), text: "nothing to compact yet" }
|
|
4980
|
+
]);
|
|
4981
|
+
} else {
|
|
4982
|
+
messagesRef.current = result.newMessages;
|
|
4983
|
+
sessionStateRef.current = result.newState;
|
|
4984
|
+
setEvents((e) => [
|
|
4985
|
+
...e,
|
|
4986
|
+
{
|
|
4987
|
+
kind: "info",
|
|
4988
|
+
key: mkKey(),
|
|
4989
|
+
text: `compacted ${result.metrics.rawTurnsRemoved} turns \u2192 ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens, ${result.metrics.archivedArtifacts} artifacts`
|
|
4990
|
+
}
|
|
4991
|
+
]);
|
|
4992
|
+
await saveSessionSafe();
|
|
4993
|
+
}
|
|
4431
4994
|
} else {
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4995
|
+
const result = await compactMessages({
|
|
4996
|
+
accountId: cfg.accountId,
|
|
4997
|
+
apiToken: cfg.apiToken,
|
|
4998
|
+
model: cfg.model,
|
|
4999
|
+
messages: messagesRef.current,
|
|
5000
|
+
signal: controller.signal
|
|
5001
|
+
});
|
|
5002
|
+
if (result.replacedCount === 0) {
|
|
5003
|
+
setEvents((e) => [
|
|
5004
|
+
...e,
|
|
5005
|
+
{ kind: "info", key: mkKey(), text: "nothing to compact yet" }
|
|
5006
|
+
]);
|
|
5007
|
+
} else {
|
|
5008
|
+
messagesRef.current = result.newMessages;
|
|
5009
|
+
setEvents((e) => [
|
|
5010
|
+
...e,
|
|
5011
|
+
{
|
|
5012
|
+
kind: "info",
|
|
5013
|
+
key: mkKey(),
|
|
5014
|
+
text: `compacted ${result.replacedCount} messages into a summary`
|
|
5015
|
+
}
|
|
5016
|
+
]);
|
|
5017
|
+
await saveSessionSafe();
|
|
5018
|
+
}
|
|
4442
5019
|
}
|
|
4443
5020
|
} catch (e) {
|
|
4444
5021
|
if (e.name !== "AbortError") {
|
|
@@ -4465,13 +5042,13 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4465
5042
|
}
|
|
4466
5043
|
const cwd = process.cwd();
|
|
4467
5044
|
for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
|
|
4468
|
-
if (existsSync(
|
|
5045
|
+
if (existsSync(join9(cwd, name))) {
|
|
4469
5046
|
setEvents((e) => [
|
|
4470
5047
|
...e,
|
|
4471
5048
|
{
|
|
4472
5049
|
kind: "info",
|
|
4473
5050
|
key: mkKey(),
|
|
4474
|
-
text: `${name} already exists at ${
|
|
5051
|
+
text: `${name} already exists at ${join9(cwd, name)} \u2014 delete it first if you want to regenerate`
|
|
4475
5052
|
}
|
|
4476
5053
|
]);
|
|
4477
5054
|
return;
|
|
@@ -4588,7 +5165,7 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4588
5165
|
})
|
|
4589
5166
|
}
|
|
4590
5167
|
});
|
|
4591
|
-
if (existsSync(
|
|
5168
|
+
if (existsSync(join9(cwd, "KIMI.md"))) {
|
|
4592
5169
|
if (cacheStableRef.current) {
|
|
4593
5170
|
messagesRef.current[1] = {
|
|
4594
5171
|
role: "system",
|
|
@@ -4637,6 +5214,10 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4637
5214
|
const file = await loadSession(picked.filePath);
|
|
4638
5215
|
messagesRef.current = file.messages;
|
|
4639
5216
|
sessionIdRef.current = file.id;
|
|
5217
|
+
if (file.sessionState && compiledContextRef.current) {
|
|
5218
|
+
sessionStateRef.current = file.sessionState;
|
|
5219
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
5220
|
+
}
|
|
4640
5221
|
setEvents([
|
|
4641
5222
|
{
|
|
4642
5223
|
kind: "info",
|
|
@@ -4699,6 +5280,8 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4699
5280
|
messagesRef.current = [messagesRef.current[0]];
|
|
4700
5281
|
}
|
|
4701
5282
|
sessionIdRef.current = null;
|
|
5283
|
+
sessionStateRef.current = emptySessionState();
|
|
5284
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
4702
5285
|
setEvents([]);
|
|
4703
5286
|
setUsage(null);
|
|
4704
5287
|
setTasks([]);
|
|
@@ -4926,7 +5509,7 @@ use: /thinking low | medium | high`
|
|
|
4926
5509
|
return true;
|
|
4927
5510
|
}
|
|
4928
5511
|
if (c === "/logout") {
|
|
4929
|
-
|
|
5512
|
+
unlink2(configPath()).catch(() => {
|
|
4930
5513
|
});
|
|
4931
5514
|
setEvents((e) => [
|
|
4932
5515
|
...e,
|
|
@@ -4987,6 +5570,17 @@ use: /thinking low | medium | high`
|
|
|
4987
5570
|
}
|
|
4988
5571
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
|
|
4989
5572
|
messagesRef.current.push({ role: "user", content });
|
|
5573
|
+
if (compiledContextRef.current) {
|
|
5574
|
+
const { ids, recalled } = recallArtifacts(messagesRef.current, artifactStoreRef.current, sessionStateRef.current);
|
|
5575
|
+
if (recalled.length > 0) {
|
|
5576
|
+
const recalledText = formatRecalledArtifacts(recalled);
|
|
5577
|
+
messagesRef.current.push({ role: "system", content: recalledText });
|
|
5578
|
+
sessionStateRef.current = {
|
|
5579
|
+
...sessionStateRef.current,
|
|
5580
|
+
artifact_index: { ...sessionStateRef.current.artifact_index }
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
4990
5584
|
setBusy(true);
|
|
4991
5585
|
setTurnStartedAt(Date.now());
|
|
4992
5586
|
const controller = new AbortController();
|
|
@@ -5096,6 +5690,26 @@ use: /thinking low | medium | high`
|
|
|
5096
5690
|
}
|
|
5097
5691
|
});
|
|
5098
5692
|
await saveSessionSafe();
|
|
5693
|
+
if (compiledContextRef.current && shouldCompact({ messages: messagesRef.current })) {
|
|
5694
|
+
const result = compactMessages2({
|
|
5695
|
+
messages: messagesRef.current,
|
|
5696
|
+
state: sessionStateRef.current,
|
|
5697
|
+
store: artifactStoreRef.current
|
|
5698
|
+
});
|
|
5699
|
+
if (result.metrics.rawTurnsRemoved > 0) {
|
|
5700
|
+
messagesRef.current = result.newMessages;
|
|
5701
|
+
sessionStateRef.current = result.newState;
|
|
5702
|
+
setEvents((e) => [
|
|
5703
|
+
...e,
|
|
5704
|
+
{
|
|
5705
|
+
kind: "info",
|
|
5706
|
+
key: mkKey(),
|
|
5707
|
+
text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
|
|
5708
|
+
}
|
|
5709
|
+
]);
|
|
5710
|
+
await saveSessionSafe();
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5099
5713
|
} catch (e) {
|
|
5100
5714
|
if (e.name === "AbortError") {
|
|
5101
5715
|
setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
|
|
@@ -5292,6 +5906,8 @@ var init_app = __esm({
|
|
|
5292
5906
|
init_loop();
|
|
5293
5907
|
init_system_prompt();
|
|
5294
5908
|
init_compact();
|
|
5909
|
+
init_compaction();
|
|
5910
|
+
init_session_state();
|
|
5295
5911
|
init_executor();
|
|
5296
5912
|
init_manager();
|
|
5297
5913
|
init_messages();
|
|
@@ -5336,11 +5952,11 @@ init_update_check();
|
|
|
5336
5952
|
import { Command } from "commander";
|
|
5337
5953
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5338
5954
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5339
|
-
import { dirname as dirname3, join as
|
|
5955
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
5340
5956
|
function readPackageVersion() {
|
|
5341
5957
|
try {
|
|
5342
5958
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
5343
|
-
const pkg = JSON.parse(readFileSync2(
|
|
5959
|
+
const pkg = JSON.parse(readFileSync2(join10(here, "..", "package.json"), "utf8"));
|
|
5344
5960
|
return pkg.version ?? "0.0.0";
|
|
5345
5961
|
} catch {
|
|
5346
5962
|
return "0.0.0";
|