kimiflare 0.40.0 → 0.42.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
@@ -204,7 +204,8 @@ async function loadConfig() {
204
204
  plumbingModel: envPlumbingModel ?? parsed.plumbingModel,
205
205
  codeMode: envCodeMode ?? parsed.codeMode,
206
206
  costAttribution: envCostAttribution ?? parsed.costAttribution ?? false,
207
- filePicker: envFilePicker ?? parsed.filePicker ?? true
207
+ filePicker: envFilePicker ?? parsed.filePicker ?? true,
208
+ theme: parsed.theme
208
209
  };
209
210
  }
210
211
  if (parsed.accountId && parsed.apiToken) {
@@ -235,7 +236,8 @@ async function loadConfig() {
235
236
  codeMode: envCodeMode ?? parsed.codeMode ?? true,
236
237
  costAttribution: envCostAttribution ?? parsed.costAttribution ?? true,
237
238
  filePicker: envFilePicker ?? parsed.filePicker ?? true,
238
- cloudMode: envCloudMode ?? parsed.cloudMode
239
+ cloudMode: envCloudMode ?? parsed.cloudMode,
240
+ theme: parsed.theme
239
241
  };
240
242
  }
241
243
  } catch {
@@ -345,10 +347,11 @@ var init_lsp_config = __esm({
345
347
  });
346
348
 
347
349
  // src/util/sse.ts
348
- async function* readSSE(stream, signal) {
350
+ async function* readSSE(stream, signal, idleTimeoutMs) {
349
351
  const reader = stream.getReader();
350
352
  const decoder = new TextDecoder("utf-8");
351
353
  let buffer = "";
354
+ let lastDataAt = Date.now();
352
355
  const onAbort = () => {
353
356
  reader.cancel(new DOMException("aborted", "AbortError")).catch(() => {
354
357
  });
@@ -357,8 +360,15 @@ async function* readSSE(stream, signal) {
357
360
  try {
358
361
  while (true) {
359
362
  if (signal?.aborted) throw new DOMException("aborted", "AbortError");
363
+ if (idleTimeoutMs !== void 0 && Date.now() - lastDataAt > idleTimeoutMs) {
364
+ throw new DOMException(
365
+ `kimiflare: stream idle for ${idleTimeoutMs}ms \u2014 no data received from API`,
366
+ "TimeoutError"
367
+ );
368
+ }
360
369
  const { done, value } = await reader.read();
361
370
  if (done) break;
371
+ lastDataAt = Date.now();
362
372
  buffer += decoder.decode(value, { stream: true });
363
373
  buffer = buffer.replace(/\r\n/g, "\n");
364
374
  let sep3;
@@ -573,7 +583,7 @@ async function* runKimi(opts2) {
573
583
  const meta = readGatewayMeta(res.headers);
574
584
  if (meta) yield { type: "gateway_meta", meta };
575
585
  let lastUsage = null;
576
- for await (const ev of parseStream(res.body, opts2.signal)) {
586
+ for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs)) {
577
587
  if (ev.type === "usage") lastUsage = ev.usage;
578
588
  yield ev;
579
589
  }
@@ -654,11 +664,12 @@ function readGatewayMeta(headers) {
654
664
  if (model) meta.model = model;
655
665
  return Object.keys(meta).length > 0 ? meta : null;
656
666
  }
657
- async function* parseStream(body, signal) {
667
+ async function* parseStream(body, signal, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS) {
658
668
  const toolCalls = /* @__PURE__ */ new Map();
659
669
  let lastUsage = null;
660
670
  let finishReason = null;
661
- for await (const dataStr of readSSE(body, signal)) {
671
+ let lastDataAt = Date.now();
672
+ for await (const dataStr of readSSE(body, signal, idleTimeoutMs)) {
662
673
  if (dataStr === "[DONE]") break;
663
674
  let chunk = null;
664
675
  try {
@@ -789,7 +800,7 @@ function sleep(ms, signal) {
789
800
  signal?.addEventListener("abort", onAbort, { once: true });
790
801
  });
791
802
  }
792
- var RETRYABLE_CODES, MAX_ATTEMPTS;
803
+ var RETRYABLE_CODES, MAX_ATTEMPTS, DEFAULT_IDLE_TIMEOUT_MS;
793
804
  var init_client = __esm({
794
805
  "src/agent/client.ts"() {
795
806
  "use strict";
@@ -799,6 +810,7 @@ var init_client = __esm({
799
810
  init_messages();
800
811
  RETRYABLE_CODES = /* @__PURE__ */ new Set([3040]);
801
812
  MAX_ATTEMPTS = 5;
813
+ DEFAULT_IDLE_TIMEOUT_MS = 6e4;
802
814
  }
803
815
  });
804
816
 
@@ -1861,6 +1873,9 @@ var init_code_mode = __esm({
1861
1873
  });
1862
1874
 
1863
1875
  // src/agent/loop.ts
1876
+ function isHighSignalMemory(memory) {
1877
+ 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;
1878
+ }
1864
1879
  async function runAgentTurn(opts2) {
1865
1880
  const turnStart = performance.now();
1866
1881
  const max = opts2.maxToolIterations ?? 50;
@@ -2260,6 +2275,15 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2260
2275
  void 0,
2261
2276
  memory.topicKey
2262
2277
  );
2278
+ if (isHighSignalMemory(memory)) {
2279
+ const sid = opts2.sessionId ?? "default";
2280
+ const current = (driftAccumulator.get(sid) ?? 0) + 1;
2281
+ driftAccumulator.set(sid, current);
2282
+ if (current >= DRIFT_THRESHOLD) {
2283
+ opts2.callbacks.onKimiMdStale?.();
2284
+ driftAccumulator.set(sid, 0);
2285
+ }
2286
+ }
2263
2287
  }
2264
2288
  } catch {
2265
2289
  }
@@ -2271,6 +2295,12 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2271
2295
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2272
2296
  }
2273
2297
  }
2298
+ if (opts2.sessionId) {
2299
+ const current = driftAccumulator.get(opts2.sessionId) ?? 0;
2300
+ if (current > 0) {
2301
+ driftAccumulator.set(opts2.sessionId, Math.max(0, current - 1));
2302
+ }
2303
+ }
2274
2304
  if (opts2.onIterationEnd) {
2275
2305
  opts2.messages = await opts2.onIterationEnd(opts2.messages, opts2.signal);
2276
2306
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
@@ -2303,7 +2333,7 @@ function validateToolArguments(raw) {
2303
2333
  return "{}";
2304
2334
  }
2305
2335
  }
2306
- var BudgetExhaustedError, codeModeApiCache;
2336
+ var BudgetExhaustedError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD;
2307
2337
  var init_loop = __esm({
2308
2338
  "src/agent/loop.ts"() {
2309
2339
  "use strict";
@@ -2321,6 +2351,8 @@ var init_loop = __esm({
2321
2351
  }
2322
2352
  };
2323
2353
  codeModeApiCache = /* @__PURE__ */ new Map();
2354
+ driftAccumulator = /* @__PURE__ */ new Map();
2355
+ DRIFT_THRESHOLD = 5;
2324
2356
  }
2325
2357
  });
2326
2358
 
@@ -6897,10 +6929,18 @@ var init_narrative = __esm({
6897
6929
  });
6898
6930
 
6899
6931
  // src/ui/tool-view.tsx
6900
- import React2 from "react";
6932
+ import React2, { useEffect, useState } from "react";
6901
6933
  import { Box as Box2, Text as Text2 } from "ink";
6902
6934
  import Spinner from "ink-spinner";
6903
6935
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
6936
+ function formatElapsed(ms) {
6937
+ const total = Math.floor(ms / 1e3);
6938
+ if (total < 1) return "<1s";
6939
+ const m = Math.floor(total / 60);
6940
+ const s = total % 60;
6941
+ if (m === 0) return `${s}s`;
6942
+ return `${m}m ${s}s`;
6943
+ }
6904
6944
  function firstLine(s) {
6905
6945
  const line = s.split("\n").find((l) => l.trim().length > 0) ?? "";
6906
6946
  return line.length <= 120 ? line : line.slice(0, 120) + "\u2026";
@@ -6913,10 +6953,20 @@ var init_tool_view = __esm({
6913
6953
  init_paths();
6914
6954
  init_theme_context();
6915
6955
  init_narrative();
6916
- ToolView = React2.memo(function ToolView2({ evt, verbose }) {
6956
+ ToolView = React2.memo(function ToolView2({ evt, verbose, isRepeated }) {
6917
6957
  const theme = useTheme();
6958
+ const [now2, setNow] = useState(Date.now());
6959
+ useEffect(() => {
6960
+ if (evt.startedAt === void 0) return;
6961
+ if (evt.status !== "running") {
6962
+ setNow(Date.now());
6963
+ return;
6964
+ }
6965
+ const id = setInterval(() => setNow(Date.now()), 1e3);
6966
+ return () => clearInterval(id);
6967
+ }, [evt.status, evt.startedAt]);
6918
6968
  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" });
6919
- const title = evt.render?.title ?? (() => {
6969
+ let title = evt.render?.title ?? (() => {
6920
6970
  try {
6921
6971
  const args = evt.args ? JSON.parse(evt.args) : {};
6922
6972
  return humanizeToolTitle(evt.name, args);
@@ -6924,6 +6974,9 @@ var init_tool_view = __esm({
6924
6974
  return humanizeToolTitle(evt.name);
6925
6975
  }
6926
6976
  })();
6977
+ if (evt.startedAt !== void 0) {
6978
+ title += ` \xB7 ${formatElapsed(now2 - evt.startedAt)}`;
6979
+ }
6927
6980
  const expand = Boolean(evt.expanded || verbose);
6928
6981
  const lines = evt.result ? evt.result.split("\n") : [];
6929
6982
  const showLimit = verbose ? 200 : 20;
@@ -6931,7 +6984,8 @@ var init_tool_view = __esm({
6931
6984
  /* @__PURE__ */ jsxs2(Text2, { children: [
6932
6985
  statusIcon,
6933
6986
  " ",
6934
- /* @__PURE__ */ jsx3(Text2, { color: theme.info.color, children: title })
6987
+ /* @__PURE__ */ jsx3(Text2, { color: theme.info.color, children: title }),
6988
+ isRepeated ? /* @__PURE__ */ jsx3(Text2, { color: theme.warn, children: " \u26A0 repeated" }) : null
6935
6989
  ] }),
6936
6990
  evt.render?.diff ? /* @__PURE__ */ jsx3(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx3(DiffView, { ...evt.render.diff }) }) : null,
6937
6991
  evt.result && expand ? /* @__PURE__ */ jsxs2(
@@ -7007,6 +7061,15 @@ function parseBlocks(src) {
7007
7061
  out.push({ kind: "quote", text: quoteLines.join("\n") });
7008
7062
  continue;
7009
7063
  }
7064
+ if (/^\s*\d+\.\s+/.test(line)) {
7065
+ const items = [];
7066
+ while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
7067
+ items.push(lines[i].replace(/^\s*\d+\.\s+/, ""));
7068
+ i++;
7069
+ }
7070
+ out.push({ kind: "numbered", items });
7071
+ continue;
7072
+ }
7010
7073
  if (/^\s*[-*]\s+/.test(line)) {
7011
7074
  const items = [];
7012
7075
  while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) {
@@ -7016,9 +7079,23 @@ function parseBlocks(src) {
7016
7079
  out.push({ kind: "bullet", items });
7017
7080
  continue;
7018
7081
  }
7082
+ if (i + 1 < lines.length && /^\|/.test(line) && /^\|[\s\-:|]+\|$/.test(lines[i + 1])) {
7083
+ const headerLine = line;
7084
+ const headers = headerLine.split("|").slice(1, -1).map((h) => h.trim());
7085
+ i += 2;
7086
+ const rows = [];
7087
+ while (i < lines.length && /^\|/.test(lines[i])) {
7088
+ rows.push(
7089
+ lines[i].split("|").slice(1, -1).map((c) => c.trim())
7090
+ );
7091
+ i++;
7092
+ }
7093
+ out.push({ kind: "table", headers, rows });
7094
+ continue;
7095
+ }
7019
7096
  const paraLines = [line];
7020
7097
  i++;
7021
- while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,3})\s+/.test(lines[i]) && !/^\s*[-*]\s+/.test(lines[i]) && !/^\s*>\s?/.test(lines[i]) && !/^```/.test(lines[i])) {
7098
+ while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,3})\s+/.test(lines[i]) && !/^\s*[-*]\s+/.test(lines[i]) && !/^\s*\d+\.\s+/.test(lines[i]) && !/^\s*>\s?/.test(lines[i]) && !/^```/.test(lines[i]) && !/^\|/.test(lines[i])) {
7022
7099
  paraLines.push(lines[i]);
7023
7100
  i++;
7024
7101
  }
@@ -7031,7 +7108,18 @@ function renderInline(src, theme) {
7031
7108
  return segments.map((seg, i) => {
7032
7109
  if (seg.kind === "bold") return /* @__PURE__ */ jsx4(Text3, { bold: true, children: seg.text }, i);
7033
7110
  if (seg.kind === "italic") return /* @__PURE__ */ jsx4(Text3, { italic: true, children: seg.text }, i);
7034
- if (seg.kind === "code") return /* @__PURE__ */ jsx4(Text3, { color: theme.tool, children: seg.text }, i);
7111
+ if (seg.kind === "code") {
7112
+ const color = theme.codeInline ?? theme.tool;
7113
+ return /* @__PURE__ */ jsx4(Text3, { color, children: seg.text }, i);
7114
+ }
7115
+ if (seg.kind === "strikethrough") {
7116
+ const color = theme.strikethrough ?? theme.muted?.color ?? theme.palette.secondary;
7117
+ return /* @__PURE__ */ jsx4(Text3, { strikethrough: true, color, children: seg.text }, i);
7118
+ }
7119
+ if (seg.kind === "link") {
7120
+ const color = theme.link ?? theme.accent;
7121
+ return /* @__PURE__ */ jsx4(Text3, { underline: true, color, children: seg.text }, i);
7122
+ }
7035
7123
  return /* @__PURE__ */ jsx4(Text3, { children: seg.text }, i);
7036
7124
  });
7037
7125
  }
@@ -7056,6 +7144,30 @@ function parseInline(src) {
7056
7144
  continue;
7057
7145
  }
7058
7146
  }
7147
+ if (ch === "~" && src[i + 1] === "~") {
7148
+ const end = src.indexOf("~~", i + 2);
7149
+ if (end > i + 1) {
7150
+ flush();
7151
+ out.push({ kind: "strikethrough", text: src.slice(i + 2, end) });
7152
+ i = end + 2;
7153
+ continue;
7154
+ }
7155
+ }
7156
+ if (ch === "[") {
7157
+ const closeBracket = src.indexOf("]", i + 1);
7158
+ const openParen = closeBracket >= 0 ? src.indexOf("(", closeBracket) : -1;
7159
+ const closeParen = openParen >= 0 ? src.indexOf(")", openParen) : -1;
7160
+ if (closeParen > openParen && openParen === closeBracket + 1) {
7161
+ flush();
7162
+ out.push({
7163
+ kind: "link",
7164
+ text: src.slice(i + 1, closeBracket),
7165
+ url: src.slice(openParen + 1, closeParen)
7166
+ });
7167
+ i = closeParen + 1;
7168
+ continue;
7169
+ }
7170
+ }
7059
7171
  if (ch === "*" && src[i + 1] === "*") {
7060
7172
  const end = src.indexOf("**", i + 2);
7061
7173
  if (end > i + 1) {
@@ -7115,11 +7227,53 @@ var init_markdown = __esm({
7115
7227
  /* @__PURE__ */ jsx4(Text3, { children: renderInline(item, theme) })
7116
7228
  ] }, i)) });
7117
7229
  }
7230
+ if (block.kind === "numbered") {
7231
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: block.items.map((item, idx) => /* @__PURE__ */ jsxs3(Box3, { children: [
7232
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.accent, children: [
7233
+ " ",
7234
+ idx + 1,
7235
+ ". "
7236
+ ] }),
7237
+ /* @__PURE__ */ jsx4(Text3, { children: renderInline(item, theme) })
7238
+ ] }, idx)) });
7239
+ }
7118
7240
  if (block.kind === "quote") {
7119
- return /* @__PURE__ */ jsx4(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsx4(Text3, { color: theme.info.color, italic: true, children: renderInline(block.text, theme) }) });
7241
+ const quoteColor = theme.blockquote?.color ?? theme.info.color;
7242
+ const dim = theme.blockquote?.dim ?? false;
7243
+ return /* @__PURE__ */ jsx4(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsx4(Text3, { color: quoteColor, dimColor: dim, italic: true, children: renderInline(block.text, theme) }) });
7120
7244
  }
7121
7245
  if (block.kind === "code") {
7122
- return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginLeft: 2, children: block.text.split("\n").map((l, i) => /* @__PURE__ */ jsx4(Text3, { color: theme.tool, children: l }, i)) });
7246
+ const codeColor = theme.codeBlock ?? theme.tool;
7247
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginLeft: 2, children: block.text.split("\n").map((l, i) => /* @__PURE__ */ jsx4(Text3, { color: codeColor, children: l }, i)) });
7248
+ }
7249
+ if (block.kind === "table") {
7250
+ const borderColor = theme.tableBorder ?? theme.accent;
7251
+ const headerColor = theme.tableHeader ?? theme.accent;
7252
+ const cellColor = theme.tableCell ?? theme.palette.foreground;
7253
+ const colCount = block.headers.length;
7254
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
7255
+ /* @__PURE__ */ jsxs3(Box3, { children: [
7256
+ /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: "\u2502 " }),
7257
+ block.headers.map((h, ci) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
7258
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: headerColor, children: h }),
7259
+ ci < colCount - 1 && /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: " \u2502 " })
7260
+ ] }, ci)),
7261
+ /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: " \u2502" })
7262
+ ] }),
7263
+ /* @__PURE__ */ jsx4(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: borderColor, children: [
7264
+ "\u251C",
7265
+ Array(colCount).fill("\u2500".repeat(8)).join("\u253C"),
7266
+ "\u2524"
7267
+ ] }) }),
7268
+ block.rows.map((row, ri) => /* @__PURE__ */ jsxs3(Box3, { children: [
7269
+ /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: "\u2502 " }),
7270
+ row.map((cell, ci) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
7271
+ /* @__PURE__ */ jsx4(Text3, { color: cellColor, children: cell }),
7272
+ ci < colCount - 1 && /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: " \u2502 " })
7273
+ ] }, ci)),
7274
+ /* @__PURE__ */ jsx4(Text3, { color: borderColor, children: " \u2502" })
7275
+ ] }, ri))
7276
+ ] });
7123
7277
  }
7124
7278
  return /* @__PURE__ */ jsx4(Text3, { children: renderInline(block.text, theme) });
7125
7279
  });
@@ -7131,6 +7285,9 @@ import React4 from "react";
7131
7285
  import { Box as Box4, Text as Text4, Static } from "ink";
7132
7286
  import Spinner2 from "ink-spinner";
7133
7287
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
7288
+ function toolSignature(name, args) {
7289
+ return `${name}:${args}`;
7290
+ }
7134
7291
  var ChatView, EventView;
7135
7292
  var init_chat = __esm({
7136
7293
  "src/ui/chat.tsx"() {
@@ -7142,6 +7299,17 @@ var init_chat = __esm({
7142
7299
  const theme = useTheme();
7143
7300
  const finalized = [];
7144
7301
  const active = [];
7302
+ const toolCounts = /* @__PURE__ */ new Map();
7303
+ for (const e of events) {
7304
+ if (e.kind === "tool") {
7305
+ const sig = toolSignature(e.name, e.args);
7306
+ toolCounts.set(sig, (toolCounts.get(sig) ?? 0) + 1);
7307
+ }
7308
+ }
7309
+ const repeatedSigs = /* @__PURE__ */ new Set();
7310
+ for (const [sig, count] of toolCounts) {
7311
+ if (count >= 3) repeatedSigs.add(sig);
7312
+ }
7145
7313
  for (let i = 0; i < events.length; i++) {
7146
7314
  const e = events[i];
7147
7315
  if (suppressTools && e.kind === "tool") continue;
@@ -7157,14 +7325,14 @@ var init_chat = __esm({
7157
7325
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
7158
7326
  /* @__PURE__ */ jsx5(Static, { items: finalized, children: (item) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
7159
7327
  item.showSeparator && /* @__PURE__ */ jsx5(Box4, { marginY: 1, children: /* @__PURE__ */ jsx5(Text4, { color: theme.info.color, children: "\u2500".repeat(40) }) }),
7160
- /* @__PURE__ */ jsx5(EventView, { evt: item.evt, showReasoning, verbose })
7328
+ /* @__PURE__ */ jsx5(EventView, { evt: item.evt, showReasoning, verbose, repeatedSigs })
7161
7329
  ] }, item.id) }),
7162
7330
  active.map((e, i) => {
7163
7331
  const prevEvt = i > 0 ? active[i - 1] : finalized[finalized.length - 1]?.evt;
7164
7332
  const showSeparator = e.kind === "user" && prevEvt && (prevEvt.kind === "assistant" || prevEvt.kind === "tool");
7165
7333
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
7166
7334
  showSeparator && /* @__PURE__ */ jsx5(Box4, { marginY: 1, children: /* @__PURE__ */ jsx5(Text4, { color: theme.info.color, children: "\u2500".repeat(40) }) }),
7167
- /* @__PURE__ */ jsx5(EventView, { evt: e, showReasoning, verbose })
7335
+ /* @__PURE__ */ jsx5(EventView, { evt: e, showReasoning, verbose, repeatedSigs })
7168
7336
  ] }, e.key);
7169
7337
  })
7170
7338
  ] });
@@ -7172,7 +7340,8 @@ var init_chat = __esm({
7172
7340
  EventView = React4.memo(function EventView2({
7173
7341
  evt,
7174
7342
  showReasoning,
7175
- verbose
7343
+ verbose,
7344
+ repeatedSigs
7176
7345
  }) {
7177
7346
  const theme = useTheme();
7178
7347
  if (evt.kind === "user") {
@@ -7202,7 +7371,8 @@ var init_chat = __esm({
7202
7371
  ] });
7203
7372
  }
7204
7373
  if (evt.kind === "tool") {
7205
- return /* @__PURE__ */ jsx5(ToolView, { evt, verbose });
7374
+ const isRepeated = repeatedSigs?.has(toolSignature(evt.name, evt.args)) ?? false;
7375
+ return /* @__PURE__ */ jsx5(ToolView, { evt, verbose, isRepeated });
7206
7376
  }
7207
7377
  if (evt.kind === "info") {
7208
7378
  return /* @__PURE__ */ jsxs4(Text4, { color: theme.info.color, children: [
@@ -7249,24 +7419,27 @@ var init_pricing = __esm({
7249
7419
  });
7250
7420
 
7251
7421
  // src/ui/status.tsx
7252
- import { useEffect, useState } from "react";
7422
+ import { useEffect as useEffect2, useState as useState2 } from "react";
7253
7423
  import { Box as Box5, Text as Text5 } from "ink";
7254
7424
  import Spinner3 from "ink-spinner";
7255
7425
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
7256
- function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode, effort, contextLimit, hasUpdate, latestVersion, gatewayMeta, codeMode, cloudMode, cloudBudget }) {
7426
+ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode, effort, contextLimit, hasUpdate, latestVersion, gatewayMeta, codeMode, cloudMode, cloudBudget, phase, currentTool, lastActivityAt, kimiMdStale }) {
7257
7427
  const theme = useTheme();
7258
- const [now2, setNow] = useState(Date.now());
7428
+ const [now2, setNow] = useState2(Date.now());
7259
7429
  const modeColor = mode === "plan" ? theme.modeBadge.plan : mode === "auto" ? theme.modeBadge.auto : theme.modeBadge.edit;
7260
7430
  const warn = usage && usage.prompt_tokens / contextLimit >= 0.8;
7261
- useEffect(() => {
7431
+ useEffect2(() => {
7262
7432
  if (!thinking || turnStartedAt === null) return;
7263
7433
  const id = setInterval(() => setNow(Date.now()), 1e3);
7264
7434
  return () => clearInterval(id);
7265
7435
  }, [thinking, turnStartedAt]);
7266
- const elapsed = turnStartedAt !== null ? formatElapsed(now2 - turnStartedAt) : null;
7436
+ const elapsed = turnStartedAt !== null ? formatElapsed2(now2 - turnStartedAt) : null;
7267
7437
  const leftParts = [`${shortModel(model)}`, effort];
7268
7438
  if (cloudMode) leftParts.push("CLOUD");
7269
7439
  if (codeMode) leftParts.push("CODE");
7440
+ const phaseLabel = phase === "generating" ? "generating" : phase === "executing" ? `executing ${currentTool ?? ""}` : phase === "waiting" ? "waiting" : "thinking";
7441
+ const idleMs = lastActivityAt && thinking ? now2 - lastActivityAt : 0;
7442
+ const idleLabel = idleMs > 3e4 ? ` (idle ${formatElapsed2(Math.floor(idleMs / 1e3))})` : "";
7270
7443
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
7271
7444
  /* @__PURE__ */ jsxs5(Box5, { children: [
7272
7445
  /* @__PURE__ */ jsxs5(Text5, { color: modeColor, bold: true, children: [
@@ -7278,8 +7451,9 @@ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode,
7278
7451
  thinking ? /* @__PURE__ */ jsxs5(Text5, { color: theme.spinner, children: [
7279
7452
  /* @__PURE__ */ jsx6(Spinner3, { type: "dots" }),
7280
7453
  " ",
7281
- "thinking",
7282
- elapsed ? ` \xB7 ${elapsed}` : ""
7454
+ phaseLabel,
7455
+ elapsed ? ` \xB7 ${elapsed}` : "",
7456
+ idleLabel
7283
7457
  ] }) : /* @__PURE__ */ jsxs5(Text5, { color: theme.info.color, children: [
7284
7458
  leftParts.join(" \xB7 "),
7285
7459
  " \xB7 ready"
@@ -7296,6 +7470,10 @@ function StatusBar({ model, usage, sessionUsage, thinking, turnStartedAt, mode,
7296
7470
  "update available",
7297
7471
  latestVersion ? ` \u2192 ${latestVersion}` : "",
7298
7472
  " \xB7 run /update"
7473
+ ] }) : null,
7474
+ kimiMdStale ? /* @__PURE__ */ jsxs5(Text5, { color: theme.warn, bold: true, children: [
7475
+ " \xB7 ",
7476
+ "\u26A0 KIMI.md stale \xB7 run /init"
7299
7477
  ] }) : null
7300
7478
  ] })
7301
7479
  ] });
@@ -7346,7 +7524,7 @@ function shortModel(m) {
7346
7524
  const last = m.split("/").at(-1) ?? m;
7347
7525
  return last;
7348
7526
  }
7349
- function formatElapsed(ms) {
7527
+ function formatElapsed2(ms) {
7350
7528
  const total = Math.floor(ms / 1e3);
7351
7529
  const m = Math.floor(total / 60);
7352
7530
  const s = total % 60;
@@ -7436,14 +7614,14 @@ var init_limit_modal = __esm({
7436
7614
  });
7437
7615
 
7438
7616
  // src/ui/resume-picker.tsx
7439
- import { useState as useState2 } from "react";
7617
+ import { useState as useState3 } from "react";
7440
7618
  import { Box as Box8, Text as Text8, useWindowSize } from "ink";
7441
7619
  import SelectInput3 from "ink-select-input";
7442
7620
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
7443
7621
  function ResumePicker({ sessions, onPick }) {
7444
7622
  const theme = useTheme();
7445
7623
  const { rows } = useWindowSize();
7446
- const [page, setPage] = useState2(0);
7624
+ const [page, setPage] = useState3(0);
7447
7625
  const pageSize = Math.max(MIN_PAGE_SIZE, rows - HEADER_ROWS - FOOTER_ROWS);
7448
7626
  const totalPages = Math.max(1, Math.ceil(sessions.length / pageSize));
7449
7627
  const safePage = Math.min(page, totalPages - 1);
@@ -7525,16 +7703,16 @@ var init_resume_picker = __esm({
7525
7703
  });
7526
7704
 
7527
7705
  // src/ui/task-list.tsx
7528
- import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
7706
+ import { useEffect as useEffect3, useRef, useState as useState4 } from "react";
7529
7707
  import { Box as Box9, Text as Text9 } from "ink";
7530
7708
  import Spinner4 from "ink-spinner";
7531
7709
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
7532
7710
  function TaskList({ tasks, startedAt, tokensDelta }) {
7533
7711
  const theme = useTheme();
7534
- const [now2, setNow] = useState3(Date.now());
7712
+ const [now2, setNow] = useState4(Date.now());
7535
7713
  const tasksRef = useRef(tasks);
7536
7714
  tasksRef.current = tasks;
7537
- useEffect2(() => {
7715
+ useEffect3(() => {
7538
7716
  if (startedAt === null) return;
7539
7717
  const id = setInterval(() => {
7540
7718
  setNow(Date.now());
@@ -7551,7 +7729,7 @@ function TaskList({ tasks, startedAt, tokensDelta }) {
7551
7729
  const total = tasks.length;
7552
7730
  const allDone = done === total;
7553
7731
  const header = active ? active.title : allDone ? `${total} tasks done` : `${done}/${total}`;
7554
- const elapsed = startedAt ? formatElapsed2(now2 - startedAt) : null;
7732
+ const elapsed = startedAt ? formatElapsed3(now2 - startedAt) : null;
7555
7733
  const headerStats = [elapsed, tokensDelta > 0 ? `\u2191 ${formatTokens2(tokensDelta)} tokens` : null].filter(Boolean).join(" \xB7 ");
7556
7734
  const visibleTasks = tasks.slice(0, MAX_VISIBLE);
7557
7735
  const hiddenPending = Math.max(0, tasks.length - visibleTasks.length);
@@ -7597,7 +7775,7 @@ function TaskRow({ task }) {
7597
7775
  task.title
7598
7776
  ] });
7599
7777
  }
7600
- function formatElapsed2(ms) {
7778
+ function formatElapsed3(ms) {
7601
7779
  const total = Math.floor(ms / 1e3);
7602
7780
  const m = Math.floor(total / 60);
7603
7781
  const s = total % 60;
@@ -8138,7 +8316,7 @@ var init_source = __esm({
8138
8316
  });
8139
8317
 
8140
8318
  // src/ui/text-input.tsx
8141
- import { useState as useState4, useEffect as useEffect3, useRef as useRef2 } from "react";
8319
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef2 } from "react";
8142
8320
  import { Text as Text10, useInput } from "ink";
8143
8321
  import { jsx as jsx11 } from "react/jsx-runtime";
8144
8322
  function shouldTreatAsPaste(input) {
@@ -8180,14 +8358,14 @@ function CustomTextInput({
8180
8358
  onPickerSelect,
8181
8359
  onPickerCancel
8182
8360
  }) {
8183
- const [internalCursor, setInternalCursor] = useState4(value.length);
8361
+ const [internalCursor, setInternalCursor] = useState5(value.length);
8184
8362
  const cursorOffset = controlledCursor ?? internalCursor;
8185
8363
  const setCursorOffset = (offset) => {
8186
8364
  setInternalCursor(offset);
8187
8365
  onCursorChange?.(offset);
8188
8366
  };
8189
8367
  const pastesRef = useRef2(/* @__PURE__ */ new Map());
8190
- useEffect3(() => {
8368
+ useEffect4(() => {
8191
8369
  if (!focus) return;
8192
8370
  const next = cursorOffset > value.length ? value.length : cursorOffset;
8193
8371
  if (next !== cursorOffset) {
@@ -8372,7 +8550,7 @@ var init_text_input = __esm({
8372
8550
  });
8373
8551
 
8374
8552
  // src/ui/onboarding.tsx
8375
- import { useState as useState5, useEffect as useEffect4, useCallback } from "react";
8553
+ import { useState as useState6, useEffect as useEffect5, useCallback } from "react";
8376
8554
  import { Box as Box10, Text as Text11, useInput as useInput2 } from "ink";
8377
8555
  import SelectInput4 from "ink-select-input";
8378
8556
  import Spinner5 from "ink-spinner";
@@ -8395,15 +8573,15 @@ function formatRemaining(ms) {
8395
8573
  }
8396
8574
  function Onboarding({ onDone, onCancel }) {
8397
8575
  const theme = useTheme();
8398
- const [step, setStep] = useState5("mode");
8399
- const [mode, setMode] = useState5("byok");
8400
- const [accountId, setAccountId] = useState5("");
8401
- const [apiToken, setApiToken] = useState5("");
8402
- const [model, setModel] = useState5(DEFAULT_MODEL);
8403
- const [savedPath, setSavedPath] = useState5(null);
8404
- const [cloudAuth, setCloudAuth] = useState5(null);
8405
- const [pollTick, setPollTick] = useState5(0);
8406
- useEffect4(() => {
8576
+ const [step, setStep] = useState6("mode");
8577
+ const [mode, setMode] = useState6("byok");
8578
+ const [accountId, setAccountId] = useState6("");
8579
+ const [apiToken, setApiToken] = useState6("");
8580
+ const [model, setModel] = useState6(DEFAULT_MODEL);
8581
+ const [savedPath, setSavedPath] = useState6(null);
8582
+ const [cloudAuth, setCloudAuth] = useState6(null);
8583
+ const [pollTick, setPollTick] = useState6(0);
8584
+ useEffect5(() => {
8407
8585
  if (step !== "cloudAuth" || !cloudAuth) return;
8408
8586
  if (cloudAuth.phase !== "polling") return;
8409
8587
  let cancelled = false;
@@ -8806,13 +8984,13 @@ var init_welcome = __esm({
8806
8984
  });
8807
8985
 
8808
8986
  // src/ui/help-menu.tsx
8809
- import { useState as useState6 } from "react";
8987
+ import { useState as useState7 } from "react";
8810
8988
  import { Box as Box12, Text as Text13, useInput as useInput3 } from "ink";
8811
8989
  import SelectInput5 from "ink-select-input";
8812
8990
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
8813
8991
  function HelpMenu({ customCommands, costAttributionEnabled, cloudMode, onDone, onCommand }) {
8814
8992
  const theme = useTheme();
8815
- const [page, setPage] = useState6("main");
8993
+ const [page, setPage] = useState7("main");
8816
8994
  const customs = customCommands ?? [];
8817
8995
  useInput3((_input, key) => {
8818
8996
  if (key.escape) {
@@ -9140,17 +9318,17 @@ var init_tui_deploy = __esm({
9140
9318
  });
9141
9319
 
9142
9320
  // src/ui/remote-dashboard.tsx
9143
- import { useEffect as useEffect5, useState as useState7 } from "react";
9321
+ import { useEffect as useEffect6, useState as useState8 } from "react";
9144
9322
  import { Box as Box13, Text as Text14, useInput as useInput4 } from "ink";
9145
9323
  import SelectInput6 from "ink-select-input";
9146
9324
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
9147
9325
  function RemoteDashboard({ onSelect, onCancel }) {
9148
9326
  const theme = useTheme();
9149
- const [sessions, setSessions] = useState7([]);
9150
- const [loading, setLoading] = useState7(true);
9151
- const [error, setError] = useState7(null);
9152
- const [refreshing, setRefreshing] = useState7(false);
9153
- useEffect5(() => {
9327
+ const [sessions, setSessions] = useState8([]);
9328
+ const [loading, setLoading] = useState8(true);
9329
+ const [error, setError] = useState8(null);
9330
+ const [refreshing, setRefreshing] = useState8(false);
9331
+ useEffect6(() => {
9154
9332
  loadSessions();
9155
9333
  }, []);
9156
9334
  async function loadSessions() {
@@ -9264,7 +9442,7 @@ function RemoteSessionDetail({
9264
9442
  onCancel
9265
9443
  }) {
9266
9444
  const theme = useTheme();
9267
- const [cancelling, setCancelling] = useState7(false);
9445
+ const [cancelling, setCancelling] = useState8(false);
9268
9446
  useInput4((input, key) => {
9269
9447
  if (key.escape) {
9270
9448
  onBack();
@@ -9712,11 +9890,21 @@ var init_usage_tracker = __esm({
9712
9890
  }
9713
9891
  });
9714
9892
 
9893
+ // src/memory/schema.ts
9894
+ var DEFAULT_EMBEDDING_DIM;
9895
+ var init_schema = __esm({
9896
+ "src/memory/schema.ts"() {
9897
+ "use strict";
9898
+ DEFAULT_EMBEDDING_DIM = 768;
9899
+ }
9900
+ });
9901
+
9715
9902
  // src/memory/db.ts
9716
9903
  var db_exports = {};
9717
9904
  __export(db_exports, {
9718
9905
  clearMemoriesForRepo: () => clearMemoriesForRepo,
9719
9906
  closeMemoryDb: () => closeMemoryDb,
9907
+ countHighSignalMemoriesSince: () => countHighSignalMemoriesSince,
9720
9908
  deleteExcessMemories: () => deleteExcessMemories,
9721
9909
  deleteMemoriesByIds: () => deleteMemoriesByIds,
9722
9910
  deleteOldMemories: () => deleteOldMemories,
@@ -10103,6 +10291,19 @@ function getMemoryById(db, id) {
10103
10291
  const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
10104
10292
  return row ? rowToMemory(row) : null;
10105
10293
  }
10294
+ function countHighSignalMemoriesSince(db, repoPath, since) {
10295
+ const row = db.prepare(
10296
+ `SELECT COUNT(*) as count FROM memories
10297
+ WHERE repo_path = ? AND created_at > ?
10298
+ AND forgotten = 0 AND superseded_by IS NULL
10299
+ AND (
10300
+ topic_key IN ('project_dependencies', 'project_tsconfig', 'project_entry_point')
10301
+ OR category IN ('instruction', 'preference')
10302
+ OR (category = 'event' AND importance >= 3)
10303
+ )`
10304
+ ).get(repoPath, since);
10305
+ return row?.count ?? 0;
10306
+ }
10106
10307
  var dbInstance, dbPathInstance;
10107
10308
  var init_db = __esm({
10108
10309
  "src/memory/db.ts"() {
@@ -10464,6 +10665,7 @@ var init_manager3 = __esm({
10464
10665
  "src/memory/manager.ts"() {
10465
10666
  "use strict";
10466
10667
  init_client();
10668
+ init_schema();
10467
10669
  init_db();
10468
10670
  init_embeddings();
10469
10671
  init_retrieval();
@@ -10593,6 +10795,48 @@ Return a JSON array of strings. Example:
10593
10795
  }
10594
10796
  return { id: memory.id, superseded: supersededIds.length > 0 ? supersededIds : void 0 };
10595
10797
  }
10798
+ /**
10799
+ * Count high-signal memories created since the given timestamp.
10800
+ * Used for KIMI.md drift detection (Trigger A: session-start check).
10801
+ */
10802
+ countHighSignalMemoriesSince(repoPath, since) {
10803
+ if (!this.db) return 0;
10804
+ return countHighSignalMemoriesSince(this.db, repoPath, since);
10805
+ }
10806
+ /**
10807
+ * Get the timestamp of the most recent KIMI.md refresh memory.
10808
+ * Returns 0 if none exists.
10809
+ */
10810
+ getLastKimiMdRefreshTime(repoPath) {
10811
+ if (!this.db) return 0;
10812
+ const rows = this.db.prepare(
10813
+ `SELECT created_at FROM memories
10814
+ WHERE repo_path = ? AND topic_key = 'kimi_md_refresh'
10815
+ AND forgotten = 0 AND superseded_by IS NULL
10816
+ ORDER BY created_at DESC LIMIT 1`
10817
+ ).all(repoPath);
10818
+ return rows[0]?.created_at ?? 0;
10819
+ }
10820
+ /**
10821
+ * Record that KIMI.md was refreshed. Creates a lightweight memory
10822
+ * so drift detection knows when the snapshot was last updated.
10823
+ */
10824
+ async recordKimiMdRefresh(repoPath, sessionId) {
10825
+ if (!this.db) return;
10826
+ const embedding = new Float32Array(DEFAULT_EMBEDDING_DIM);
10827
+ insertMemory(
10828
+ this.db,
10829
+ {
10830
+ content: `KIMI.md refreshed for ${repoPath}`,
10831
+ category: "event",
10832
+ sourceSessionId: sessionId,
10833
+ repoPath,
10834
+ importance: 2,
10835
+ topicKey: "kimi_md_refresh"
10836
+ },
10837
+ embedding
10838
+ );
10839
+ }
10596
10840
  /**
10597
10841
  * Recall memories using the full hybrid retrieval pipeline.
10598
10842
  */
@@ -11265,21 +11509,21 @@ var init_save = __esm({
11265
11509
  });
11266
11510
 
11267
11511
  // src/ui/command-wizard.tsx
11268
- import { useState as useState8 } from "react";
11512
+ import { useState as useState9 } from "react";
11269
11513
  import { Box as Box14, Text as Text15, useInput as useInput5, useWindowSize as useWindowSize2 } from "ink";
11270
11514
  import SelectInput7 from "ink-select-input";
11271
11515
  import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
11272
11516
  function CommandWizard({ mode, initial, existingNames, builtinNames, onDone, onSave }) {
11273
11517
  const theme = useTheme();
11274
- const [step, setStep] = useState8("name");
11275
- const [name, setName] = useState8(initial?.name ?? "");
11276
- const [description, setDescription] = useState8(initial?.description ?? "");
11277
- const [template, setTemplate] = useState8(initial?.template ?? "");
11278
- const [cmdMode, setCmdMode] = useState8(initial?.mode);
11279
- const [cmdEffort, setCmdEffort] = useState8(initial?.effort);
11280
- const [cmdModel, setCmdModel] = useState8(initial?.model);
11281
- const [source, setSource] = useState8(initial?.source ?? "project");
11282
- const [error, setError] = useState8(null);
11518
+ const [step, setStep] = useState9("name");
11519
+ const [name, setName] = useState9(initial?.name ?? "");
11520
+ const [description, setDescription] = useState9(initial?.description ?? "");
11521
+ const [template, setTemplate] = useState9(initial?.template ?? "");
11522
+ const [cmdMode, setCmdMode] = useState9(initial?.mode);
11523
+ const [cmdEffort, setCmdEffort] = useState9(initial?.effort);
11524
+ const [cmdModel, setCmdModel] = useState9(initial?.model);
11525
+ const [source, setSource] = useState9(initial?.source ?? "project");
11526
+ const [error, setError] = useState9(null);
11283
11527
  const { columns } = useWindowSize2();
11284
11528
  const totalSteps = 5;
11285
11529
  const stepIndex = step === "name" ? 1 : step === "description" ? 2 : step === "template" ? 3 : step === "advanced" || step === "mode" || step === "effort" || step === "model" ? 4 : step === "location" ? 4 : 5;
@@ -12221,20 +12465,20 @@ var init_command_list = __esm({
12221
12465
  });
12222
12466
 
12223
12467
  // src/ui/lsp-wizard.tsx
12224
- import { useState as useState9 } from "react";
12468
+ import { useState as useState10 } from "react";
12225
12469
  import { Box as Box17, Text as Text18 } from "ink";
12226
12470
  import SelectInput9 from "ink-select-input";
12227
12471
  import { spawn as spawn3 } from "child_process";
12228
12472
  import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
12229
12473
  function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
12230
12474
  const theme = useTheme();
12231
- const [page, setPage] = useState9("main");
12232
- const [selectedPreset, setSelectedPreset] = useState9(null);
12233
- const [customName, setCustomName] = useState9("");
12234
- const [customCommand, setCustomCommand] = useState9("");
12235
- const [installState, setInstallState] = useState9({ status: "idle", output: "" });
12236
- const [pendingServers, setPendingServers] = useState9(null);
12237
- const [pendingEnabled, setPendingEnabled] = useState9(true);
12475
+ const [page, setPage] = useState10("main");
12476
+ const [selectedPreset, setSelectedPreset] = useState10(null);
12477
+ const [customName, setCustomName] = useState10("");
12478
+ const [customCommand, setCustomCommand] = useState10("");
12479
+ const [installState, setInstallState] = useState10({ status: "idle", output: "" });
12480
+ const [pendingServers, setPendingServers] = useState10(null);
12481
+ const [pendingEnabled, setPendingEnabled] = useState10(true);
12238
12482
  const runInstall = (command) => {
12239
12483
  setInstallState({ status: "running", output: "Installing..." });
12240
12484
  const child = spawn3("bash", ["-lc", command], {
@@ -12795,39 +13039,628 @@ var init_theme_picker = __esm({
12795
13039
  }
12796
13040
  });
12797
13041
 
13042
+ // src/ui/themes/everforest-dark.json
13043
+ var everforest_dark_default;
13044
+ var init_everforest_dark = __esm({
13045
+ "src/ui/themes/everforest-dark.json"() {
13046
+ everforest_dark_default = {
13047
+ name: "everforest-dark",
13048
+ label: "Everforest Dark",
13049
+ palette: {
13050
+ background: "#2b3339",
13051
+ foreground: "#d3c6aa",
13052
+ primary: "#a7c080",
13053
+ secondary: "#7a8478",
13054
+ success: "#a7c080",
13055
+ error: "#e67e80"
13056
+ },
13057
+ user: "#a7c080",
13058
+ assistant: null,
13059
+ reasoning: { color: "#7a8478", dim: true },
13060
+ info: { color: "#d3c6aa", dim: false },
13061
+ error: "#e67e80",
13062
+ warn: "#dbbc7f",
13063
+ tool: "#7fbbb3",
13064
+ spinner: "#a7c080",
13065
+ permission: "#e67e80",
13066
+ queue: { color: "#7a8478", dim: true },
13067
+ accent: "#a7c080",
13068
+ modeBadge: {
13069
+ plan: "#a7c080",
13070
+ auto: "#a7c080",
13071
+ edit: "#e67e80"
13072
+ },
13073
+ blockquote: { color: "#7a8478", dim: true },
13074
+ codeInline: "#7fbbb3",
13075
+ codeBlock: "#7fbbb3",
13076
+ link: "#a7c080",
13077
+ strikethrough: "#7a8478",
13078
+ tableBorder: "#4b565c",
13079
+ tableHeader: "#d3c6aa",
13080
+ tableCell: "#d3c6aa",
13081
+ muted: { color: "#7a8478", dim: true }
13082
+ };
13083
+ }
13084
+ });
13085
+
13086
+ // src/ui/themes/everforest-light.json
13087
+ var everforest_light_default;
13088
+ var init_everforest_light = __esm({
13089
+ "src/ui/themes/everforest-light.json"() {
13090
+ everforest_light_default = {
13091
+ name: "everforest-light",
13092
+ label: "Everforest Light",
13093
+ palette: {
13094
+ background: "#fdf6e3",
13095
+ foreground: "#5c6a72",
13096
+ primary: "#8da101",
13097
+ secondary: "#939f91",
13098
+ success: "#8da101",
13099
+ error: "#f85552"
13100
+ },
13101
+ user: "#8da101",
13102
+ assistant: null,
13103
+ reasoning: { color: "#939f91", dim: true },
13104
+ info: { color: "#5c6a72", dim: false },
13105
+ error: "#f85552",
13106
+ warn: "#dfa000",
13107
+ tool: "#3a94c5",
13108
+ spinner: "#8da101",
13109
+ permission: "#f85552",
13110
+ queue: { color: "#939f91", dim: true },
13111
+ accent: "#8da101",
13112
+ modeBadge: {
13113
+ plan: "#8da101",
13114
+ auto: "#8da101",
13115
+ edit: "#f85552"
13116
+ },
13117
+ blockquote: { color: "#939f91", dim: true },
13118
+ codeInline: "#3a94c5",
13119
+ codeBlock: "#3a94c5",
13120
+ link: "#8da101",
13121
+ strikethrough: "#939f91",
13122
+ tableBorder: "#bdc3af",
13123
+ tableHeader: "#5c6a72",
13124
+ tableCell: "#5c6a72",
13125
+ muted: { color: "#939f91", dim: true }
13126
+ };
13127
+ }
13128
+ });
13129
+
13130
+ // src/ui/themes/kanagawa-dark.json
13131
+ var kanagawa_dark_default;
13132
+ var init_kanagawa_dark = __esm({
13133
+ "src/ui/themes/kanagawa-dark.json"() {
13134
+ kanagawa_dark_default = {
13135
+ name: "kanagawa-dark",
13136
+ label: "Kanagawa Dark",
13137
+ palette: {
13138
+ background: "#1f1f28",
13139
+ foreground: "#dcd7ba",
13140
+ primary: "#7e9cd8",
13141
+ secondary: "#727169",
13142
+ success: "#98bb6c",
13143
+ error: "#ff5d62"
13144
+ },
13145
+ user: "#7e9cd8",
13146
+ assistant: null,
13147
+ reasoning: { color: "#727169", dim: true },
13148
+ info: { color: "#dcd7ba", dim: false },
13149
+ error: "#ff5d62",
13150
+ warn: "#e6c384",
13151
+ tool: "#7fb4ca",
13152
+ spinner: "#7e9cd8",
13153
+ permission: "#ff5d62",
13154
+ queue: { color: "#727169", dim: true },
13155
+ accent: "#7e9cd8",
13156
+ modeBadge: {
13157
+ plan: "#7e9cd8",
13158
+ auto: "#98bb6c",
13159
+ edit: "#ff5d62"
13160
+ },
13161
+ blockquote: { color: "#727169", dim: true },
13162
+ codeInline: "#7fb4ca",
13163
+ codeBlock: "#7fb4ca",
13164
+ link: "#7e9cd8",
13165
+ strikethrough: "#727169",
13166
+ tableBorder: "#54546d",
13167
+ tableHeader: "#dcd7ba",
13168
+ tableCell: "#dcd7ba",
13169
+ muted: { color: "#727169", dim: true }
13170
+ };
13171
+ }
13172
+ });
13173
+
13174
+ // src/ui/themes/dracula-dark.json
13175
+ var dracula_dark_default;
13176
+ var init_dracula_dark = __esm({
13177
+ "src/ui/themes/dracula-dark.json"() {
13178
+ dracula_dark_default = {
13179
+ name: "dracula-dark",
13180
+ label: "Dracula Dark",
13181
+ palette: {
13182
+ background: "#282a36",
13183
+ foreground: "#f8f8f2",
13184
+ primary: "#ff79c6",
13185
+ secondary: "#6272a4",
13186
+ success: "#50fa7b",
13187
+ error: "#ff5555"
13188
+ },
13189
+ user: "#ff79c6",
13190
+ assistant: null,
13191
+ reasoning: { color: "#6272a4", dim: true },
13192
+ info: { color: "#f8f8f2", dim: false },
13193
+ error: "#ff5555",
13194
+ warn: "#f1fa8c",
13195
+ tool: "#8be9fd",
13196
+ spinner: "#ff79c6",
13197
+ permission: "#ff5555",
13198
+ queue: { color: "#6272a4", dim: true },
13199
+ accent: "#ff79c6",
13200
+ modeBadge: {
13201
+ plan: "#ff79c6",
13202
+ auto: "#50fa7b",
13203
+ edit: "#ff5555"
13204
+ },
13205
+ blockquote: { color: "#6272a4", dim: true },
13206
+ codeInline: "#8be9fd",
13207
+ codeBlock: "#8be9fd",
13208
+ link: "#ff79c6",
13209
+ strikethrough: "#6272a4",
13210
+ tableBorder: "#44475a",
13211
+ tableHeader: "#f8f8f2",
13212
+ tableCell: "#f8f8f2",
13213
+ muted: { color: "#6272a4", dim: true }
13214
+ };
13215
+ }
13216
+ });
13217
+
13218
+ // src/ui/themes/tokyo-night.json
13219
+ var tokyo_night_default;
13220
+ var init_tokyo_night = __esm({
13221
+ "src/ui/themes/tokyo-night.json"() {
13222
+ tokyo_night_default = {
13223
+ name: "tokyo-night",
13224
+ label: "Tokyo Night",
13225
+ palette: {
13226
+ background: "#1a1b26",
13227
+ foreground: "#a9b1d6",
13228
+ primary: "#7aa2f7",
13229
+ secondary: "#565f89",
13230
+ success: "#9ece6a",
13231
+ error: "#f7768e"
13232
+ },
13233
+ user: "#7aa2f7",
13234
+ assistant: null,
13235
+ reasoning: { color: "#565f89", dim: true },
13236
+ info: { color: "#a9b1d6", dim: false },
13237
+ error: "#f7768e",
13238
+ warn: "#e0af68",
13239
+ tool: "#bb9af7",
13240
+ spinner: "#7aa2f7",
13241
+ permission: "#f7768e",
13242
+ queue: { color: "#565f89", dim: true },
13243
+ accent: "#7aa2f7",
13244
+ modeBadge: {
13245
+ plan: "#7aa2f7",
13246
+ auto: "#9ece6a",
13247
+ edit: "#f7768e"
13248
+ },
13249
+ blockquote: { color: "#565f89", dim: true },
13250
+ codeInline: "#bb9af7",
13251
+ codeBlock: "#bb9af7",
13252
+ link: "#7aa2f7",
13253
+ strikethrough: "#565f89",
13254
+ tableBorder: "#414868",
13255
+ tableHeader: "#c0caf5",
13256
+ tableCell: "#a9b1d6",
13257
+ muted: { color: "#565f89", dim: true }
13258
+ };
13259
+ }
13260
+ });
13261
+
13262
+ // src/ui/themes/catppuccin-mocha.json
13263
+ var catppuccin_mocha_default;
13264
+ var init_catppuccin_mocha = __esm({
13265
+ "src/ui/themes/catppuccin-mocha.json"() {
13266
+ catppuccin_mocha_default = {
13267
+ name: "catppuccin-mocha",
13268
+ label: "Catppuccin Mocha",
13269
+ palette: {
13270
+ background: "#1e1e2e",
13271
+ foreground: "#cdd6f4",
13272
+ primary: "#89b4fa",
13273
+ secondary: "#6c7086",
13274
+ success: "#a6e3a1",
13275
+ error: "#f38ba8"
13276
+ },
13277
+ user: "#89b4fa",
13278
+ assistant: null,
13279
+ reasoning: { color: "#6c7086", dim: true },
13280
+ info: { color: "#cdd6f4", dim: false },
13281
+ error: "#f38ba8",
13282
+ warn: "#f9e2af",
13283
+ tool: "#cba6f7",
13284
+ spinner: "#89b4fa",
13285
+ permission: "#f38ba8",
13286
+ queue: { color: "#6c7086", dim: true },
13287
+ accent: "#89b4fa",
13288
+ modeBadge: {
13289
+ plan: "#89b4fa",
13290
+ auto: "#a6e3a1",
13291
+ edit: "#f38ba8"
13292
+ },
13293
+ blockquote: { color: "#6c7086", dim: true },
13294
+ codeInline: "#cba6f7",
13295
+ codeBlock: "#cba6f7",
13296
+ link: "#89b4fa",
13297
+ strikethrough: "#6c7086",
13298
+ tableBorder: "#45475a",
13299
+ tableHeader: "#cdd6f4",
13300
+ tableCell: "#cdd6f4",
13301
+ muted: { color: "#6c7086", dim: true }
13302
+ };
13303
+ }
13304
+ });
13305
+
13306
+ // src/ui/themes/catppuccin-latte.json
13307
+ var catppuccin_latte_default;
13308
+ var init_catppuccin_latte = __esm({
13309
+ "src/ui/themes/catppuccin-latte.json"() {
13310
+ catppuccin_latte_default = {
13311
+ name: "catppuccin-latte",
13312
+ label: "Catppuccin Latte",
13313
+ palette: {
13314
+ background: "#eff1f5",
13315
+ foreground: "#4c4f69",
13316
+ primary: "#1e66f5",
13317
+ secondary: "#8c8fa1",
13318
+ success: "#40a02b",
13319
+ error: "#d20f39"
13320
+ },
13321
+ user: "#1e66f5",
13322
+ assistant: null,
13323
+ reasoning: { color: "#8c8fa1", dim: true },
13324
+ info: { color: "#4c4f69", dim: false },
13325
+ error: "#d20f39",
13326
+ warn: "#df8e1d",
13327
+ tool: "#8839ef",
13328
+ spinner: "#1e66f5",
13329
+ permission: "#d20f39",
13330
+ queue: { color: "#8c8fa1", dim: true },
13331
+ accent: "#1e66f5",
13332
+ modeBadge: {
13333
+ plan: "#1e66f5",
13334
+ auto: "#40a02b",
13335
+ edit: "#d20f39"
13336
+ },
13337
+ blockquote: { color: "#8c8fa1", dim: true },
13338
+ codeInline: "#8839ef",
13339
+ codeBlock: "#8839ef",
13340
+ link: "#1e66f5",
13341
+ strikethrough: "#8c8fa1",
13342
+ tableBorder: "#bcc0cc",
13343
+ tableHeader: "#4c4f69",
13344
+ tableCell: "#4c4f69",
13345
+ muted: { color: "#8c8fa1", dim: true }
13346
+ };
13347
+ }
13348
+ });
13349
+
13350
+ // src/ui/themes/solarized-dark.json
13351
+ var solarized_dark_default;
13352
+ var init_solarized_dark = __esm({
13353
+ "src/ui/themes/solarized-dark.json"() {
13354
+ solarized_dark_default = {
13355
+ name: "solarized-dark",
13356
+ label: "Solarized Dark",
13357
+ palette: {
13358
+ background: "#002b36",
13359
+ foreground: "#839496",
13360
+ primary: "#268bd2",
13361
+ secondary: "#586e75",
13362
+ success: "#859900",
13363
+ error: "#dc322f"
13364
+ },
13365
+ user: "#268bd2",
13366
+ assistant: null,
13367
+ reasoning: { color: "#586e75", dim: true },
13368
+ info: { color: "#839496", dim: false },
13369
+ error: "#dc322f",
13370
+ warn: "#b58900",
13371
+ tool: "#2aa198",
13372
+ spinner: "#268bd2",
13373
+ permission: "#dc322f",
13374
+ queue: { color: "#586e75", dim: true },
13375
+ accent: "#268bd2",
13376
+ modeBadge: {
13377
+ plan: "#268bd2",
13378
+ auto: "#859900",
13379
+ edit: "#dc322f"
13380
+ },
13381
+ blockquote: { color: "#586e75", dim: true },
13382
+ codeInline: "#2aa198",
13383
+ codeBlock: "#2aa198",
13384
+ link: "#268bd2",
13385
+ strikethrough: "#586e75",
13386
+ tableBorder: "#073642",
13387
+ tableHeader: "#93a1a1",
13388
+ tableCell: "#839496",
13389
+ muted: { color: "#586e75", dim: true }
13390
+ };
13391
+ }
13392
+ });
13393
+
13394
+ // src/ui/themes/solarized-light.json
13395
+ var solarized_light_default;
13396
+ var init_solarized_light = __esm({
13397
+ "src/ui/themes/solarized-light.json"() {
13398
+ solarized_light_default = {
13399
+ name: "solarized-light",
13400
+ label: "Solarized Light",
13401
+ palette: {
13402
+ background: "#fdf6e3",
13403
+ foreground: "#657b83",
13404
+ primary: "#268bd2",
13405
+ secondary: "#93a1a1",
13406
+ success: "#859900",
13407
+ error: "#dc322f"
13408
+ },
13409
+ user: "#268bd2",
13410
+ assistant: null,
13411
+ reasoning: { color: "#93a1a1", dim: true },
13412
+ info: { color: "#657b83", dim: false },
13413
+ error: "#dc322f",
13414
+ warn: "#b58900",
13415
+ tool: "#2aa198",
13416
+ spinner: "#268bd2",
13417
+ permission: "#dc322f",
13418
+ queue: { color: "#93a1a1", dim: true },
13419
+ accent: "#268bd2",
13420
+ modeBadge: {
13421
+ plan: "#268bd2",
13422
+ auto: "#859900",
13423
+ edit: "#dc322f"
13424
+ },
13425
+ blockquote: { color: "#93a1a1", dim: true },
13426
+ codeInline: "#2aa198",
13427
+ codeBlock: "#2aa198",
13428
+ link: "#268bd2",
13429
+ strikethrough: "#93a1a1",
13430
+ tableBorder: "#eee8d5",
13431
+ tableHeader: "#586e75",
13432
+ tableCell: "#657b83",
13433
+ muted: { color: "#93a1a1", dim: true }
13434
+ };
13435
+ }
13436
+ });
13437
+
13438
+ // src/ui/themes/nord.json
13439
+ var nord_default;
13440
+ var init_nord = __esm({
13441
+ "src/ui/themes/nord.json"() {
13442
+ nord_default = {
13443
+ name: "nord",
13444
+ label: "Nord",
13445
+ palette: {
13446
+ background: "#2e3440",
13447
+ foreground: "#d8dee9",
13448
+ primary: "#88c0d0",
13449
+ secondary: "#4c566a",
13450
+ success: "#a3be8c",
13451
+ error: "#bf616a"
13452
+ },
13453
+ user: "#88c0d0",
13454
+ assistant: null,
13455
+ reasoning: { color: "#4c566a", dim: true },
13456
+ info: { color: "#d8dee9", dim: false },
13457
+ error: "#bf616a",
13458
+ warn: "#ebcb8b",
13459
+ tool: "#81a1c1",
13460
+ spinner: "#88c0d0",
13461
+ permission: "#bf616a",
13462
+ queue: { color: "#4c566a", dim: true },
13463
+ accent: "#88c0d0",
13464
+ modeBadge: {
13465
+ plan: "#88c0d0",
13466
+ auto: "#a3be8c",
13467
+ edit: "#bf616a"
13468
+ },
13469
+ blockquote: { color: "#4c566a", dim: true },
13470
+ codeInline: "#81a1c1",
13471
+ codeBlock: "#81a1c1",
13472
+ link: "#88c0d0",
13473
+ strikethrough: "#4c566a",
13474
+ tableBorder: "#434c5e",
13475
+ tableHeader: "#eceff4",
13476
+ tableCell: "#d8dee9",
13477
+ muted: { color: "#4c566a", dim: true }
13478
+ };
13479
+ }
13480
+ });
13481
+
13482
+ // src/ui/themes/gruvbox-dark.json
13483
+ var gruvbox_dark_default;
13484
+ var init_gruvbox_dark = __esm({
13485
+ "src/ui/themes/gruvbox-dark.json"() {
13486
+ gruvbox_dark_default = {
13487
+ name: "gruvbox-dark",
13488
+ label: "Gruvbox Dark",
13489
+ palette: {
13490
+ background: "#282828",
13491
+ foreground: "#ebdbb2",
13492
+ primary: "#b8bb26",
13493
+ secondary: "#928374",
13494
+ success: "#b8bb26",
13495
+ error: "#fb4934"
13496
+ },
13497
+ user: "#b8bb26",
13498
+ assistant: null,
13499
+ reasoning: { color: "#928374", dim: true },
13500
+ info: { color: "#ebdbb2", dim: false },
13501
+ error: "#fb4934",
13502
+ warn: "#fabd2f",
13503
+ tool: "#83a598",
13504
+ spinner: "#b8bb26",
13505
+ permission: "#fb4934",
13506
+ queue: { color: "#928374", dim: true },
13507
+ accent: "#b8bb26",
13508
+ modeBadge: {
13509
+ plan: "#b8bb26",
13510
+ auto: "#b8bb26",
13511
+ edit: "#fb4934"
13512
+ },
13513
+ blockquote: { color: "#928374", dim: true },
13514
+ codeInline: "#83a598",
13515
+ codeBlock: "#83a598",
13516
+ link: "#b8bb26",
13517
+ strikethrough: "#928374",
13518
+ tableBorder: "#504945",
13519
+ tableHeader: "#ebdbb2",
13520
+ tableCell: "#ebdbb2",
13521
+ muted: { color: "#928374", dim: true }
13522
+ };
13523
+ }
13524
+ });
13525
+
13526
+ // src/ui/themes/gruvbox-light.json
13527
+ var gruvbox_light_default;
13528
+ var init_gruvbox_light = __esm({
13529
+ "src/ui/themes/gruvbox-light.json"() {
13530
+ gruvbox_light_default = {
13531
+ name: "gruvbox-light",
13532
+ label: "Gruvbox Light",
13533
+ palette: {
13534
+ background: "#fbf1c7",
13535
+ foreground: "#3c3836",
13536
+ primary: "#79740e",
13537
+ secondary: "#928374",
13538
+ success: "#79740e",
13539
+ error: "#9d0006"
13540
+ },
13541
+ user: "#79740e",
13542
+ assistant: null,
13543
+ reasoning: { color: "#928374", dim: true },
13544
+ info: { color: "#3c3836", dim: false },
13545
+ error: "#9d0006",
13546
+ warn: "#b57614",
13547
+ tool: "#076678",
13548
+ spinner: "#79740e",
13549
+ permission: "#9d0006",
13550
+ queue: { color: "#928374", dim: true },
13551
+ accent: "#79740e",
13552
+ modeBadge: {
13553
+ plan: "#79740e",
13554
+ auto: "#79740e",
13555
+ edit: "#9d0006"
13556
+ },
13557
+ blockquote: { color: "#928374", dim: true },
13558
+ codeInline: "#076678",
13559
+ codeBlock: "#076678",
13560
+ link: "#79740e",
13561
+ strikethrough: "#928374",
13562
+ tableBorder: "#d5c4a1",
13563
+ tableHeader: "#3c3836",
13564
+ tableCell: "#3c3836",
13565
+ muted: { color: "#928374", dim: true }
13566
+ };
13567
+ }
13568
+ });
13569
+
13570
+ // src/ui/themes/one-dark.json
13571
+ var one_dark_default;
13572
+ var init_one_dark = __esm({
13573
+ "src/ui/themes/one-dark.json"() {
13574
+ one_dark_default = {
13575
+ name: "one-dark",
13576
+ label: "One Dark",
13577
+ palette: {
13578
+ background: "#282c34",
13579
+ foreground: "#abb2bf",
13580
+ primary: "#61afef",
13581
+ secondary: "#5c6370",
13582
+ success: "#98c379",
13583
+ error: "#e06c75"
13584
+ },
13585
+ user: "#61afef",
13586
+ assistant: null,
13587
+ reasoning: { color: "#5c6370", dim: true },
13588
+ info: { color: "#abb2bf", dim: false },
13589
+ error: "#e06c75",
13590
+ warn: "#e5c07b",
13591
+ tool: "#c678dd",
13592
+ spinner: "#61afef",
13593
+ permission: "#e06c75",
13594
+ queue: { color: "#5c6370", dim: true },
13595
+ accent: "#61afef",
13596
+ modeBadge: {
13597
+ plan: "#61afef",
13598
+ auto: "#98c379",
13599
+ edit: "#e06c75"
13600
+ },
13601
+ blockquote: { color: "#5c6370", dim: true },
13602
+ codeInline: "#c678dd",
13603
+ codeBlock: "#c678dd",
13604
+ link: "#61afef",
13605
+ strikethrough: "#5c6370",
13606
+ tableBorder: "#3e4451",
13607
+ tableHeader: "#abb2bf",
13608
+ tableCell: "#abb2bf",
13609
+ muted: { color: "#5c6370", dim: true }
13610
+ };
13611
+ }
13612
+ });
13613
+
12798
13614
  // src/ui/theme.ts
12799
- function buildTheme(name, label, palette, overrides) {
12800
- const base = {
12801
- name,
12802
- label,
13615
+ function normalizeTheme(json) {
13616
+ const obj = json;
13617
+ const palette = obj.palette;
13618
+ const normalizeDim = (v) => {
13619
+ if (v === void 0) return void 0;
13620
+ if (typeof v === "string") return { color: v, dim: false };
13621
+ const d = v;
13622
+ return { color: String(d.color), dim: d.dim === true };
13623
+ };
13624
+ const normalizeColor2 = (v) => {
13625
+ if (v === void 0) return void 0;
13626
+ if (typeof v === "string") return v;
13627
+ const d = v;
13628
+ return String(d.color);
13629
+ };
13630
+ return {
13631
+ name: String(obj.name),
13632
+ label: String(obj.label),
12803
13633
  palette,
12804
- user: palette.primary,
12805
- tool: palette.secondary,
12806
- spinner: palette.primary,
12807
- accent: palette.primary,
12808
- error: palette.error,
12809
- warn: palette.error,
12810
- info: { color: palette.secondary, dim: false },
12811
- reasoning: { color: palette.secondary, dim: false },
12812
- permission: palette.error,
12813
- queue: { color: palette.secondary, dim: false },
12814
- assistant: void 0,
12815
- modeBadge: {
13634
+ user: String(obj.user ?? palette.primary),
13635
+ assistant: obj.assistant === null ? void 0 : typeof obj.assistant === "string" ? obj.assistant : void 0,
13636
+ reasoning: normalizeDim(obj.reasoning) ?? { color: palette.secondary, dim: false },
13637
+ info: normalizeDim(obj.info) ?? { color: palette.secondary, dim: false },
13638
+ error: String(obj.error ?? palette.error),
13639
+ warn: String(obj.warn ?? palette.error),
13640
+ tool: String(obj.tool ?? palette.secondary),
13641
+ spinner: String(obj.spinner ?? palette.primary),
13642
+ permission: String(obj.permission ?? palette.error),
13643
+ queue: normalizeDim(obj.queue) ?? { color: palette.secondary, dim: false },
13644
+ accent: String(obj.accent ?? palette.primary),
13645
+ modeBadge: obj.modeBadge ?? {
12816
13646
  plan: palette.primary,
12817
13647
  auto: palette.success,
12818
13648
  edit: palette.error
12819
- }
12820
- };
12821
- if (!overrides) return base;
12822
- return {
12823
- ...base,
12824
- ...overrides,
12825
- modeBadge: overrides.modeBadge ?? base.modeBadge,
12826
- info: overrides.info ?? base.info,
12827
- reasoning: overrides.reasoning ?? base.reasoning,
12828
- queue: overrides.queue ?? base.queue
13649
+ },
13650
+ blockquote: normalizeDim(obj.blockquote),
13651
+ codeInline: normalizeColor2(obj.codeInline),
13652
+ codeBlock: normalizeColor2(obj.codeBlock),
13653
+ link: normalizeColor2(obj.link),
13654
+ strikethrough: normalizeColor2(obj.strikethrough),
13655
+ tableBorder: normalizeColor2(obj.tableBorder),
13656
+ tableHeader: normalizeColor2(obj.tableHeader),
13657
+ tableCell: normalizeColor2(obj.tableCell),
13658
+ muted: normalizeDim(obj.muted)
12829
13659
  };
12830
13660
  }
13661
+ function setThemes(themes) {
13662
+ THEMES = themes;
13663
+ }
12831
13664
  function resolveTheme(name) {
12832
13665
  if (!name) return THEMES[DEFAULT_THEME_NAME];
12833
13666
  return THEMES[name] ?? THEMES[DEFAULT_THEME_NAME];
@@ -12838,44 +13671,341 @@ function themeNames() {
12838
13671
  function themeList() {
12839
13672
  return Object.values(THEMES);
12840
13673
  }
12841
- var everforestDark, everforestLight, kanagawaDark, draculaDark, THEMES, DEFAULT_THEME_NAME;
13674
+ var BUILT_IN_THEMES, THEMES, DEFAULT_THEME_NAME;
12842
13675
  var init_theme = __esm({
12843
13676
  "src/ui/theme.ts"() {
12844
13677
  "use strict";
12845
- everforestDark = buildTheme("everforest-dark", "everforest-dark", {
12846
- primary: "#a7c080",
12847
- secondary: "#d3c6aa",
12848
- success: "#a7c080",
12849
- error: "#e67e80"
12850
- });
12851
- everforestLight = buildTheme("everforest-light", "everforest-light", {
12852
- primary: "#c5e49a",
12853
- secondary: "#f0e6c8",
12854
- success: "#c5e49a",
12855
- error: "#e07070"
12856
- });
12857
- kanagawaDark = buildTheme("kanagawa-dark", "kanagawa-dark", {
12858
- primary: "#8aadf4",
12859
- secondary: "#f0e6c8",
12860
- success: "#a6e3a1",
12861
- error: "#f38ba8"
12862
- });
12863
- draculaDark = buildTheme("dracula-dark", "dracula-dark", {
12864
- primary: "#ff79c6",
12865
- secondary: "#f8f8f2",
12866
- success: "#50fa7b",
12867
- error: "#ff5555"
12868
- });
12869
- THEMES = {
12870
- "everforest-dark": everforestDark,
12871
- "everforest-light": everforestLight,
12872
- "kanagawa-dark": kanagawaDark,
12873
- "dracula-dark": draculaDark
13678
+ init_everforest_dark();
13679
+ init_everforest_light();
13680
+ init_kanagawa_dark();
13681
+ init_dracula_dark();
13682
+ init_tokyo_night();
13683
+ init_catppuccin_mocha();
13684
+ init_catppuccin_latte();
13685
+ init_solarized_dark();
13686
+ init_solarized_light();
13687
+ init_nord();
13688
+ init_gruvbox_dark();
13689
+ init_gruvbox_light();
13690
+ init_one_dark();
13691
+ BUILT_IN_THEMES = {
13692
+ "everforest-dark": normalizeTheme(everforest_dark_default),
13693
+ "everforest-light": normalizeTheme(everforest_light_default),
13694
+ "kanagawa-dark": normalizeTheme(kanagawa_dark_default),
13695
+ "dracula-dark": normalizeTheme(dracula_dark_default),
13696
+ "tokyo-night": normalizeTheme(tokyo_night_default),
13697
+ "catppuccin-mocha": normalizeTheme(catppuccin_mocha_default),
13698
+ "catppuccin-latte": normalizeTheme(catppuccin_latte_default),
13699
+ "solarized-dark": normalizeTheme(solarized_dark_default),
13700
+ "solarized-light": normalizeTheme(solarized_light_default),
13701
+ "nord": normalizeTheme(nord_default),
13702
+ "gruvbox-dark": normalizeTheme(gruvbox_dark_default),
13703
+ "gruvbox-light": normalizeTheme(gruvbox_light_default),
13704
+ "one-dark": normalizeTheme(one_dark_default)
12874
13705
  };
13706
+ THEMES = { ...BUILT_IN_THEMES };
12875
13707
  DEFAULT_THEME_NAME = "everforest-dark";
12876
13708
  }
12877
13709
  });
12878
13710
 
13711
+ // src/ui/wcag.ts
13712
+ function hexToRgb(hex) {
13713
+ const m = hex.match(/^#([0-9a-fA-F]{6})$/);
13714
+ if (!m) return null;
13715
+ const v = parseInt(m[1], 16);
13716
+ return {
13717
+ r: v >> 16 & 255,
13718
+ g: v >> 8 & 255,
13719
+ b: v & 255
13720
+ };
13721
+ }
13722
+ function relativeLuminance({ r, g, b }) {
13723
+ const toLinear = (c) => {
13724
+ const s = c / 255;
13725
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
13726
+ };
13727
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
13728
+ }
13729
+ function contrastRatio(a, b) {
13730
+ const rgbA = hexToRgb(a);
13731
+ const rgbB = hexToRgb(b);
13732
+ if (!rgbA || !rgbB) return null;
13733
+ const l1 = relativeLuminance(rgbA);
13734
+ const l2 = relativeLuminance(rgbB);
13735
+ const lighter = Math.max(l1, l2);
13736
+ const darker = Math.min(l1, l2);
13737
+ return (lighter + 0.05) / (darker + 0.05);
13738
+ }
13739
+ function checkContrast(foreground, background, required = 4.5) {
13740
+ const ratio = contrastRatio(foreground, background);
13741
+ if (ratio === null) return null;
13742
+ if (ratio >= required) return null;
13743
+ return {
13744
+ pair: `${foreground} on ${background}`,
13745
+ foreground,
13746
+ background,
13747
+ ratio: Math.round(ratio * 100) / 100,
13748
+ required
13749
+ };
13750
+ }
13751
+ var init_wcag = __esm({
13752
+ "src/ui/wcag.ts"() {
13753
+ "use strict";
13754
+ }
13755
+ });
13756
+
13757
+ // src/ui/theme-loader.ts
13758
+ import { readFile as readFile16, readdir as readdir4 } from "fs/promises";
13759
+ import { join as join21 } from "path";
13760
+ import { homedir as homedir14 } from "os";
13761
+ function projectThemesDir(cwd = process.cwd()) {
13762
+ return join21(cwd, ".kimiflare", "themes");
13763
+ }
13764
+ function isHexColor(c) {
13765
+ return /^#[0-9a-fA-F]{6}$/.test(c);
13766
+ }
13767
+ function validateHex(field, value, errors) {
13768
+ if (value === void 0) return;
13769
+ if (!isHexColor(value)) {
13770
+ errors.push(`${field}: "${value}" is not a valid #RRGGBB hex color`);
13771
+ }
13772
+ }
13773
+ function validatePalette(p, errors) {
13774
+ if (!p || typeof p !== "object") {
13775
+ errors.push("palette must be an object");
13776
+ return null;
13777
+ }
13778
+ const palette = p;
13779
+ const required = ["background", "foreground", "primary", "secondary", "success", "error"];
13780
+ for (const key of required) {
13781
+ if (typeof palette[key] !== "string") {
13782
+ errors.push(`palette.${key} is required and must be a string`);
13783
+ } else {
13784
+ validateHex(`palette.${key}`, palette[key], errors);
13785
+ }
13786
+ }
13787
+ if (errors.length > 0) return null;
13788
+ return {
13789
+ background: palette.background,
13790
+ foreground: palette.foreground,
13791
+ primary: palette.primary,
13792
+ secondary: palette.secondary,
13793
+ success: palette.success,
13794
+ error: palette.error
13795
+ };
13796
+ }
13797
+ function validateDimColor(field, value, errors) {
13798
+ if (value === void 0) return void 0;
13799
+ if (typeof value === "string") {
13800
+ validateHex(field, value, errors);
13801
+ return { color: value, dim: false };
13802
+ }
13803
+ if (!value || typeof value !== "object") {
13804
+ errors.push(`${field} must be a string or { color, dim } object`);
13805
+ return void 0;
13806
+ }
13807
+ const obj = value;
13808
+ if (typeof obj.color !== "string") {
13809
+ errors.push(`${field}.color is required`);
13810
+ return void 0;
13811
+ }
13812
+ validateHex(`${field}.color`, obj.color, errors);
13813
+ return {
13814
+ color: obj.color,
13815
+ dim: obj.dim === true
13816
+ };
13817
+ }
13818
+ function validateModeBadge(value, errors) {
13819
+ if (value === void 0) return void 0;
13820
+ if (!value || typeof value !== "object") {
13821
+ errors.push("modeBadge must be an object");
13822
+ return void 0;
13823
+ }
13824
+ const obj = value;
13825
+ const result = {};
13826
+ for (const key of ["plan", "auto", "edit"]) {
13827
+ if (typeof obj[key] !== "string") {
13828
+ errors.push(`modeBadge.${key} is required`);
13829
+ } else {
13830
+ validateHex(`modeBadge.${key}`, obj[key], errors);
13831
+ result[key] = obj[key];
13832
+ }
13833
+ }
13834
+ return result;
13835
+ }
13836
+ function normalizeColor(v) {
13837
+ if (v === void 0) return void 0;
13838
+ if (typeof v === "string") return v;
13839
+ const d = v;
13840
+ return typeof d.color === "string" ? d.color : void 0;
13841
+ }
13842
+ async function loadThemesFromDir(dir, source) {
13843
+ const themes = [];
13844
+ const errors = [];
13845
+ let files;
13846
+ try {
13847
+ files = await readdir4(dir);
13848
+ } catch {
13849
+ return { themes, errors };
13850
+ }
13851
+ for (const file of files.filter((f) => f.endsWith(".json"))) {
13852
+ const path = join21(dir, file);
13853
+ let raw;
13854
+ try {
13855
+ raw = await readFile16(path, "utf-8");
13856
+ } catch (e) {
13857
+ errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
13858
+ continue;
13859
+ }
13860
+ let json;
13861
+ try {
13862
+ json = JSON.parse(raw);
13863
+ } catch (e) {
13864
+ errors.push(`${path}: invalid JSON \u2014 ${e instanceof Error ? e.message : String(e)}`);
13865
+ continue;
13866
+ }
13867
+ if (!json || typeof json !== "object") {
13868
+ errors.push(`${path}: root must be an object`);
13869
+ continue;
13870
+ }
13871
+ const obj = json;
13872
+ const fileErrors = [];
13873
+ if (typeof obj.name !== "string" || obj.name.length === 0) {
13874
+ fileErrors.push("name is required");
13875
+ }
13876
+ if (typeof obj.label !== "string" || obj.label.length === 0) {
13877
+ fileErrors.push("label is required");
13878
+ }
13879
+ const palette = validatePalette(obj.palette, fileErrors);
13880
+ if (fileErrors.length > 0) {
13881
+ errors.push(...fileErrors.map((e) => `${path}: ${e}`));
13882
+ continue;
13883
+ }
13884
+ if (!palette) continue;
13885
+ const theme = {
13886
+ name: obj.name,
13887
+ label: obj.label,
13888
+ palette,
13889
+ user: typeof obj.user === "string" ? obj.user : palette.primary,
13890
+ assistant: obj.assistant === null ? void 0 : typeof obj.assistant === "string" ? obj.assistant : void 0,
13891
+ reasoning: validateDimColor("reasoning", obj.reasoning, fileErrors) ?? { color: palette.secondary, dim: false },
13892
+ info: validateDimColor("info", obj.info, fileErrors) ?? { color: palette.secondary, dim: false },
13893
+ error: typeof obj.error === "string" ? obj.error : palette.error,
13894
+ warn: typeof obj.warn === "string" ? obj.warn : palette.error,
13895
+ tool: typeof obj.tool === "string" ? obj.tool : palette.secondary,
13896
+ spinner: typeof obj.spinner === "string" ? obj.spinner : palette.primary,
13897
+ permission: typeof obj.permission === "string" ? obj.permission : palette.error,
13898
+ queue: validateDimColor("queue", obj.queue, fileErrors) ?? { color: palette.secondary, dim: false },
13899
+ accent: typeof obj.accent === "string" ? obj.accent : palette.primary,
13900
+ modeBadge: validateModeBadge(obj.modeBadge, fileErrors) ?? {
13901
+ plan: palette.primary,
13902
+ auto: palette.success,
13903
+ edit: palette.error
13904
+ },
13905
+ blockquote: validateDimColor("blockquote", obj.blockquote, fileErrors),
13906
+ codeInline: normalizeColor(obj.codeInline),
13907
+ codeBlock: normalizeColor(obj.codeBlock),
13908
+ link: normalizeColor(obj.link),
13909
+ strikethrough: normalizeColor(obj.strikethrough),
13910
+ tableBorder: normalizeColor(obj.tableBorder),
13911
+ tableHeader: normalizeColor(obj.tableHeader),
13912
+ tableCell: normalizeColor(obj.tableCell),
13913
+ muted: validateDimColor("muted", obj.muted, fileErrors)
13914
+ };
13915
+ if (fileErrors.length > 0) {
13916
+ errors.push(...fileErrors.map((e) => `${path}: ${e}`));
13917
+ continue;
13918
+ }
13919
+ const wcagIssues = [];
13920
+ const bg = palette.background;
13921
+ const check = (label, color) => {
13922
+ if (!color) return;
13923
+ const issue = checkContrast(color, bg);
13924
+ if (issue) wcagIssues.push({ ...issue, pair: `${label} (${issue.pair})` });
13925
+ };
13926
+ check("foreground", palette.foreground);
13927
+ check("user", theme.user);
13928
+ check("assistant", theme.assistant);
13929
+ check("reasoning", theme.reasoning.color);
13930
+ check("info", theme.info.color);
13931
+ check("error", theme.error);
13932
+ check("warn", theme.warn);
13933
+ check("tool", theme.tool);
13934
+ check("accent", theme.accent);
13935
+ check("link", theme.link);
13936
+ check("codeInline", theme.codeInline);
13937
+ check("codeBlock", theme.codeBlock);
13938
+ check("tableHeader", theme.tableHeader);
13939
+ check("tableCell", theme.tableCell);
13940
+ themes.push({ theme, source, path, wcagIssues });
13941
+ }
13942
+ return { themes, errors };
13943
+ }
13944
+ async function loadAllThemes(cwd = process.cwd()) {
13945
+ if (cachedResult) return cachedResult;
13946
+ const themes = {};
13947
+ const errors = [];
13948
+ for (const [name, theme] of Object.entries(THEMES)) {
13949
+ themes[name] = {
13950
+ theme,
13951
+ source: "built-in",
13952
+ path: "<built-in>",
13953
+ wcagIssues: []
13954
+ };
13955
+ }
13956
+ const user = await loadThemesFromDir(USER_THEMES_DIR, "user");
13957
+ for (const t of user.themes) {
13958
+ themes[t.theme.name] = t;
13959
+ }
13960
+ errors.push(...user.errors);
13961
+ const project = await loadThemesFromDir(projectThemesDir(cwd), "project");
13962
+ for (const t of project.themes) {
13963
+ themes[t.theme.name] = t;
13964
+ }
13965
+ errors.push(...project.errors);
13966
+ cachedResult = { themes, errors };
13967
+ return cachedResult;
13968
+ }
13969
+ function clearThemeCache() {
13970
+ cachedResult = null;
13971
+ }
13972
+ async function loadAndMergeThemes(cwd = process.cwd()) {
13973
+ clearThemeCache();
13974
+ const { themes, errors } = await loadAllThemes(cwd);
13975
+ const merged = {};
13976
+ for (const [name, t] of Object.entries(THEMES)) {
13977
+ merged[name] = t;
13978
+ }
13979
+ for (const t of Object.values(themes)) {
13980
+ merged[t.theme.name] = t.theme;
13981
+ }
13982
+ setThemes(merged);
13983
+ const wcagWarnings = [];
13984
+ for (const t of Object.values(themes)) {
13985
+ if (t.wcagIssues.length > 0 && t.source !== "built-in") {
13986
+ wcagWarnings.push(
13987
+ `Theme "${t.theme.label}" has WCAG contrast issues:
13988
+ ` + t.wcagIssues.map((i) => ` ${i.pair}: ${i.ratio}:1 (needs ${i.required}:1)`).join("\n")
13989
+ );
13990
+ }
13991
+ }
13992
+ return { errors, wcagWarnings };
13993
+ }
13994
+ var USER_THEMES_DIR, cachedResult;
13995
+ var init_theme_loader = __esm({
13996
+ "src/ui/theme-loader.ts"() {
13997
+ "use strict";
13998
+ init_wcag();
13999
+ init_theme();
14000
+ USER_THEMES_DIR = join21(
14001
+ process.env.XDG_CONFIG_HOME || join21(homedir14(), ".config"),
14002
+ "kimiflare",
14003
+ "themes"
14004
+ );
14005
+ cachedResult = null;
14006
+ }
14007
+ });
14008
+
12879
14009
  // src/util/lsp-nudge.ts
12880
14010
  function maybeLspNudge(userText, lspEnabled, lspServers) {
12881
14011
  if (lspEnabled && Object.keys(lspServers).length > 0) {
@@ -13098,15 +14228,15 @@ var tui_report_exports = {};
13098
14228
  __export(tui_report_exports, {
13099
14229
  getCategoryReportText: () => getCategoryReportText
13100
14230
  });
13101
- import { readFile as readFile16 } from "fs/promises";
13102
- import { join as join21 } from "path";
13103
- import { homedir as homedir14 } from "os";
14231
+ import { readFile as readFile17 } from "fs/promises";
14232
+ import { join as join22 } from "path";
14233
+ import { homedir as homedir15 } from "os";
13104
14234
  function usageDir3() {
13105
- const xdg = process.env.XDG_DATA_HOME || join21(homedir14(), ".local", "share");
13106
- return join21(xdg, "kimiflare");
14235
+ const xdg = process.env.XDG_DATA_HOME || join22(homedir15(), ".local", "share");
14236
+ return join22(xdg, "kimiflare");
13107
14237
  }
13108
14238
  function usagePath3() {
13109
- return join21(usageDir3(), "usage.json");
14239
+ return join22(usageDir3(), "usage.json");
13110
14240
  }
13111
14241
  function today3() {
13112
14242
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -13118,7 +14248,7 @@ function daysAgo2(n) {
13118
14248
  }
13119
14249
  async function loadLog3() {
13120
14250
  try {
13121
- const raw = await readFile16(usagePath3(), "utf8");
14251
+ const raw = await readFile17(usagePath3(), "utf8");
13122
14252
  return JSON.parse(raw);
13123
14253
  } catch {
13124
14254
  return { version: 1, days: [], sessions: [] };
@@ -13184,11 +14314,11 @@ __export(app_exports, {
13184
14314
  shouldOpenMentionPicker: () => shouldOpenMentionPicker,
13185
14315
  shouldOpenSlashPicker: () => shouldOpenSlashPicker
13186
14316
  });
13187
- import React14, { useState as useState10, useRef as useRef3, useEffect as useEffect6, useCallback as useCallback2 } from "react";
14317
+ import React14, { useState as useState11, useRef as useRef3, useEffect as useEffect7, useCallback as useCallback2 } from "react";
13188
14318
  import { Box as Box21, Text as Text22, useApp, useInput as useInput7, render } from "ink";
13189
14319
  import SelectInput11 from "ink-select-input";
13190
14320
  import { existsSync as existsSync3, statSync as statSync4 } from "fs";
13191
- import { join as join22 } from "path";
14321
+ import { join as join23 } from "path";
13192
14322
  import { unlink as unlink3 } from "fs/promises";
13193
14323
  import { execSync as execSync2 } from "child_process";
13194
14324
  import { spawn as spawn4 } from "child_process";
@@ -13266,7 +14396,7 @@ function buildFilePickerIgnoreList(cwd) {
13266
14396
  ];
13267
14397
  const gitignorePatterns = [];
13268
14398
  try {
13269
- const gitignorePath = join22(cwd, ".gitignore");
14399
+ const gitignorePath = join23(cwd, ".gitignore");
13270
14400
  const stats = statSync4(gitignorePath);
13271
14401
  if (stats.size > MAX_GITIGNORE_SIZE) {
13272
14402
  return hardcoded;
@@ -13424,12 +14554,12 @@ function App({
13424
14554
  initialCloudDeviceId
13425
14555
  }) {
13426
14556
  const { exit } = useApp();
13427
- const [cfg, setCfg] = useState10(initialCfg);
13428
- const [lspScope, setLspScope] = useState10(initialLspScope);
13429
- const [lspProjectPath, setLspProjectPath] = useState10(initialLspProjectPath);
13430
- const [cloudToken, setCloudToken] = useState10(initialCloudToken);
13431
- const [cloudDeviceId, setCloudDeviceId] = useState10(initialCloudDeviceId);
13432
- const [events, setRawEvents] = useState10([]);
14557
+ const [cfg, setCfg] = useState11(initialCfg);
14558
+ const [lspScope, setLspScope] = useState11(initialLspScope);
14559
+ const [lspProjectPath, setLspProjectPath] = useState11(initialLspProjectPath);
14560
+ const [cloudToken, setCloudToken] = useState11(initialCloudToken);
14561
+ const [cloudDeviceId, setCloudDeviceId] = useState11(initialCloudDeviceId);
14562
+ const [events, setRawEvents] = useState11([]);
13433
14563
  const setEvents = useCallback2(
13434
14564
  (updater) => {
13435
14565
  setRawEvents((prev) => {
@@ -13439,44 +14569,72 @@ function App({
13439
14569
  },
13440
14570
  []
13441
14571
  );
13442
- const [input, setInput] = useState10("");
13443
- const [busy, setBusy] = useState10(false);
13444
- const [usage, setUsage] = useState10(null);
13445
- const [sessionUsage, setSessionUsage] = useState10(null);
13446
- const [gatewayMeta, setGatewayMeta] = useState10(null);
13447
- const [cloudBudget, setCloudBudget] = useState10(null);
13448
- const [showReasoning, setShowReasoning] = useState10(false);
13449
- const [perm, setPerm] = useState10(null);
13450
- const [limitModal, setLimitModal] = useState10(null);
13451
- const [queue, setQueue] = useState10([]);
13452
- const [history, setHistory] = useState10([]);
13453
- const [historyIndex, setHistoryIndex] = useState10(-1);
13454
- const [draftInput, setDraftInput] = useState10("");
13455
- const [mode, setMode] = useState10("edit");
13456
- const [codeMode, setCodeMode] = useState10(false);
14572
+ const [input, setInput] = useState11("");
14573
+ const [busy, setBusy] = useState11(false);
14574
+ const [usage, setUsage] = useState11(null);
14575
+ const [sessionUsage, setSessionUsage] = useState11(null);
14576
+ const [gatewayMeta, setGatewayMeta] = useState11(null);
14577
+ const [cloudBudget, setCloudBudget] = useState11(null);
14578
+ const [showReasoning, setShowReasoning] = useState11(false);
14579
+ const [perm, setPerm] = useState11(null);
14580
+ const [limitModal, setLimitModal] = useState11(null);
14581
+ const [queue, setQueue] = useState11([]);
14582
+ const [history, setHistory] = useState11([]);
14583
+ const [historyIndex, setHistoryIndex] = useState11(-1);
14584
+ const [draftInput, setDraftInput] = useState11("");
14585
+ const [mode, setMode] = useState11("edit");
14586
+ const [codeMode, setCodeMode] = useState11(false);
13457
14587
  const filePickerEnabled = initialCfg?.filePicker ?? true;
13458
- const [effort, setEffort] = useState10(
14588
+ const [effort, setEffort] = useState11(
13459
14589
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
13460
14590
  );
13461
- const [resumeSessions, setResumeSessions] = useState10(null);
13462
- const [showHelpMenu, setShowHelpMenu] = useState10(false);
13463
- const [commandWizard, setCommandWizard] = useState10(null);
13464
- const [commandPicker, setCommandPicker] = useState10(null);
13465
- const [commandToDelete, setCommandToDelete] = useState10(null);
13466
- const [showCommandList, setShowCommandList] = useState10(false);
13467
- const [showLspWizard, setShowLspWizard] = useState10(false);
13468
- const [showRemoteDashboard, setShowRemoteDashboard] = useState10(false);
13469
- const [selectedRemoteSession, setSelectedRemoteSession] = useState10(null);
13470
- const [tasks, setTasks] = useState10([]);
13471
- const [tasksStartedAt, setTasksStartedAt] = useState10(null);
13472
- const [tasksStartTokens, setTasksStartTokens] = useState10(0);
13473
- const [turnStartedAt, setTurnStartedAt] = useState10(null);
13474
- const [verbose, setVerbose] = useState10(false);
13475
- const [hasUpdate, setHasUpdate] = useState10(initialUpdateResult?.hasUpdate ?? false);
13476
- const [latestVersion, setLatestVersion] = useState10(initialUpdateResult?.latestVersion ?? null);
13477
- const [theme, setTheme] = useState10(resolveTheme(initialCfg?.theme));
13478
- const [showThemePicker, setShowThemePicker] = useState10(false);
13479
- useEffect6(() => {
14591
+ const [resumeSessions, setResumeSessions] = useState11(null);
14592
+ const [showHelpMenu, setShowHelpMenu] = useState11(false);
14593
+ const [commandWizard, setCommandWizard] = useState11(null);
14594
+ const [commandPicker, setCommandPicker] = useState11(null);
14595
+ const [commandToDelete, setCommandToDelete] = useState11(null);
14596
+ const [showCommandList, setShowCommandList] = useState11(false);
14597
+ const [showLspWizard, setShowLspWizard] = useState11(false);
14598
+ const [showRemoteDashboard, setShowRemoteDashboard] = useState11(false);
14599
+ const [selectedRemoteSession, setSelectedRemoteSession] = useState11(null);
14600
+ const [tasks, setTasks] = useState11([]);
14601
+ const [tasksStartedAt, setTasksStartedAt] = useState11(null);
14602
+ const [tasksStartTokens, setTasksStartTokens] = useState11(0);
14603
+ const [turnStartedAt, setTurnStartedAt] = useState11(null);
14604
+ const [turnPhase, setTurnPhase] = useState11("waiting");
14605
+ const [currentToolName, setCurrentToolName] = useState11(null);
14606
+ const [lastActivityAt, setLastActivityAt] = useState11(null);
14607
+ const [verbose, setVerbose] = useState11(false);
14608
+ const [hasUpdate, setHasUpdate] = useState11(initialUpdateResult?.hasUpdate ?? false);
14609
+ const [latestVersion, setLatestVersion] = useState11(initialUpdateResult?.latestVersion ?? null);
14610
+ const [theme, setTheme] = useState11(resolveTheme(initialCfg?.theme));
14611
+ const [showThemePicker, setShowThemePicker] = useState11(false);
14612
+ const [kimiMdStale, setKimiMdStale] = useState11(false);
14613
+ useEffect7(() => {
14614
+ let cancelled = false;
14615
+ loadAndMergeThemes().then(({ errors, wcagWarnings }) => {
14616
+ if (cancelled) return;
14617
+ if (errors.length > 0) {
14618
+ setEvents((e) => [
14619
+ ...e,
14620
+ { kind: "error", key: mkKey(), text: `theme load errors:
14621
+ ${errors.join("\n")}` }
14622
+ ]);
14623
+ }
14624
+ if (wcagWarnings.length > 0) {
14625
+ setEvents((e) => [
14626
+ ...e,
14627
+ { kind: "info", key: mkKey(), text: `theme WCAG warnings:
14628
+ ${wcagWarnings.join("\n")}` }
14629
+ ]);
14630
+ }
14631
+ setTheme(resolveTheme(initialCfg?.theme));
14632
+ });
14633
+ return () => {
14634
+ cancelled = true;
14635
+ };
14636
+ }, []);
14637
+ useEffect7(() => {
13480
14638
  if (!cfg?.cloudMode || !initialCloudToken) return;
13481
14639
  let cancelled = false;
13482
14640
  const fetchBudget = async () => {
@@ -13491,11 +14649,11 @@ function App({
13491
14649
  cancelled = true;
13492
14650
  };
13493
14651
  }, [cfg?.cloudMode, initialCloudToken]);
13494
- const [cursorOffset, setCursorOffset] = useState10(0);
13495
- const [activePicker, setActivePicker] = useState10(null);
13496
- const [filePickerItems, setFilePickerItems] = useState10([]);
14652
+ const [cursorOffset, setCursorOffset] = useState11(0);
14653
+ const [activePicker, setActivePicker] = useState11(null);
14654
+ const [filePickerItems, setFilePickerItems] = useState11([]);
13497
14655
  const filePickerLoadedRef = useRef3(false);
13498
- const [customCommandsVersion, setCustomCommandsVersion] = useState10(0);
14656
+ const [customCommandsVersion, setCustomCommandsVersion] = useState11(0);
13499
14657
  const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
13500
14658
  const messagesRef = useRef3(
13501
14659
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
@@ -13531,11 +14689,13 @@ function App({
13531
14689
  const busyRef = useRef3(busy);
13532
14690
  const memoryManagerRef = useRef3(null);
13533
14691
  const sessionStartRecallRef = useRef3(null);
14692
+ const kimiMdStaleNudgedRef = useRef3(false);
14693
+ const turnCounterRef = useRef3(0);
13534
14694
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
13535
14695
  const flushTimeoutRef = useRef3(null);
13536
14696
  const customCommandsRef = useRef3([]);
13537
14697
  const pickerCancelRef = useRef3(null);
13538
- useEffect6(() => {
14698
+ useEffect7(() => {
13539
14699
  busyRef.current = busy;
13540
14700
  }, [busy]);
13541
14701
  const pickerAnchor = activePicker?.anchor ?? null;
@@ -13560,7 +14720,7 @@ function App({
13560
14720
  if (pickerKind !== "slash" || pickerQuery === null) return [];
13561
14721
  return fuzzyFilter(allSlashCommands, pickerQuery, (c) => c.name).slice(0, 50);
13562
14722
  }, [pickerKind, allSlashCommands, pickerQuery]);
13563
- useEffect6(() => {
14723
+ useEffect7(() => {
13564
14724
  if (activePicker !== null) {
13565
14725
  const trigger = activePicker.kind === "file" ? "@" : "/";
13566
14726
  if (cursorOffset < activePicker.anchor) {
@@ -13617,14 +14777,14 @@ function App({
13617
14777
  return;
13618
14778
  }
13619
14779
  }, [input, cursorOffset, activePicker, filePickerEnabled]);
13620
- useEffect6(() => {
14780
+ useEffect7(() => {
13621
14781
  if (activePicker?.kind !== "file") return;
13622
14782
  const max = Math.max(0, filteredFileItems.length - 1);
13623
14783
  if (activePicker.selected > max) {
13624
14784
  setActivePicker({ ...activePicker, selected: max });
13625
14785
  }
13626
14786
  }, [filteredFileItems.length, activePicker]);
13627
- useEffect6(() => {
14787
+ useEffect7(() => {
13628
14788
  if (activePicker?.kind !== "slash") return;
13629
14789
  const max = Math.max(0, filteredSlashItems.length - 1);
13630
14790
  if (activePicker.selected > max) {
@@ -13668,7 +14828,7 @@ function App({
13668
14828
  pickerCancelRef.current = cursorOffset;
13669
14829
  setActivePicker(null);
13670
14830
  }, [cursorOffset]);
13671
- useEffect6(() => {
14831
+ useEffect7(() => {
13672
14832
  const modalActive = showHelpMenu || commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || perm !== null || limitModal !== null;
13673
14833
  if (modalActive && activePicker !== null) {
13674
14834
  setActivePicker(null);
@@ -13685,7 +14845,7 @@ function App({
13685
14845
  limitModal,
13686
14846
  activePicker
13687
14847
  ]);
13688
- useEffect6(() => {
14848
+ useEffect7(() => {
13689
14849
  if (!cfg) return;
13690
14850
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
13691
14851
  ({ pruneSessions: pruneSessions2 }) => pruneSessions2().then((removed) => {
@@ -13711,7 +14871,7 @@ function App({
13711
14871
  }
13712
14872
  });
13713
14873
  if (cfg.memoryEnabled) {
13714
- const dbPath = cfg.memoryDbPath ?? join22(process.cwd(), ".kimiflare", "memory.db");
14874
+ const dbPath = cfg.memoryDbPath ?? join23(process.cwd(), ".kimiflare", "memory.db");
13715
14875
  const manager = new MemoryManager({
13716
14876
  dbPath,
13717
14877
  accountId: cfg.accountId,
@@ -13760,6 +14920,13 @@ function App({
13760
14920
  } catch {
13761
14921
  }
13762
14922
  })();
14923
+ if (existsSync3(join23(cwd, "KIMI.md"))) {
14924
+ const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
14925
+ const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
14926
+ if (driftCount >= 5) {
14927
+ setKimiMdStale(true);
14928
+ }
14929
+ }
13763
14930
  } else {
13764
14931
  memoryManagerRef.current?.close();
13765
14932
  memoryManagerRef.current = null;
@@ -13779,7 +14946,7 @@ function App({
13779
14946
  }
13780
14947
  });
13781
14948
  }, [cfg, setEvents]);
13782
- useEffect6(() => {
14949
+ useEffect7(() => {
13783
14950
  const id = setInterval(() => {
13784
14951
  try {
13785
14952
  performance.clearMarks();
@@ -13804,7 +14971,7 @@ function App({
13804
14971
  ]);
13805
14972
  }
13806
14973
  }, [setEvents]);
13807
- useEffect6(() => {
14974
+ useEffect7(() => {
13808
14975
  if (!cfg || updateCheckedRef.current) return;
13809
14976
  updateCheckedRef.current = true;
13810
14977
  if (initialUpdateResult) {
@@ -13855,7 +15022,7 @@ function App({
13855
15022
  }
13856
15023
  });
13857
15024
  }, [cfg, initialUpdateResult]);
13858
- useEffect6(() => {
15025
+ useEffect7(() => {
13859
15026
  modeRef.current = mode;
13860
15027
  if (cacheStableRef.current) {
13861
15028
  messagesRef.current[1] = {
@@ -13882,10 +15049,10 @@ function App({
13882
15049
  executorRef.current.clearSessionPermissions();
13883
15050
  }
13884
15051
  }, [mode, cfg?.model]);
13885
- useEffect6(() => {
15052
+ useEffect7(() => {
13886
15053
  effortRef.current = effort;
13887
15054
  }, [effort]);
13888
- useEffect6(() => {
15055
+ useEffect7(() => {
13889
15056
  if (!cfg) return;
13890
15057
  const id = setInterval(() => {
13891
15058
  void checkForUpdate().then((result) => {
@@ -14040,7 +15207,7 @@ function App({
14040
15207
  ]);
14041
15208
  }
14042
15209
  }, [cfg]);
14043
- useEffect6(() => {
15210
+ useEffect7(() => {
14044
15211
  if (cfg && !mcpInitRef.current) {
14045
15212
  void initMcp();
14046
15213
  }
@@ -14365,6 +15532,9 @@ function App({
14365
15532
  } finally {
14366
15533
  setBusy(false);
14367
15534
  setTurnStartedAt(null);
15535
+ setTurnPhase("waiting");
15536
+ setCurrentToolName(null);
15537
+ setLastActivityAt(null);
14368
15538
  activeControllerRef.current = null;
14369
15539
  permResolveRef.current = null;
14370
15540
  limitResolveRef.current = null;
@@ -14424,7 +15594,7 @@ function App({
14424
15594
  lspManagerRef.current.notifyChange(path, content);
14425
15595
  } else {
14426
15596
  void import("fs/promises").then(
14427
- ({ readFile: readFile17 }) => readFile17(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
15597
+ ({ readFile: readFile18 }) => readFile18(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
14428
15598
  })
14429
15599
  );
14430
15600
  }
@@ -14531,10 +15701,20 @@ function App({
14531
15701
  }
14532
15702
  permResolveRef.current = resolve2;
14533
15703
  setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
14534
- })
15704
+ }),
15705
+ onKimiMdStale: () => {
15706
+ if (!kimiMdStaleNudgedRef.current) {
15707
+ kimiMdStaleNudgedRef.current = true;
15708
+ setKimiMdStale(true);
15709
+ setEvents((e) => [
15710
+ ...e,
15711
+ { kind: "info", key: mkKey(), text: "Project context may be stale. Run /init to refresh KIMI.md based on recent changes." }
15712
+ ]);
15713
+ }
15714
+ }
14535
15715
  }
14536
15716
  });
14537
- if (existsSync3(join22(cwd, "KIMI.md"))) {
15717
+ if (existsSync3(join23(cwd, "KIMI.md"))) {
14538
15718
  if (cacheStableRef.current) {
14539
15719
  messagesRef.current[1] = {
14540
15720
  role: "system",
@@ -14560,6 +15740,9 @@ function App({
14560
15740
  ...e,
14561
15741
  { kind: "info", key: mkKey(), text: "KIMI.md generated; context loaded for future turns" }
14562
15742
  ]);
15743
+ void memoryManagerRef.current?.recordKimiMdRefresh(cwd, ensureSessionId());
15744
+ setKimiMdStale(false);
15745
+ kimiMdStaleNudgedRef.current = false;
14563
15746
  }
14564
15747
  } catch (e) {
14565
15748
  if (e.name === "AbortError") {
@@ -14586,6 +15769,9 @@ function App({
14586
15769
  if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
14587
15770
  setBusy(false);
14588
15771
  setTurnStartedAt(null);
15772
+ setTurnPhase("waiting");
15773
+ setCurrentToolName(null);
15774
+ setLastActivityAt(null);
14589
15775
  activeAsstIdRef.current = null;
14590
15776
  activeControllerRef.current = null;
14591
15777
  permResolveRef.current = null;
@@ -14604,15 +15790,19 @@ function App({
14604
15790
  (picked) => {
14605
15791
  setShowThemePicker(false);
14606
15792
  if (!picked) return;
14607
- setCfg((c) => c ? { ...c, theme: picked.name } : c);
14608
- if (cfg) void saveConfig({ ...cfg, theme: picked.name }).catch(() => {
15793
+ setCfg((c) => {
15794
+ if (!c) return c;
15795
+ const updated = { ...c, theme: picked.name };
15796
+ void saveConfig(updated).catch(() => {
15797
+ });
15798
+ return updated;
14609
15799
  });
14610
15800
  setEvents((e) => [
14611
15801
  ...e,
14612
15802
  { kind: "info", key: mkKey(), text: `theme: ${picked.label} \u2014 restart to apply` }
14613
15803
  ]);
14614
15804
  },
14615
- [cfg]
15805
+ []
14616
15806
  );
14617
15807
  const handleResumePick = useCallback2(
14618
15808
  async (picked) => {
@@ -14936,8 +16126,12 @@ use: /thinking low | medium | high`
14936
16126
  ]);
14937
16127
  return true;
14938
16128
  }
14939
- setCfg((c2) => c2 ? { ...c2, theme: next.name } : c2);
14940
- if (cfg) void saveConfig({ ...cfg, theme: next.name }).catch(() => {
16129
+ setCfg((prev) => {
16130
+ if (!prev) return prev;
16131
+ const updated = { ...prev, theme: next.name };
16132
+ void saveConfig(updated).catch(() => {
16133
+ });
16134
+ return updated;
14941
16135
  });
14942
16136
  setEvents((e) => [
14943
16137
  ...e,
@@ -15506,6 +16700,13 @@ ${lines.join("\n")}` }]);
15506
16700
  };
15507
16701
  }
15508
16702
  }
16703
+ turnCounterRef.current += 1;
16704
+ if (turnCounterRef.current % 15 === 0 && existsSync3(join23(process.cwd(), "KIMI.md")) && !kimiMdStale) {
16705
+ setEvents((e) => [
16706
+ ...e,
16707
+ { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps KimiFlare stay accurate as your project evolves." }
16708
+ ]);
16709
+ }
15509
16710
  setBusy(true);
15510
16711
  gatewayMetaRef.current = null;
15511
16712
  setGatewayMeta(null);
@@ -15529,6 +16730,8 @@ ${lines.join("\n")}` }]);
15529
16730
  onAssistantStart: () => {
15530
16731
  const id = nextAssistantId++;
15531
16732
  activeAsstIdRef.current = id;
16733
+ setTurnPhase("generating");
16734
+ setLastActivityAt(Date.now());
15532
16735
  setEvents((e) => [
15533
16736
  ...e,
15534
16737
  { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true }
@@ -15537,17 +16740,23 @@ ${lines.join("\n")}` }]);
15537
16740
  onReasoningDelta: (d) => {
15538
16741
  const id = activeAsstIdRef.current;
15539
16742
  if (id !== null) updateAssistant(id, (e) => ({ reasoning: e.reasoning + d }));
16743
+ setLastActivityAt(Date.now());
15540
16744
  },
15541
16745
  onTextDelta: (d) => {
15542
16746
  const id = activeAsstIdRef.current;
15543
16747
  if (id !== null) updateAssistant(id, (e) => ({ text: e.text + d }));
16748
+ setLastActivityAt(Date.now());
15544
16749
  },
15545
16750
  onAssistantFinal: () => {
15546
16751
  const id = activeAsstIdRef.current;
15547
16752
  if (id !== null) updateAssistant(id, () => ({ streaming: false }));
16753
+ setTurnPhase("waiting");
15548
16754
  },
15549
16755
  onToolCallFinalized: (call) => {
15550
16756
  pendingToolCallsRef.current.set(call.id, call.function.name);
16757
+ setTurnPhase("executing");
16758
+ setCurrentToolName(call.function.name);
16759
+ setLastActivityAt(Date.now());
15551
16760
  const spec = executorRef.current.list().find((t) => t.name === call.function.name);
15552
16761
  let renderMeta;
15553
16762
  let argsParsed = {};
@@ -15570,12 +16779,18 @@ ${lines.join("\n")}` }]);
15570
16779
  args: call.function.arguments,
15571
16780
  status: "running",
15572
16781
  render: renderMeta,
15573
- expanded: false
16782
+ expanded: false,
16783
+ startedAt: Date.now()
15574
16784
  }
15575
16785
  ]);
15576
16786
  },
15577
16787
  onToolResult: (r) => {
15578
16788
  pendingToolCallsRef.current.delete(r.tool_call_id);
16789
+ setLastActivityAt(Date.now());
16790
+ if (pendingToolCallsRef.current.size === 0) {
16791
+ setTurnPhase("waiting");
16792
+ setCurrentToolName(null);
16793
+ }
15579
16794
  updateTool(r.tool_call_id, {
15580
16795
  status: r.ok ? "done" : "error",
15581
16796
  result: r.content
@@ -15643,7 +16858,17 @@ ${lines.join("\n")}` }]);
15643
16858
  onToolLimitReached: () => new Promise((resolve2) => {
15644
16859
  limitResolveRef.current = resolve2;
15645
16860
  setLimitModal({ limit: 50, resolve: resolve2 });
15646
- })
16861
+ }),
16862
+ onKimiMdStale: () => {
16863
+ if (!kimiMdStaleNudgedRef.current) {
16864
+ kimiMdStaleNudgedRef.current = true;
16865
+ setKimiMdStale(true);
16866
+ setEvents((e) => [
16867
+ ...e,
16868
+ { kind: "info", key: mkKey(), text: "Project context may be stale. Run /init to refresh KIMI.md based on recent changes." }
16869
+ ]);
16870
+ }
16871
+ }
15647
16872
  };
15648
16873
  try {
15649
16874
  await runAgentTurn({
@@ -15672,7 +16897,7 @@ ${lines.join("\n")}` }]);
15672
16897
  lspManagerRef.current.notifyChange(path, content2);
15673
16898
  } else {
15674
16899
  void import("fs/promises").then(
15675
- ({ readFile: readFile17 }) => readFile17(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
16900
+ ({ readFile: readFile18 }) => readFile18(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
15676
16901
  })
15677
16902
  );
15678
16903
  }
@@ -15804,6 +17029,9 @@ ${lines.join("\n")}` }]);
15804
17029
  if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
15805
17030
  setBusy(false);
15806
17031
  setTurnStartedAt(null);
17032
+ setTurnPhase("waiting");
17033
+ setCurrentToolName(null);
17034
+ setLastActivityAt(null);
15807
17035
  activeAsstIdRef.current = null;
15808
17036
  activeControllerRef.current = null;
15809
17037
  permResolveRef.current = null;
@@ -15820,7 +17048,7 @@ ${lines.join("\n")}` }]);
15820
17048
  },
15821
17049
  [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateGatewayMeta, flushToolBatch]
15822
17050
  );
15823
- useEffect6(() => {
17051
+ useEffect7(() => {
15824
17052
  if (!busy && queue.length > 0) {
15825
17053
  const next = queue[0];
15826
17054
  setQueue((q) => q.slice(1));
@@ -15848,7 +17076,7 @@ ${lines.join("\n")}` }]);
15848
17076
  [busy, processMessage]
15849
17077
  );
15850
17078
  submitRef.current = submit;
15851
- useEffect6(() => {
17079
+ useEffect7(() => {
15852
17080
  if (compactSuggestedRef.current) return;
15853
17081
  if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
15854
17082
  compactSuggestedRef.current = true;
@@ -15947,7 +17175,7 @@ ${lines.join("\n")}` }]);
15947
17175
  {
15948
17176
  servers: cfg?.lspServers ?? {},
15949
17177
  currentScope: lspScope,
15950
- hasProjectDir: existsSync3(join22(process.cwd(), ".kimiflare")),
17178
+ hasProjectDir: existsSync3(join23(process.cwd(), ".kimiflare")),
15951
17179
  onDone: () => setShowLspWizard(false),
15952
17180
  onSave: (servers, enabled, scope) => {
15953
17181
  setCfg((c) => c ? { ...c, lspEnabled: enabled, lspServers: servers } : c);
@@ -16100,7 +17328,11 @@ ${lines.join("\n")}` }]);
16100
17328
  gatewayMeta,
16101
17329
  codeMode,
16102
17330
  cloudMode: cfg.cloudMode,
16103
- cloudBudget
17331
+ cloudBudget,
17332
+ phase: turnPhase,
17333
+ currentTool: currentToolName,
17334
+ lastActivityAt,
17335
+ kimiMdStale
16104
17336
  }
16105
17337
  ),
16106
17338
  activePicker?.kind === "file" && /* @__PURE__ */ jsx23(
@@ -16249,6 +17481,7 @@ var init_app = __esm({
16249
17481
  init_theme_context();
16250
17482
  init_theme_picker();
16251
17483
  init_theme();
17484
+ init_theme_loader();
16252
17485
  init_lsp_config();
16253
17486
  init_lsp_nudge();
16254
17487
  init_file_picker();