kimiflare 0.64.0 → 0.66.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
@@ -401,11 +401,12 @@ var init_logger = __esm({
401
401
  });
402
402
 
403
403
  // src/util/sse.ts
404
- async function* readSSE(stream, signal, idleTimeoutMs) {
404
+ async function* readSSE(stream, signal, idleTimeoutMs, postFirstByteIdleTimeoutMs) {
405
405
  const reader = stream.getReader();
406
406
  const decoder = new TextDecoder("utf-8");
407
407
  let buffer = "";
408
408
  let lastDataAt = Date.now();
409
+ let gotFirstByte = false;
409
410
  const onAbort = () => {
410
411
  reader.cancel(new DOMException("aborted", "AbortError")).catch(() => {
411
412
  });
@@ -427,16 +428,18 @@ async function* readSSE(stream, signal, idleTimeoutMs) {
427
428
  try {
428
429
  while (true) {
429
430
  if (signal?.aborted) throw new DOMException("aborted", "AbortError");
430
- if (idleTimeoutMs !== void 0 && Date.now() - lastDataAt > idleTimeoutMs) {
431
- logger.warn("sse:idle_timeout", { idleTimeoutMs });
431
+ const activeIdleTimeoutMs = gotFirstByte && postFirstByteIdleTimeoutMs !== void 0 ? postFirstByteIdleTimeoutMs : idleTimeoutMs;
432
+ if (activeIdleTimeoutMs !== void 0 && Date.now() - lastDataAt > activeIdleTimeoutMs) {
433
+ logger.warn("sse:idle_timeout", { idleTimeoutMs: activeIdleTimeoutMs, gotFirstByte });
432
434
  throw new DOMException(
433
- `kimiflare: stream idle for ${idleTimeoutMs}ms \u2014 no data received from API`,
435
+ `kimiflare: stream idle for ${activeIdleTimeoutMs}ms \u2014 no data received from API`,
434
436
  "TimeoutError"
435
437
  );
436
438
  }
437
439
  const { done, value } = await abortRace(reader.read());
438
440
  if (done) break;
439
441
  lastDataAt = Date.now();
442
+ gotFirstByte = true;
440
443
  buffer += decoder.decode(value, { stream: true });
441
444
  buffer = buffer.replace(/\r\n/g, "\n");
442
445
  let sep3;
@@ -717,7 +720,7 @@ async function* runKimi(opts2) {
717
720
  if (meta) yield { type: "gateway_meta", meta };
718
721
  let lastUsage = null;
719
722
  logger.debug("runKimi:stream_start", { requestId });
720
- for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs)) {
723
+ for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs, opts2.postFirstByteIdleTimeoutMs)) {
721
724
  if (ev.type === "usage") lastUsage = ev.usage;
722
725
  yield ev;
723
726
  }
@@ -799,12 +802,11 @@ function readGatewayMeta(headers) {
799
802
  if (model) meta.model = model;
800
803
  return Object.keys(meta).length > 0 ? meta : null;
801
804
  }
802
- async function* parseStream(body, signal, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS) {
805
+ async function* parseStream(body, signal, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, postFirstByteIdleTimeoutMs = DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS) {
803
806
  const toolCalls = /* @__PURE__ */ new Map();
804
807
  let lastUsage = null;
805
808
  let finishReason = null;
806
- let lastDataAt = Date.now();
807
- for await (const dataStr of readSSE(body, signal, idleTimeoutMs)) {
809
+ for await (const dataStr of readSSE(body, signal, idleTimeoutMs, postFirstByteIdleTimeoutMs)) {
808
810
  if (dataStr === "[DONE]") break;
809
811
  let chunk = null;
810
812
  try {
@@ -942,7 +944,7 @@ function sleep(ms, signal) {
942
944
  signal?.addEventListener("abort", onAbort, { once: true });
943
945
  });
944
946
  }
945
- var RETRYABLE_CODES, MAX_ATTEMPTS, DEFAULT_IDLE_TIMEOUT_MS;
947
+ var RETRYABLE_CODES, MAX_ATTEMPTS, DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS;
946
948
  var init_client = __esm({
947
949
  "src/agent/client.ts"() {
948
950
  "use strict";
@@ -954,6 +956,7 @@ var init_client = __esm({
954
956
  RETRYABLE_CODES = /* @__PURE__ */ new Set([3040]);
955
957
  MAX_ATTEMPTS = 5;
956
958
  DEFAULT_IDLE_TIMEOUT_MS = 6e4;
959
+ DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS = 3e4;
957
960
  }
958
961
  });
959
962
 
@@ -2155,7 +2158,7 @@ var init_session_state = __esm({
2155
2158
  }
2156
2159
  add(a) {
2157
2160
  while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
2158
- this.evictOldest();
2161
+ this.evictSizeWeighted();
2159
2162
  }
2160
2163
  while (this.artifacts.size >= this.maxArtifacts) {
2161
2164
  this.evictOldest();
@@ -2196,6 +2199,21 @@ var init_session_state = __esm({
2196
2199
  }
2197
2200
  if (oldest) this.artifacts.delete(oldest.id);
2198
2201
  }
2202
+ /** Evict the largest artifact among the oldest quartile (by timestamp).
2203
+ * Bounded by the oldest quartile so we never evict freshly-added artifacts;
2204
+ * size-weighted within that window so one big artifact gets dropped instead
2205
+ * of many small ones. */
2206
+ evictSizeWeighted() {
2207
+ const sorted = [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
2208
+ if (sorted.length === 0) return;
2209
+ const quartile = Math.max(1, Math.ceil(sorted.length / 4));
2210
+ const candidates = sorted.slice(0, quartile);
2211
+ let pick3 = candidates[0];
2212
+ for (const a of candidates) {
2213
+ if (a.raw.length > pick3.raw.length) pick3 = a;
2214
+ }
2215
+ this.artifacts.delete(pick3.id);
2216
+ }
2199
2217
  };
2200
2218
  }
2201
2219
  });
@@ -3404,7 +3422,8 @@ Use console.log() to return results. Only console.log output will be sent back t
3404
3422
  cloudMode: opts2.cloudMode,
3405
3423
  cloudToken: opts2.cloudToken,
3406
3424
  cloudDeviceId: opts2.cloudDeviceId,
3407
- idleTimeoutMs: 6e4
3425
+ idleTimeoutMs: opts2.idleTimeoutMs ?? 6e4,
3426
+ postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs
3408
3427
  });
3409
3428
  let gotFirstChunk = false;
3410
3429
  for await (const ev of events) {
@@ -3454,7 +3473,7 @@ Use console.log() to return results. Only console.log output will be sent back t
3454
3473
  if (lastUsage) {
3455
3474
  opts2.callbacks.onUsageFinal?.(lastUsage, gatewayMeta);
3456
3475
  cumulativePromptTokens += lastUsage.prompt_tokens;
3457
- if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens && toolCalls.length > 0) {
3476
+ if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens) {
3458
3477
  budgetExhausted = true;
3459
3478
  }
3460
3479
  }
@@ -3693,7 +3712,22 @@ ${sandboxResult.output}` : sandboxResult.output;
3693
3712
  }
3694
3713
  }
3695
3714
  }
3696
- } catch {
3715
+ } catch (err) {
3716
+ const sid = opts2.sessionId ?? "default";
3717
+ const next = (memoryExtractionErrorCounts.get(sid) ?? 0) + 1;
3718
+ memoryExtractionErrorCounts.set(sid, next);
3719
+ const msg = err instanceof Error ? err.message : String(err);
3720
+ logger.debug("memory:extract_error", {
3721
+ sessionId: opts2.sessionId,
3722
+ tool: tc.function.name,
3723
+ count: next,
3724
+ error: msg
3725
+ });
3726
+ if (next === 1) {
3727
+ opts2.callbacks.onWarning?.(
3728
+ `[memory] auto-extraction failed (${msg}). Subsequent failures will be counted silently; check /memory health.`
3729
+ );
3730
+ }
3697
3731
  }
3698
3732
  })();
3699
3733
  }
@@ -3770,7 +3804,7 @@ function validateToolArguments(raw) {
3770
3804
  return "{}";
3771
3805
  }
3772
3806
  }
3773
- var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3807
+ var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, memoryExtractionErrorCounts, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3774
3808
  var init_loop = __esm({
3775
3809
  "src/agent/loop.ts"() {
3776
3810
  "use strict";
@@ -3800,6 +3834,7 @@ var init_loop = __esm({
3800
3834
  codeModeApiCache = /* @__PURE__ */ new Map();
3801
3835
  driftAccumulator = /* @__PURE__ */ new Map();
3802
3836
  DRIFT_THRESHOLD = 5;
3837
+ memoryExtractionErrorCounts = /* @__PURE__ */ new Map();
3803
3838
  MAX_PROMPT_TOKENS = 24e4;
3804
3839
  MAX_TOOL_CONTENT_CHARS = 1e4;
3805
3840
  }
@@ -3867,10 +3902,13 @@ var init_read = __esm({
3867
3902
  needsPermission: false,
3868
3903
  render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
3869
3904
  async run(args, ctx) {
3905
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3870
3906
  const abs = resolvePath(ctx.cwd, args.path);
3871
3907
  const st = await stat2(abs);
3872
3908
  if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
3873
- const text = await readFile3(abs, "utf8");
3909
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3910
+ const text = await readFile3(abs, { encoding: "utf8", signal: ctx.signal });
3911
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3874
3912
  const lines = text.split("\n");
3875
3913
  const start = Math.max(0, (args.offset ?? 1) - 1);
3876
3914
  const end = args.limit ? Math.min(lines.length, start + args.limit) : lines.length;
@@ -4162,14 +4200,31 @@ var init_glob = __esm({
4162
4200
  needsPermission: false,
4163
4201
  render: (args) => ({ title: `glob ${args.pattern ?? ""}${args.path ? ` in ${collapsePath(String(args.path), process.cwd())}` : ""}` }),
4164
4202
  async run(args, ctx) {
4203
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4165
4204
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4166
- const entries = await fg(args.pattern, {
4205
+ const stream = fg.stream(args.pattern, {
4167
4206
  cwd: root,
4168
4207
  absolute: true,
4169
4208
  dot: false,
4170
4209
  onlyFiles: false,
4171
4210
  stats: true
4172
4211
  });
4212
+ const entries = [];
4213
+ const onAbort = () => {
4214
+ try {
4215
+ stream.destroy(new DOMException("aborted", "AbortError"));
4216
+ } catch {
4217
+ }
4218
+ };
4219
+ ctx.signal?.addEventListener("abort", onAbort, { once: true });
4220
+ try {
4221
+ for await (const entry of stream) {
4222
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4223
+ entries.push(entry);
4224
+ }
4225
+ } finally {
4226
+ ctx.signal?.removeEventListener("abort", onAbort);
4227
+ }
4173
4228
  entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
4174
4229
  const paths = entries.slice(0, 200).map((e) => e.path);
4175
4230
  return paths.length ? paths.join("\n") : "(no matches)";
@@ -4193,14 +4248,14 @@ async function hasRipgrep() {
4193
4248
  }
4194
4249
  return cachedHasRg;
4195
4250
  }
4196
- async function runRipgrep(args, root, mode) {
4251
+ async function runRipgrep(args, root, mode, signal) {
4197
4252
  const rgArgs = ["--no-heading", "--color=never", "--line-number"];
4198
4253
  if (args.case_insensitive) rgArgs.push("-i");
4199
4254
  if (args.glob) rgArgs.push("--glob", args.glob);
4200
4255
  if (mode === "files") rgArgs.push("-l");
4201
4256
  rgArgs.push("--", args.pattern, root);
4202
4257
  try {
4203
- const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
4258
+ const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024, signal });
4204
4259
  const trimmed = stdout.trim();
4205
4260
  if (!trimmed) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
4206
4261
  return {
@@ -4214,7 +4269,7 @@ async function runRipgrep(args, root, mode) {
4214
4269
  throw new Error(err.stderr || String(e));
4215
4270
  }
4216
4271
  }
4217
- async function runJsFallback(args, root, mode) {
4272
+ async function runJsFallback(args, root, mode, signal) {
4218
4273
  const re = new RegExp(args.pattern, args.case_insensitive ? "i" : "");
4219
4274
  const globPattern = args.glob ? `**/${args.glob}` : "**/*";
4220
4275
  const files = await fg2(globPattern, {
@@ -4225,7 +4280,9 @@ async function runJsFallback(args, root, mode) {
4225
4280
  ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"]
4226
4281
  });
4227
4282
  const out = [];
4228
- for (const file of files.slice(0, 5e3)) {
4283
+ for (let fi = 0; fi < Math.min(files.length, 5e3); fi++) {
4284
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
4285
+ const file = files[fi];
4229
4286
  try {
4230
4287
  const content = await readFile6(file, "utf8");
4231
4288
  if (mode === "files") {
@@ -4280,10 +4337,11 @@ var init_grep = __esm({
4280
4337
  needsPermission: false,
4281
4338
  render: (args) => ({ title: `grep ${args.pattern ?? ""}${args.glob ? ` (${args.glob})` : ""}` }),
4282
4339
  async run(args, ctx) {
4340
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4283
4341
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4284
4342
  const mode = args.output_mode ?? "content";
4285
- if (await hasRipgrep()) return runRipgrep(args, root, mode);
4286
- return runJsFallback(args, root, mode);
4343
+ if (await hasRipgrep()) return runRipgrep(args, root, mode, ctx.signal);
4344
+ return runJsFallback(args, root, mode, ctx.signal);
4287
4345
  }
4288
4346
  };
4289
4347
  }
@@ -9098,6 +9156,8 @@ var init_pricing = __esm({
9098
9156
  import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir10 } from "fs/promises";
9099
9157
  import { homedir as homedir11 } from "os";
9100
9158
  import { join as join18 } from "path";
9159
+ import { EventEmitter as EventEmitter2 } from "events";
9160
+ import { randomUUID } from "crypto";
9101
9161
  function usageDir2() {
9102
9162
  const xdg = process.env.XDG_DATA_HOME || join18(homedir11(), ".local", "share");
9103
9163
  return join18(xdg, "kimiflare");
@@ -9128,6 +9188,11 @@ async function saveLog(log2) {
9128
9188
  await mkdir10(usageDir2(), { recursive: true });
9129
9189
  await writeFile10(usagePath2(), JSON.stringify(log2, null, 2), "utf8");
9130
9190
  }
9191
+ function withLock(fn) {
9192
+ const next = writeChain.then(fn, fn);
9193
+ writeChain = next.catch(() => void 0);
9194
+ return next;
9195
+ }
9131
9196
  async function loadHistory() {
9132
9197
  try {
9133
9198
  const raw = await readFile13(historyPath(), "utf8");
@@ -9229,34 +9294,100 @@ function pruneUsageLog(log2) {
9229
9294
  return { ...log2, days, sessions };
9230
9295
  }
9231
9296
  async function recordUsage(sessionId, usage, gateway) {
9232
- const log2 = pruneUsageLog(await loadLog2());
9233
- const date = today2();
9234
- const gatewaySnapshot = gateway ? await fetchGatewayUsageSnapshot(gateway).catch(() => gatewaySnapshotFromMeta(gateway.meta)) : void 0;
9235
- const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, usage.prompt_tokens_details?.cached_tokens ?? 0);
9236
- const totalCost = gatewaySnapshot?.cost ?? cost.total;
9237
- const day = getOrCreateDay(log2, date);
9238
- day.promptTokens += usage.prompt_tokens;
9239
- day.completionTokens += usage.completion_tokens;
9240
- day.cachedTokens += usage.prompt_tokens_details?.cached_tokens ?? 0;
9241
- day.cost += totalCost;
9242
- if (gatewaySnapshot) {
9243
- day.gatewayRequests = (day.gatewayRequests ?? 0) + 1;
9244
- day.gatewayCachedRequests = (day.gatewayCachedRequests ?? 0) + (gatewaySnapshot.cached ? 1 : 0);
9245
- day.gatewayCost = (day.gatewayCost ?? 0) + (gatewaySnapshot.cost ?? 0);
9246
- }
9247
- const session = getOrCreateSession(log2, sessionId, date);
9248
- session.promptTokens += usage.prompt_tokens;
9249
- session.completionTokens += usage.completion_tokens;
9250
- session.cachedTokens += usage.prompt_tokens_details?.cached_tokens ?? 0;
9251
- session.cost += totalCost;
9252
- if (gatewaySnapshot) {
9253
- session.gatewayRequests = (session.gatewayRequests ?? 0) + 1;
9254
- session.gatewayCachedRequests = (session.gatewayCachedRequests ?? 0) + (gatewaySnapshot.cached ? 1 : 0);
9255
- session.gatewayCost = (session.gatewayCost ?? 0) + (gatewaySnapshot.cost ?? 0);
9256
- session.gatewayLogs = [...session.gatewayLogs ?? [], gatewaySnapshot].slice(-100);
9257
- }
9258
- await saveLog(log2);
9259
- await upsertHistoryDay(day);
9297
+ const cost = calculateCost(
9298
+ usage.prompt_tokens,
9299
+ usage.completion_tokens,
9300
+ usage.prompt_tokens_details?.cached_tokens ?? 0
9301
+ );
9302
+ const estimatedCost = cost.total;
9303
+ const cachedTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
9304
+ const turnId = randomUUID();
9305
+ const logId = gateway?.meta.logId;
9306
+ await withLock(async () => {
9307
+ const log2 = pruneUsageLog(await loadLog2());
9308
+ const date = today2();
9309
+ const day = getOrCreateDay(log2, date);
9310
+ day.promptTokens += usage.prompt_tokens;
9311
+ day.completionTokens += usage.completion_tokens;
9312
+ day.cachedTokens += cachedTokens;
9313
+ day.cost += estimatedCost;
9314
+ const session = getOrCreateSession(log2, sessionId, date);
9315
+ session.promptTokens += usage.prompt_tokens;
9316
+ session.completionTokens += usage.completion_tokens;
9317
+ session.cachedTokens += cachedTokens;
9318
+ session.cost += estimatedCost;
9319
+ const turn = {
9320
+ turnId,
9321
+ logId,
9322
+ estimatedCost,
9323
+ cacheStatus: gateway?.meta.cacheStatus
9324
+ };
9325
+ session.turns = [...session.turns ?? [], turn].slice(-MAX_TURNS_PER_SESSION);
9326
+ if (gateway) {
9327
+ const stub = gatewaySnapshotFromMeta(gateway.meta);
9328
+ if (stub) {
9329
+ session.gatewayRequests = (session.gatewayRequests ?? 0) + 1;
9330
+ session.gatewayCachedRequests = (session.gatewayCachedRequests ?? 0) + (stub.cached ? 1 : 0);
9331
+ session.gatewayLogs = [...session.gatewayLogs ?? [], stub].slice(-100);
9332
+ day.gatewayRequests = (day.gatewayRequests ?? 0) + 1;
9333
+ day.gatewayCachedRequests = (day.gatewayCachedRequests ?? 0) + (stub.cached ? 1 : 0);
9334
+ }
9335
+ }
9336
+ await saveLog(log2);
9337
+ await upsertHistoryDay(day);
9338
+ });
9339
+ usageEvents.emit("update", sessionId);
9340
+ if (gateway && logId) {
9341
+ void reconcileTurnCost(sessionId, turnId, gateway).catch(() => void 0);
9342
+ }
9343
+ }
9344
+ async function reconcileTurnCost(sessionId, turnId, gateway) {
9345
+ for (const delay of RECONCILE_DELAYS_MS) {
9346
+ await new Promise((r) => setTimeout(r, delay));
9347
+ let snapshot;
9348
+ try {
9349
+ snapshot = await fetchGatewayUsageSnapshot(gateway);
9350
+ } catch {
9351
+ continue;
9352
+ }
9353
+ if (!snapshot || typeof snapshot.cost !== "number") continue;
9354
+ const patched = await withLock(async () => {
9355
+ const log2 = pruneUsageLog(await loadLog2());
9356
+ const session = log2.sessions.find((s) => s.id === sessionId);
9357
+ const turn = session?.turns?.find((t) => t.turnId === turnId);
9358
+ if (!session || !turn || turn.confirmedCost !== void 0) return false;
9359
+ const delta = snapshot.cost - turn.estimatedCost;
9360
+ turn.confirmedCost = snapshot.cost;
9361
+ turn.durationMs = snapshot.duration;
9362
+ turn.cacheStatus = snapshot.cacheStatus ?? turn.cacheStatus;
9363
+ turn.reconciledAt = Date.now();
9364
+ session.cost += delta;
9365
+ session.gatewayCost = (session.gatewayCost ?? 0) + snapshot.cost;
9366
+ const day = getOrCreateDay(log2, session.date);
9367
+ day.cost += delta;
9368
+ day.gatewayCost = (day.gatewayCost ?? 0) + snapshot.cost;
9369
+ const logs = session.gatewayLogs ?? [];
9370
+ const idx = snapshot.logId ? logs.findIndex((l) => l.logId === snapshot.logId) : -1;
9371
+ if (idx >= 0) logs[idx] = snapshot;
9372
+ else logs.push(snapshot);
9373
+ session.gatewayLogs = logs.slice(-100);
9374
+ await saveLog(log2);
9375
+ await upsertHistoryDay(day);
9376
+ return true;
9377
+ });
9378
+ if (patched) {
9379
+ usageEvents.emit("update", sessionId);
9380
+ }
9381
+ return;
9382
+ }
9383
+ await withLock(async () => {
9384
+ const log2 = await loadLog2();
9385
+ const turn = log2.sessions.find((s) => s.id === sessionId)?.turns?.find((t) => t.turnId === turnId);
9386
+ if (!turn || turn.confirmedCost !== void 0) return;
9387
+ turn.reconcileFailed = true;
9388
+ await saveLog(log2);
9389
+ });
9390
+ usageEvents.emit("update", sessionId);
9260
9391
  }
9261
9392
  function mergeDays(usageDays, historyDays) {
9262
9393
  const map = /* @__PURE__ */ new Map();
@@ -9270,7 +9401,18 @@ async function getCostReport(sessionId) {
9270
9401
  const allDays = mergeDays(log2.days, history);
9271
9402
  const date = today2();
9272
9403
  const currentMonth = date.slice(0, 7);
9273
- const session = sessionId ? log2.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 } : { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
9404
+ const rawSession = sessionId ? log2.sessions.find((s) => s.id === sessionId) : void 0;
9405
+ const session = rawSession ? {
9406
+ date: rawSession.date,
9407
+ promptTokens: rawSession.promptTokens,
9408
+ completionTokens: rawSession.completionTokens,
9409
+ cachedTokens: rawSession.cachedTokens,
9410
+ cost: rawSession.cost,
9411
+ gatewayRequests: rawSession.gatewayRequests,
9412
+ gatewayCachedRequests: rawSession.gatewayCachedRequests,
9413
+ gatewayCost: rawSession.gatewayCost,
9414
+ reconcilePending: hasPendingReconcile(rawSession)
9415
+ } : { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
9274
9416
  const todayUsage = log2.days.find((d) => d.date === date) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
9275
9417
  const monthUsage = {
9276
9418
  date: currentMonth,
@@ -9308,6 +9450,12 @@ async function getCostReport(sessionId) {
9308
9450
  }
9309
9451
  return { session, today: todayUsage, month: monthUsage, allTime };
9310
9452
  }
9453
+ function hasPendingReconcile(session) {
9454
+ if (!session.turns) return false;
9455
+ return session.turns.some(
9456
+ (t) => t.logId && t.confirmedCost === void 0 && !t.reconcileFailed
9457
+ );
9458
+ }
9311
9459
  async function getSessionGatewayLogs(sessionId) {
9312
9460
  const log2 = await loadLog2();
9313
9461
  const session = log2.sessions.find((s) => s.id === sessionId);
@@ -9367,7 +9515,7 @@ function formatCostReport(report) {
9367
9515
  add("All time", report.allTime);
9368
9516
  return lines.join("\n");
9369
9517
  }
9370
- var LOG_VERSION2;
9518
+ var LOG_VERSION2, usageEvents, MAX_TURNS_PER_SESSION, RECONCILE_DELAYS_MS, writeChain;
9371
9519
  var init_usage_tracker = __esm({
9372
9520
  "src/usage-tracker.ts"() {
9373
9521
  "use strict";
@@ -9375,6 +9523,10 @@ var init_usage_tracker = __esm({
9375
9523
  init_pricing();
9376
9524
  init_storage_limits();
9377
9525
  LOG_VERSION2 = 1;
9526
+ usageEvents = new EventEmitter2();
9527
+ MAX_TURNS_PER_SESSION = 50;
9528
+ RECONCILE_DELAYS_MS = [500, 1e3, 2e3, 4e3];
9529
+ writeChain = Promise.resolve();
9378
9530
  }
9379
9531
  });
9380
9532
 
@@ -11523,6 +11675,10 @@ function StatusBar({ usage, sessionUsage, thinking, turnStartedAt, mode, context
11523
11675
  ] }),
11524
11676
  usage && /* @__PURE__ */ jsxs8(Box8, { children: [
11525
11677
  /* @__PURE__ */ jsx9(Text8, { color: theme.info.color, children: buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta, cloudMode, cloudBudget).join(" \xB7 ") }),
11678
+ sessionUsage?.reconcilePending ? /* @__PURE__ */ jsxs8(Text8, { color: theme.muted?.color ?? theme.info.color, dimColor: theme.muted?.dim ?? true, children: [
11679
+ " ",
11680
+ /* @__PURE__ */ jsx9(Spinner3, { type: "dots" })
11681
+ ] }) : null,
11526
11682
  warn ? /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, bold: true, children: [
11527
11683
  " \xB7 ",
11528
11684
  "/compact recommended"
@@ -11542,10 +11698,11 @@ function buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta, cloudMo
11542
11698
  const cached = sessionUsage.cachedTokens;
11543
11699
  parts.push(`in ${sessionUsage.promptTokens}${cached ? ` (${cached} cached)` : ""}`);
11544
11700
  parts.push(`ctx ${pct}%`);
11701
+ const prefix = sessionUsage.reconcilePending ? "\u2248$" : "$";
11545
11702
  if (cloudMode) {
11546
- parts.push(`\x1B[9m$${sessionUsage.cost.toFixed(2)}\x1B[29m`);
11703
+ parts.push(`\x1B[9m${prefix}${sessionUsage.cost.toFixed(2)}\x1B[29m`);
11547
11704
  } else {
11548
- parts.push(`$${sessionUsage.cost.toFixed(2)}`);
11705
+ parts.push(`${prefix}${sessionUsage.cost.toFixed(2)}`);
11549
11706
  }
11550
11707
  } else {
11551
11708
  const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
@@ -17804,6 +17961,210 @@ var init_slash_picker = __esm({
17804
17961
  }
17805
17962
  });
17806
17963
 
17964
+ // src/ui/use-picker-controller.ts
17965
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4, useState as useState14 } from "react";
17966
+ function filterPickerItems(items, query) {
17967
+ return fuzzyFilter(items, query, (item) => item.name).slice(0, 50);
17968
+ }
17969
+ function shouldOpenMentionPicker(input, cursorOffset, pickerCancelOffset) {
17970
+ if (pickerCancelOffset === cursorOffset) return false;
17971
+ if (cursorOffset > 0 && input[cursorOffset - 1] === "@") {
17972
+ const beforeAt = cursorOffset - 2;
17973
+ return beforeAt < 0 || /\s/.test(input[beforeAt]);
17974
+ }
17975
+ return false;
17976
+ }
17977
+ function shouldOpenSlashPicker(input, cursorOffset, cancelOffset) {
17978
+ if (cancelOffset === cursorOffset) return false;
17979
+ if (cursorOffset === 0 || input[cursorOffset - 1] !== "/") return false;
17980
+ return /^\s*$/.test(input.slice(0, cursorOffset - 1));
17981
+ }
17982
+ function insertSlashCommand(input, anchor, name) {
17983
+ let tokenEnd = anchor + 1;
17984
+ while (tokenEnd < input.length && !/\s/.test(input[tokenEnd])) tokenEnd++;
17985
+ const head = input.slice(0, anchor + 1) + name;
17986
+ const tail = " " + input.slice(tokenEnd).replace(/^\s+/, "");
17987
+ return { value: head + tail, cursor: head.length + 1 };
17988
+ }
17989
+ function decidePickerTransition(active, input, cursorOffset, pickerCancelOffset, filePickerEnabled) {
17990
+ if (active !== null) {
17991
+ const trigger = active.kind === "file" ? "@" : "/";
17992
+ if (cursorOffset < active.anchor) return { kind: "close" };
17993
+ if (input[active.anchor] !== trigger) return { kind: "close" };
17994
+ const query = input.slice(active.anchor + 1, cursorOffset);
17995
+ if (/\s/.test(query)) return { kind: "close" };
17996
+ return { kind: "none" };
17997
+ }
17998
+ if (pickerCancelOffset === cursorOffset) {
17999
+ return { kind: "dropCancel" };
18000
+ }
18001
+ if (filePickerEnabled && shouldOpenMentionPicker(input, cursorOffset, pickerCancelOffset)) {
18002
+ return {
18003
+ kind: "open",
18004
+ picker: { kind: "file", anchor: cursorOffset - 1, selected: 0 },
18005
+ loadFiles: true
18006
+ };
18007
+ }
18008
+ if (shouldOpenSlashPicker(input, cursorOffset, pickerCancelOffset)) {
18009
+ return {
18010
+ kind: "open",
18011
+ picker: { kind: "slash", anchor: cursorOffset - 1, selected: 0 },
18012
+ loadFiles: false
18013
+ };
18014
+ }
18015
+ return { kind: "none" };
18016
+ }
18017
+ function usePickerController(opts2) {
18018
+ const {
18019
+ input,
18020
+ cursorOffset,
18021
+ setInput,
18022
+ setCursorOffset,
18023
+ filePickerEnabled,
18024
+ allSlashCommands,
18025
+ modalActive,
18026
+ loadFilePickerItems,
18027
+ onFileSelected,
18028
+ onSlashSelected,
18029
+ getRecentFiles
18030
+ } = opts2;
18031
+ const [active, setActive] = useState14(null);
18032
+ const [fileItemsRaw, setFileItemsRaw] = useState14([]);
18033
+ const filesLoadedRef = useRef4(false);
18034
+ const cancelOffsetRef = useRef4(null);
18035
+ const onFileSelectedRef = useRef4(onFileSelected);
18036
+ onFileSelectedRef.current = onFileSelected;
18037
+ const onSlashSelectedRef = useRef4(onSlashSelected);
18038
+ onSlashSelectedRef.current = onSlashSelected;
18039
+ const getRecentFilesRef = useRef4(getRecentFiles);
18040
+ getRecentFilesRef.current = getRecentFiles;
18041
+ const loadFilePickerItemsRef = useRef4(loadFilePickerItems);
18042
+ loadFilePickerItemsRef.current = loadFilePickerItems;
18043
+ const activeAnchor = active?.anchor ?? null;
18044
+ const activeKind = active?.kind ?? null;
18045
+ const query = useMemo2(() => {
18046
+ if (activeAnchor === null) return "";
18047
+ return input.slice(activeAnchor + 1, cursorOffset);
18048
+ }, [input, cursorOffset, activeAnchor]);
18049
+ const fileItems = useMemo2(() => {
18050
+ if (activeKind !== "file") return [];
18051
+ const items = filterPickerItems(fileItemsRaw, query).slice();
18052
+ const recents = getRecentFilesRef.current();
18053
+ return items.sort((a, b) => {
18054
+ const aRecent = recents.get(a.name) ?? 0;
18055
+ const bRecent = recents.get(b.name) ?? 0;
18056
+ if (aRecent && !bRecent) return -1;
18057
+ if (!aRecent && bRecent) return 1;
18058
+ if (aRecent && bRecent) return bRecent - aRecent;
18059
+ if (a.isDirectory && !b.isDirectory) return -1;
18060
+ if (!a.isDirectory && b.isDirectory) return 1;
18061
+ return a.name.localeCompare(b.name);
18062
+ });
18063
+ }, [activeKind, fileItemsRaw, query]);
18064
+ const slashItems = useMemo2(() => {
18065
+ if (activeKind !== "slash") return [];
18066
+ return fuzzyFilter(allSlashCommands, query, (c) => c.name).slice(0, 50);
18067
+ }, [activeKind, allSlashCommands, query]);
18068
+ useEffect7(() => {
18069
+ const t = decidePickerTransition(
18070
+ active,
18071
+ input,
18072
+ cursorOffset,
18073
+ cancelOffsetRef.current,
18074
+ filePickerEnabled
18075
+ );
18076
+ if (t.kind === "close") {
18077
+ setActive(null);
18078
+ return;
18079
+ }
18080
+ if (t.kind === "dropCancel") {
18081
+ cancelOffsetRef.current = null;
18082
+ return;
18083
+ }
18084
+ if (t.kind === "open") {
18085
+ setActive(t.picker);
18086
+ if (t.loadFiles && !filesLoadedRef.current) {
18087
+ filesLoadedRef.current = true;
18088
+ void loadFilePickerItemsRef.current().then((items) => setFileItemsRaw(items)).catch(() => setFileItemsRaw([]));
18089
+ }
18090
+ }
18091
+ }, [input, cursorOffset, active, filePickerEnabled]);
18092
+ useEffect7(() => {
18093
+ if (active?.kind !== "file") return;
18094
+ const max = Math.max(0, fileItems.length - 1);
18095
+ if (active.selected > max) {
18096
+ setActive({ ...active, selected: max });
18097
+ }
18098
+ }, [fileItems.length, active]);
18099
+ useEffect7(() => {
18100
+ if (active?.kind !== "slash") return;
18101
+ const max = Math.max(0, slashItems.length - 1);
18102
+ if (active.selected > max) {
18103
+ setActive({ ...active, selected: max });
18104
+ }
18105
+ }, [slashItems.length, active]);
18106
+ useEffect7(() => {
18107
+ if (modalActive && active !== null) {
18108
+ setActive(null);
18109
+ }
18110
+ }, [modalActive, active]);
18111
+ const onUp = useCallback5(() => {
18112
+ setActive((p) => {
18113
+ if (!p) return null;
18114
+ const next = Math.max(0, p.selected - 1);
18115
+ return next === p.selected ? p : { ...p, selected: next };
18116
+ });
18117
+ }, []);
18118
+ const onDown = useCallback5(() => {
18119
+ setActive((p) => {
18120
+ if (!p) return null;
18121
+ const max = p.kind === "file" ? Math.max(0, fileItems.length - 1) : Math.max(0, slashItems.length - 1);
18122
+ const next = Math.min(max, p.selected + 1);
18123
+ return next === p.selected ? p : { ...p, selected: next };
18124
+ });
18125
+ }, [fileItems.length, slashItems.length]);
18126
+ const onSelect = useCallback5(() => {
18127
+ if (!active) return;
18128
+ if (active.kind === "file") {
18129
+ const item2 = fileItems[active.selected];
18130
+ if (!item2) return;
18131
+ onFileSelectedRef.current?.(item2.name);
18132
+ const insert = item2.name + (item2.isDirectory ? "/" : " ");
18133
+ const newInput = input.slice(0, active.anchor) + insert + input.slice(cursorOffset);
18134
+ setInput(newInput);
18135
+ setCursorOffset(active.anchor + insert.length);
18136
+ setActive(null);
18137
+ return;
18138
+ }
18139
+ const item = slashItems[active.selected];
18140
+ if (!item) return;
18141
+ const { value } = insertSlashCommand(input, active.anchor, item.name);
18142
+ setActive(null);
18143
+ onSlashSelectedRef.current(value);
18144
+ }, [active, fileItems, slashItems, input, cursorOffset, setInput, setCursorOffset]);
18145
+ const onCancel = useCallback5(() => {
18146
+ cancelOffsetRef.current = cursorOffset;
18147
+ setActive(null);
18148
+ }, [cursorOffset]);
18149
+ return {
18150
+ active,
18151
+ isActive: active !== null,
18152
+ query,
18153
+ fileItems,
18154
+ slashItems,
18155
+ onUp,
18156
+ onDown,
18157
+ onSelect,
18158
+ onCancel
18159
+ };
18160
+ }
18161
+ var init_use_picker_controller = __esm({
18162
+ "src/ui/use-picker-controller.ts"() {
18163
+ "use strict";
18164
+ init_fuzzy();
18165
+ }
18166
+ });
18167
+
17807
18168
  // src/cost-attribution/tui-report.ts
17808
18169
  var tui_report_exports = {};
17809
18170
  __export(tui_report_exports, {
@@ -17889,13 +18250,9 @@ var init_tui_report = __esm({
17889
18250
  var app_exports = {};
17890
18251
  __export(app_exports, {
17891
18252
  buildFilePickerIgnoreList: () => buildFilePickerIgnoreList,
17892
- filterPickerItems: () => filterPickerItems,
17893
- insertSlashCommand: () => insertSlashCommand,
17894
- renderApp: () => renderApp,
17895
- shouldOpenMentionPicker: () => shouldOpenMentionPicker,
17896
- shouldOpenSlashPicker: () => shouldOpenSlashPicker
18253
+ renderApp: () => renderApp
17897
18254
  });
17898
- import React15, { useState as useState14, useRef as useRef4, useEffect as useEffect7, useCallback as useCallback5 } from "react";
18255
+ import React15, { useState as useState15, useRef as useRef5, useEffect as useEffect8, useCallback as useCallback6 } from "react";
17899
18256
  import { Box as Box25, Text as Text26, useApp, useInput as useInput10, render } from "ink";
17900
18257
  import SelectInput9 from "ink-select-input";
17901
18258
  import { existsSync as existsSync4, statSync as statSync4 } from "fs";
@@ -18004,29 +18361,6 @@ function buildFilePickerIgnoreList(cwd) {
18004
18361
  }
18005
18362
  return [...hardcoded, ...gitignorePatterns];
18006
18363
  }
18007
- function filterPickerItems(items, query) {
18008
- return fuzzyFilter(items, query, (item) => item.name).slice(0, 50);
18009
- }
18010
- function shouldOpenMentionPicker(input, cursorOffset, pickerCancelOffset) {
18011
- if (pickerCancelOffset === cursorOffset) return false;
18012
- if (cursorOffset > 0 && input[cursorOffset - 1] === "@") {
18013
- const beforeAt = cursorOffset - 2;
18014
- return beforeAt < 0 || /\s/.test(input[beforeAt]);
18015
- }
18016
- return false;
18017
- }
18018
- function shouldOpenSlashPicker(input, cursorOffset, cancelOffset) {
18019
- if (cancelOffset === cursorOffset) return false;
18020
- if (cursorOffset === 0 || input[cursorOffset - 1] !== "/") return false;
18021
- return /^\s*$/.test(input.slice(0, cursorOffset - 1));
18022
- }
18023
- function insertSlashCommand(input, anchor, name) {
18024
- let tokenEnd = anchor + 1;
18025
- while (tokenEnd < input.length && !/\s/.test(input[tokenEnd])) tokenEnd++;
18026
- const head = input.slice(0, anchor + 1) + name;
18027
- const tail = " " + input.slice(tokenEnd).replace(/^\s+/, "");
18028
- return { value: head + tail, cursor: head.length + 1 };
18029
- }
18030
18364
  function gatewayFromConfig(cfg) {
18031
18365
  if (process.env.KIMIFLARE_DISABLE_AI_GATEWAY === "1") return void 0;
18032
18366
  if (!cfg.aiGatewayId) return void 0;
@@ -18158,13 +18492,13 @@ function App({
18158
18492
  initialCloudDeviceId
18159
18493
  }) {
18160
18494
  const { exit } = useApp();
18161
- const [cfg, setCfg] = useState14(initialCfg);
18162
- const [lspScope, setLspScope] = useState14(initialLspScope);
18163
- const [lspProjectPath, setLspProjectPath] = useState14(initialLspProjectPath);
18164
- const [cloudToken, setCloudToken] = useState14(initialCloudToken);
18165
- const [cloudDeviceId, setCloudDeviceId] = useState14(initialCloudDeviceId);
18166
- const [events, setRawEvents] = useState14([]);
18167
- const setEvents = useCallback5(
18495
+ const [cfg, setCfg] = useState15(initialCfg);
18496
+ const [lspScope, setLspScope] = useState15(initialLspScope);
18497
+ const [lspProjectPath, setLspProjectPath] = useState15(initialLspProjectPath);
18498
+ const [cloudToken, setCloudToken] = useState15(initialCloudToken);
18499
+ const [cloudDeviceId, setCloudDeviceId] = useState15(initialCloudDeviceId);
18500
+ const [events, setRawEvents] = useState15([]);
18501
+ const setEvents = useCallback6(
18168
18502
  (updater) => {
18169
18503
  setRawEvents((prev) => {
18170
18504
  const next = typeof updater === "function" ? updater(prev) : updater;
@@ -18173,13 +18507,24 @@ function App({
18173
18507
  },
18174
18508
  []
18175
18509
  );
18176
- const [input, setInput] = useState14("");
18177
- const [busy, setBusy] = useState14(false);
18178
- const [usage, setUsage] = useState14(null);
18179
- const [sessionUsage, setSessionUsage] = useState14(null);
18180
- const [gatewayMeta, setGatewayMeta] = useState14(null);
18181
- const [cloudBudget, setCloudBudget] = useState14(null);
18182
- const [showReasoning, setShowReasoning] = useState14(false);
18510
+ const [input, setInput] = useState15("");
18511
+ const [busy, setBusy] = useState15(false);
18512
+ const [usage, setUsage] = useState15(null);
18513
+ const [sessionUsage, setSessionUsage] = useState15(null);
18514
+ useEffect8(() => {
18515
+ const handler = (sid) => {
18516
+ if (sessionIdRef.current && sid === sessionIdRef.current) {
18517
+ void getCostReport(sid).then((report) => setSessionUsage(report.session));
18518
+ }
18519
+ };
18520
+ usageEvents.on("update", handler);
18521
+ return () => {
18522
+ usageEvents.off("update", handler);
18523
+ };
18524
+ }, []);
18525
+ const [gatewayMeta, setGatewayMeta] = useState15(null);
18526
+ const [cloudBudget, setCloudBudget] = useState15(null);
18527
+ const [showReasoning, setShowReasoning] = useState15(false);
18183
18528
  const {
18184
18529
  pending: perm,
18185
18530
  askPermission: askForPermission,
@@ -18200,52 +18545,52 @@ function App({
18200
18545
  ]);
18201
18546
  }
18202
18547
  );
18203
- const [limitModal, setLimitModal] = useState14(null);
18204
- const [loopModal, setLoopModal] = useState14(null);
18205
- const [queue, setQueue] = useState14([]);
18206
- const [history, setHistory] = useState14([]);
18207
- const [historyIndex, setHistoryIndex] = useState14(-1);
18208
- const [draftInput, setDraftInput] = useState14("");
18209
- const [mode, setMode] = useState14("edit");
18210
- const [codeMode, setCodeMode] = useState14(false);
18548
+ const [limitModal, setLimitModal] = useState15(null);
18549
+ const [loopModal, setLoopModal] = useState15(null);
18550
+ const [queue, setQueue] = useState15([]);
18551
+ const [history, setHistory] = useState15([]);
18552
+ const [historyIndex, setHistoryIndex] = useState15(-1);
18553
+ const [draftInput, setDraftInput] = useState15("");
18554
+ const [mode, setMode] = useState15("edit");
18555
+ const [codeMode, setCodeMode] = useState15(false);
18211
18556
  const filePickerEnabled = initialCfg?.filePicker ?? true;
18212
- const [effort, setEffort] = useState14(
18557
+ const [effort, setEffort] = useState15(
18213
18558
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
18214
18559
  );
18215
- const [resumeSessions, setResumeSessions] = useState14(null);
18216
- const [checkpointSession, setCheckpointSession] = useState14(null);
18217
- const [checkpointList, setCheckpointList] = useState14([]);
18218
- const [commandWizard, setCommandWizard] = useState14(null);
18219
- const [commandPicker, setCommandPicker] = useState14(null);
18220
- const [commandToDelete, setCommandToDelete] = useState14(null);
18221
- const [showCommandList, setShowCommandList] = useState14(false);
18222
- const [showLspWizard, setShowLspWizard] = useState14(false);
18223
- const [showRemoteDashboard, setShowRemoteDashboard] = useState14(false);
18224
- const [selectedRemoteSession, setSelectedRemoteSession] = useState14(null);
18225
- const [showInboxModal, setShowInboxModal] = useState14(false);
18226
- const [tasks, setTasks] = useState14([]);
18227
- const [tasksStartedAt, setTasksStartedAt] = useState14(null);
18228
- const [tasksStartTokens, setTasksStartTokens] = useState14(0);
18229
- const [turnStartedAt, setTurnStartedAt] = useState14(null);
18230
- const [turnPhase, setTurnPhase] = useState14("waiting");
18231
- const [currentToolName, setCurrentToolName] = useState14(null);
18232
- const [lastActivityAt, setLastActivityAt] = useState14(null);
18233
- const [verbose, setVerbose] = useState14(false);
18234
- const [hasUpdate, setHasUpdate] = useState14(initialUpdateResult?.hasUpdate ?? false);
18235
- const [latestVersion, setLatestVersion] = useState14(initialUpdateResult?.latestVersion ?? null);
18236
- const [theme, setTheme] = useState14(resolveTheme(initialCfg?.theme));
18237
- const [showThemePicker, setShowThemePicker] = useState14(false);
18238
- const [originalTheme, setOriginalTheme] = useState14(null);
18239
- const [skillsActive, setSkillsActive] = useState14(0);
18240
- const [memoryRecalled, setMemoryRecalled] = useState14(false);
18241
- const [intentTier, setIntentTier] = useState14(null);
18242
- const [kimiMdStale, setKimiMdStale] = useState14(false);
18243
- const [gitBranch, setGitBranch] = useState14(null);
18244
- const [lastSessionTopic, setLastSessionTopic] = useState14(null);
18245
- useEffect7(() => {
18560
+ const [resumeSessions, setResumeSessions] = useState15(null);
18561
+ const [checkpointSession, setCheckpointSession] = useState15(null);
18562
+ const [checkpointList, setCheckpointList] = useState15([]);
18563
+ const [commandWizard, setCommandWizard] = useState15(null);
18564
+ const [commandPicker, setCommandPicker] = useState15(null);
18565
+ const [commandToDelete, setCommandToDelete] = useState15(null);
18566
+ const [showCommandList, setShowCommandList] = useState15(false);
18567
+ const [showLspWizard, setShowLspWizard] = useState15(false);
18568
+ const [showRemoteDashboard, setShowRemoteDashboard] = useState15(false);
18569
+ const [selectedRemoteSession, setSelectedRemoteSession] = useState15(null);
18570
+ const [showInboxModal, setShowInboxModal] = useState15(false);
18571
+ const [tasks, setTasks] = useState15([]);
18572
+ const [tasksStartedAt, setTasksStartedAt] = useState15(null);
18573
+ const [tasksStartTokens, setTasksStartTokens] = useState15(0);
18574
+ const [turnStartedAt, setTurnStartedAt] = useState15(null);
18575
+ const [turnPhase, setTurnPhase] = useState15("waiting");
18576
+ const [currentToolName, setCurrentToolName] = useState15(null);
18577
+ const [lastActivityAt, setLastActivityAt] = useState15(null);
18578
+ const [verbose, setVerbose] = useState15(false);
18579
+ const [hasUpdate, setHasUpdate] = useState15(initialUpdateResult?.hasUpdate ?? false);
18580
+ const [latestVersion, setLatestVersion] = useState15(initialUpdateResult?.latestVersion ?? null);
18581
+ const [theme, setTheme] = useState15(resolveTheme(initialCfg?.theme));
18582
+ const [showThemePicker, setShowThemePicker] = useState15(false);
18583
+ const [originalTheme, setOriginalTheme] = useState15(null);
18584
+ const [skillsActive, setSkillsActive] = useState15(0);
18585
+ const [memoryRecalled, setMemoryRecalled] = useState15(false);
18586
+ const [intentTier, setIntentTier] = useState15(null);
18587
+ const [kimiMdStale, setKimiMdStale] = useState15(false);
18588
+ const [gitBranch, setGitBranch] = useState15(null);
18589
+ const [lastSessionTopic, setLastSessionTopic] = useState15(null);
18590
+ useEffect8(() => {
18246
18591
  setGitBranch(detectGitBranch());
18247
18592
  }, []);
18248
- useEffect7(() => {
18593
+ useEffect8(() => {
18249
18594
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
18250
18595
  ({ listSessions: listSessions2 }) => listSessions2(1).then((sessions) => {
18251
18596
  const last = sessions[0];
@@ -18255,7 +18600,7 @@ function App({
18255
18600
  })
18256
18601
  );
18257
18602
  }, []);
18258
- useEffect7(() => {
18603
+ useEffect8(() => {
18259
18604
  const onSigint = () => {
18260
18605
  logger.info("sigint:fired", {
18261
18606
  hasHandler: sigintHandlerRef.current !== null
@@ -18267,7 +18612,7 @@ function App({
18267
18612
  process.off("SIGINT", onSigint);
18268
18613
  };
18269
18614
  }, []);
18270
- useEffect7(() => {
18615
+ useEffect8(() => {
18271
18616
  let cancelled = false;
18272
18617
  loadAndMergeThemes().then(({ errors, wcagWarnings }) => {
18273
18618
  if (cancelled) return;
@@ -18291,7 +18636,7 @@ ${wcagWarnings.join("\n")}` }
18291
18636
  cancelled = true;
18292
18637
  };
18293
18638
  }, []);
18294
- useEffect7(() => {
18639
+ useEffect8(() => {
18295
18640
  if (!cfg?.cloudMode || !initialCloudToken) return;
18296
18641
  let cancelled = false;
18297
18642
  const fetchBudget = async () => {
@@ -18317,81 +18662,56 @@ ${wcagWarnings.join("\n")}` }
18317
18662
  cancelled = true;
18318
18663
  };
18319
18664
  }, [cfg?.cloudMode, initialCloudToken]);
18320
- const [cursorOffset, setCursorOffset] = useState14(0);
18321
- const [activePicker, setActivePicker] = useState14(null);
18322
- const [filePickerItems, setFilePickerItems] = useState14([]);
18323
- const filePickerLoadedRef = useRef4(false);
18324
- const [customCommandsVersion, setCustomCommandsVersion] = useState14(0);
18325
- const cacheStableRef = useRef4(initialCfg?.cacheStablePrompts !== false);
18326
- const messagesRef = useRef4(
18665
+ const [cursorOffset, setCursorOffset] = useState15(0);
18666
+ const [customCommandsVersion, setCustomCommandsVersion] = useState15(0);
18667
+ const cacheStableRef = useRef5(initialCfg?.cacheStablePrompts !== false);
18668
+ const messagesRef = useRef5(
18327
18669
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
18328
18670
  );
18329
- const executorRef = useRef4(new ToolExecutor(ALL_TOOLS));
18330
- const activeAsstIdRef = useRef4(null);
18331
- const sessionScopeRef = useRef4(new AbortScope());
18332
- const activeScopeRef = useRef4(null);
18333
- const supervisorRef = useRef4(new TurnSupervisor());
18334
- const isAbortingRef = useRef4(false);
18335
- const lastEscapeAtRef = useRef4(0);
18336
- const sigintHandlerRef = useRef4(null);
18337
- const limitResolveRef = useRef4(null);
18338
- const loopResolveRef = useRef4(null);
18339
- const pendingToolCallsRef = useRef4(/* @__PURE__ */ new Map());
18340
- const sessionIdRef = useRef4(null);
18341
- const sessionCreatedAtRef = useRef4(null);
18342
- const sessionTitleRef = useRef4(null);
18343
- const modeRef = useRef4(mode);
18344
- const effortRef = useRef4(effort);
18345
- const tasksRef = useRef4([]);
18346
- const usageRef = useRef4(null);
18347
- const gatewayMetaRef = useRef4(null);
18348
- const lastApiErrorRef = useRef4(null);
18349
- const updateCheckedRef = useRef4(false);
18350
- const sessionStateRef = useRef4(emptySessionState());
18351
- const artifactStoreRef = useRef4(new ArtifactStore());
18352
- const compiledContextRef = useRef4(initialCfg?.compiledContext === true);
18353
- const updateNudgedRef = useRef4(false);
18354
- const compactSuggestedRef = useRef4(false);
18355
- const mcpManagerRef = useRef4(new McpManager());
18356
- const mcpToolsRef = useRef4([]);
18357
- const mcpInitRef = useRef4(false);
18358
- const submitRef = useRef4(() => {
18671
+ const executorRef = useRef5(new ToolExecutor(ALL_TOOLS));
18672
+ const activeAsstIdRef = useRef5(null);
18673
+ const sessionScopeRef = useRef5(new AbortScope());
18674
+ const activeScopeRef = useRef5(null);
18675
+ const supervisorRef = useRef5(new TurnSupervisor());
18676
+ const isAbortingRef = useRef5(false);
18677
+ const lastEscapeAtRef = useRef5(0);
18678
+ const sigintHandlerRef = useRef5(null);
18679
+ const limitResolveRef = useRef5(null);
18680
+ const loopResolveRef = useRef5(null);
18681
+ const pendingToolCallsRef = useRef5(/* @__PURE__ */ new Map());
18682
+ const sessionIdRef = useRef5(null);
18683
+ const sessionCreatedAtRef = useRef5(null);
18684
+ const sessionTitleRef = useRef5(null);
18685
+ const modeRef = useRef5(mode);
18686
+ const effortRef = useRef5(effort);
18687
+ const tasksRef = useRef5([]);
18688
+ const usageRef = useRef5(null);
18689
+ const gatewayMetaRef = useRef5(null);
18690
+ const lastApiErrorRef = useRef5(null);
18691
+ const updateCheckedRef = useRef5(false);
18692
+ const sessionStateRef = useRef5(emptySessionState());
18693
+ const artifactStoreRef = useRef5(new ArtifactStore());
18694
+ const compiledContextRef = useRef5(initialCfg?.compiledContext === true);
18695
+ const updateNudgedRef = useRef5(false);
18696
+ const compactSuggestedRef = useRef5(false);
18697
+ const mcpManagerRef = useRef5(new McpManager());
18698
+ const mcpToolsRef = useRef5([]);
18699
+ const mcpInitRef = useRef5(false);
18700
+ const submitRef = useRef5(() => {
18359
18701
  });
18360
- const lspManagerRef = useRef4(new LspManager());
18361
- const lspToolsRef = useRef4([]);
18362
- const lspInitRef = useRef4(false);
18363
- const busyRef = useRef4(busy);
18364
- const memoryManagerRef = useRef4(null);
18365
- const sessionStartRecallRef = useRef4(null);
18366
- const kimiMdStaleNudgedRef = useRef4(false);
18367
- const turnCounterRef = useRef4(0);
18368
- const pendingTextRef = useRef4(/* @__PURE__ */ new Map());
18369
- const flushTimeoutRef = useRef4(null);
18370
- const customCommandsRef = useRef4([]);
18371
- const pickerCancelRef = useRef4(null);
18372
- const recentFilesRef = useRef4(/* @__PURE__ */ new Map());
18702
+ const lspManagerRef = useRef5(new LspManager());
18703
+ const lspToolsRef = useRef5([]);
18704
+ const lspInitRef = useRef5(false);
18705
+ const busyRef = useRef5(busy);
18706
+ const memoryManagerRef = useRef5(null);
18707
+ const sessionStartRecallRef = useRef5(null);
18708
+ const kimiMdStaleNudgedRef = useRef5(false);
18709
+ const turnCounterRef = useRef5(0);
18710
+ const pendingTextRef = useRef5(/* @__PURE__ */ new Map());
18711
+ const flushTimeoutRef = useRef5(null);
18712
+ const customCommandsRef = useRef5([]);
18713
+ const recentFilesRef = useRef5(/* @__PURE__ */ new Map());
18373
18714
  const MAX_RECENT_FILES = 10;
18374
- const pickerAnchor = activePicker?.anchor ?? null;
18375
- const pickerKind = activePicker?.kind ?? null;
18376
- const pickerQuery = React15.useMemo(() => {
18377
- if (pickerAnchor === null) return null;
18378
- return input.slice(pickerAnchor + 1, cursorOffset);
18379
- }, [input, cursorOffset, pickerAnchor]);
18380
- const filteredFileItems = React15.useMemo(() => {
18381
- if (pickerKind !== "file" || pickerQuery === null) return [];
18382
- const items = filterPickerItems(filePickerItems, pickerQuery).slice();
18383
- const now2 = Date.now();
18384
- return items.sort((a, b) => {
18385
- const aRecent = recentFilesRef.current.get(a.name) ?? 0;
18386
- const bRecent = recentFilesRef.current.get(b.name) ?? 0;
18387
- if (aRecent && !bRecent) return -1;
18388
- if (!aRecent && bRecent) return 1;
18389
- if (aRecent && bRecent) return bRecent - aRecent;
18390
- if (a.isDirectory && !b.isDirectory) return -1;
18391
- if (!a.isDirectory && b.isDirectory) return 1;
18392
- return a.name.localeCompare(b.name);
18393
- });
18394
- }, [pickerKind, filePickerItems, pickerQuery]);
18395
18715
  const allSlashCommands = React15.useMemo(() => {
18396
18716
  const customs = customCommandsRef.current.filter((c) => !BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase())).map((c) => ({
18397
18717
  name: c.name,
@@ -18400,138 +18720,43 @@ ${wcagWarnings.join("\n")}` }
18400
18720
  }));
18401
18721
  return [...BUILTIN_COMMANDS, ...customs];
18402
18722
  }, [customCommandsVersion]);
18403
- const filteredSlashItems = React15.useMemo(() => {
18404
- if (pickerKind !== "slash" || pickerQuery === null) return [];
18405
- return fuzzyFilter(allSlashCommands, pickerQuery, (c) => c.name).slice(0, 50);
18406
- }, [pickerKind, allSlashCommands, pickerQuery]);
18407
- useEffect7(() => {
18408
- if (activePicker !== null) {
18409
- const trigger = activePicker.kind === "file" ? "@" : "/";
18410
- if (cursorOffset < activePicker.anchor) {
18411
- setActivePicker(null);
18412
- return;
18413
- }
18414
- if (input[activePicker.anchor] !== trigger) {
18415
- setActivePicker(null);
18416
- return;
18417
- }
18418
- const query = input.slice(activePicker.anchor + 1, cursorOffset);
18419
- if (/\s/.test(query)) {
18420
- setActivePicker(null);
18421
- return;
18422
- }
18423
- return;
18424
- }
18425
- if (pickerCancelRef.current === cursorOffset) {
18426
- pickerCancelRef.current = null;
18427
- return;
18428
- }
18429
- if (filePickerEnabled && shouldOpenMentionPicker(input, cursorOffset, pickerCancelRef.current)) {
18430
- setActivePicker({ kind: "file", anchor: cursorOffset - 1, selected: 0 });
18431
- if (!filePickerLoadedRef.current) {
18432
- filePickerLoadedRef.current = true;
18433
- const cwd = process.cwd();
18434
- void fg4("**/*", {
18435
- cwd,
18436
- ignore: buildFilePickerIgnoreList(cwd),
18437
- dot: false,
18438
- absolute: false,
18439
- onlyFiles: false,
18440
- markDirectories: true
18441
- }).then((entries) => {
18442
- const strings = entries.slice(0, 300);
18443
- const items = strings.map((e) => ({
18444
- name: e.endsWith("/") ? e.slice(0, -1) : e,
18445
- isDirectory: e.endsWith("/")
18446
- }));
18447
- items.sort((a, b) => {
18448
- if (a.isDirectory && !b.isDirectory) return -1;
18449
- if (!a.isDirectory && b.isDirectory) return 1;
18450
- return a.name.localeCompare(b.name);
18451
- });
18452
- setFilePickerItems(items);
18453
- }).catch(() => {
18454
- setFilePickerItems([]);
18455
- });
18456
- }
18457
- return;
18458
- }
18459
- if (shouldOpenSlashPicker(input, cursorOffset, pickerCancelRef.current)) {
18460
- setActivePicker({ kind: "slash", anchor: cursorOffset - 1, selected: 0 });
18461
- return;
18462
- }
18463
- }, [input, cursorOffset, activePicker, filePickerEnabled]);
18464
- useEffect7(() => {
18465
- if (activePicker?.kind !== "file") return;
18466
- const max = Math.max(0, filteredFileItems.length - 1);
18467
- if (activePicker.selected > max) {
18468
- setActivePicker({ ...activePicker, selected: max });
18469
- }
18470
- }, [filteredFileItems.length, activePicker]);
18471
- useEffect7(() => {
18472
- if (activePicker?.kind !== "slash") return;
18473
- const max = Math.max(0, filteredSlashItems.length - 1);
18474
- if (activePicker.selected > max) {
18475
- setActivePicker({ ...activePicker, selected: max });
18476
- }
18477
- }, [filteredSlashItems.length, activePicker]);
18478
- const handlePickerUp = useCallback5(() => {
18479
- setActivePicker((p) => {
18480
- if (!p) return null;
18481
- const next = Math.max(0, p.selected - 1);
18482
- return next === p.selected ? p : { ...p, selected: next };
18723
+ const modalActive = commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || checkpointSession !== null || perm !== null || limitModal !== null || loopModal !== null || showInboxModal;
18724
+ const loadFilePickerItems = useCallback6(async () => {
18725
+ const cwd = process.cwd();
18726
+ const entries = await fg4("**/*", {
18727
+ cwd,
18728
+ ignore: buildFilePickerIgnoreList(cwd),
18729
+ dot: false,
18730
+ absolute: false,
18731
+ onlyFiles: false,
18732
+ markDirectories: true
18483
18733
  });
18484
- }, []);
18485
- const handlePickerDown = useCallback5(() => {
18486
- setActivePicker((p) => {
18487
- if (!p) return null;
18488
- const max = p.kind === "file" ? Math.max(0, filteredFileItems.length - 1) : Math.max(0, filteredSlashItems.length - 1);
18489
- const next = Math.min(max, p.selected + 1);
18490
- return next === p.selected ? p : { ...p, selected: next };
18734
+ const strings = entries.slice(0, 300);
18735
+ const items = strings.map((e) => ({
18736
+ name: e.endsWith("/") ? e.slice(0, -1) : e,
18737
+ isDirectory: e.endsWith("/")
18738
+ }));
18739
+ items.sort((a, b) => {
18740
+ if (a.isDirectory && !b.isDirectory) return -1;
18741
+ if (!a.isDirectory && b.isDirectory) return 1;
18742
+ return a.name.localeCompare(b.name);
18491
18743
  });
18492
- }, [filteredFileItems.length, filteredSlashItems.length]);
18493
- const handlePickerSelect = useCallback5(() => {
18494
- if (!activePicker) return;
18495
- if (activePicker.kind === "file") {
18496
- const item2 = filteredFileItems[activePicker.selected];
18497
- if (!item2) return;
18498
- trackRecentFile(recentFilesRef, item2.name, MAX_RECENT_FILES);
18499
- const insert = item2.name + (item2.isDirectory ? "/" : " ");
18500
- const newInput = input.slice(0, activePicker.anchor) + insert + input.slice(cursorOffset);
18501
- setInput(newInput);
18502
- setCursorOffset(activePicker.anchor + insert.length);
18503
- setActivePicker(null);
18504
- return;
18505
- }
18506
- const item = filteredSlashItems[activePicker.selected];
18507
- if (!item) return;
18508
- const { value } = insertSlashCommand(input, activePicker.anchor, item.name);
18509
- setActivePicker(null);
18510
- submitRef.current(value);
18511
- }, [activePicker, filteredFileItems, filteredSlashItems, input, cursorOffset]);
18512
- const handlePickerCancel = useCallback5(() => {
18513
- pickerCancelRef.current = cursorOffset;
18514
- setActivePicker(null);
18515
- }, [cursorOffset]);
18516
- useEffect7(() => {
18517
- const modalActive = commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || checkpointSession !== null || perm !== null || limitModal !== null || loopModal !== null || showInboxModal;
18518
- if (modalActive && activePicker !== null) {
18519
- setActivePicker(null);
18520
- }
18521
- }, [
18522
- commandWizard,
18523
- commandPicker,
18524
- commandToDelete,
18525
- showCommandList,
18526
- showLspWizard,
18527
- resumeSessions,
18528
- perm,
18529
- limitModal,
18530
- loopModal,
18531
- showInboxModal,
18532
- activePicker
18533
- ]);
18534
- useEffect7(() => {
18744
+ return items;
18745
+ }, []);
18746
+ const picker = usePickerController({
18747
+ input,
18748
+ cursorOffset,
18749
+ setInput,
18750
+ setCursorOffset,
18751
+ filePickerEnabled,
18752
+ allSlashCommands,
18753
+ modalActive,
18754
+ loadFilePickerItems,
18755
+ onFileSelected: (name) => trackRecentFile(recentFilesRef, name, MAX_RECENT_FILES),
18756
+ onSlashSelected: (value) => submitRef.current(value),
18757
+ getRecentFiles: () => recentFilesRef.current
18758
+ });
18759
+ useEffect8(() => {
18535
18760
  if (!cfg) return;
18536
18761
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
18537
18762
  ({ pruneSessions: pruneSessions2 }) => pruneSessions2().then((removed) => {
@@ -18643,7 +18868,7 @@ ${wcagWarnings.join("\n")}` }
18643
18868
  }
18644
18869
  });
18645
18870
  }, [cfg, setEvents]);
18646
- useEffect7(() => {
18871
+ useEffect8(() => {
18647
18872
  const id = setInterval(() => {
18648
18873
  try {
18649
18874
  performance.clearMarks();
@@ -18653,7 +18878,7 @@ ${wcagWarnings.join("\n")}` }
18653
18878
  }, 3e5);
18654
18879
  return () => clearInterval(id);
18655
18880
  }, []);
18656
- const reloadCustomCommands = useCallback5(async () => {
18881
+ const reloadCustomCommands = useCallback6(async () => {
18657
18882
  const { commands, warnings } = await loadCustomCommands(process.cwd());
18658
18883
  customCommandsRef.current = commands;
18659
18884
  setCustomCommandsVersion((v) => v + 1);
@@ -18668,7 +18893,7 @@ ${wcagWarnings.join("\n")}` }
18668
18893
  ]);
18669
18894
  }
18670
18895
  }, [setEvents]);
18671
- useEffect7(() => {
18896
+ useEffect8(() => {
18672
18897
  if (!cfg || updateCheckedRef.current) return;
18673
18898
  updateCheckedRef.current = true;
18674
18899
  if (initialUpdateResult) {
@@ -18719,7 +18944,7 @@ ${wcagWarnings.join("\n")}` }
18719
18944
  }
18720
18945
  });
18721
18946
  }, [cfg, initialUpdateResult]);
18722
- useEffect7(() => {
18947
+ useEffect8(() => {
18723
18948
  modeRef.current = mode;
18724
18949
  if (cacheStableRef.current) {
18725
18950
  messagesRef.current[1] = {
@@ -18746,10 +18971,10 @@ ${wcagWarnings.join("\n")}` }
18746
18971
  executorRef.current.clearSessionPermissions();
18747
18972
  }
18748
18973
  }, [mode, cfg?.model]);
18749
- useEffect7(() => {
18974
+ useEffect8(() => {
18750
18975
  effortRef.current = effort;
18751
18976
  }, [effort]);
18752
- useEffect7(() => {
18977
+ useEffect8(() => {
18753
18978
  if (!cfg) return;
18754
18979
  const id = setInterval(() => {
18755
18980
  void checkForUpdate().then((result) => {
@@ -18780,7 +19005,7 @@ ${wcagWarnings.join("\n")}` }
18780
19005
  }, 30 * 60 * 1e3);
18781
19006
  return () => clearInterval(id);
18782
19007
  }, [cfg]);
18783
- const initMcp = useCallback5(async () => {
19008
+ const initMcp = useCallback6(async () => {
18784
19009
  if (!cfg?.mcpServers || mcpInitRef.current) return;
18785
19010
  mcpInitRef.current = true;
18786
19011
  const manager = mcpManagerRef.current;
@@ -18845,7 +19070,7 @@ ${wcagWarnings.join("\n")}` }
18845
19070
  ]);
18846
19071
  }
18847
19072
  }, [cfg]);
18848
- const initLsp = useCallback5(async () => {
19073
+ const initLsp = useCallback6(async () => {
18849
19074
  if (!cfg?.lspEnabled || !cfg?.lspServers || lspInitRef.current) {
18850
19075
  if (lspInitRef.current) return;
18851
19076
  if (!cfg?.lspEnabled) {
@@ -18908,7 +19133,7 @@ ${wcagWarnings.join("\n")}` }
18908
19133
  ]);
18909
19134
  }
18910
19135
  }, [cfg]);
18911
- useEffect7(() => {
19136
+ useEffect8(() => {
18912
19137
  if (cfg && !mcpInitRef.current) {
18913
19138
  void initMcp();
18914
19139
  }
@@ -18916,7 +19141,7 @@ ${wcagWarnings.join("\n")}` }
18916
19141
  void initLsp();
18917
19142
  }
18918
19143
  }, [cfg, initMcp, initLsp]);
18919
- const ensureSessionId = useCallback5(() => {
19144
+ const ensureSessionId = useCallback6(() => {
18920
19145
  if (sessionIdRef.current) return sessionIdRef.current;
18921
19146
  const firstUser = messagesRef.current.find((m) => m.role === "user");
18922
19147
  let firstText = "session";
@@ -18929,7 +19154,7 @@ ${wcagWarnings.join("\n")}` }
18929
19154
  sessionIdRef.current = makeSessionId(firstText);
18930
19155
  return sessionIdRef.current;
18931
19156
  }, []);
18932
- const saveSessionSafe = useCallback5(async () => {
19157
+ const saveSessionSafe = useCallback6(async () => {
18933
19158
  if (!cfg) return;
18934
19159
  ensureSessionId();
18935
19160
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -18955,7 +19180,7 @@ ${wcagWarnings.join("\n")}` }
18955
19180
  ]);
18956
19181
  }
18957
19182
  }, [cfg, ensureSessionId]);
18958
- const onIterationEnd = useCallback5(
19183
+ const onIterationEnd = useCallback6(
18959
19184
  async (messages, signal) => {
18960
19185
  if (signal.aborted) return messages;
18961
19186
  if (!shouldCompact({ messages })) return messages;
@@ -19156,7 +19381,7 @@ ${wcagWarnings.join("\n")}` }
19156
19381
  void lspManagerRef.current.stopAll().finally(() => exit());
19157
19382
  }
19158
19383
  };
19159
- const flushAssistantUpdates = useCallback5(() => {
19384
+ const flushAssistantUpdates = useCallback6(() => {
19160
19385
  flushTimeoutRef.current = null;
19161
19386
  const pending = pendingTextRef.current;
19162
19387
  if (pending.size === 0) return;
@@ -19174,7 +19399,7 @@ ${wcagWarnings.join("\n")}` }
19174
19399
  })
19175
19400
  );
19176
19401
  }, []);
19177
- const updateAssistant = useCallback5(
19402
+ const updateAssistant = useCallback6(
19178
19403
  (id, patch) => {
19179
19404
  const result = patch({ text: "", reasoning: "" });
19180
19405
  const assistantResult = result;
@@ -19203,7 +19428,7 @@ ${wcagWarnings.join("\n")}` }
19203
19428
  },
19204
19429
  [flushAssistantUpdates]
19205
19430
  );
19206
- const updateTool = useCallback5(
19431
+ const updateTool = useCallback6(
19207
19432
  (id, patch) => {
19208
19433
  setEvents(
19209
19434
  (evts) => evts.map(
@@ -19213,11 +19438,11 @@ ${wcagWarnings.join("\n")}` }
19213
19438
  },
19214
19439
  []
19215
19440
  );
19216
- const updateGatewayMeta = useCallback5((meta) => {
19441
+ const updateGatewayMeta = useCallback6((meta) => {
19217
19442
  gatewayMetaRef.current = meta;
19218
19443
  setGatewayMeta(meta);
19219
19444
  }, []);
19220
- const runCompact = useCallback5(async () => {
19445
+ const runCompact = useCallback6(async () => {
19221
19446
  if (!cfg) return;
19222
19447
  if (busy) {
19223
19448
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't compact while model is running" }]);
@@ -19313,11 +19538,11 @@ ${wcagWarnings.join("\n")}` }
19313
19538
  pendingToolCallsRef.current.clear();
19314
19539
  }
19315
19540
  }, [cfg, busy, saveSessionSafe]);
19316
- const openResumePicker = useCallback5(async () => {
19541
+ const openResumePicker = useCallback6(async () => {
19317
19542
  const sessions = await listSessions(200, process.cwd());
19318
19543
  setResumeSessions(sessions);
19319
19544
  }, []);
19320
- const runInit = useCallback5(async () => {
19545
+ const runInit = useCallback6(async () => {
19321
19546
  if (!cfg) return;
19322
19547
  if (busy) {
19323
19548
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't /init while model is running" }]);
@@ -19612,7 +19837,7 @@ ${wcagWarnings.join("\n")}` }
19612
19837
  pendingToolCallsRef.current.clear();
19613
19838
  }
19614
19839
  }, [cfg, busy, updateAssistant, updateTool, updateGatewayMeta]);
19615
- const handleThemePick = useCallback5(
19840
+ const handleThemePick = useCallback6(
19616
19841
  (picked) => {
19617
19842
  setShowThemePicker(false);
19618
19843
  if (!picked) return;
@@ -19630,7 +19855,7 @@ ${wcagWarnings.join("\n")}` }
19630
19855
  },
19631
19856
  []
19632
19857
  );
19633
- const doResumeSession = useCallback5(
19858
+ const doResumeSession = useCallback6(
19634
19859
  async (filePath, checkpointId) => {
19635
19860
  try {
19636
19861
  const file = checkpointId ? (await loadSessionFromCheckpoint(filePath, checkpointId)).file : await loadSession(filePath);
@@ -19682,7 +19907,7 @@ ${wcagWarnings.join("\n")}` }
19682
19907
  },
19683
19908
  []
19684
19909
  );
19685
- const handleResumePick = useCallback5(
19910
+ const handleResumePick = useCallback6(
19686
19911
  async (picked) => {
19687
19912
  setResumeSessions(null);
19688
19913
  if (!picked) return;
@@ -19704,7 +19929,7 @@ ${wcagWarnings.join("\n")}` }
19704
19929
  },
19705
19930
  [doResumeSession]
19706
19931
  );
19707
- const handleCheckpointPick = useCallback5(
19932
+ const handleCheckpointPick = useCallback6(
19708
19933
  async (checkpointId) => {
19709
19934
  const session = checkpointSession;
19710
19935
  setCheckpointSession(null);
@@ -19723,7 +19948,7 @@ ${wcagWarnings.join("\n")}` }
19723
19948
  },
19724
19949
  [checkpointSession, doResumeSession]
19725
19950
  );
19726
- const handleSlash = useCallback5(
19951
+ const handleSlash = useCallback6(
19727
19952
  (cmd) => {
19728
19953
  const raw = cmd.trim();
19729
19954
  const [head, ...rest] = raw.split(/\s+/);
@@ -20696,7 +20921,7 @@ ${lines.join("\n")}` }]);
20696
20921
  },
20697
20922
  [cfg, exit, usage, theme, mode, openResumePicker, runCompact, runInit, initMcp, setCfg, setShowRemoteDashboard, setSelectedRemoteSession]
20698
20923
  );
20699
- const handleCommandSave = useCallback5(
20924
+ const handleCommandSave = useCallback6(
20700
20925
  async (opts2) => {
20701
20926
  setCommandWizard(null);
20702
20927
  try {
@@ -20718,7 +20943,7 @@ ${lines.join("\n")}` }]);
20718
20943
  },
20719
20944
  [commandWizard, reloadCustomCommands, setEvents]
20720
20945
  );
20721
- const handleCommandDelete = useCallback5(
20946
+ const handleCommandDelete = useCallback6(
20722
20947
  async (cmd) => {
20723
20948
  setCommandToDelete(null);
20724
20949
  try {
@@ -20737,7 +20962,7 @@ ${lines.join("\n")}` }]);
20737
20962
  },
20738
20963
  [reloadCustomCommands, setEvents]
20739
20964
  );
20740
- const processMessage = useCallback5(
20965
+ const processMessage = useCallback6(
20741
20966
  async (text, displayText, opts2) => {
20742
20967
  if (!cfg) return;
20743
20968
  let trimmed = text.trim();
@@ -21254,14 +21479,14 @@ ${lines.join("\n")}` }]);
21254
21479
  },
21255
21480
  [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateGatewayMeta]
21256
21481
  );
21257
- useEffect7(() => {
21482
+ useEffect8(() => {
21258
21483
  if (!busy && queue.length > 0 && supervisorRef.current.phase === "idle") {
21259
21484
  const next = queue[0];
21260
21485
  setQueue((q) => q.slice(1));
21261
21486
  processMessage(next.full, next.display, { queuedKey: next.key });
21262
21487
  }
21263
21488
  }, [busy, queue, processMessage]);
21264
- const submit = useCallback5(
21489
+ const submit = useCallback6(
21265
21490
  (full, display) => {
21266
21491
  const trimmedFull = full.trim();
21267
21492
  if (!trimmedFull) return;
@@ -21284,7 +21509,7 @@ ${lines.join("\n")}` }]);
21284
21509
  [processMessage]
21285
21510
  );
21286
21511
  submitRef.current = submit;
21287
- useEffect7(() => {
21512
+ useEffect8(() => {
21288
21513
  if (compactSuggestedRef.current) return;
21289
21514
  if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
21290
21515
  compactSuggestedRef.current = true;
@@ -21548,21 +21773,21 @@ ${lines.join("\n")}` }]);
21548
21773
  intentTier: intentTier ?? void 0
21549
21774
  }
21550
21775
  ),
21551
- activePicker?.kind === "file" && /* @__PURE__ */ jsx27(
21776
+ picker.active?.kind === "file" && /* @__PURE__ */ jsx27(
21552
21777
  FilePicker,
21553
21778
  {
21554
- items: filteredFileItems,
21555
- selectedIndex: activePicker.selected,
21556
- query: pickerQuery ?? "",
21779
+ items: picker.fileItems,
21780
+ selectedIndex: picker.active.selected,
21781
+ query: picker.query,
21557
21782
  recentFiles: new Set(recentFilesRef.current.keys())
21558
21783
  }
21559
21784
  ),
21560
- activePicker?.kind === "slash" && /* @__PURE__ */ jsx27(
21785
+ picker.active?.kind === "slash" && /* @__PURE__ */ jsx27(
21561
21786
  SlashPicker,
21562
21787
  {
21563
- items: filteredSlashItems,
21564
- selectedIndex: activePicker.selected,
21565
- query: pickerQuery ?? ""
21788
+ items: picker.slashItems,
21789
+ selectedIndex: picker.active.selected,
21790
+ query: picker.query
21566
21791
  }
21567
21792
  ),
21568
21793
  /* @__PURE__ */ jsxs25(Box25, { marginTop: 1, children: [
@@ -21576,11 +21801,11 @@ ${lines.join("\n")}` }]);
21576
21801
  enablePaste: true,
21577
21802
  cursorOffset,
21578
21803
  onCursorChange: setCursorOffset,
21579
- pickerActive: activePicker !== null,
21580
- onPickerUp: handlePickerUp,
21581
- onPickerDown: handlePickerDown,
21582
- onPickerSelect: handlePickerSelect,
21583
- onPickerCancel: handlePickerCancel,
21804
+ pickerActive: picker.isActive,
21805
+ onPickerUp: picker.onUp,
21806
+ onPickerDown: picker.onDown,
21807
+ onPickerSelect: picker.onSelect,
21808
+ onPickerCancel: picker.onCancel,
21584
21809
  onHistoryUp: () => {
21585
21810
  if (history.length === 0) return;
21586
21811
  if (historyIndex === -1) {
@@ -21718,7 +21943,7 @@ var init_app = __esm({
21718
21943
  init_lsp_nudge();
21719
21944
  init_file_picker();
21720
21945
  init_slash_picker();
21721
- init_fuzzy();
21946
+ init_use_picker_controller();
21722
21947
  MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
21723
21948
  FEEDBACK_WORKER_URL2 = "https://hello.kimiflare.com";
21724
21949
  CONTEXT_LIMIT = 262e3;