kimiflare 0.14.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 +895 -131
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -35,6 +35,10 @@ async function loadConfig() {
|
|
|
35
35
|
const envEffort = readReasoningEffortEnv();
|
|
36
36
|
const envTheme = process.env.KIMI_THEME;
|
|
37
37
|
const envCoauthor = readCoauthorEnv();
|
|
38
|
+
const envCacheStable = process.env.KIMIFLARE_CACHE_STABLE_PROMPTS;
|
|
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;
|
|
38
42
|
if (envAccount && envToken) {
|
|
39
43
|
return {
|
|
40
44
|
accountId: envAccount,
|
|
@@ -44,7 +48,9 @@ async function loadConfig() {
|
|
|
44
48
|
reasoningEffort: envEffort,
|
|
45
49
|
coauthor: envCoauthor?.enabled ?? true,
|
|
46
50
|
coauthorName: envCoauthor?.name,
|
|
47
|
-
coauthorEmail: envCoauthor?.email
|
|
51
|
+
coauthorEmail: envCoauthor?.email,
|
|
52
|
+
cacheStablePrompts,
|
|
53
|
+
compiledContext
|
|
48
54
|
};
|
|
49
55
|
}
|
|
50
56
|
try {
|
|
@@ -60,7 +66,9 @@ async function loadConfig() {
|
|
|
60
66
|
coauthor: envCoauthor?.enabled ?? parsed.coauthor ?? true,
|
|
61
67
|
coauthorName: envCoauthor?.name ?? parsed.coauthorName,
|
|
62
68
|
coauthorEmail: envCoauthor?.email ?? parsed.coauthorEmail,
|
|
63
|
-
mcpServers: parsed.mcpServers
|
|
69
|
+
mcpServers: parsed.mcpServers,
|
|
70
|
+
cacheStablePrompts: parsed.cacheStablePrompts ?? cacheStablePrompts,
|
|
71
|
+
compiledContext: parsed.compiledContext ?? compiledContext
|
|
64
72
|
};
|
|
65
73
|
}
|
|
66
74
|
} catch {
|
|
@@ -153,6 +161,20 @@ function jsonReplacer(_key, value) {
|
|
|
153
161
|
}
|
|
154
162
|
return value;
|
|
155
163
|
}
|
|
164
|
+
function stableStringify(value, replacer, space) {
|
|
165
|
+
function sortKeys(obj) {
|
|
166
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
167
|
+
if (Array.isArray(obj)) return obj.map(sortKeys);
|
|
168
|
+
const sorted2 = {};
|
|
169
|
+
const keys = Object.keys(obj).sort();
|
|
170
|
+
for (const k of keys) {
|
|
171
|
+
sorted2[k] = sortKeys(obj[k]);
|
|
172
|
+
}
|
|
173
|
+
return sorted2;
|
|
174
|
+
}
|
|
175
|
+
const sorted = sortKeys(value);
|
|
176
|
+
return JSON.stringify(sorted, replacer, space);
|
|
177
|
+
}
|
|
156
178
|
var init_messages = __esm({
|
|
157
179
|
"src/agent/messages.ts"() {
|
|
158
180
|
"use strict";
|
|
@@ -185,13 +207,17 @@ async function* runKimi(opts2) {
|
|
|
185
207
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
186
208
|
let res;
|
|
187
209
|
try {
|
|
210
|
+
const headers = {
|
|
211
|
+
Authorization: `Bearer ${opts2.apiToken}`,
|
|
212
|
+
"Content-Type": "application/json"
|
|
213
|
+
};
|
|
214
|
+
if (opts2.sessionId) {
|
|
215
|
+
headers["X-Session-ID"] = opts2.sessionId;
|
|
216
|
+
}
|
|
188
217
|
res = await fetch(url, {
|
|
189
218
|
method: "POST",
|
|
190
|
-
headers
|
|
191
|
-
|
|
192
|
-
"Content-Type": "application/json"
|
|
193
|
-
},
|
|
194
|
-
body: JSON.stringify(body, jsonReplacer),
|
|
219
|
+
headers,
|
|
220
|
+
body: stableStringify(body, jsonReplacer),
|
|
195
221
|
signal: opts2.signal
|
|
196
222
|
});
|
|
197
223
|
} catch (fetchErr) {
|
|
@@ -384,16 +410,107 @@ var init_registry = __esm({
|
|
|
384
410
|
}
|
|
385
411
|
});
|
|
386
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
|
+
|
|
387
504
|
// src/cost-debug.ts
|
|
388
505
|
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
389
506
|
import { homedir as homedir2 } from "os";
|
|
390
|
-
import { join as
|
|
507
|
+
import { join as join3 } from "path";
|
|
391
508
|
function debugDir() {
|
|
392
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
393
|
-
return
|
|
509
|
+
const xdg = process.env.XDG_DATA_HOME || join3(homedir2(), ".local", "share");
|
|
510
|
+
return join3(xdg, "kimiflare");
|
|
394
511
|
}
|
|
395
512
|
function debugPath() {
|
|
396
|
-
return
|
|
513
|
+
return join3(debugDir(), "cost-debug.jsonl");
|
|
397
514
|
}
|
|
398
515
|
function now() {
|
|
399
516
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -449,14 +566,79 @@ function buildToolStats(results) {
|
|
|
449
566
|
}
|
|
450
567
|
async function logCostDebug(entry) {
|
|
451
568
|
await mkdir2(debugDir(), { recursive: true });
|
|
569
|
+
await rotateJsonl(debugPath(), RETENTION.costDebugMaxBytes, RETENTION.costDebugRotations);
|
|
452
570
|
await appendFile(debugPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
453
571
|
}
|
|
572
|
+
function serializePrefix(messages) {
|
|
573
|
+
let end = 0;
|
|
574
|
+
while (end < messages.length && messages[end].role === "system") {
|
|
575
|
+
end++;
|
|
576
|
+
}
|
|
577
|
+
return messages.slice(0, end).map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n---\n");
|
|
578
|
+
}
|
|
579
|
+
function comparePromptPrefixes(prev, curr) {
|
|
580
|
+
const prevPrefix = prev ? serializePrefix(prev) : "";
|
|
581
|
+
const currPrefix = serializePrefix(curr);
|
|
582
|
+
const totalChars = curr.reduce((sum, m) => {
|
|
583
|
+
if (typeof m.content === "string") return sum + m.content.length;
|
|
584
|
+
if (Array.isArray(m.content)) return sum + m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
|
|
585
|
+
return sum;
|
|
586
|
+
}, 0);
|
|
587
|
+
let firstDiffByte = null;
|
|
588
|
+
let changedSegment = null;
|
|
589
|
+
if (prevPrefix !== currPrefix) {
|
|
590
|
+
const minLen = Math.min(prevPrefix.length, currPrefix.length);
|
|
591
|
+
for (let i = 0; i < minLen; i++) {
|
|
592
|
+
if (prevPrefix[i] !== currPrefix[i]) {
|
|
593
|
+
firstDiffByte = i;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (firstDiffByte === null && prevPrefix.length !== currPrefix.length) {
|
|
598
|
+
firstDiffByte = minLen;
|
|
599
|
+
}
|
|
600
|
+
if (curr.length >= 1 && curr[0].role === "system") {
|
|
601
|
+
const staticLen = typeof curr[0].content === "string" ? curr[0].content.length : JSON.stringify(curr[0].content).length;
|
|
602
|
+
if (firstDiffByte !== null && firstDiffByte < staticLen) {
|
|
603
|
+
changedSegment = "static";
|
|
604
|
+
} else if (curr.length >= 2 && curr[1].role === "system") {
|
|
605
|
+
const sessionLen = typeof curr[1].content === "string" ? curr[1].content.length : JSON.stringify(curr[1].content).length;
|
|
606
|
+
if (firstDiffByte !== null && firstDiffByte < staticLen + 5 + sessionLen) {
|
|
607
|
+
changedSegment = "session";
|
|
608
|
+
} else {
|
|
609
|
+
changedSegment = "dynamic";
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
changedSegment = "dynamic";
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
changedSegment = "dynamic";
|
|
616
|
+
}
|
|
617
|
+
} else {
|
|
618
|
+
changedSegment = "none";
|
|
619
|
+
}
|
|
620
|
+
const staticPrefixChars = curr.length > 0 && curr[0].role === "system" && typeof curr[0].content === "string" ? curr[0].content.length : 0;
|
|
621
|
+
const sessionPrefixChars = curr.length > 1 && curr[1].role === "system" && typeof curr[1].content === "string" ? curr[1].content.length : 0;
|
|
622
|
+
const dynamicSuffixChars = totalChars - staticPrefixChars - sessionPrefixChars;
|
|
623
|
+
return {
|
|
624
|
+
staticPrefixChars,
|
|
625
|
+
sessionPrefixChars,
|
|
626
|
+
dynamicSuffixChars,
|
|
627
|
+
firstDiffByte,
|
|
628
|
+
changedSegment,
|
|
629
|
+
cacheHitRatio: 0
|
|
630
|
+
// populated by caller with actual usage data
|
|
631
|
+
};
|
|
632
|
+
}
|
|
454
633
|
async function logTurnDebug(ctx) {
|
|
455
634
|
const promptSections = analyzePrompt(ctx.messages);
|
|
456
635
|
const promptTotalChars = promptSections.reduce((sum, s) => sum + s.chars, 0);
|
|
457
636
|
const toolStats = buildToolStats(ctx.toolResults);
|
|
458
637
|
const toolTotalRaw = toolStats.reduce((sum, t) => sum + t.rawBytes, 0);
|
|
459
638
|
const toolTotalReduced = toolStats.reduce((sum, t) => sum + t.reducedBytes, 0);
|
|
639
|
+
const cacheDiagnostics = comparePromptPrefixes(ctx.previousMessages, ctx.messages);
|
|
640
|
+
const cachedTokens = ctx.usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
641
|
+
cacheDiagnostics.cacheHitRatio = ctx.usage.prompt_tokens > 0 ? cachedTokens / ctx.usage.prompt_tokens : 0;
|
|
460
642
|
await logCostDebug({
|
|
461
643
|
v: LOG_VERSION,
|
|
462
644
|
ts: now(),
|
|
@@ -469,13 +651,16 @@ async function logTurnDebug(ctx) {
|
|
|
469
651
|
toolStats,
|
|
470
652
|
toolTotalRawBytes: toolTotalRaw,
|
|
471
653
|
toolTotalReducedBytes: toolTotalReduced,
|
|
472
|
-
toolSavingsPct: toolTotalRaw > 0 ? Math.round((toolTotalRaw - toolTotalReduced) / toolTotalRaw * 100) : 0
|
|
654
|
+
toolSavingsPct: toolTotalRaw > 0 ? Math.round((toolTotalRaw - toolTotalReduced) / toolTotalRaw * 100) : 0,
|
|
655
|
+
cacheDiagnostics,
|
|
656
|
+
compaction: ctx.compaction
|
|
473
657
|
});
|
|
474
658
|
}
|
|
475
659
|
var LOG_VERSION;
|
|
476
660
|
var init_cost_debug = __esm({
|
|
477
661
|
"src/cost-debug.ts"() {
|
|
478
662
|
"use strict";
|
|
663
|
+
init_storage_limits();
|
|
479
664
|
LOG_VERSION = 1;
|
|
480
665
|
}
|
|
481
666
|
});
|
|
@@ -488,6 +673,7 @@ async function runAgentTurn(opts2) {
|
|
|
488
673
|
let lastUsage = null;
|
|
489
674
|
for (let iter = 0; iter < max; iter++) {
|
|
490
675
|
turn++;
|
|
676
|
+
const previousMessages = opts2.messages.slice();
|
|
491
677
|
const toolCalls = [];
|
|
492
678
|
const toolResults = [];
|
|
493
679
|
let content = "";
|
|
@@ -502,7 +688,8 @@ async function runAgentTurn(opts2) {
|
|
|
502
688
|
signal: opts2.signal,
|
|
503
689
|
temperature: opts2.temperature,
|
|
504
690
|
maxCompletionTokens: opts2.maxCompletionTokens,
|
|
505
|
-
reasoningEffort: opts2.reasoningEffort
|
|
691
|
+
reasoningEffort: opts2.reasoningEffort,
|
|
692
|
+
sessionId: opts2.sessionId
|
|
506
693
|
});
|
|
507
694
|
for await (const ev of events) {
|
|
508
695
|
switch (ev.type) {
|
|
@@ -561,6 +748,7 @@ async function runAgentTurn(opts2) {
|
|
|
561
748
|
sessionId: opts2.sessionId,
|
|
562
749
|
turn,
|
|
563
750
|
messages: opts2.messages,
|
|
751
|
+
previousMessages,
|
|
564
752
|
toolResults,
|
|
565
753
|
usage: lastUsage
|
|
566
754
|
});
|
|
@@ -588,6 +776,7 @@ async function runAgentTurn(opts2) {
|
|
|
588
776
|
sessionId: opts2.sessionId,
|
|
589
777
|
turn,
|
|
590
778
|
messages: opts2.messages,
|
|
779
|
+
previousMessages,
|
|
591
780
|
toolResults,
|
|
592
781
|
usage: lastUsage
|
|
593
782
|
});
|
|
@@ -832,11 +1021,11 @@ var init_mode = __esm({
|
|
|
832
1021
|
|
|
833
1022
|
// src/agent/system-prompt.ts
|
|
834
1023
|
import { platform, release, homedir as homedir3 } from "os";
|
|
835
|
-
import { basename, join as
|
|
1024
|
+
import { basename, join as join4 } from "path";
|
|
836
1025
|
import { readFileSync, statSync } from "fs";
|
|
837
1026
|
function loadContextFile(cwd) {
|
|
838
1027
|
for (const name of CONTEXT_FILENAMES) {
|
|
839
|
-
const path =
|
|
1028
|
+
const path = join4(cwd, name);
|
|
840
1029
|
try {
|
|
841
1030
|
const s = statSync(path);
|
|
842
1031
|
if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
|
|
@@ -847,25 +1036,8 @@ function loadContextFile(cwd) {
|
|
|
847
1036
|
}
|
|
848
1037
|
return null;
|
|
849
1038
|
}
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
const date = now2.toISOString().slice(0, 10);
|
|
853
|
-
const shell = process.env.SHELL ? basename(process.env.SHELL) : "sh";
|
|
854
|
-
const toolsBlock = opts2.tools.map((t) => {
|
|
855
|
-
const perm = t.needsPermission ? " [needs user permission]" : "";
|
|
856
|
-
return `- \`${t.name}\`${perm}: ${t.description.split("\n")[0]}`;
|
|
857
|
-
}).join("\n");
|
|
858
|
-
const base = `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
|
|
859
|
-
|
|
860
|
-
Environment:
|
|
861
|
-
- Working directory: ${opts2.cwd}
|
|
862
|
-
- Platform: ${platform()} ${release()}
|
|
863
|
-
- Shell: ${shell}
|
|
864
|
-
- Home: ${homedir3()}
|
|
865
|
-
- Today: ${date}
|
|
866
|
-
|
|
867
|
-
Tools available:
|
|
868
|
-
${toolsBlock}
|
|
1039
|
+
function buildStaticPrefix(opts2) {
|
|
1040
|
+
return `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
|
|
869
1041
|
|
|
870
1042
|
How to work:
|
|
871
1043
|
- Prefer calling tools over guessing. Read files before editing them. Use \`glob\` and \`grep\` to explore code before assuming structure.
|
|
@@ -878,13 +1050,39 @@ How to work:
|
|
|
878
1050
|
- If a request is ambiguous, ask one focused question instead of making large assumptions.
|
|
879
1051
|
- When you finish a task, stop. Do not add a closing summary.
|
|
880
1052
|
- 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.`;
|
|
1053
|
+
}
|
|
1054
|
+
function buildSessionPrefix(opts2) {
|
|
1055
|
+
const now2 = opts2.now ?? /* @__PURE__ */ new Date();
|
|
1056
|
+
const date = now2.toISOString().slice(0, 10);
|
|
1057
|
+
const shell = process.env.SHELL ? basename(process.env.SHELL) : "sh";
|
|
1058
|
+
const toolsBlock = opts2.tools.map((t) => {
|
|
1059
|
+
const perm = t.needsPermission ? " [needs user permission]" : "";
|
|
1060
|
+
return `- \`${t.name}\`${perm}: ${t.description.split("\n")[0]}`;
|
|
1061
|
+
}).join("\n");
|
|
1062
|
+
const env2 = `Environment:
|
|
1063
|
+
- Working directory: ${opts2.cwd}
|
|
1064
|
+
- Platform: ${platform()} ${release()}
|
|
1065
|
+
- Shell: ${shell}
|
|
1066
|
+
- Home: ${homedir3()}
|
|
1067
|
+
- Today: ${date}`;
|
|
1068
|
+
const tools = `Tools available:
|
|
1069
|
+
${toolsBlock}`;
|
|
881
1070
|
const ctx = loadContextFile(opts2.cwd);
|
|
882
1071
|
const contextBlock = ctx ? `
|
|
883
1072
|
|
|
884
1073
|
Project context from ${ctx.name} (${ctx.lineCount} lines, treat as authoritative):
|
|
885
1074
|
${ctx.content.trim()}` : "";
|
|
886
1075
|
const modeBlock = opts2.mode ? systemPromptForMode(opts2.mode) : "";
|
|
887
|
-
return
|
|
1076
|
+
return env2 + "\n\n" + tools + contextBlock + modeBlock;
|
|
1077
|
+
}
|
|
1078
|
+
function buildSystemPrompt(opts2) {
|
|
1079
|
+
return buildStaticPrefix(opts2) + "\n\n" + buildSessionPrefix(opts2);
|
|
1080
|
+
}
|
|
1081
|
+
function buildSystemMessages(opts2) {
|
|
1082
|
+
return [
|
|
1083
|
+
{ role: "system", content: buildStaticPrefix(opts2) },
|
|
1084
|
+
{ role: "system", content: buildSessionPrefix(opts2) }
|
|
1085
|
+
];
|
|
888
1086
|
}
|
|
889
1087
|
var CONTEXT_FILENAMES, MAX_CONTEXT_BYTES;
|
|
890
1088
|
var init_system_prompt = __esm({
|
|
@@ -937,7 +1135,7 @@ var init_paths = __esm({
|
|
|
937
1135
|
});
|
|
938
1136
|
|
|
939
1137
|
// src/tools/read.ts
|
|
940
|
-
import { readFile as readFile2, stat } from "fs/promises";
|
|
1138
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
941
1139
|
var MAX_BYTES, readTool;
|
|
942
1140
|
var init_read = __esm({
|
|
943
1141
|
"src/tools/read.ts"() {
|
|
@@ -961,7 +1159,7 @@ var init_read = __esm({
|
|
|
961
1159
|
render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
|
|
962
1160
|
async run(args, ctx) {
|
|
963
1161
|
const abs = resolvePath(ctx.cwd, args.path);
|
|
964
|
-
const st = await
|
|
1162
|
+
const st = await stat2(abs);
|
|
965
1163
|
if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
|
|
966
1164
|
const text = await readFile2(abs, "utf8");
|
|
967
1165
|
const lines = text.split("\n");
|
|
@@ -1073,7 +1271,7 @@ var init_edit = __esm({
|
|
|
1073
1271
|
// src/tools/bash.ts
|
|
1074
1272
|
import { spawn } from "child_process";
|
|
1075
1273
|
import { tmpdir } from "os";
|
|
1076
|
-
import { join as
|
|
1274
|
+
import { join as join5 } from "path";
|
|
1077
1275
|
function formatBashTitle(raw) {
|
|
1078
1276
|
let cmd = (raw ?? "").trim();
|
|
1079
1277
|
const m = cmd.match(/^cd\s+([^\s&;]+)\s*(?:&&|;)\s*(.*)$/);
|
|
@@ -1089,7 +1287,7 @@ function injectCoauthor(command, coauthor) {
|
|
|
1089
1287
|
const isRebaseContinue = /\bgit\s+rebase\b/.test(trimmed) && !/\b--abort\b|\b--skip\b/.test(trimmed);
|
|
1090
1288
|
const mentionsGit = /\bgit\b/.test(trimmed);
|
|
1091
1289
|
if (!createsCommit && !isRebaseContinue && !mentionsGit) return command;
|
|
1092
|
-
const tmpFile =
|
|
1290
|
+
const tmpFile = join5(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1093
1291
|
const amendBlock = `
|
|
1094
1292
|
if ! git log -1 --pretty=%B 2>/dev/null | grep -qF "${trailer}"; then
|
|
1095
1293
|
git log -1 --pretty=%B | git interpret-trailers --trailer "${trailer}" > "${tmpFile}" && git commit --amend -F "${tmpFile}" --no-edit && rm -f "${tmpFile}"
|
|
@@ -1600,16 +1798,16 @@ var init_executor = __esm({
|
|
|
1600
1798
|
// src/util/update-check.ts
|
|
1601
1799
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1602
1800
|
import { homedir as homedir5 } from "os";
|
|
1603
|
-
import { join as
|
|
1801
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
1604
1802
|
import { fileURLToPath } from "url";
|
|
1605
1803
|
function cachePath() {
|
|
1606
|
-
const xdg = process.env.XDG_CONFIG_HOME ||
|
|
1607
|
-
return
|
|
1804
|
+
const xdg = process.env.XDG_CONFIG_HOME || join6(homedir5(), ".config");
|
|
1805
|
+
return join6(xdg, "kimiflare", "update-check.json");
|
|
1608
1806
|
}
|
|
1609
1807
|
async function findPackageJson(startDir) {
|
|
1610
1808
|
let dir = startDir;
|
|
1611
1809
|
while (true) {
|
|
1612
|
-
const candidate =
|
|
1810
|
+
const candidate = join6(dir, "package.json");
|
|
1613
1811
|
try {
|
|
1614
1812
|
const raw = await readFile6(candidate, "utf8");
|
|
1615
1813
|
const parsed = JSON.parse(raw);
|
|
@@ -1712,11 +1910,15 @@ function indexOfNthUserFromEnd(messages, n) {
|
|
|
1712
1910
|
async function compactMessages(opts2) {
|
|
1713
1911
|
const keep = opts2.keepLastTurns ?? 4;
|
|
1714
1912
|
const messages = opts2.messages;
|
|
1715
|
-
|
|
1716
|
-
|
|
1913
|
+
let prefixEnd = 0;
|
|
1914
|
+
while (prefixEnd < messages.length && messages[prefixEnd].role === "system") {
|
|
1915
|
+
prefixEnd++;
|
|
1916
|
+
}
|
|
1917
|
+
const prefix = messages.slice(0, prefixEnd);
|
|
1918
|
+
if (prefix.length === 0) throw new Error("compact: no system message found");
|
|
1717
1919
|
const cutoffUserIdx = indexOfNthUserFromEnd(messages, keep);
|
|
1718
1920
|
const firstKeepIdx = cutoffUserIdx >= 0 ? cutoffUserIdx : messages.length;
|
|
1719
|
-
const toSummarize = messages.slice(
|
|
1921
|
+
const toSummarize = messages.slice(prefixEnd, firstKeepIdx);
|
|
1720
1922
|
const toKeep = messages.slice(firstKeepIdx);
|
|
1721
1923
|
if (toSummarize.length === 0) {
|
|
1722
1924
|
return { summary: "", newMessages: messages, replacedCount: 0 };
|
|
@@ -1758,7 +1960,7 @@ ${summary.trim()}`
|
|
|
1758
1960
|
};
|
|
1759
1961
|
return {
|
|
1760
1962
|
summary: summary.trim(),
|
|
1761
|
-
newMessages: [
|
|
1963
|
+
newMessages: [...prefix, summaryMsg, ...toKeep],
|
|
1762
1964
|
replacedCount: toSummarize.length
|
|
1763
1965
|
};
|
|
1764
1966
|
}
|
|
@@ -1778,6 +1980,413 @@ Do not include speculation. Do not include chat-style pleasantries. Use short bu
|
|
|
1778
1980
|
}
|
|
1779
1981
|
});
|
|
1780
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
|
+
|
|
1781
2390
|
// src/mcp/adapter.ts
|
|
1782
2391
|
function mcpToolToSpec(serverName, mcpTool, client) {
|
|
1783
2392
|
const prefix = `mcp_${sanitizeName(serverName)}_`;
|
|
@@ -3730,12 +4339,20 @@ var init_theme = __esm({
|
|
|
3730
4339
|
});
|
|
3731
4340
|
|
|
3732
4341
|
// src/sessions.ts
|
|
3733
|
-
|
|
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";
|
|
3734
4351
|
import { homedir as homedir6 } from "os";
|
|
3735
|
-
import { join as
|
|
4352
|
+
import { join as join7 } from "path";
|
|
3736
4353
|
function sessionsDir() {
|
|
3737
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3738
|
-
return
|
|
4354
|
+
const xdg = process.env.XDG_DATA_HOME || join7(homedir6(), ".local", "share");
|
|
4355
|
+
return join7(xdg, "kimiflare", "sessions");
|
|
3739
4356
|
}
|
|
3740
4357
|
function sanitize(text) {
|
|
3741
4358
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -3748,24 +4365,29 @@ function makeSessionId(firstPrompt) {
|
|
|
3748
4365
|
async function saveSession(file) {
|
|
3749
4366
|
const dir = sessionsDir();
|
|
3750
4367
|
await mkdir5(dir, { recursive: true });
|
|
3751
|
-
const path =
|
|
4368
|
+
const path = join7(dir, `${file.id}.json`);
|
|
3752
4369
|
await writeFile5(path, JSON.stringify(file, null, 2), "utf8");
|
|
3753
4370
|
return path;
|
|
3754
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
|
+
}
|
|
3755
4377
|
async function listSessions(limit = 30) {
|
|
3756
4378
|
const dir = sessionsDir();
|
|
3757
4379
|
let entries;
|
|
3758
4380
|
try {
|
|
3759
|
-
entries = await
|
|
4381
|
+
entries = await readdir2(dir);
|
|
3760
4382
|
} catch {
|
|
3761
4383
|
return [];
|
|
3762
4384
|
}
|
|
3763
4385
|
const summaries = [];
|
|
3764
4386
|
for (const name of entries) {
|
|
3765
4387
|
if (!name.endsWith(".json")) continue;
|
|
3766
|
-
const path =
|
|
4388
|
+
const path = join7(dir, name);
|
|
3767
4389
|
try {
|
|
3768
|
-
const [s, raw] = await Promise.all([
|
|
4390
|
+
const [s, raw] = await Promise.all([stat3(path), readFile7(path, "utf8")]);
|
|
3769
4391
|
const parsed = JSON.parse(raw);
|
|
3770
4392
|
const firstUser = parsed.messages.find((m) => m.role === "user");
|
|
3771
4393
|
const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : firstUser?.content ? firstUser.content.find((p) => p.type === "text")?.text ?? "(no prompt)" : "(no prompt)";
|
|
@@ -3790,6 +4412,7 @@ async function loadSession(filePath) {
|
|
|
3790
4412
|
var init_sessions = __esm({
|
|
3791
4413
|
"src/sessions.ts"() {
|
|
3792
4414
|
"use strict";
|
|
4415
|
+
init_storage_limits();
|
|
3793
4416
|
}
|
|
3794
4417
|
});
|
|
3795
4418
|
|
|
@@ -3835,17 +4458,21 @@ var init_image = __esm({
|
|
|
3835
4458
|
// src/usage-tracker.ts
|
|
3836
4459
|
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
3837
4460
|
import { homedir as homedir7 } from "os";
|
|
3838
|
-
import { join as
|
|
4461
|
+
import { join as join8 } from "path";
|
|
3839
4462
|
function usageDir() {
|
|
3840
|
-
const xdg = process.env.XDG_DATA_HOME ||
|
|
3841
|
-
return
|
|
4463
|
+
const xdg = process.env.XDG_DATA_HOME || join8(homedir7(), ".local", "share");
|
|
4464
|
+
return join8(xdg, "kimiflare");
|
|
3842
4465
|
}
|
|
3843
4466
|
function usagePath() {
|
|
3844
|
-
return
|
|
4467
|
+
return join8(usageDir(), "usage.json");
|
|
3845
4468
|
}
|
|
3846
4469
|
function today() {
|
|
3847
4470
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3848
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
|
+
}
|
|
3849
4476
|
async function loadLog() {
|
|
3850
4477
|
try {
|
|
3851
4478
|
const raw = await readFile9(usagePath(), "utf8");
|
|
@@ -3875,8 +4502,18 @@ function getOrCreateSession(log, sessionId, date) {
|
|
|
3875
4502
|
}
|
|
3876
4503
|
return session;
|
|
3877
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
|
+
}
|
|
3878
4515
|
async function recordUsage(sessionId, usage) {
|
|
3879
|
-
const log = await loadLog();
|
|
4516
|
+
const log = pruneUsageLog(await loadLog());
|
|
3880
4517
|
const date = today();
|
|
3881
4518
|
const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, usage.prompt_tokens_details?.cached_tokens ?? 0);
|
|
3882
4519
|
const day = getOrCreateDay(log, date);
|
|
@@ -3892,7 +4529,7 @@ async function recordUsage(sessionId, usage) {
|
|
|
3892
4529
|
await saveLog(log);
|
|
3893
4530
|
}
|
|
3894
4531
|
async function getCostReport(sessionId) {
|
|
3895
|
-
const log = await loadLog();
|
|
4532
|
+
const log = pruneUsageLog(await loadLog());
|
|
3896
4533
|
const date = today();
|
|
3897
4534
|
const currentMonth = date.slice(0, 7);
|
|
3898
4535
|
const session = log.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
|
|
@@ -3951,6 +4588,7 @@ var init_usage_tracker = __esm({
|
|
|
3951
4588
|
"src/usage-tracker.ts"() {
|
|
3952
4589
|
"use strict";
|
|
3953
4590
|
init_pricing();
|
|
4591
|
+
init_storage_limits();
|
|
3954
4592
|
LOG_VERSION2 = 1;
|
|
3955
4593
|
}
|
|
3956
4594
|
});
|
|
@@ -3963,13 +4601,24 @@ __export(app_exports, {
|
|
|
3963
4601
|
import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
|
|
3964
4602
|
import { Box as Box12, Text as Text13, useApp, useInput as useInput2, render } from "ink";
|
|
3965
4603
|
import { existsSync } from "fs";
|
|
3966
|
-
import { join as
|
|
3967
|
-
import { unlink } from "fs/promises";
|
|
4604
|
+
import { join as join9 } from "path";
|
|
4605
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
3968
4606
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3969
4607
|
function capEvents(prev) {
|
|
3970
4608
|
if (prev.length <= MAX_EVENTS) return prev;
|
|
3971
4609
|
return prev.slice(prev.length - MAX_EVENTS);
|
|
3972
4610
|
}
|
|
4611
|
+
function makePrefixMessages(cacheStable, model, mode, tools) {
|
|
4612
|
+
if (cacheStable) {
|
|
4613
|
+
return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
|
|
4614
|
+
}
|
|
4615
|
+
return [
|
|
4616
|
+
{
|
|
4617
|
+
role: "system",
|
|
4618
|
+
content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode })
|
|
4619
|
+
}
|
|
4620
|
+
];
|
|
4621
|
+
}
|
|
3973
4622
|
function findImagePaths(text) {
|
|
3974
4623
|
const paths = [];
|
|
3975
4624
|
for (const token of text.split(/\s+/)) {
|
|
@@ -4017,17 +4666,10 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4017
4666
|
const [verbose, setVerbose] = useState6(false);
|
|
4018
4667
|
const [hasUpdate, setHasUpdate] = useState6(initialUpdateResult?.hasUpdate ?? false);
|
|
4019
4668
|
const [latestVersion, setLatestVersion] = useState6(initialUpdateResult?.latestVersion ?? null);
|
|
4020
|
-
const
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
cwd: process.cwd(),
|
|
4025
|
-
tools: ALL_TOOLS,
|
|
4026
|
-
model: cfg?.model ?? DEFAULT_MODEL,
|
|
4027
|
-
mode: "edit"
|
|
4028
|
-
})
|
|
4029
|
-
}
|
|
4030
|
-
]);
|
|
4669
|
+
const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
|
|
4670
|
+
const messagesRef = useRef3(
|
|
4671
|
+
makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
|
|
4672
|
+
);
|
|
4031
4673
|
const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
|
|
4032
4674
|
const activeAsstIdRef = useRef3(null);
|
|
4033
4675
|
const activeControllerRef = useRef3(null);
|
|
@@ -4037,11 +4679,27 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4037
4679
|
const tasksRef = useRef3([]);
|
|
4038
4680
|
const usageRef = useRef3(null);
|
|
4039
4681
|
const updateCheckedRef = useRef3(false);
|
|
4682
|
+
const sessionStateRef = useRef3(emptySessionState());
|
|
4683
|
+
const artifactStoreRef = useRef3(new ArtifactStore());
|
|
4684
|
+
const compiledContextRef = useRef3(initialCfg?.compiledContext === true);
|
|
4040
4685
|
const updateNudgedRef = useRef3(false);
|
|
4041
4686
|
const compactSuggestedRef = useRef3(false);
|
|
4042
4687
|
const mcpManagerRef = useRef3(new McpManager());
|
|
4043
4688
|
const mcpToolsRef = useRef3([]);
|
|
4044
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]);
|
|
4045
4703
|
useEffect4(() => {
|
|
4046
4704
|
if (!cfg || updateCheckedRef.current) return;
|
|
4047
4705
|
updateCheckedRef.current = true;
|
|
@@ -4095,15 +4753,27 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4095
4753
|
}, [cfg, initialUpdateResult]);
|
|
4096
4754
|
useEffect4(() => {
|
|
4097
4755
|
modeRef.current = mode;
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4756
|
+
if (cacheStableRef.current) {
|
|
4757
|
+
messagesRef.current[1] = {
|
|
4758
|
+
role: "system",
|
|
4759
|
+
content: buildSessionPrefix({
|
|
4760
|
+
cwd: process.cwd(),
|
|
4761
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
4762
|
+
model: cfg?.model ?? DEFAULT_MODEL,
|
|
4763
|
+
mode
|
|
4764
|
+
})
|
|
4765
|
+
};
|
|
4766
|
+
} else {
|
|
4767
|
+
messagesRef.current[0] = {
|
|
4768
|
+
role: "system",
|
|
4769
|
+
content: buildSystemPrompt({
|
|
4770
|
+
cwd: process.cwd(),
|
|
4771
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
4772
|
+
model: cfg?.model ?? DEFAULT_MODEL,
|
|
4773
|
+
mode
|
|
4774
|
+
})
|
|
4775
|
+
};
|
|
4776
|
+
}
|
|
4107
4777
|
if (mode === "plan") {
|
|
4108
4778
|
executorRef.current.clearSessionPermissions();
|
|
4109
4779
|
}
|
|
@@ -4176,15 +4846,27 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4176
4846
|
}
|
|
4177
4847
|
}
|
|
4178
4848
|
if (totalTools > 0) {
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4849
|
+
if (cacheStableRef.current) {
|
|
4850
|
+
messagesRef.current[1] = {
|
|
4851
|
+
role: "system",
|
|
4852
|
+
content: buildSessionPrefix({
|
|
4853
|
+
cwd: process.cwd(),
|
|
4854
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
4855
|
+
model: cfg.model ?? DEFAULT_MODEL,
|
|
4856
|
+
mode: modeRef.current
|
|
4857
|
+
})
|
|
4858
|
+
};
|
|
4859
|
+
} else {
|
|
4860
|
+
messagesRef.current[0] = {
|
|
4861
|
+
role: "system",
|
|
4862
|
+
content: buildSystemPrompt({
|
|
4863
|
+
cwd: process.cwd(),
|
|
4864
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
4865
|
+
model: cfg.model ?? DEFAULT_MODEL,
|
|
4866
|
+
mode: modeRef.current
|
|
4867
|
+
})
|
|
4868
|
+
};
|
|
4869
|
+
}
|
|
4188
4870
|
setEvents((e) => [
|
|
4189
4871
|
...e,
|
|
4190
4872
|
{ kind: "info", key: mkKey(), text: `MCP connected \u2014 ${totalTools} external tool${totalTools === 1 ? "" : "s"} available` }
|
|
@@ -4219,7 +4901,8 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4219
4901
|
model: cfg.model,
|
|
4220
4902
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4221
4903
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4222
|
-
messages: messagesRef.current
|
|
4904
|
+
messages: messagesRef.current,
|
|
4905
|
+
sessionState: compiledContextRef.current ? sessionStateRef.current : void 0
|
|
4223
4906
|
});
|
|
4224
4907
|
} catch {
|
|
4225
4908
|
}
|
|
@@ -4284,29 +4967,55 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4284
4967
|
const controller = new AbortController();
|
|
4285
4968
|
activeControllerRef.current = controller;
|
|
4286
4969
|
try {
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
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
|
+
}
|
|
4299
4994
|
} else {
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
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
|
+
}
|
|
4310
5019
|
}
|
|
4311
5020
|
} catch (e) {
|
|
4312
5021
|
if (e.name !== "AbortError") {
|
|
@@ -4333,13 +5042,13 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4333
5042
|
}
|
|
4334
5043
|
const cwd = process.cwd();
|
|
4335
5044
|
for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
|
|
4336
|
-
if (existsSync(
|
|
5045
|
+
if (existsSync(join9(cwd, name))) {
|
|
4337
5046
|
setEvents((e) => [
|
|
4338
5047
|
...e,
|
|
4339
5048
|
{
|
|
4340
5049
|
kind: "info",
|
|
4341
5050
|
key: mkKey(),
|
|
4342
|
-
text: `${name} already exists at ${
|
|
5051
|
+
text: `${name} already exists at ${join9(cwd, name)} \u2014 delete it first if you want to regenerate`
|
|
4343
5052
|
}
|
|
4344
5053
|
]);
|
|
4345
5054
|
return;
|
|
@@ -4456,16 +5165,28 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4456
5165
|
})
|
|
4457
5166
|
}
|
|
4458
5167
|
});
|
|
4459
|
-
if (existsSync(
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
5168
|
+
if (existsSync(join9(cwd, "KIMI.md"))) {
|
|
5169
|
+
if (cacheStableRef.current) {
|
|
5170
|
+
messagesRef.current[1] = {
|
|
5171
|
+
role: "system",
|
|
5172
|
+
content: buildSessionPrefix({
|
|
5173
|
+
cwd,
|
|
5174
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
5175
|
+
model: cfg.model,
|
|
5176
|
+
mode: modeRef.current
|
|
5177
|
+
})
|
|
5178
|
+
};
|
|
5179
|
+
} else {
|
|
5180
|
+
messagesRef.current[0] = {
|
|
5181
|
+
role: "system",
|
|
5182
|
+
content: buildSystemPrompt({
|
|
5183
|
+
cwd,
|
|
5184
|
+
tools: [...ALL_TOOLS, ...mcpToolsRef.current],
|
|
5185
|
+
model: cfg.model,
|
|
5186
|
+
mode: modeRef.current
|
|
5187
|
+
})
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
4469
5190
|
setEvents((e) => [
|
|
4470
5191
|
...e,
|
|
4471
5192
|
{ kind: "info", key: mkKey(), text: "KIMI.md generated; context loaded for future turns" }
|
|
@@ -4493,6 +5214,10 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4493
5214
|
const file = await loadSession(picked.filePath);
|
|
4494
5215
|
messagesRef.current = file.messages;
|
|
4495
5216
|
sessionIdRef.current = file.id;
|
|
5217
|
+
if (file.sessionState && compiledContextRef.current) {
|
|
5218
|
+
sessionStateRef.current = file.sessionState;
|
|
5219
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
5220
|
+
}
|
|
4496
5221
|
setEvents([
|
|
4497
5222
|
{
|
|
4498
5223
|
kind: "info",
|
|
@@ -4549,8 +5274,14 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
4549
5274
|
return true;
|
|
4550
5275
|
}
|
|
4551
5276
|
if (c === "/clear") {
|
|
4552
|
-
|
|
5277
|
+
if (cacheStableRef.current && messagesRef.current.length >= 2) {
|
|
5278
|
+
messagesRef.current = [messagesRef.current[0], messagesRef.current[1]];
|
|
5279
|
+
} else {
|
|
5280
|
+
messagesRef.current = [messagesRef.current[0]];
|
|
5281
|
+
}
|
|
4553
5282
|
sessionIdRef.current = null;
|
|
5283
|
+
sessionStateRef.current = emptySessionState();
|
|
5284
|
+
artifactStoreRef.current = new ArtifactStore();
|
|
4554
5285
|
setEvents([]);
|
|
4555
5286
|
setUsage(null);
|
|
4556
5287
|
setTasks([]);
|
|
@@ -4778,7 +5509,7 @@ use: /thinking low | medium | high`
|
|
|
4778
5509
|
return true;
|
|
4779
5510
|
}
|
|
4780
5511
|
if (c === "/logout") {
|
|
4781
|
-
|
|
5512
|
+
unlink2(configPath()).catch(() => {
|
|
4782
5513
|
});
|
|
4783
5514
|
setEvents((e) => [
|
|
4784
5515
|
...e,
|
|
@@ -4839,6 +5570,17 @@ use: /thinking low | medium | high`
|
|
|
4839
5570
|
}
|
|
4840
5571
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
|
|
4841
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
|
+
}
|
|
4842
5584
|
setBusy(true);
|
|
4843
5585
|
setTurnStartedAt(Date.now());
|
|
4844
5586
|
const controller = new AbortController();
|
|
@@ -4948,6 +5690,26 @@ use: /thinking low | medium | high`
|
|
|
4948
5690
|
}
|
|
4949
5691
|
});
|
|
4950
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
|
+
}
|
|
4951
5713
|
} catch (e) {
|
|
4952
5714
|
if (e.name === "AbortError") {
|
|
4953
5715
|
setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
|
|
@@ -5144,6 +5906,8 @@ var init_app = __esm({
|
|
|
5144
5906
|
init_loop();
|
|
5145
5907
|
init_system_prompt();
|
|
5146
5908
|
init_compact();
|
|
5909
|
+
init_compaction();
|
|
5910
|
+
init_session_state();
|
|
5147
5911
|
init_executor();
|
|
5148
5912
|
init_manager();
|
|
5149
5913
|
init_messages();
|
|
@@ -5188,11 +5952,11 @@ init_update_check();
|
|
|
5188
5952
|
import { Command } from "commander";
|
|
5189
5953
|
import { readFileSync as readFileSync2 } from "fs";
|
|
5190
5954
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5191
|
-
import { dirname as dirname3, join as
|
|
5955
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
5192
5956
|
function readPackageVersion() {
|
|
5193
5957
|
try {
|
|
5194
5958
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
5195
|
-
const pkg = JSON.parse(readFileSync2(
|
|
5959
|
+
const pkg = JSON.parse(readFileSync2(join10(here, "..", "package.json"), "utf8"));
|
|
5196
5960
|
return pkg.version ?? "0.0.0";
|
|
5197
5961
|
} catch {
|
|
5198
5962
|
return "0.0.0";
|