kimiflare 0.39.1 → 0.41.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 CHANGED
@@ -1861,6 +1861,9 @@ var init_code_mode = __esm({
1861
1861
  });
1862
1862
 
1863
1863
  // src/agent/loop.ts
1864
+ function isHighSignalMemory(memory) {
1865
+ return memory.topicKey === "project_dependencies" || memory.topicKey === "project_tsconfig" || memory.topicKey === "project_entry_point" || memory.category === "instruction" || memory.category === "preference" || memory.category === "event" && memory.importance >= 3;
1866
+ }
1864
1867
  async function runAgentTurn(opts2) {
1865
1868
  const turnStart = performance.now();
1866
1869
  const max = opts2.maxToolIterations ?? 50;
@@ -2260,6 +2263,15 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2260
2263
  void 0,
2261
2264
  memory.topicKey
2262
2265
  );
2266
+ if (isHighSignalMemory(memory)) {
2267
+ const sid = opts2.sessionId ?? "default";
2268
+ const current = (driftAccumulator.get(sid) ?? 0) + 1;
2269
+ driftAccumulator.set(sid, current);
2270
+ if (current >= DRIFT_THRESHOLD) {
2271
+ opts2.callbacks.onKimiMdStale?.();
2272
+ driftAccumulator.set(sid, 0);
2273
+ }
2274
+ }
2263
2275
  }
2264
2276
  } catch {
2265
2277
  }
@@ -2271,6 +2283,12 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2271
2283
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2272
2284
  }
2273
2285
  }
2286
+ if (opts2.sessionId) {
2287
+ const current = driftAccumulator.get(opts2.sessionId) ?? 0;
2288
+ if (current > 0) {
2289
+ driftAccumulator.set(opts2.sessionId, Math.max(0, current - 1));
2290
+ }
2291
+ }
2274
2292
  if (opts2.onIterationEnd) {
2275
2293
  opts2.messages = await opts2.onIterationEnd(opts2.messages, opts2.signal);
2276
2294
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
@@ -2303,7 +2321,7 @@ function validateToolArguments(raw) {
2303
2321
  return "{}";
2304
2322
  }
2305
2323
  }
2306
- var BudgetExhaustedError, codeModeApiCache;
2324
+ var BudgetExhaustedError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD;
2307
2325
  var init_loop = __esm({
2308
2326
  "src/agent/loop.ts"() {
2309
2327
  "use strict";
@@ -2321,6 +2339,8 @@ var init_loop = __esm({
2321
2339
  }
2322
2340
  };
2323
2341
  codeModeApiCache = /* @__PURE__ */ new Map();
2342
+ driftAccumulator = /* @__PURE__ */ new Map();
2343
+ DRIFT_THRESHOLD = 5;
2324
2344
  }
2325
2345
  });
2326
2346
 
@@ -2654,9 +2674,6 @@ function collapsePath(input, cwd, maxLen = 40) {
2654
2674
  if (parts.length <= 2) return input;
2655
2675
  return `\u2026/${parts.slice(-2).join(sep)}`;
2656
2676
  }
2657
- function collapsePathsInText(s, cwd, maxLen = 40) {
2658
- return s.replace(/([~/][^\s"',)}\]]+)/g, (match) => collapsePath(match, cwd, maxLen));
2659
- }
2660
2677
  var init_paths = __esm({
2661
2678
  "src/util/paths.ts"() {
2662
2679
  "use strict";
@@ -6719,16 +6736,191 @@ var init_diff_view = __esm({
6719
6736
  }
6720
6737
  });
6721
6738
 
6739
+ // src/ui/narrative.ts
6740
+ function countByCategory(items) {
6741
+ let reads = 0;
6742
+ let writes = 0;
6743
+ let shells = 0;
6744
+ let webs = 0;
6745
+ let memories = 0;
6746
+ let others = 0;
6747
+ for (const t of items) {
6748
+ if (READING_TOOLS.has(t.name)) reads++;
6749
+ else if (WRITING_TOOLS.has(t.name)) writes++;
6750
+ else if (SHELL_TOOLS.has(t.name)) shells++;
6751
+ else if (WEB_TOOLS.has(t.name)) webs++;
6752
+ else if (MEMORY_TOOLS.has(t.name)) memories++;
6753
+ else others++;
6754
+ }
6755
+ return { reads, writes, shells, webs, memories, others };
6756
+ }
6757
+ function pickOne(arr) {
6758
+ return arr[Math.floor(Math.random() * arr.length)];
6759
+ }
6760
+ function generateActivityText(items, _ctx) {
6761
+ if (items.length === 0) return null;
6762
+ const { reads, writes, shells, webs, memories } = countByCategory(items);
6763
+ const total = items.length;
6764
+ if (total === 1) {
6765
+ const t = items[0];
6766
+ if (t.name === "read") {
6767
+ const path = typeof t.args?.path === "string" ? t.args.path : "a file";
6768
+ return pickOne([`Reading ${path}\u2026`, `Opening ${path}\u2026`, `Taking a look at ${path}\u2026`]);
6769
+ }
6770
+ if (t.name === "grep") {
6771
+ const pattern = typeof t.args?.pattern === "string" ? `"${t.args.pattern}"` : "for patterns";
6772
+ return pickOne([`Searching for ${pattern}\u2026`, `Hunting for ${pattern}\u2026`]);
6773
+ }
6774
+ if (t.name === "glob") {
6775
+ const pattern = typeof t.args?.pattern === "string" ? t.args.pattern : "files";
6776
+ return pickOne([`Finding ${pattern}\u2026`, `Gathering ${pattern}\u2026`]);
6777
+ }
6778
+ if (t.name === "write") {
6779
+ const path = typeof t.args?.path === "string" ? t.args.path : "a file";
6780
+ return pickOne([`Creating ${path}\u2026`, `Writing ${path}\u2026`]);
6781
+ }
6782
+ if (t.name === "edit") {
6783
+ const path = typeof t.args?.path === "string" ? t.args.path : "a file";
6784
+ return pickOne([`Patching ${path}\u2026`, `Editing ${path}\u2026`]);
6785
+ }
6786
+ if (t.name === "bash") {
6787
+ return pickOne([`Running a shell command\u2026`, `Executing something in the terminal\u2026`]);
6788
+ }
6789
+ if (t.name === "web_fetch") {
6790
+ return pickOne([`Fetching docs\u2026`, `Checking a reference\u2026`, `Looking something up\u2026`]);
6791
+ }
6792
+ if (t.name === "memory_remember") {
6793
+ return pickOne([`Taking notes for next time\u2026`, `Committing that to memory\u2026`]);
6794
+ }
6795
+ if (t.name === "memory_recall") {
6796
+ return pickOne([`Recalling what we know\u2026`, `Searching past notes\u2026`]);
6797
+ }
6798
+ return null;
6799
+ }
6800
+ if (webs >= 2) {
6801
+ return pickOne([`Digging through documentation\u2026`, `Cross-referencing sources\u2026`, `Reading up on this\u2026`]);
6802
+ }
6803
+ if (reads >= 2 && writes === 0 && shells === 0) {
6804
+ return pickOne([`Surveying the landscape\u2026`, `Getting the lay of the land\u2026`, `Mapping out the files\u2026`]);
6805
+ }
6806
+ if (reads >= 1 && (writes >= 1 || shells >= 1)) {
6807
+ return pickOne([`Reading, then making changes\u2026`, `Exploring and editing\u2026`, `Survey and patch\u2026`]);
6808
+ }
6809
+ if (writes >= 1 && shells >= 1) {
6810
+ return pickOne([`Committing the changes\u2026`, `Writing and verifying\u2026`, `Editing and checking\u2026`]);
6811
+ }
6812
+ if (reads >= 1 && webs >= 1) {
6813
+ return pickOne([`Exploring the codebase and docs\u2026`, `Cross-referencing code with references\u2026`]);
6814
+ }
6815
+ if (memories >= 1) {
6816
+ return pickOne([`Jogging the memory\u2026`, `Checking past notes\u2026`]);
6817
+ }
6818
+ if (shells >= 1) {
6819
+ return pickOne([`Running some commands\u2026`, `Working in the shell\u2026`]);
6820
+ }
6821
+ return null;
6822
+ }
6823
+ function narrativizeInfo(text, ctx) {
6824
+ if (ctx?.tier === "heavy") {
6825
+ return { kind: "activity", text: "Sizing this up\u2026 feels like a deep one.", feature: "triage" };
6826
+ }
6827
+ if (ctx?.tier === "light") {
6828
+ return { kind: "activity", text: "Quick check \u2014 this looks light.", feature: "triage" };
6829
+ }
6830
+ if (ctx?.tier === "medium") {
6831
+ return { kind: "activity", text: "This one feels medium weight.", feature: "triage" };
6832
+ }
6833
+ if (ctx?.codeMode) {
6834
+ return { kind: "activity", text: "The toolbox feels right for this. Switching to code mode\u2026", feature: "code" };
6835
+ }
6836
+ if (text.includes("auto-compacted") || text.includes("compacted")) {
6837
+ return { kind: "activity", text: "Making room by summarizing older turns\u2026", feature: "compact" };
6838
+ }
6839
+ if (text.includes("recalled") && text.includes("memory")) {
6840
+ return { kind: "activity", text: "Remembering what we learned before\u2026", feature: "memory" };
6841
+ }
6842
+ if (text.includes("memory cleanup") || text.includes("memory backfill")) {
6843
+ return null;
6844
+ }
6845
+ if (text.startsWith("LSP ready")) {
6846
+ return { kind: "activity", text: "Waking up the language servers\u2026" };
6847
+ }
6848
+ if (text.startsWith("LSP reload complete")) {
6849
+ return null;
6850
+ }
6851
+ if (text.startsWith("MCP connected")) {
6852
+ return { kind: "activity", text: "Plugging in external tools\u2026" };
6853
+ }
6854
+ if (text.includes("research budget") || text.includes("web request")) {
6855
+ return { kind: "activity", text: "Researching\u2026 gathering what we can from the web.", feature: "explore" };
6856
+ }
6857
+ return null;
6858
+ }
6859
+ function humanizeToolTitle(name, args) {
6860
+ const path = typeof args?.path === "string" ? args.path : void 0;
6861
+ const pattern = typeof args?.pattern === "string" ? args.pattern : void 0;
6862
+ const command = typeof args?.command === "string" ? args.command : void 0;
6863
+ const url = typeof args?.url === "string" ? args.url : void 0;
6864
+ switch (name) {
6865
+ case "read":
6866
+ return path ? `Reading ${path}` : "Reading a file\u2026";
6867
+ case "grep":
6868
+ return pattern ? `Searching for "${pattern}"` : "Searching\u2026";
6869
+ case "glob":
6870
+ return pattern ? `Finding ${pattern}` : "Finding files\u2026";
6871
+ case "write":
6872
+ return path ? `Creating ${path}` : "Creating a file\u2026";
6873
+ case "edit":
6874
+ return path ? `Patching ${path}` : "Patching a file\u2026";
6875
+ case "bash":
6876
+ return command ? `Running: ${command.split(/\s+/).slice(0, 4).join(" ")}${command.split(/\s+/).length > 4 ? "\u2026" : ""}` : "Running shell command\u2026";
6877
+ case "web_fetch":
6878
+ return url ? `Fetching ${url.replace(/^https?:\/\//, "").split("/")[0]}` : "Fetching docs\u2026";
6879
+ case "memory_remember":
6880
+ return "Committing to memory\u2026";
6881
+ case "memory_recall":
6882
+ return "Recalling memories\u2026";
6883
+ case "memory_forget":
6884
+ return "Forgetting a memory\u2026";
6885
+ case "lsp_hover":
6886
+ return "Inspecting symbol\u2026";
6887
+ case "lsp_definition":
6888
+ return "Jumping to definition\u2026";
6889
+ case "lsp_references":
6890
+ return "Finding references\u2026";
6891
+ case "lsp_implementation":
6892
+ return "Finding implementations\u2026";
6893
+ case "lsp_typeDefinition":
6894
+ return "Finding type definition\u2026";
6895
+ case "lsp_documentSymbols":
6896
+ return "Listing symbols\u2026";
6897
+ case "lsp_workspaceSymbol":
6898
+ return "Searching workspace symbols\u2026";
6899
+ case "lsp_rename":
6900
+ return "Renaming symbol\u2026";
6901
+ case "lsp_codeAction":
6902
+ return "Applying quick fix\u2026";
6903
+ default:
6904
+ return name;
6905
+ }
6906
+ }
6907
+ var READING_TOOLS, WRITING_TOOLS, SHELL_TOOLS, WEB_TOOLS, MEMORY_TOOLS;
6908
+ var init_narrative = __esm({
6909
+ "src/ui/narrative.ts"() {
6910
+ "use strict";
6911
+ READING_TOOLS = /* @__PURE__ */ new Set(["read", "glob", "grep", "lsp_hover", "lsp_definition", "lsp_references", "lsp_implementation", "lsp_typeDefinition", "lsp_documentSymbols", "lsp_workspaceSymbol"]);
6912
+ WRITING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "lsp_rename", "lsp_codeAction"]);
6913
+ SHELL_TOOLS = /* @__PURE__ */ new Set(["bash"]);
6914
+ WEB_TOOLS = /* @__PURE__ */ new Set(["web_fetch"]);
6915
+ MEMORY_TOOLS = /* @__PURE__ */ new Set(["memory_remember", "memory_recall", "memory_forget"]);
6916
+ }
6917
+ });
6918
+
6722
6919
  // src/ui/tool-view.tsx
6723
6920
  import React2 from "react";
6724
6921
  import { Box as Box2, Text as Text2 } from "ink";
6725
6922
  import Spinner from "ink-spinner";
6726
6923
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
6727
- function compactArgs(raw) {
6728
- const collapsed = collapsePathsInText(raw, process.cwd());
6729
- const s = collapsed.replace(/\s+/g, " ");
6730
- return s.length <= 80 ? s : s.slice(0, 80) + "\u2026";
6731
- }
6732
6924
  function firstLine(s) {
6733
6925
  const line = s.split("\n").find((l) => l.trim().length > 0) ?? "";
6734
6926
  return line.length <= 120 ? line : line.slice(0, 120) + "\u2026";
@@ -6740,10 +6932,18 @@ var init_tool_view = __esm({
6740
6932
  init_diff_view();
6741
6933
  init_paths();
6742
6934
  init_theme_context();
6935
+ init_narrative();
6743
6936
  ToolView = React2.memo(function ToolView2({ evt, verbose }) {
6744
6937
  const theme = useTheme();
6745
6938
  const statusIcon = evt.status === "running" ? /* @__PURE__ */ jsx3(Text2, { color: theme.info.color, children: /* @__PURE__ */ jsx3(Spinner, { type: "dots" }) }) : evt.status === "error" ? /* @__PURE__ */ jsx3(Text2, { color: theme.palette.error, children: "\u2717" }) : /* @__PURE__ */ jsx3(Text2, { color: theme.palette.success, children: "\u2713" });
6746
- const title = evt.render?.title ?? `${evt.name}(${compactArgs(evt.args)})`;
6939
+ const title = evt.render?.title ?? (() => {
6940
+ try {
6941
+ const args = evt.args ? JSON.parse(evt.args) : {};
6942
+ return humanizeToolTitle(evt.name, args);
6943
+ } catch {
6944
+ return humanizeToolTitle(evt.name);
6945
+ }
6946
+ })();
6747
6947
  const expand = Boolean(evt.expanded || verbose);
6748
6948
  const lines = evt.result ? evt.result.split("\n") : [];
6749
6949
  const showLimit = verbose ? 200 : 20;
@@ -6958,12 +7158,13 @@ var init_chat = __esm({
6958
7158
  init_tool_view();
6959
7159
  init_markdown();
6960
7160
  init_theme_context();
6961
- ChatView = React4.memo(function ChatView2({ events, showReasoning, verbose }) {
7161
+ ChatView = React4.memo(function ChatView2({ events, showReasoning, verbose, suppressTools }) {
6962
7162
  const theme = useTheme();
6963
7163
  const finalized = [];
6964
7164
  const active = [];
6965
7165
  for (let i = 0; i < events.length; i++) {
6966
7166
  const e = events[i];
7167
+ if (suppressTools && e.kind === "tool") continue;
6967
7168
  const isStreaming = e.kind === "assistant" && e.streaming;
6968
7169
  if (isStreaming) {
6969
7170
  active.push(e);
@@ -7035,6 +7236,12 @@ var init_chat = __esm({
7035
7236
  evt.text
7036
7237
  ] });
7037
7238
  }
7239
+ if (evt.kind === "activity") {
7240
+ return /* @__PURE__ */ jsxs4(Text4, { italic: true, color: theme.info.dim ? theme.info.color : theme.palette.secondary, children: [
7241
+ "~ ",
7242
+ evt.text
7243
+ ] });
7244
+ }
7038
7245
  return /* @__PURE__ */ jsxs4(Text4, { color: theme.error, children: [
7039
7246
  "! ",
7040
7247
  evt.text
@@ -7066,7 +7273,7 @@ import { useEffect, useState } from "react";
7066
7273
  import { Box as Box5, Text as Text5 } from "ink";
7067
7274
  import Spinner3 from "ink-spinner";
7068
7275
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
7069
- function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode, effort, contextLimit, hasUpdate, latestVersion, gatewayMeta, codeMode, cloudMode, cloudBudget }) {
7276
+ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode, effort, contextLimit, hasUpdate, latestVersion, gatewayMeta, codeMode, cloudMode, cloudBudget, kimiMdStale }) {
7070
7277
  const theme = useTheme();
7071
7278
  const [now2, setNow] = useState(Date.now());
7072
7279
  const modeColor = mode === "plan" ? theme.modeBadge.plan : mode === "auto" ? theme.modeBadge.auto : theme.modeBadge.edit;
@@ -7109,6 +7316,10 @@ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode,
7109
7316
  "update available",
7110
7317
  latestVersion ? ` \u2192 ${latestVersion}` : "",
7111
7318
  " \xB7 run /update"
7319
+ ] }) : null,
7320
+ kimiMdStale ? /* @__PURE__ */ jsxs5(Text5, { color: theme.warn, bold: true, children: [
7321
+ " \xB7 ",
7322
+ "\u26A0 KIMI.md stale \xB7 run /init"
7112
7323
  ] }) : null
7113
7324
  ] })
7114
7325
  ] });
@@ -9525,11 +9736,21 @@ var init_usage_tracker = __esm({
9525
9736
  }
9526
9737
  });
9527
9738
 
9739
+ // src/memory/schema.ts
9740
+ var DEFAULT_EMBEDDING_DIM;
9741
+ var init_schema = __esm({
9742
+ "src/memory/schema.ts"() {
9743
+ "use strict";
9744
+ DEFAULT_EMBEDDING_DIM = 768;
9745
+ }
9746
+ });
9747
+
9528
9748
  // src/memory/db.ts
9529
9749
  var db_exports = {};
9530
9750
  __export(db_exports, {
9531
9751
  clearMemoriesForRepo: () => clearMemoriesForRepo,
9532
9752
  closeMemoryDb: () => closeMemoryDb,
9753
+ countHighSignalMemoriesSince: () => countHighSignalMemoriesSince,
9533
9754
  deleteExcessMemories: () => deleteExcessMemories,
9534
9755
  deleteMemoriesByIds: () => deleteMemoriesByIds,
9535
9756
  deleteOldMemories: () => deleteOldMemories,
@@ -9916,6 +10137,19 @@ function getMemoryById(db, id) {
9916
10137
  const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
9917
10138
  return row ? rowToMemory(row) : null;
9918
10139
  }
10140
+ function countHighSignalMemoriesSince(db, repoPath, since) {
10141
+ const row = db.prepare(
10142
+ `SELECT COUNT(*) as count FROM memories
10143
+ WHERE repo_path = ? AND created_at > ?
10144
+ AND forgotten = 0 AND superseded_by IS NULL
10145
+ AND (
10146
+ topic_key IN ('project_dependencies', 'project_tsconfig', 'project_entry_point')
10147
+ OR category IN ('instruction', 'preference')
10148
+ OR (category = 'event' AND importance >= 3)
10149
+ )`
10150
+ ).get(repoPath, since);
10151
+ return row?.count ?? 0;
10152
+ }
9919
10153
  var dbInstance, dbPathInstance;
9920
10154
  var init_db = __esm({
9921
10155
  "src/memory/db.ts"() {
@@ -10277,6 +10511,7 @@ var init_manager3 = __esm({
10277
10511
  "src/memory/manager.ts"() {
10278
10512
  "use strict";
10279
10513
  init_client();
10514
+ init_schema();
10280
10515
  init_db();
10281
10516
  init_embeddings();
10282
10517
  init_retrieval();
@@ -10406,6 +10641,48 @@ Return a JSON array of strings. Example:
10406
10641
  }
10407
10642
  return { id: memory.id, superseded: supersededIds.length > 0 ? supersededIds : void 0 };
10408
10643
  }
10644
+ /**
10645
+ * Count high-signal memories created since the given timestamp.
10646
+ * Used for KIMI.md drift detection (Trigger A: session-start check).
10647
+ */
10648
+ countHighSignalMemoriesSince(repoPath, since) {
10649
+ if (!this.db) return 0;
10650
+ return countHighSignalMemoriesSince(this.db, repoPath, since);
10651
+ }
10652
+ /**
10653
+ * Get the timestamp of the most recent KIMI.md refresh memory.
10654
+ * Returns 0 if none exists.
10655
+ */
10656
+ getLastKimiMdRefreshTime(repoPath) {
10657
+ if (!this.db) return 0;
10658
+ const rows = this.db.prepare(
10659
+ `SELECT created_at FROM memories
10660
+ WHERE repo_path = ? AND topic_key = 'kimi_md_refresh'
10661
+ AND forgotten = 0 AND superseded_by IS NULL
10662
+ ORDER BY created_at DESC LIMIT 1`
10663
+ ).all(repoPath);
10664
+ return rows[0]?.created_at ?? 0;
10665
+ }
10666
+ /**
10667
+ * Record that KIMI.md was refreshed. Creates a lightweight memory
10668
+ * so drift detection knows when the snapshot was last updated.
10669
+ */
10670
+ async recordKimiMdRefresh(repoPath, sessionId) {
10671
+ if (!this.db) return;
10672
+ const embedding = new Float32Array(DEFAULT_EMBEDDING_DIM);
10673
+ insertMemory(
10674
+ this.db,
10675
+ {
10676
+ content: `KIMI.md refreshed for ${repoPath}`,
10677
+ category: "event",
10678
+ sourceSessionId: sessionId,
10679
+ repoPath,
10680
+ importance: 2,
10681
+ topicKey: "kimi_md_refresh"
10682
+ },
10683
+ embedding
10684
+ );
10685
+ }
10409
10686
  /**
10410
10687
  * Recall memories using the full hybrid retrieval pipeline.
10411
10688
  */
@@ -13289,6 +13566,7 @@ function App({
13289
13566
  const [latestVersion, setLatestVersion] = useState10(initialUpdateResult?.latestVersion ?? null);
13290
13567
  const [theme, setTheme] = useState10(resolveTheme(initialCfg?.theme));
13291
13568
  const [showThemePicker, setShowThemePicker] = useState10(false);
13569
+ const [kimiMdStale, setKimiMdStale] = useState10(false);
13292
13570
  useEffect6(() => {
13293
13571
  if (!cfg?.cloudMode || !initialCloudToken) return;
13294
13572
  let cancelled = false;
@@ -13319,6 +13597,8 @@ function App({
13319
13597
  const permResolveRef = useRef3(null);
13320
13598
  const limitResolveRef = useRef3(null);
13321
13599
  const pendingToolCallsRef = useRef3(/* @__PURE__ */ new Map());
13600
+ const toolBatchRef = useRef3([]);
13601
+ const toolBatchTimerRef = useRef3(null);
13322
13602
  const sessionIdRef = useRef3(null);
13323
13603
  const modeRef = useRef3(mode);
13324
13604
  const effortRef = useRef3(effort);
@@ -13342,6 +13622,8 @@ function App({
13342
13622
  const busyRef = useRef3(busy);
13343
13623
  const memoryManagerRef = useRef3(null);
13344
13624
  const sessionStartRecallRef = useRef3(null);
13625
+ const kimiMdStaleNudgedRef = useRef3(false);
13626
+ const turnCounterRef = useRef3(0);
13345
13627
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
13346
13628
  const flushTimeoutRef = useRef3(null);
13347
13629
  const customCommandsRef = useRef3([]);
@@ -13565,12 +13847,19 @@ function App({
13565
13847
  messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
13566
13848
  setEvents((e) => [
13567
13849
  ...e,
13568
- { kind: "memory", key: mkKey(), text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} about this repo` }
13850
+ { kind: "activity", key: mkKey(), text: "Remembering what we know about this repo\u2026", feature: "memory" }
13569
13851
  ]);
13570
13852
  }
13571
13853
  } catch {
13572
13854
  }
13573
13855
  })();
13856
+ if (existsSync3(join22(cwd, "KIMI.md"))) {
13857
+ const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
13858
+ const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
13859
+ if (driftCount >= 5) {
13860
+ setKimiMdStale(true);
13861
+ }
13862
+ }
13574
13863
  } else {
13575
13864
  memoryManagerRef.current?.close();
13576
13865
  memoryManagerRef.current = null;
@@ -13784,7 +14073,7 @@ function App({
13784
14073
  }
13785
14074
  setEvents((e) => [
13786
14075
  ...e,
13787
- { kind: "info", key: mkKey(), text: `MCP connected \u2014 ${totalTools} external tool${totalTools === 1 ? "" : "s"} available` }
14076
+ { kind: "activity", key: mkKey(), text: "Plugging in external tools\u2026" }
13788
14077
  ]);
13789
14078
  }
13790
14079
  }, [cfg]);
@@ -13842,7 +14131,7 @@ function App({
13842
14131
  }
13843
14132
  setEvents((e) => [
13844
14133
  ...e,
13845
- { kind: "info", key: mkKey(), text: `LSP ready \u2014 ${totalServers} server${totalServers === 1 ? "" : "s"} active` }
14134
+ { kind: "activity", key: mkKey(), text: "Waking up the language servers\u2026" }
13846
14135
  ]);
13847
14136
  } else {
13848
14137
  setEvents((e) => [
@@ -14079,6 +14368,16 @@ function App({
14079
14368
  },
14080
14369
  []
14081
14370
  );
14371
+ const flushToolBatch = useCallback2(() => {
14372
+ toolBatchTimerRef.current = null;
14373
+ const batch = toolBatchRef.current;
14374
+ toolBatchRef.current = [];
14375
+ if (batch.length === 0) return;
14376
+ const text = generateActivityText(batch, { mode: modeRef.current });
14377
+ if (text) {
14378
+ setEvents((e) => [...e, { kind: "activity", key: mkKey(), text, feature: "explore" }]);
14379
+ }
14380
+ }, []);
14082
14381
  const updateGatewayMeta = useCallback2((meta) => {
14083
14382
  gatewayMetaRef.current = meta;
14084
14383
  setGatewayMeta(meta);
@@ -14255,11 +14554,16 @@ function App({
14255
14554
  pendingToolCallsRef.current.set(call.id, call.function.name);
14256
14555
  const spec = executorRef.current.list().find((t) => t.name === call.function.name);
14257
14556
  let renderMeta;
14557
+ let argsParsed = {};
14258
14558
  try {
14259
- const args = call.function.arguments ? JSON.parse(call.function.arguments) : {};
14260
- renderMeta = spec?.render?.(args);
14559
+ argsParsed = call.function.arguments ? JSON.parse(call.function.arguments) : {};
14560
+ renderMeta = spec?.render?.(argsParsed);
14261
14561
  } catch {
14262
14562
  }
14563
+ toolBatchRef.current.push({ name: call.function.name, args: argsParsed });
14564
+ if (toolBatchTimerRef.current) clearTimeout(toolBatchTimerRef.current);
14565
+ toolBatchTimerRef.current = setTimeout(flushToolBatch, 120);
14566
+ if (modeRef.current === "plan") return;
14263
14567
  setEvents((e) => [
14264
14568
  ...e,
14265
14569
  {
@@ -14327,7 +14631,17 @@ function App({
14327
14631
  }
14328
14632
  permResolveRef.current = resolve2;
14329
14633
  setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
14330
- })
14634
+ }),
14635
+ onKimiMdStale: () => {
14636
+ if (!kimiMdStaleNudgedRef.current) {
14637
+ kimiMdStaleNudgedRef.current = true;
14638
+ setKimiMdStale(true);
14639
+ setEvents((e) => [
14640
+ ...e,
14641
+ { kind: "info", key: mkKey(), text: "Project context may be stale. Run /init to refresh KIMI.md based on recent changes." }
14642
+ ]);
14643
+ }
14644
+ }
14331
14645
  }
14332
14646
  });
14333
14647
  if (existsSync3(join22(cwd, "KIMI.md"))) {
@@ -14356,6 +14670,9 @@ function App({
14356
14670
  ...e,
14357
14671
  { kind: "info", key: mkKey(), text: "KIMI.md generated; context loaded for future turns" }
14358
14672
  ]);
14673
+ void memoryManagerRef.current?.recordKimiMdRefresh(cwd, ensureSessionId());
14674
+ setKimiMdStale(false);
14675
+ kimiMdStaleNudgedRef.current = false;
14359
14676
  }
14360
14677
  } catch (e) {
14361
14678
  if (e.name === "AbortError") {
@@ -14387,8 +14704,15 @@ function App({
14387
14704
  permResolveRef.current = null;
14388
14705
  limitResolveRef.current = null;
14389
14706
  pendingToolCallsRef.current.clear();
14707
+ tasksRef.current = [];
14708
+ setTasks([]);
14709
+ setTasksStartedAt(null);
14710
+ setTasksStartTokens(0);
14711
+ setEvents(
14712
+ (evts) => evts.map((e) => e.kind === "tool" && e.status === "running" ? { ...e, status: "error", result: "(interrupted)" } : e)
14713
+ );
14390
14714
  }
14391
- }, [cfg, busy, updateAssistant, updateTool, updateGatewayMeta]);
14715
+ }, [cfg, busy, updateAssistant, updateTool, updateGatewayMeta, flushToolBatch]);
14392
14716
  const handleThemePick = useCallback2(
14393
14717
  (picked) => {
14394
14718
  setShowThemePicker(false);
@@ -15295,6 +15619,13 @@ ${lines.join("\n")}` }]);
15295
15619
  };
15296
15620
  }
15297
15621
  }
15622
+ turnCounterRef.current += 1;
15623
+ if (turnCounterRef.current % 15 === 0 && existsSync3(join22(process.cwd(), "KIMI.md")) && !kimiMdStale) {
15624
+ setEvents((e) => [
15625
+ ...e,
15626
+ { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps KimiFlare stay accurate as your project evolves." }
15627
+ ]);
15628
+ }
15298
15629
  setBusy(true);
15299
15630
  gatewayMetaRef.current = null;
15300
15631
  setGatewayMeta(null);
@@ -15308,6 +15639,10 @@ ${lines.join("\n")}` }]);
15308
15639
  const turnReasoningEffort = overrideEffort ?? effortForTier[classification.tier] ?? effortRef.current;
15309
15640
  const effectiveCodeMode = classification.tier === "heavy";
15310
15641
  setCodeMode(effectiveCodeMode);
15642
+ const triageActivity = narrativizeInfo("", { tier: classification.tier, codeMode: effectiveCodeMode });
15643
+ if (triageActivity) {
15644
+ setEvents((e) => [...e, { kind: "activity", key: mkKey(), text: triageActivity.text, feature: triageActivity.feature }]);
15645
+ }
15311
15646
  const controller = new AbortController();
15312
15647
  activeControllerRef.current = controller;
15313
15648
  const sharedCallbacks = {
@@ -15335,11 +15670,16 @@ ${lines.join("\n")}` }]);
15335
15670
  pendingToolCallsRef.current.set(call.id, call.function.name);
15336
15671
  const spec = executorRef.current.list().find((t) => t.name === call.function.name);
15337
15672
  let renderMeta;
15673
+ let argsParsed = {};
15338
15674
  try {
15339
- const args = call.function.arguments ? JSON.parse(call.function.arguments) : {};
15340
- renderMeta = spec?.render?.(args);
15675
+ argsParsed = call.function.arguments ? JSON.parse(call.function.arguments) : {};
15676
+ renderMeta = spec?.render?.(argsParsed);
15341
15677
  } catch {
15342
15678
  }
15679
+ toolBatchRef.current.push({ name: call.function.name, args: argsParsed });
15680
+ if (toolBatchTimerRef.current) clearTimeout(toolBatchTimerRef.current);
15681
+ toolBatchTimerRef.current = setTimeout(flushToolBatch, 120);
15682
+ if (modeRef.current === "plan") return;
15343
15683
  setEvents((e) => [
15344
15684
  ...e,
15345
15685
  {
@@ -15423,7 +15763,17 @@ ${lines.join("\n")}` }]);
15423
15763
  onToolLimitReached: () => new Promise((resolve2) => {
15424
15764
  limitResolveRef.current = resolve2;
15425
15765
  setLimitModal({ limit: 50, resolve: resolve2 });
15426
- })
15766
+ }),
15767
+ onKimiMdStale: () => {
15768
+ if (!kimiMdStaleNudgedRef.current) {
15769
+ kimiMdStaleNudgedRef.current = true;
15770
+ setKimiMdStale(true);
15771
+ setEvents((e) => [
15772
+ ...e,
15773
+ { kind: "info", key: mkKey(), text: "Project context may be stale. Run /init to refresh KIMI.md based on recent changes." }
15774
+ ]);
15775
+ }
15776
+ }
15427
15777
  };
15428
15778
  try {
15429
15779
  await runAgentTurn({
@@ -15474,9 +15824,10 @@ ${lines.join("\n")}` }]);
15474
15824
  setEvents((e) => [
15475
15825
  ...e,
15476
15826
  {
15477
- kind: "info",
15827
+ kind: "activity",
15478
15828
  key: mkKey(),
15479
- text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
15829
+ text: "Making room by summarizing older turns\u2026",
15830
+ feature: "compact"
15480
15831
  }
15481
15832
  ]);
15482
15833
  await saveSessionSafe();
@@ -15496,9 +15847,10 @@ ${lines.join("\n")}` }]);
15496
15847
  setEvents((e) => [
15497
15848
  ...e,
15498
15849
  {
15499
- kind: "info",
15850
+ kind: "activity",
15500
15851
  key: mkKey(),
15501
- text: `auto-compacted: ${result.replacedCount} messages summarized`
15852
+ text: "Making room by summarizing older turns\u2026",
15853
+ feature: "compact"
15502
15854
  }
15503
15855
  ]);
15504
15856
  await saveSessionSafe();
@@ -15533,9 +15885,10 @@ ${lines.join("\n")}` }]);
15533
15885
  setEvents((e) => [
15534
15886
  ...e,
15535
15887
  {
15536
- kind: "memory",
15888
+ kind: "activity",
15537
15889
  key: mkKey(),
15538
- text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} after compaction`
15890
+ text: "Remembering what we learned before\u2026",
15891
+ feature: "memory"
15539
15892
  }
15540
15893
  ]);
15541
15894
  await saveSessionSafe();
@@ -15586,9 +15939,16 @@ ${lines.join("\n")}` }]);
15586
15939
  permResolveRef.current = null;
15587
15940
  limitResolveRef.current = null;
15588
15941
  pendingToolCallsRef.current.clear();
15942
+ tasksRef.current = [];
15943
+ setTasks([]);
15944
+ setTasksStartedAt(null);
15945
+ setTasksStartTokens(0);
15946
+ setEvents(
15947
+ (evts) => evts.map((e) => e.kind === "tool" && e.status === "running" ? { ...e, status: "error", result: "(interrupted)" } : e)
15948
+ );
15589
15949
  }
15590
15950
  },
15591
- [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateGatewayMeta]
15951
+ [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateGatewayMeta, flushToolBatch]
15592
15952
  );
15593
15953
  useEffect6(() => {
15594
15954
  if (!busy && queue.length > 0) {
@@ -15819,7 +16179,7 @@ ${lines.join("\n")}` }]);
15819
16179
  }
15820
16180
  const hasConversation = events.some((e) => e.kind === "user" || e.kind === "assistant");
15821
16181
  return /* @__PURE__ */ jsx23(ThemeProvider, { theme, children: /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
15822
- !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx23(Welcome, { accountId: cfg.accountId }) : /* @__PURE__ */ jsx23(ChatView, { events, showReasoning, verbose }),
16182
+ !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx23(Welcome, { accountId: cfg.accountId }) : /* @__PURE__ */ jsx23(ChatView, { events, showReasoning, verbose, suppressTools: mode === "plan" }),
15823
16183
  perm ? /* @__PURE__ */ jsx23(
15824
16184
  PermissionModal,
15825
16185
  {
@@ -15870,7 +16230,8 @@ ${lines.join("\n")}` }]);
15870
16230
  gatewayMeta,
15871
16231
  codeMode,
15872
16232
  cloudMode: cfg.cloudMode,
15873
- cloudBudget
16233
+ cloudBudget,
16234
+ kimiMdStale
15874
16235
  }
15875
16236
  ),
15876
16237
  activePicker?.kind === "file" && /* @__PURE__ */ jsx23(
@@ -15982,6 +16343,7 @@ var init_app = __esm({
15982
16343
  init_errors();
15983
16344
  init_chat();
15984
16345
  init_status();
16346
+ init_narrative();
15985
16347
  init_permission();
15986
16348
  init_limit_modal();
15987
16349
  init_resume_picker();