kimiflare 0.68.1 → 0.69.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
@@ -1244,7 +1244,10 @@ async function logTurnDebug(ctx) {
1244
1244
  durationMs: ctx.durationMs,
1245
1245
  intentClassification: ctx.intentClassification,
1246
1246
  codeMode: ctx.codeMode,
1247
- selectedSkills: ctx.selectedSkills?.map((s) => s.name)
1247
+ selectedSkills: ctx.selectedSkills?.map((s) => s.name),
1248
+ userPromptPreview: ctx.userPromptPreview,
1249
+ preTurnMs: ctx.preTurnMs,
1250
+ memoryRecalled: ctx.memoryRecalled
1248
1251
  });
1249
1252
  }
1250
1253
  var LOG_VERSION;
@@ -2703,6 +2706,10 @@ function deleteOrphanedSkills(db, existingPaths) {
2703
2706
  const result = db.prepare(`DELETE FROM skill_index WHERE file_path NOT IN (${placeholders})`).run(...existingPaths);
2704
2707
  return Number(result.changes);
2705
2708
  }
2709
+ function hasAnySections(db) {
2710
+ const row = db.prepare("SELECT 1 FROM skill_sections LIMIT 1").get();
2711
+ return row !== void 0;
2712
+ }
2706
2713
  function listAllSectionRows(db) {
2707
2714
  return db.prepare(
2708
2715
  `SELECT s.id, s.heading, s.body, s.embedding, i.name, i.description, i.file_path
@@ -2728,6 +2735,7 @@ var init_db = __esm({
2728
2735
 
2729
2736
  // src/skills/search.ts
2730
2737
  async function searchSections(query, db, opts2) {
2738
+ if (!hasAnySections(db)) return [];
2731
2739
  const embeddings = await fetchEmbeddings({
2732
2740
  accountId: opts2.accountId,
2733
2741
  apiToken: opts2.apiToken,
@@ -3162,6 +3170,15 @@ function getSessionWebFetchHistory(sessionId) {
3162
3170
  function isHighSignalMemory(memory) {
3163
3171
  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;
3164
3172
  }
3173
+ function extractLastUserText(messages) {
3174
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
3175
+ if (!lastUser) return "";
3176
+ if (typeof lastUser.content === "string") return lastUser.content;
3177
+ if (Array.isArray(lastUser.content)) {
3178
+ return lastUser.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
3179
+ }
3180
+ return "";
3181
+ }
3165
3182
  function raceWithSignal(promise, signal) {
3166
3183
  return Promise.race([
3167
3184
  promise,
@@ -3179,87 +3196,84 @@ async function runAgentTurn(opts2) {
3179
3196
  logger.info("turn:start", { sessionId: opts2.sessionId, codeMode: opts2.codeMode ?? false });
3180
3197
  const max = opts2.maxToolIterations ?? 50;
3181
3198
  const codeMode = opts2.codeMode ?? false;
3199
+ const preTurnStart = performance.now();
3182
3200
  let memoryRecalledCount = 0;
3183
3201
  let skillResult;
3184
- if (opts2.sessionStartRecall) {
3185
- try {
3186
- const results = await raceWithSignal(opts2.sessionStartRecall, opts2.signal);
3187
- if (results.length > 0 && opts2.memoryManager) {
3188
- const text = await raceWithSignal(
3189
- opts2.memoryManager.synthesizeRecalled(results, opts2.signal),
3190
- opts2.signal
3191
- );
3192
- memoryRecalledCount = results.length;
3193
- const lastSystemIdx = opts2.messages.findLastIndex((m) => m.role === "system");
3194
- const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : opts2.messages.length;
3195
- opts2.messages.splice(insertIdx, 0, { role: "system", content: text });
3196
- opts2.callbacks.onMemoryRecalled?.(results.length);
3197
- }
3198
- } catch (err) {
3199
- if (err instanceof DOMException && err.name === "AbortError") throw err;
3200
- }
3201
- }
3202
- if (opts2.signal.aborted) {
3203
- throw new DOMException("aborted", "AbortError");
3204
- }
3205
- if (opts2.skillsDb && opts2.skillRoutingConfig && opts2.intentClassification) {
3206
- try {
3207
- const lastUserMsg = [...opts2.messages].reverse().find((m) => m.role === "user");
3208
- const prompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : Array.isArray(lastUserMsg?.content) ? lastUserMsg.content.filter((p) => p.type === "text").map((p) => p.text).join(" ") : "";
3209
- if (prompt) {
3210
- skillResult = await raceWithSignal(
3211
- selectSkills(
3212
- {
3213
- prompt,
3214
- tier: opts2.intentClassification.tier,
3215
- maxSkillTokens: opts2.skillRoutingConfig.maxSkillTokens ?? 25e4 - 1e4
3216
- },
3217
- {
3218
- db: opts2.skillsDb,
3219
- accountId: opts2.skillRoutingConfig.accountId,
3220
- apiToken: opts2.skillRoutingConfig.apiToken,
3221
- embeddingModel: opts2.skillRoutingConfig.embeddingModel,
3222
- gateway: opts2.skillRoutingConfig.gateway,
3223
- cloudMode: opts2.skillRoutingConfig.cloudMode,
3224
- cloudToken: opts2.skillRoutingConfig.cloudToken,
3225
- cloudDeviceId: opts2.skillRoutingConfig.cloudDeviceId
3226
- }
3227
- ),
3228
- opts2.signal
3229
- );
3230
- opts2.callbacks.onSkillsSelected?.(skillResult);
3231
- const allTools = opts2.tools;
3232
- if (opts2.cacheStable) {
3233
- opts2.messages[1] = {
3234
- role: "system",
3235
- content: buildSessionPrefix({
3236
- cwd: opts2.cwd,
3237
- tools: allTools,
3238
- model: opts2.model,
3239
- mode: opts2.mode,
3240
- skillContext: skillResult.skillContext
3241
- })
3242
- };
3243
- } else {
3244
- opts2.messages[0] = {
3245
- role: "system",
3246
- content: buildSystemPrompt({
3247
- cwd: opts2.cwd,
3248
- tools: allTools,
3249
- model: opts2.model,
3250
- mode: opts2.mode,
3251
- skillContext: skillResult.skillContext
3252
- })
3253
- };
3254
- }
3255
- }
3256
- } catch (err) {
3257
- if (err instanceof DOMException && err.name === "AbortError") throw err;
3202
+ const lastUserPrompt = extractLastUserText(opts2.messages);
3203
+ const userPromptPreview = lastUserPrompt.slice(0, 200);
3204
+ const skipSkillRouting = opts2.intentClassification?.tier === "light" && lastUserPrompt.length < 40;
3205
+ const recallPromise = opts2.sessionStartRecall && opts2.memoryManager ? (async () => {
3206
+ const results = await opts2.sessionStartRecall;
3207
+ if (results.length === 0 || !opts2.memoryManager) return null;
3208
+ const text = await opts2.memoryManager.synthesizeRecalled(results, opts2.signal);
3209
+ return { text, count: results.length };
3210
+ })() : Promise.resolve(null);
3211
+ const skillsPromise = opts2.skillsDb && opts2.skillRoutingConfig && opts2.intentClassification && lastUserPrompt && !skipSkillRouting ? selectSkills(
3212
+ {
3213
+ prompt: lastUserPrompt,
3214
+ tier: opts2.intentClassification.tier,
3215
+ maxSkillTokens: opts2.skillRoutingConfig.maxSkillTokens ?? 25e4 - 1e4
3216
+ },
3217
+ {
3218
+ db: opts2.skillsDb,
3219
+ accountId: opts2.skillRoutingConfig.accountId,
3220
+ apiToken: opts2.skillRoutingConfig.apiToken,
3221
+ embeddingModel: opts2.skillRoutingConfig.embeddingModel,
3222
+ gateway: opts2.skillRoutingConfig.gateway,
3223
+ cloudMode: opts2.skillRoutingConfig.cloudMode,
3224
+ cloudToken: opts2.skillRoutingConfig.cloudToken,
3225
+ cloudDeviceId: opts2.skillRoutingConfig.cloudDeviceId
3226
+ }
3227
+ ) : Promise.resolve(void 0);
3228
+ const [recallSettled, skillsSettled] = await Promise.allSettled([
3229
+ raceWithSignal(recallPromise, opts2.signal),
3230
+ raceWithSignal(skillsPromise, opts2.signal)
3231
+ ]);
3232
+ for (const settled of [recallSettled, skillsSettled]) {
3233
+ if (settled.status === "rejected" && settled.reason instanceof DOMException && settled.reason.name === "AbortError") {
3234
+ throw settled.reason;
3235
+ }
3236
+ }
3237
+ if (recallSettled.status === "fulfilled" && recallSettled.value) {
3238
+ const { text, count } = recallSettled.value;
3239
+ const lastSystemIdx = opts2.messages.findLastIndex((m) => m.role === "system");
3240
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : opts2.messages.length;
3241
+ opts2.messages.splice(insertIdx, 0, { role: "system", content: text });
3242
+ memoryRecalledCount = count;
3243
+ opts2.callbacks.onMemoryRecalled?.(count);
3244
+ }
3245
+ if (skillsSettled.status === "fulfilled" && skillsSettled.value) {
3246
+ skillResult = skillsSettled.value;
3247
+ opts2.callbacks.onSkillsSelected?.(skillResult);
3248
+ const allTools = opts2.tools;
3249
+ if (opts2.cacheStable) {
3250
+ opts2.messages[1] = {
3251
+ role: "system",
3252
+ content: buildSessionPrefix({
3253
+ cwd: opts2.cwd,
3254
+ tools: allTools,
3255
+ model: opts2.model,
3256
+ mode: opts2.mode,
3257
+ skillContext: skillResult.skillContext
3258
+ })
3259
+ };
3260
+ } else {
3261
+ opts2.messages[0] = {
3262
+ role: "system",
3263
+ content: buildSystemPrompt({
3264
+ cwd: opts2.cwd,
3265
+ tools: allTools,
3266
+ model: opts2.model,
3267
+ mode: opts2.mode,
3268
+ skillContext: skillResult.skillContext
3269
+ })
3270
+ };
3258
3271
  }
3259
3272
  }
3260
3273
  if (opts2.signal.aborted) {
3261
3274
  throw new DOMException("aborted", "AbortError");
3262
3275
  }
3276
+ const preTurnMs = Math.round(performance.now() - preTurnStart);
3263
3277
  opts2.callbacks.onMetaBanner?.({
3264
3278
  intentTier: opts2.intentClassification?.tier ?? "medium",
3265
3279
  skillsActive: skillResult?.sectionCount ?? 0,
@@ -3414,7 +3428,9 @@ Use console.log() to return results. Only console.log output will be sent back t
3414
3428
  ...opts2.gateway.metadata ?? {},
3415
3429
  feature: "chat",
3416
3430
  ...opts2.sessionId ? { sessionId: opts2.sessionId } : {},
3417
- turnIdx: turn
3431
+ tier: opts2.intentClassification?.tier ?? "medium",
3432
+ cm: codeMode ? "1" : "0",
3433
+ skl: String(skillResult?.sectionCount ?? 0)
3418
3434
  }
3419
3435
  } : void 0;
3420
3436
  const events = runKimi({
@@ -3512,7 +3528,14 @@ Use console.log() to return results. Only console.log output will be sent back t
3512
3528
  previousMessages,
3513
3529
  toolResults,
3514
3530
  usage: lastUsage,
3515
- shadowStrip: shadowStripMetrics
3531
+ shadowStrip: shadowStripMetrics,
3532
+ durationMs: Math.round(performance.now() - turnStart),
3533
+ intentClassification: opts2.intentClassification,
3534
+ codeMode: opts2.codeMode,
3535
+ selectedSkills: opts2.selectedSkills,
3536
+ userPromptPreview,
3537
+ preTurnMs,
3538
+ memoryRecalled: memoryRecalledCount > 0
3516
3539
  });
3517
3540
  }
3518
3541
  if (budgetExhausted) {
@@ -3821,7 +3844,10 @@ ${sandboxResult.output}` : sandboxResult.output;
3821
3844
  durationMs: Math.round(performance.now() - turnStart),
3822
3845
  intentClassification: opts2.intentClassification,
3823
3846
  codeMode: opts2.codeMode,
3824
- selectedSkills: opts2.selectedSkills
3847
+ selectedSkills: opts2.selectedSkills,
3848
+ userPromptPreview,
3849
+ preTurnMs,
3850
+ memoryRecalled: memoryRecalledCount > 0
3825
3851
  });
3826
3852
  }
3827
3853
  if (budgetExhausted) {
@@ -3942,15 +3968,61 @@ var init_paths = __esm({
3942
3968
 
3943
3969
  // src/tools/read.ts
3944
3970
  import { readFile as readFile3, stat as stat2 } from "fs/promises";
3945
- var MAX_BYTES, readTool;
3971
+ import { createReadStream } from "fs";
3972
+ import { createInterface } from "readline";
3973
+ function aborted(signal) {
3974
+ return signal?.aborted === true;
3975
+ }
3976
+ function abortError() {
3977
+ return new DOMException("aborted", "AbortError");
3978
+ }
3979
+ async function readSliceStreaming(abs, offset, limit, signal) {
3980
+ if (aborted(signal)) throw abortError();
3981
+ const rs = createReadStream(abs, { encoding: "utf8" });
3982
+ const onAbort = () => rs.destroy(abortError());
3983
+ signal?.addEventListener("abort", onAbort);
3984
+ try {
3985
+ const rl = createInterface({ input: rs, crlfDelay: Infinity });
3986
+ const startLine = Math.max(1, offset);
3987
+ const endLine = startLine + Math.max(0, limit) - 1;
3988
+ const collected = [];
3989
+ let lineNum = 0;
3990
+ let bytesScanned = 0;
3991
+ for await (const line of rl) {
3992
+ if (aborted(signal)) throw abortError();
3993
+ lineNum += 1;
3994
+ bytesScanned += Buffer.byteLength(line, "utf8") + 1;
3995
+ if (bytesScanned > MAX_STREAM_BYTES) {
3996
+ throw new Error(
3997
+ `file too large to stream: exceeded ${MAX_STREAM_BYTES} bytes while seeking slice at line ${startLine}`
3998
+ );
3999
+ }
4000
+ if (lineNum >= startLine && lineNum <= endLine) {
4001
+ collected.push(line);
4002
+ }
4003
+ if (lineNum >= endLine) break;
4004
+ }
4005
+ return collected;
4006
+ } finally {
4007
+ signal?.removeEventListener("abort", onAbort);
4008
+ rs.destroy();
4009
+ }
4010
+ }
4011
+ function formatLines(lines, startLine) {
4012
+ const endLine = startLine + lines.length - 1;
4013
+ const width = String(endLine).length;
4014
+ return lines.map((l, i) => `${String(startLine + i).padStart(width, " ")} ${l}`).join("\n");
4015
+ }
4016
+ var MAX_BYTES, MAX_STREAM_BYTES, readTool;
3946
4017
  var init_read = __esm({
3947
4018
  "src/tools/read.ts"() {
3948
4019
  "use strict";
3949
4020
  init_paths();
3950
4021
  MAX_BYTES = 2 * 1024 * 1024;
4022
+ MAX_STREAM_BYTES = 50 * 1024 * 1024;
3951
4023
  readTool = {
3952
4024
  name: "read",
3953
- description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style. When reading a full file without offset/limit, the output is reduced to a compact outline (imports, exports, signatures, preview) by default; use expand_artifact to retrieve the full content or specify offset/limit for a targeted slice.",
4025
+ description: "Read a text file from the local filesystem. Supports optional line offset/limit. Files up to 2MB are read in a single pass; larger files require an explicit offset+limit slice and are streamed line by line (cancellable mid-stream). Returns contents with 1-indexed line numbers prefixed, cat -n style. When reading a full file without offset/limit, the output is reduced to a compact outline (imports, exports, signatures, preview) by default; use expand_artifact to retrieve the full content or specify offset/limit for a targeted slice.",
3954
4026
  parameters: {
3955
4027
  type: "object",
3956
4028
  properties: {
@@ -3964,13 +4036,21 @@ var init_read = __esm({
3964
4036
  needsPermission: false,
3965
4037
  render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
3966
4038
  async run(args, ctx) {
3967
- if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4039
+ if (aborted(ctx.signal)) throw abortError();
3968
4040
  const abs = resolvePath(ctx.cwd, args.path);
3969
4041
  const st = await stat2(abs);
3970
- if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
3971
- if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4042
+ if (aborted(ctx.signal)) throw abortError();
4043
+ if (st.size > MAX_BYTES) {
4044
+ if (args.offset === void 0 || args.limit === void 0) {
4045
+ throw new Error(
4046
+ `file too large: ${st.size} bytes (max ${MAX_BYTES} for full read; supply offset+limit to stream a slice)`
4047
+ );
4048
+ }
4049
+ const lines2 = await readSliceStreaming(abs, args.offset, args.limit, ctx.signal);
4050
+ return formatLines(lines2, args.offset);
4051
+ }
3972
4052
  const text = await readFile3(abs, { encoding: "utf8", signal: ctx.signal });
3973
- if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4053
+ if (aborted(ctx.signal)) throw abortError();
3974
4054
  const lines = text.split("\n");
3975
4055
  const start = Math.max(0, (args.offset ?? 1) - 1);
3976
4056
  const end = args.limit ? Math.min(lines.length, start + args.limit) : lines.length;
@@ -10164,7 +10244,7 @@ var rpc_exports = {};
10164
10244
  __export(rpc_exports, {
10165
10245
  startRpcServer: () => startRpcServer
10166
10246
  });
10167
- import { createInterface } from "readline";
10247
+ import { createInterface as createInterface2 } from "readline";
10168
10248
  async function startRpcServer(input = process.stdin, output = process.stdout) {
10169
10249
  let session = null;
10170
10250
  let unsubscribe = null;
@@ -10174,7 +10254,7 @@ async function startRpcServer(input = process.stdin, output = process.stdout) {
10174
10254
  function sendEvent(event) {
10175
10255
  send({ ...event });
10176
10256
  }
10177
- const rl = createInterface({
10257
+ const rl = createInterface2({
10178
10258
  input,
10179
10259
  crlfDelay: Infinity
10180
10260
  });
@@ -10318,69 +10398,6 @@ var init_rpc = __esm({
10318
10398
  }
10319
10399
  });
10320
10400
 
10321
- // src/agent/supervisor.ts
10322
- var TurnSupervisor;
10323
- var init_supervisor = __esm({
10324
- "src/agent/supervisor.ts"() {
10325
- "use strict";
10326
- init_loop();
10327
- init_logger();
10328
- TurnSupervisor = class {
10329
- currentTurn = null;
10330
- _phase = "idle";
10331
- _killRequested = false;
10332
- get phase() {
10333
- return this._phase;
10334
- }
10335
- get isRunning() {
10336
- return this._phase !== "idle";
10337
- }
10338
- get killRequested() {
10339
- return this._killRequested;
10340
- }
10341
- startTurn(opts2, callbacks) {
10342
- if (this.isRunning) {
10343
- logger.warn("supervisor:start_rejected", { reason: "turn_already_running", phase: this._phase });
10344
- throw new Error("TurnSupervisor: turn already in progress");
10345
- }
10346
- this._phase = "preparing";
10347
- this._killRequested = false;
10348
- logger.debug("supervisor:turn_start", { sessionId: opts2.sessionId });
10349
- this.currentTurn = runAgentTurn(opts2).then(async () => {
10350
- this._phase = "idle";
10351
- if (this._killRequested) {
10352
- logger.debug("supervisor:turn_killed", { sessionId: opts2.sessionId });
10353
- } else {
10354
- logger.debug("supervisor:turn_done", { sessionId: opts2.sessionId });
10355
- }
10356
- await callbacks?.onDone?.();
10357
- }).catch(async (error) => {
10358
- this._phase = "idle";
10359
- const err = error;
10360
- logger.warn("supervisor:turn_error", {
10361
- sessionId: opts2.sessionId,
10362
- error: err.message ?? String(err),
10363
- name: err.name
10364
- });
10365
- await callbacks?.onError?.(err);
10366
- }).finally(() => {
10367
- this.currentTurn = null;
10368
- this._killRequested = false;
10369
- });
10370
- }
10371
- /** Request that the current turn be killed. This does NOT directly abort
10372
- * the turn — the caller must abort the AbortScope that was passed to
10373
- * `startTurn`. This method only records the intent so the supervisor
10374
- * knows the turn was intentionally killed rather than failing. */
10375
- killTurn() {
10376
- if (!this.isRunning) return;
10377
- this._killRequested = true;
10378
- logger.debug("supervisor:kill_requested", { phase: this._phase });
10379
- }
10380
- };
10381
- }
10382
- });
10383
-
10384
10401
  // src/agent/llm-summarize.ts
10385
10402
  function indexOfNthUserFromEnd(messages, n) {
10386
10403
  let seen = 0;
@@ -18537,6 +18554,401 @@ var init_modal_host = __esm({
18537
18554
  }
18538
18555
  });
18539
18556
 
18557
+ // src/ui/use-session-manager.ts
18558
+ import { useCallback as useCallback6, useRef as useRef5, useState as useState16 } from "react";
18559
+ function extractFirstUserText(messages) {
18560
+ const firstUser = messages.find((m) => m.role === "user");
18561
+ if (!firstUser) return "session";
18562
+ if (typeof firstUser.content === "string") {
18563
+ return firstUser.content || "session";
18564
+ }
18565
+ if (Array.isArray(firstUser.content)) {
18566
+ const textPart = firstUser.content.find((p) => p.type === "text");
18567
+ if (textPart?.text) return textPart.text;
18568
+ }
18569
+ return "session";
18570
+ }
18571
+ function useSessionManager(deps) {
18572
+ const sessionIdRef = useRef5(null);
18573
+ const sessionCreatedAtRef = useRef5(null);
18574
+ const sessionTitleRef = useRef5(null);
18575
+ const [resumeSessions, setResumeSessions] = useState16(null);
18576
+ const [checkpointSession, setCheckpointSession] = useState16(null);
18577
+ const [checkpointList, setCheckpointList] = useState16([]);
18578
+ const depsRef = useRef5(deps);
18579
+ depsRef.current = deps;
18580
+ const ensureSessionId = useCallback6(() => {
18581
+ if (sessionIdRef.current) return sessionIdRef.current;
18582
+ const text = extractFirstUserText(depsRef.current.messagesRef.current);
18583
+ sessionIdRef.current = makeSessionId(text);
18584
+ return sessionIdRef.current;
18585
+ }, []);
18586
+ const saveSessionSafe = useCallback6(async () => {
18587
+ const d = depsRef.current;
18588
+ if (!d.cfg) return;
18589
+ ensureSessionId();
18590
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
18591
+ if (!sessionCreatedAtRef.current) {
18592
+ sessionCreatedAtRef.current = now2;
18593
+ }
18594
+ try {
18595
+ await saveSession({
18596
+ id: sessionIdRef.current,
18597
+ cwd: process.cwd(),
18598
+ model: d.cfg.model,
18599
+ createdAt: sessionCreatedAtRef.current,
18600
+ updatedAt: now2,
18601
+ title: sessionTitleRef.current ?? void 0,
18602
+ messages: d.messagesRef.current,
18603
+ sessionState: d.compiledContextRef.current ? d.sessionStateRef.current : void 0,
18604
+ artifactStore: serializeArtifactStore(d.artifactStoreRef.current)
18605
+ });
18606
+ } catch (e) {
18607
+ d.setEvents((es) => [
18608
+ ...es,
18609
+ { kind: "error", key: d.mkKey(), text: `session save failed: ${e.message}` }
18610
+ ]);
18611
+ }
18612
+ }, [ensureSessionId]);
18613
+ const openResumePicker = useCallback6(async () => {
18614
+ const sessions = await listSessions(200, process.cwd());
18615
+ setResumeSessions(sessions);
18616
+ }, []);
18617
+ const doResumeSession = useCallback6(
18618
+ async (filePath, checkpointId) => {
18619
+ const d = depsRef.current;
18620
+ try {
18621
+ const file = checkpointId ? (await loadSessionFromCheckpoint(filePath, checkpointId)).file : await loadSession(filePath);
18622
+ d.messagesRef.current = file.messages;
18623
+ sessionIdRef.current = file.id;
18624
+ sessionCreatedAtRef.current = file.createdAt;
18625
+ if (file.sessionState && d.compiledContextRef.current) {
18626
+ d.sessionStateRef.current = file.sessionState;
18627
+ }
18628
+ if (file.artifactStore) {
18629
+ d.artifactStoreRef.current = deserializeArtifactStore(file.artifactStore);
18630
+ } else {
18631
+ d.artifactStoreRef.current = new ArtifactStore();
18632
+ }
18633
+ const manager = d.memoryManagerRef.current;
18634
+ if (manager) {
18635
+ try {
18636
+ const cwd = process.cwd();
18637
+ const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
18638
+ if (results.length > 0) {
18639
+ const text = await manager.synthesizeRecalled(results);
18640
+ const lastSystemIdx = d.messagesRef.current.findLastIndex((m) => m.role === "system");
18641
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : d.messagesRef.current.length;
18642
+ d.messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
18643
+ }
18644
+ } catch {
18645
+ }
18646
+ }
18647
+ const msg = checkpointId ? `resumed session ${file.id} from checkpoint` : `resumed session ${file.id} (${file.messages.filter((m) => m.role !== "system").length} msgs)`;
18648
+ d.setEvents([{ kind: "info", key: d.mkKey(), text: msg }]);
18649
+ const userMsgs = file.messages.filter((m) => m.role === "user" && m.content).map((m) => {
18650
+ if (!m.content) return "";
18651
+ if (typeof m.content === "string") return m.content;
18652
+ const textPart = m.content.find((p) => p.type === "text");
18653
+ return textPart?.text ?? "";
18654
+ }).filter((text) => text.length > 0);
18655
+ if (userMsgs.length > 0) d.setHistory(userMsgs);
18656
+ d.setUsage(null);
18657
+ d.setSessionUsage(null);
18658
+ d.gatewayMetaRef.current = null;
18659
+ d.setGatewayMeta(null);
18660
+ void getCostReport(file.id).then((report) => d.setSessionUsage(report.session));
18661
+ } catch (e) {
18662
+ d.setEvents((es) => [
18663
+ ...es,
18664
+ { kind: "error", key: d.mkKey(), text: `failed to load session: ${e.message}` }
18665
+ ]);
18666
+ }
18667
+ },
18668
+ []
18669
+ );
18670
+ const handleResumePick = useCallback6(
18671
+ async (picked) => {
18672
+ setResumeSessions(null);
18673
+ if (!picked) return;
18674
+ if (picked.checkpointCount > 0) {
18675
+ try {
18676
+ const file = await loadSession(picked.filePath);
18677
+ setCheckpointList(file.checkpoints ?? []);
18678
+ setCheckpointSession(picked);
18679
+ } catch (e) {
18680
+ depsRef.current.setEvents((es) => [
18681
+ ...es,
18682
+ { kind: "error", key: depsRef.current.mkKey(), text: `failed to load checkpoints: ${e.message}` }
18683
+ ]);
18684
+ await doResumeSession(picked.filePath);
18685
+ }
18686
+ return;
18687
+ }
18688
+ await doResumeSession(picked.filePath);
18689
+ },
18690
+ [doResumeSession]
18691
+ );
18692
+ const handleCheckpointPick = useCallback6(
18693
+ async (checkpointId) => {
18694
+ const session = checkpointSession;
18695
+ setCheckpointSession(null);
18696
+ setCheckpointList([]);
18697
+ if (!session || !checkpointId) {
18698
+ if (session) {
18699
+ setResumeSessions(await listSessions(200, process.cwd()));
18700
+ }
18701
+ return;
18702
+ }
18703
+ if (checkpointId === "__start__") {
18704
+ await doResumeSession(session.filePath);
18705
+ return;
18706
+ }
18707
+ await doResumeSession(session.filePath, checkpointId);
18708
+ },
18709
+ [checkpointSession, doResumeSession]
18710
+ );
18711
+ const resetSession = useCallback6(() => {
18712
+ sessionIdRef.current = null;
18713
+ sessionCreatedAtRef.current = null;
18714
+ sessionTitleRef.current = null;
18715
+ const d = depsRef.current;
18716
+ d.sessionStateRef.current = emptySessionState();
18717
+ d.artifactStoreRef.current = new ArtifactStore();
18718
+ }, []);
18719
+ return {
18720
+ sessionIdRef,
18721
+ sessionCreatedAtRef,
18722
+ sessionTitleRef,
18723
+ resumeSessions,
18724
+ setResumeSessions,
18725
+ checkpointSession,
18726
+ setCheckpointSession,
18727
+ checkpointList,
18728
+ setCheckpointList,
18729
+ hasPickerOpen: resumeSessions !== null || checkpointSession !== null,
18730
+ ensureSessionId,
18731
+ saveSessionSafe,
18732
+ openResumePicker,
18733
+ doResumeSession,
18734
+ handleResumePick,
18735
+ handleCheckpointPick,
18736
+ resetSession
18737
+ };
18738
+ }
18739
+ var init_use_session_manager = __esm({
18740
+ "src/ui/use-session-manager.ts"() {
18741
+ "use strict";
18742
+ init_sessions();
18743
+ init_session_state();
18744
+ init_usage_tracker();
18745
+ }
18746
+ });
18747
+
18748
+ // src/agent/supervisor.ts
18749
+ var TurnSupervisor;
18750
+ var init_supervisor = __esm({
18751
+ "src/agent/supervisor.ts"() {
18752
+ "use strict";
18753
+ init_loop();
18754
+ init_logger();
18755
+ TurnSupervisor = class {
18756
+ currentTurn = null;
18757
+ _phase = "idle";
18758
+ _killRequested = false;
18759
+ get phase() {
18760
+ return this._phase;
18761
+ }
18762
+ get isRunning() {
18763
+ return this._phase !== "idle";
18764
+ }
18765
+ get killRequested() {
18766
+ return this._killRequested;
18767
+ }
18768
+ startTurn(opts2, callbacks) {
18769
+ if (this.isRunning) {
18770
+ logger.warn("supervisor:start_rejected", { reason: "turn_already_running", phase: this._phase });
18771
+ throw new Error("TurnSupervisor: turn already in progress");
18772
+ }
18773
+ this._phase = "preparing";
18774
+ this._killRequested = false;
18775
+ logger.debug("supervisor:turn_start", { sessionId: opts2.sessionId });
18776
+ this.currentTurn = runAgentTurn(opts2).then(async () => {
18777
+ this._phase = "idle";
18778
+ if (this._killRequested) {
18779
+ logger.debug("supervisor:turn_killed", { sessionId: opts2.sessionId });
18780
+ } else {
18781
+ logger.debug("supervisor:turn_done", { sessionId: opts2.sessionId });
18782
+ }
18783
+ await callbacks?.onDone?.();
18784
+ }).catch(async (error) => {
18785
+ this._phase = "idle";
18786
+ const err = error;
18787
+ logger.warn("supervisor:turn_error", {
18788
+ sessionId: opts2.sessionId,
18789
+ error: err.message ?? String(err),
18790
+ name: err.name
18791
+ });
18792
+ await callbacks?.onError?.(err);
18793
+ }).finally(() => {
18794
+ this.currentTurn = null;
18795
+ this._killRequested = false;
18796
+ });
18797
+ }
18798
+ /** Request that the current turn be killed. This does NOT directly abort
18799
+ * the turn — the caller must abort the AbortScope that was passed to
18800
+ * `startTurn`. This method only records the intent so the supervisor
18801
+ * knows the turn was intentionally killed rather than failing. */
18802
+ killTurn() {
18803
+ if (!this.isRunning) return;
18804
+ this._killRequested = true;
18805
+ logger.debug("supervisor:kill_requested", { phase: this._phase });
18806
+ }
18807
+ };
18808
+ }
18809
+ });
18810
+
18811
+ // src/ui/use-turn-controller.ts
18812
+ import { useCallback as useCallback7, useRef as useRef6, useState as useState17 } from "react";
18813
+ function useTurnController() {
18814
+ const [busy, setBusy] = useState17(false);
18815
+ const busyRef = useRef6(false);
18816
+ const isAbortingRef = useRef6(false);
18817
+ const lastEscapeAtRef = useRef6(0);
18818
+ const supervisorRef = useRef6(new TurnSupervisor());
18819
+ const [turnPhase, setTurnPhase] = useState17("waiting");
18820
+ const [turnStartedAt, setTurnStartedAt] = useState17(null);
18821
+ const [currentToolName, setCurrentToolName] = useState17(null);
18822
+ const [lastActivityAt, setLastActivityAt] = useState17(null);
18823
+ const turnCounterRef = useRef6(0);
18824
+ const [showReasoning, setShowReasoning] = useState17(false);
18825
+ const toggleReasoning = useCallback7(() => {
18826
+ setShowReasoning((s) => !s);
18827
+ }, []);
18828
+ const [tasks, setTasks] = useState17([]);
18829
+ const tasksRef = useRef6([]);
18830
+ const [tasksStartedAt, setTasksStartedAt] = useState17(null);
18831
+ const [tasksStartTokens, setTasksStartTokens] = useState17(0);
18832
+ const beginTurn = useCallback7(() => {
18833
+ setBusy(true);
18834
+ busyRef.current = true;
18835
+ setTurnStartedAt(Date.now());
18836
+ }, []);
18837
+ const endTurn = useCallback7(() => {
18838
+ setBusy(false);
18839
+ busyRef.current = false;
18840
+ setTurnStartedAt(null);
18841
+ setTurnPhase("waiting");
18842
+ setCurrentToolName(null);
18843
+ setLastActivityAt(null);
18844
+ isAbortingRef.current = false;
18845
+ }, []);
18846
+ const clearTaskTracking = useCallback7(() => {
18847
+ setTasks([]);
18848
+ setTasksStartedAt(null);
18849
+ setTasksStartTokens(0);
18850
+ tasksRef.current = [];
18851
+ }, []);
18852
+ const markAborting = useCallback7(() => {
18853
+ if (isAbortingRef.current) return false;
18854
+ isAbortingRef.current = true;
18855
+ supervisorRef.current.killTurn();
18856
+ return true;
18857
+ }, []);
18858
+ return {
18859
+ busy,
18860
+ setBusy,
18861
+ busyRef,
18862
+ isAbortingRef,
18863
+ lastEscapeAtRef,
18864
+ supervisorRef,
18865
+ turnPhase,
18866
+ setTurnPhase,
18867
+ turnStartedAt,
18868
+ setTurnStartedAt,
18869
+ currentToolName,
18870
+ setCurrentToolName,
18871
+ lastActivityAt,
18872
+ setLastActivityAt,
18873
+ turnCounterRef,
18874
+ showReasoning,
18875
+ setShowReasoning,
18876
+ toggleReasoning,
18877
+ tasks,
18878
+ setTasks,
18879
+ tasksRef,
18880
+ tasksStartedAt,
18881
+ setTasksStartedAt,
18882
+ tasksStartTokens,
18883
+ setTasksStartTokens,
18884
+ beginTurn,
18885
+ endTurn,
18886
+ clearTaskTracking,
18887
+ markAborting
18888
+ };
18889
+ }
18890
+ var init_use_turn_controller = __esm({
18891
+ "src/ui/use-turn-controller.ts"() {
18892
+ "use strict";
18893
+ init_supervisor();
18894
+ }
18895
+ });
18896
+
18897
+ // src/ui/input-handlers.ts
18898
+ function clearLimitLoopResolvers(deps) {
18899
+ const hadLimit = deps.limitResolveRef.current !== null;
18900
+ const hadLoop = deps.loopResolveRef.current !== null;
18901
+ if (hadLimit) {
18902
+ deps.limitResolveRef.current("stop");
18903
+ deps.limitResolveRef.current = null;
18904
+ deps.setLimitModal(null);
18905
+ }
18906
+ if (hadLoop) {
18907
+ deps.loopResolveRef.current("stop");
18908
+ deps.loopResolveRef.current = null;
18909
+ deps.setLoopModal(null);
18910
+ }
18911
+ return { hadLimit, hadLoop };
18912
+ }
18913
+ function interruptTurn(deps) {
18914
+ const hadPermission = deps.denyPendingPermission();
18915
+ const { hadLimit, hadLoop } = clearLimitLoopResolvers(deps);
18916
+ if (deps.busyRef.current && deps.activeScopeRef.current && !deps.isAbortingRef.current) {
18917
+ deps.isAbortingRef.current = true;
18918
+ deps.supervisorRef.current.killTurn();
18919
+ deps.activeScopeRef.current.abort("user_stopped");
18920
+ deps.setEvents((e) => [
18921
+ ...e,
18922
+ { kind: "info", key: deps.mkKey(), text: "(interrupted)" }
18923
+ ]);
18924
+ if (!deps.skipPendingToolCleanup) {
18925
+ for (const [toolId] of deps.pendingToolCallsRef.current) {
18926
+ deps.updateTool(toolId, { status: "cancelled" });
18927
+ }
18928
+ deps.pendingToolCallsRef.current.clear();
18929
+ }
18930
+ void deps.saveSessionSafe();
18931
+ deps.clearTaskTracking();
18932
+ return { hadPermission, hadLimit, hadLoop, didInterruptTurn: true };
18933
+ }
18934
+ return { hadPermission, hadLimit, hadLoop, didInterruptTurn: false };
18935
+ }
18936
+ function exitApp(deps) {
18937
+ void deps.lspManagerRef.current.stopAll().finally(() => deps.exit());
18938
+ }
18939
+ function interruptOrExit(deps) {
18940
+ const outcome = interruptTurn(deps);
18941
+ if (!outcome.didInterruptTurn && !outcome.hadPermission && !outcome.hadLimit && !outcome.hadLoop) {
18942
+ exitApp(deps);
18943
+ }
18944
+ return outcome;
18945
+ }
18946
+ var init_input_handlers = __esm({
18947
+ "src/ui/input-handlers.ts"() {
18948
+ "use strict";
18949
+ }
18950
+ });
18951
+
18540
18952
  // src/cost-attribution/tui-report.ts
18541
18953
  var tui_report_exports = {};
18542
18954
  __export(tui_report_exports, {
@@ -18624,7 +19036,7 @@ __export(app_exports, {
18624
19036
  buildFilePickerIgnoreList: () => buildFilePickerIgnoreList,
18625
19037
  renderApp: () => renderApp
18626
19038
  });
18627
- import React15, { useState as useState16, useRef as useRef5, useEffect as useEffect8, useCallback as useCallback6 } from "react";
19039
+ import React17, { useState as useState18, useRef as useRef7, useEffect as useEffect8, useCallback as useCallback8 } from "react";
18628
19040
  import { Box as Box26, Text as Text27, useApp, useInput as useInput10, render } from "ink";
18629
19041
  import { existsSync as existsSync4, statSync as statSync4 } from "fs";
18630
19042
  import { join as join28 } from "path";
@@ -18863,13 +19275,13 @@ function App({
18863
19275
  initialCloudDeviceId
18864
19276
  }) {
18865
19277
  const { exit } = useApp();
18866
- const [cfg, setCfg] = useState16(initialCfg);
18867
- const [lspScope, setLspScope] = useState16(initialLspScope);
18868
- const [lspProjectPath, setLspProjectPath] = useState16(initialLspProjectPath);
18869
- const [cloudToken, setCloudToken] = useState16(initialCloudToken);
18870
- const [cloudDeviceId, setCloudDeviceId] = useState16(initialCloudDeviceId);
18871
- const [events, setRawEvents] = useState16([]);
18872
- const setEvents = useCallback6(
19278
+ const [cfg, setCfg] = useState18(initialCfg);
19279
+ const [lspScope, setLspScope] = useState18(initialLspScope);
19280
+ const [lspProjectPath, setLspProjectPath] = useState18(initialLspProjectPath);
19281
+ const [cloudToken, setCloudToken] = useState18(initialCloudToken);
19282
+ const [cloudDeviceId, setCloudDeviceId] = useState18(initialCloudDeviceId);
19283
+ const [events, setRawEvents] = useState18([]);
19284
+ const setEvents = useCallback8(
18873
19285
  (updater) => {
18874
19286
  setRawEvents((prev) => {
18875
19287
  const next = typeof updater === "function" ? updater(prev) : updater;
@@ -18878,10 +19290,9 @@ function App({
18878
19290
  },
18879
19291
  []
18880
19292
  );
18881
- const [input, setInput] = useState16("");
18882
- const [busy, setBusy] = useState16(false);
18883
- const [usage, setUsage] = useState16(null);
18884
- const [sessionUsage, setSessionUsage] = useState16(null);
19293
+ const [input, setInput] = useState18("");
19294
+ const [usage, setUsage] = useState18(null);
19295
+ const [sessionUsage, setSessionUsage] = useState18(null);
18885
19296
  useEffect8(() => {
18886
19297
  const handler = (sid) => {
18887
19298
  if (sessionIdRef.current && sid === sessionIdRef.current) {
@@ -18893,9 +19304,35 @@ function App({
18893
19304
  usageEvents.off("update", handler);
18894
19305
  };
18895
19306
  }, []);
18896
- const [gatewayMeta, setGatewayMeta] = useState16(null);
18897
- const [cloudBudget, setCloudBudget] = useState16(null);
18898
- const [showReasoning, setShowReasoning] = useState16(false);
19307
+ const [gatewayMeta, setGatewayMeta] = useState18(null);
19308
+ const [cloudBudget, setCloudBudget] = useState18(null);
19309
+ const turn = useTurnController();
19310
+ const {
19311
+ busy,
19312
+ busyRef,
19313
+ isAbortingRef,
19314
+ lastEscapeAtRef,
19315
+ supervisorRef,
19316
+ turnPhase,
19317
+ setTurnPhase,
19318
+ turnStartedAt,
19319
+ currentToolName,
19320
+ setCurrentToolName,
19321
+ lastActivityAt,
19322
+ setLastActivityAt,
19323
+ turnCounterRef,
19324
+ showReasoning,
19325
+ tasks,
19326
+ setTasks,
19327
+ tasksRef,
19328
+ tasksStartedAt,
19329
+ setTasksStartedAt,
19330
+ tasksStartTokens,
19331
+ setTasksStartTokens,
19332
+ beginTurn,
19333
+ endTurn,
19334
+ clearTaskTracking
19335
+ } = turn;
18899
19336
  const {
18900
19337
  pending: perm,
18901
19338
  askPermission: askForPermission,
@@ -18941,38 +19378,28 @@ function App({
18941
19378
  hasFullscreenModal,
18942
19379
  hasAnyModal
18943
19380
  } = modals;
18944
- const [queue, setQueue] = useState16([]);
18945
- const [history, setHistory] = useState16([]);
18946
- const [historyIndex, setHistoryIndex] = useState16(-1);
18947
- const [draftInput, setDraftInput] = useState16("");
18948
- const [mode, setMode] = useState16("edit");
18949
- const [codeMode, setCodeMode] = useState16(false);
19381
+ const [queue, setQueue] = useState18([]);
19382
+ const [history, setHistory] = useState18([]);
19383
+ const [historyIndex, setHistoryIndex] = useState18(-1);
19384
+ const [draftInput, setDraftInput] = useState18("");
19385
+ const [mode, setMode] = useState18("edit");
19386
+ const [codeMode, setCodeMode] = useState18(false);
18950
19387
  const filePickerEnabled = initialCfg?.filePicker ?? true;
18951
- const [effort, setEffort] = useState16(
19388
+ const [effort, setEffort] = useState18(
18952
19389
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
18953
19390
  );
18954
- const [resumeSessions, setResumeSessions] = useState16(null);
18955
- const [checkpointSession, setCheckpointSession] = useState16(null);
18956
- const [checkpointList, setCheckpointList] = useState16([]);
18957
- const [selectedRemoteSession, setSelectedRemoteSession] = useState16(null);
18958
- const [tasks, setTasks] = useState16([]);
18959
- const [tasksStartedAt, setTasksStartedAt] = useState16(null);
18960
- const [tasksStartTokens, setTasksStartTokens] = useState16(0);
18961
- const [turnStartedAt, setTurnStartedAt] = useState16(null);
18962
- const [turnPhase, setTurnPhase] = useState16("waiting");
18963
- const [currentToolName, setCurrentToolName] = useState16(null);
18964
- const [lastActivityAt, setLastActivityAt] = useState16(null);
18965
- const [verbose, setVerbose] = useState16(false);
18966
- const [hasUpdate, setHasUpdate] = useState16(initialUpdateResult?.hasUpdate ?? false);
18967
- const [latestVersion, setLatestVersion] = useState16(initialUpdateResult?.latestVersion ?? null);
18968
- const [theme, setTheme] = useState16(resolveTheme(initialCfg?.theme));
18969
- const [originalTheme, setOriginalTheme] = useState16(null);
18970
- const [skillsActive, setSkillsActive] = useState16(0);
18971
- const [memoryRecalled, setMemoryRecalled] = useState16(false);
18972
- const [intentTier, setIntentTier] = useState16(null);
18973
- const [kimiMdStale, setKimiMdStale] = useState16(false);
18974
- const [gitBranch, setGitBranch] = useState16(null);
18975
- const [lastSessionTopic, setLastSessionTopic] = useState16(null);
19391
+ const [selectedRemoteSession, setSelectedRemoteSession] = useState18(null);
19392
+ const [verbose, setVerbose] = useState18(false);
19393
+ const [hasUpdate, setHasUpdate] = useState18(initialUpdateResult?.hasUpdate ?? false);
19394
+ const [latestVersion, setLatestVersion] = useState18(initialUpdateResult?.latestVersion ?? null);
19395
+ const [theme, setTheme] = useState18(resolveTheme(initialCfg?.theme));
19396
+ const [originalTheme, setOriginalTheme] = useState18(null);
19397
+ const [skillsActive, setSkillsActive] = useState18(0);
19398
+ const [memoryRecalled, setMemoryRecalled] = useState18(false);
19399
+ const [intentTier, setIntentTier] = useState18(null);
19400
+ const [kimiMdStale, setKimiMdStale] = useState18(false);
19401
+ const [gitBranch, setGitBranch] = useState18(null);
19402
+ const [lastSessionTopic, setLastSessionTopic] = useState18(null);
18976
19403
  useEffect8(() => {
18977
19404
  setGitBranch(detectGitBranch());
18978
19405
  }, []);
@@ -19048,57 +19475,80 @@ ${wcagWarnings.join("\n")}` }
19048
19475
  cancelled = true;
19049
19476
  };
19050
19477
  }, [cfg?.cloudMode, initialCloudToken]);
19051
- const [cursorOffset, setCursorOffset] = useState16(0);
19052
- const [customCommandsVersion, setCustomCommandsVersion] = useState16(0);
19053
- const cacheStableRef = useRef5(initialCfg?.cacheStablePrompts !== false);
19054
- const messagesRef = useRef5(
19478
+ const [cursorOffset, setCursorOffset] = useState18(0);
19479
+ const [customCommandsVersion, setCustomCommandsVersion] = useState18(0);
19480
+ const cacheStableRef = useRef7(initialCfg?.cacheStablePrompts !== false);
19481
+ const messagesRef = useRef7(
19055
19482
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
19056
19483
  );
19057
- const executorRef = useRef5(new ToolExecutor(ALL_TOOLS));
19058
- const activeAsstIdRef = useRef5(null);
19059
- const sessionScopeRef = useRef5(new AbortScope());
19060
- const activeScopeRef = useRef5(null);
19061
- const supervisorRef = useRef5(new TurnSupervisor());
19062
- const isAbortingRef = useRef5(false);
19063
- const lastEscapeAtRef = useRef5(0);
19064
- const sigintHandlerRef = useRef5(null);
19065
- const limitResolveRef = useRef5(null);
19066
- const loopResolveRef = useRef5(null);
19067
- const pendingToolCallsRef = useRef5(/* @__PURE__ */ new Map());
19068
- const sessionIdRef = useRef5(null);
19069
- const sessionCreatedAtRef = useRef5(null);
19070
- const sessionTitleRef = useRef5(null);
19071
- const modeRef = useRef5(mode);
19072
- const effortRef = useRef5(effort);
19073
- const tasksRef = useRef5([]);
19074
- const usageRef = useRef5(null);
19075
- const gatewayMetaRef = useRef5(null);
19076
- const lastApiErrorRef = useRef5(null);
19077
- const updateCheckedRef = useRef5(false);
19078
- const sessionStateRef = useRef5(emptySessionState());
19079
- const artifactStoreRef = useRef5(new ArtifactStore());
19080
- const compiledContextRef = useRef5(initialCfg?.compiledContext === true);
19081
- const updateNudgedRef = useRef5(false);
19082
- const compactSuggestedRef = useRef5(false);
19083
- const mcpManagerRef = useRef5(new McpManager());
19084
- const mcpToolsRef = useRef5([]);
19085
- const mcpInitRef = useRef5(false);
19086
- const submitRef = useRef5(() => {
19484
+ const executorRef = useRef7(new ToolExecutor(ALL_TOOLS));
19485
+ const activeAsstIdRef = useRef7(null);
19486
+ const sessionScopeRef = useRef7(new AbortScope());
19487
+ const activeScopeRef = useRef7(null);
19488
+ const sigintHandlerRef = useRef7(null);
19489
+ const limitResolveRef = useRef7(null);
19490
+ const loopResolveRef = useRef7(null);
19491
+ const pendingToolCallsRef = useRef7(/* @__PURE__ */ new Map());
19492
+ const modeRef = useRef7(mode);
19493
+ const effortRef = useRef7(effort);
19494
+ const usageRef = useRef7(null);
19495
+ const gatewayMetaRef = useRef7(null);
19496
+ const lastApiErrorRef = useRef7(null);
19497
+ const updateCheckedRef = useRef7(false);
19498
+ const sessionStateRef = useRef7(emptySessionState());
19499
+ const artifactStoreRef = useRef7(new ArtifactStore());
19500
+ const compiledContextRef = useRef7(initialCfg?.compiledContext === true);
19501
+ const updateNudgedRef = useRef7(false);
19502
+ const compactSuggestedRef = useRef7(false);
19503
+ const mcpManagerRef = useRef7(new McpManager());
19504
+ const mcpToolsRef = useRef7([]);
19505
+ const mcpInitRef = useRef7(false);
19506
+ const submitRef = useRef7(() => {
19087
19507
  });
19088
- const lspManagerRef = useRef5(new LspManager());
19089
- const lspToolsRef = useRef5([]);
19090
- const lspInitRef = useRef5(false);
19091
- const busyRef = useRef5(busy);
19092
- const memoryManagerRef = useRef5(null);
19093
- const sessionStartRecallRef = useRef5(null);
19094
- const kimiMdStaleNudgedRef = useRef5(false);
19095
- const turnCounterRef = useRef5(0);
19096
- const pendingTextRef = useRef5(/* @__PURE__ */ new Map());
19097
- const flushTimeoutRef = useRef5(null);
19098
- const customCommandsRef = useRef5([]);
19099
- const recentFilesRef = useRef5(/* @__PURE__ */ new Map());
19508
+ const lspManagerRef = useRef7(new LspManager());
19509
+ const lspToolsRef = useRef7([]);
19510
+ const lspInitRef = useRef7(false);
19511
+ const memoryManagerRef = useRef7(null);
19512
+ const sessionStartRecallRef = useRef7(null);
19513
+ const kimiMdStaleNudgedRef = useRef7(false);
19514
+ const sessionMgr = useSessionManager({
19515
+ cfg,
19516
+ messagesRef,
19517
+ sessionStateRef,
19518
+ artifactStoreRef,
19519
+ compiledContextRef,
19520
+ gatewayMetaRef,
19521
+ memoryManagerRef,
19522
+ setEvents,
19523
+ setHistory,
19524
+ setUsage,
19525
+ setSessionUsage,
19526
+ setGatewayMeta,
19527
+ mkKey
19528
+ });
19529
+ const {
19530
+ sessionIdRef,
19531
+ sessionCreatedAtRef,
19532
+ sessionTitleRef,
19533
+ resumeSessions,
19534
+ setResumeSessions,
19535
+ checkpointSession,
19536
+ setCheckpointSession,
19537
+ checkpointList,
19538
+ ensureSessionId,
19539
+ saveSessionSafe,
19540
+ openResumePicker,
19541
+ doResumeSession,
19542
+ handleResumePick,
19543
+ handleCheckpointPick,
19544
+ resetSession
19545
+ } = sessionMgr;
19546
+ const pendingTextRef = useRef7(/* @__PURE__ */ new Map());
19547
+ const flushTimeoutRef = useRef7(null);
19548
+ const customCommandsRef = useRef7([]);
19549
+ const recentFilesRef = useRef7(/* @__PURE__ */ new Map());
19100
19550
  const MAX_RECENT_FILES = 10;
19101
- const allSlashCommands = React15.useMemo(() => {
19551
+ const allSlashCommands = React17.useMemo(() => {
19102
19552
  const customs = customCommandsRef.current.filter((c) => !BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase())).map((c) => ({
19103
19553
  name: c.name,
19104
19554
  description: c.description ?? "",
@@ -19107,7 +19557,7 @@ ${wcagWarnings.join("\n")}` }
19107
19557
  return [...BUILTIN_COMMANDS, ...customs];
19108
19558
  }, [customCommandsVersion]);
19109
19559
  const modalActive = commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || checkpointSession !== null || perm !== null || limitModal !== null || loopModal !== null || showInboxModal;
19110
- const loadFilePickerItems = useCallback6(async () => {
19560
+ const loadFilePickerItems = useCallback8(async () => {
19111
19561
  const cwd = process.cwd();
19112
19562
  const entries = await fg4("**/*", {
19113
19563
  cwd,
@@ -19264,7 +19714,7 @@ ${wcagWarnings.join("\n")}` }
19264
19714
  }, 3e5);
19265
19715
  return () => clearInterval(id);
19266
19716
  }, []);
19267
- const reloadCustomCommands = useCallback6(async () => {
19717
+ const reloadCustomCommands = useCallback8(async () => {
19268
19718
  const { commands, warnings } = await loadCustomCommands(process.cwd());
19269
19719
  customCommandsRef.current = commands;
19270
19720
  setCustomCommandsVersion((v) => v + 1);
@@ -19391,7 +19841,7 @@ ${wcagWarnings.join("\n")}` }
19391
19841
  }, 30 * 60 * 1e3);
19392
19842
  return () => clearInterval(id);
19393
19843
  }, [cfg]);
19394
- const initMcp = useCallback6(async () => {
19844
+ const initMcp = useCallback8(async () => {
19395
19845
  if (!cfg?.mcpServers || mcpInitRef.current) return;
19396
19846
  mcpInitRef.current = true;
19397
19847
  const manager = mcpManagerRef.current;
@@ -19456,7 +19906,7 @@ ${wcagWarnings.join("\n")}` }
19456
19906
  ]);
19457
19907
  }
19458
19908
  }, [cfg]);
19459
- const initLsp = useCallback6(async () => {
19909
+ const initLsp = useCallback8(async () => {
19460
19910
  if (!cfg?.lspEnabled || !cfg?.lspServers || lspInitRef.current) {
19461
19911
  if (lspInitRef.current) return;
19462
19912
  if (!cfg?.lspEnabled) {
@@ -19527,46 +19977,7 @@ ${wcagWarnings.join("\n")}` }
19527
19977
  void initLsp();
19528
19978
  }
19529
19979
  }, [cfg, initMcp, initLsp]);
19530
- const ensureSessionId = useCallback6(() => {
19531
- if (sessionIdRef.current) return sessionIdRef.current;
19532
- const firstUser = messagesRef.current.find((m) => m.role === "user");
19533
- let firstText = "session";
19534
- if (typeof firstUser?.content === "string") {
19535
- firstText = firstUser.content;
19536
- } else if (Array.isArray(firstUser?.content)) {
19537
- const textPart = firstUser.content.find((p) => p.type === "text");
19538
- if (textPart?.text) firstText = textPart.text;
19539
- }
19540
- sessionIdRef.current = makeSessionId(firstText);
19541
- return sessionIdRef.current;
19542
- }, []);
19543
- const saveSessionSafe = useCallback6(async () => {
19544
- if (!cfg) return;
19545
- ensureSessionId();
19546
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
19547
- if (!sessionCreatedAtRef.current) {
19548
- sessionCreatedAtRef.current = now2;
19549
- }
19550
- try {
19551
- await saveSession({
19552
- id: sessionIdRef.current,
19553
- cwd: process.cwd(),
19554
- model: cfg.model,
19555
- createdAt: sessionCreatedAtRef.current,
19556
- updatedAt: now2,
19557
- title: sessionTitleRef.current ?? void 0,
19558
- messages: messagesRef.current,
19559
- sessionState: compiledContextRef.current ? sessionStateRef.current : void 0,
19560
- artifactStore: serializeArtifactStore(artifactStoreRef.current)
19561
- });
19562
- } catch (e) {
19563
- setEvents((es) => [
19564
- ...es,
19565
- { kind: "error", key: mkKey(), text: `session save failed: ${e.message}` }
19566
- ]);
19567
- }
19568
- }, [cfg, ensureSessionId]);
19569
- const onIterationEnd = useCallback6(
19980
+ const onIterationEnd = useCallback8(
19570
19981
  async (messages, signal) => {
19571
19982
  if (signal.aborted) return messages;
19572
19983
  if (!shouldCompact({ messages })) return messages;
@@ -19644,6 +20055,7 @@ ${wcagWarnings.join("\n")}` }
19644
20055
  },
19645
20056
  [cfg]
19646
20057
  );
20058
+ const interruptDepsRef = useRef7(null);
19647
20059
  useInput10((inputChar, key) => {
19648
20060
  if (key.ctrl && inputChar === "c") {
19649
20061
  logger.info("input:ctrl+c", {
@@ -19653,36 +20065,9 @@ ${wcagWarnings.join("\n")}` }
19653
20065
  hasPerm: hasPendingPermission(),
19654
20066
  hasLimit: limitResolveRef.current !== null
19655
20067
  });
19656
- const hadPerm = denyPendingPermission();
19657
- const hadLimit = limitResolveRef.current !== null;
19658
- const hadLoop = loopResolveRef.current !== null;
19659
- if (hadLimit) {
19660
- limitResolveRef.current("stop");
19661
- limitResolveRef.current = null;
19662
- setLimitModal(null);
19663
- }
19664
- if (hadLoop) {
19665
- loopResolveRef.current("stop");
19666
- loopResolveRef.current = null;
19667
- setLoopModal(null);
19668
- }
19669
- if (busyRef.current && activeScopeRef.current && !isAbortingRef.current) {
19670
- isAbortingRef.current = true;
19671
- supervisorRef.current.killTurn();
19672
- activeScopeRef.current.abort("user_stopped");
19673
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
19674
- for (const [toolId] of pendingToolCallsRef.current) {
19675
- updateTool(toolId, { status: "cancelled" });
19676
- }
19677
- pendingToolCallsRef.current.clear();
19678
- void saveSessionSafe();
19679
- setTasks([]);
19680
- setTasksStartedAt(null);
19681
- setTasksStartTokens(0);
19682
- tasksRef.current = [];
19683
- } else if (!hadPerm && !hadLimit && !hadLoop) {
20068
+ const outcome = interruptOrExit(interruptDepsRef.current);
20069
+ if (!outcome.didInterruptTurn && !outcome.hadPermission && !outcome.hadLimit && !outcome.hadLoop) {
19684
20070
  logger.info("input:ctrl+c:exiting");
19685
- void lspManagerRef.current.stopAll().finally(() => exit());
19686
20071
  }
19687
20072
  return;
19688
20073
  }
@@ -19691,34 +20076,12 @@ ${wcagWarnings.join("\n")}` }
19691
20076
  const modalOpen = perm !== null || limitModal !== null || loopModal !== null || showLspWizard || showCommandList || commandWizard !== null || commandToDelete !== null || resumeSessions !== null || checkpointSession !== null || showThemePicker;
19692
20077
  if (!modalOpen && busyRef.current && activeScopeRef.current && !isAbortingRef.current && now2 - lastEscapeAtRef.current > 500) {
19693
20078
  lastEscapeAtRef.current = now2;
19694
- isAbortingRef.current = true;
19695
- supervisorRef.current.killTurn();
19696
- denyPendingPermission();
19697
- if (limitResolveRef.current) {
19698
- limitResolveRef.current("stop");
19699
- limitResolveRef.current = null;
19700
- setLimitModal(null);
19701
- }
19702
- if (loopResolveRef.current) {
19703
- loopResolveRef.current("stop");
19704
- loopResolveRef.current = null;
19705
- setLoopModal(null);
19706
- }
19707
- activeScopeRef.current.abort("user_stopped");
19708
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
19709
- for (const [toolId] of pendingToolCallsRef.current) {
19710
- updateTool(toolId, { status: "cancelled" });
19711
- }
19712
- pendingToolCallsRef.current.clear();
19713
- setTasks([]);
19714
- setTasksStartedAt(null);
19715
- setTasksStartTokens(0);
19716
- tasksRef.current = [];
20079
+ interruptTurn(interruptDepsRef.current);
19717
20080
  return;
19718
20081
  }
19719
20082
  }
19720
20083
  if (key.ctrl && inputChar === "r") {
19721
- setShowReasoning((s) => !s);
20084
+ turn.toggleReasoning();
19722
20085
  return;
19723
20086
  }
19724
20087
  if (key.shift && key.tab) {
@@ -19739,35 +20102,15 @@ ${wcagWarnings.join("\n")}` }
19739
20102
  hasLimit: limitResolveRef.current !== null,
19740
20103
  hasLoop: loopResolveRef.current !== null
19741
20104
  });
19742
- const hadPerm = denyPendingPermission();
19743
- const hadLimit = limitResolveRef.current !== null;
19744
- const hadLoop = loopResolveRef.current !== null;
19745
- if (hadLimit) {
19746
- limitResolveRef.current("stop");
19747
- limitResolveRef.current = null;
19748
- setLimitModal(null);
19749
- }
19750
- if (hadLoop) {
19751
- loopResolveRef.current("stop");
19752
- loopResolveRef.current = null;
19753
- setLoopModal(null);
19754
- }
19755
- if (busyRef.current && activeScopeRef.current && !isAbortingRef.current) {
19756
- isAbortingRef.current = true;
19757
- supervisorRef.current.killTurn();
19758
- activeScopeRef.current.abort("user_stopped");
19759
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
19760
- void saveSessionSafe();
19761
- setTasks([]);
19762
- setTasksStartedAt(null);
19763
- setTasksStartTokens(0);
19764
- tasksRef.current = [];
19765
- } else if (!hadPerm && !hadLimit) {
20105
+ const outcome = interruptOrExit({
20106
+ ...interruptDepsRef.current,
20107
+ skipPendingToolCleanup: true
20108
+ });
20109
+ if (!outcome.didInterruptTurn && !outcome.hadPermission && !outcome.hadLimit && !outcome.hadLoop) {
19766
20110
  logger.info("sigint:handler:exiting");
19767
- void lspManagerRef.current.stopAll().finally(() => exit());
19768
20111
  }
19769
20112
  };
19770
- const flushAssistantUpdates = useCallback6(() => {
20113
+ const flushAssistantUpdates = useCallback8(() => {
19771
20114
  flushTimeoutRef.current = null;
19772
20115
  const pending = pendingTextRef.current;
19773
20116
  if (pending.size === 0) return;
@@ -19785,7 +20128,7 @@ ${wcagWarnings.join("\n")}` }
19785
20128
  })
19786
20129
  );
19787
20130
  }, []);
19788
- const updateAssistant = useCallback6(
20131
+ const updateAssistant = useCallback8(
19789
20132
  (id, patch) => {
19790
20133
  const result = patch({ text: "", reasoning: "" });
19791
20134
  const assistantResult = result;
@@ -19814,7 +20157,7 @@ ${wcagWarnings.join("\n")}` }
19814
20157
  },
19815
20158
  [flushAssistantUpdates]
19816
20159
  );
19817
- const updateTool = useCallback6(
20160
+ const updateTool = useCallback8(
19818
20161
  (id, patch) => {
19819
20162
  setEvents(
19820
20163
  (evts) => evts.map(
@@ -19824,19 +20167,37 @@ ${wcagWarnings.join("\n")}` }
19824
20167
  },
19825
20168
  []
19826
20169
  );
19827
- const updateGatewayMeta = useCallback6((meta) => {
20170
+ const updateGatewayMeta = useCallback8((meta) => {
19828
20171
  gatewayMetaRef.current = meta;
19829
20172
  setGatewayMeta(meta);
19830
20173
  }, []);
19831
- const runCompact = useCallback6(async () => {
20174
+ interruptDepsRef.current = {
20175
+ busyRef,
20176
+ activeScopeRef,
20177
+ isAbortingRef,
20178
+ supervisorRef,
20179
+ limitResolveRef,
20180
+ loopResolveRef,
20181
+ setLimitModal,
20182
+ setLoopModal,
20183
+ hasPendingPermission,
20184
+ denyPendingPermission,
20185
+ pendingToolCallsRef,
20186
+ updateTool,
20187
+ setEvents,
20188
+ mkKey,
20189
+ saveSessionSafe,
20190
+ clearTaskTracking,
20191
+ lspManagerRef,
20192
+ exit
20193
+ };
20194
+ const runCompact = useCallback8(async () => {
19832
20195
  if (!cfg) return;
19833
20196
  if (busy) {
19834
20197
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't compact while model is running" }]);
19835
20198
  return;
19836
20199
  }
19837
- setBusy(true);
19838
- busyRef.current = true;
19839
- setTurnStartedAt(Date.now());
20200
+ beginTurn();
19840
20201
  const turnScope = sessionScopeRef.current.createChild();
19841
20202
  activeScopeRef.current = turnScope;
19842
20203
  try {
@@ -19911,24 +20272,14 @@ ${wcagWarnings.join("\n")}` }
19911
20272
  }
19912
20273
  } finally {
19913
20274
  logger.info("runCompact:finally");
19914
- setBusy(false);
19915
- busyRef.current = false;
19916
- setTurnStartedAt(null);
19917
- setTurnPhase("waiting");
19918
- setCurrentToolName(null);
19919
- setLastActivityAt(null);
20275
+ endTurn();
19920
20276
  activeScopeRef.current = null;
19921
- isAbortingRef.current = false;
19922
20277
  clearPermissionResolveRef();
19923
20278
  limitResolveRef.current = null;
19924
20279
  pendingToolCallsRef.current.clear();
19925
20280
  }
19926
20281
  }, [cfg, busy, saveSessionSafe]);
19927
- const openResumePicker = useCallback6(async () => {
19928
- const sessions = await listSessions(200, process.cwd());
19929
- setResumeSessions(sessions);
19930
- }, []);
19931
- const runInit = useCallback6(async () => {
20282
+ const runInit = useCallback8(async () => {
19932
20283
  if (!cfg) return;
19933
20284
  if (busy) {
19934
20285
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't /init while model is running" }]);
@@ -19938,9 +20289,7 @@ ${wcagWarnings.join("\n")}` }
19938
20289
  const { prompt, targetFilename, isRefresh } = buildInitPrompt(cwd);
19939
20290
  setEvents((e) => [...e, { kind: "user", key: mkKey(), text: isRefresh ? `/init (refreshing ${targetFilename})` : "/init" }]);
19940
20291
  messagesRef.current.push({ role: "user", content: sanitizeString(prompt) });
19941
- setBusy(true);
19942
- busyRef.current = true;
19943
- setTurnStartedAt(Date.now());
20292
+ beginTurn();
19944
20293
  const turnScope = sessionScopeRef.current.createChild();
19945
20294
  activeScopeRef.current = turnScope;
19946
20295
  const initClassification = classifyIntent(prompt);
@@ -20207,15 +20556,9 @@ ${wcagWarnings.join("\n")}` }
20207
20556
  setCodeMode(false);
20208
20557
  const asstId = activeAsstIdRef.current;
20209
20558
  if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
20210
- setBusy(false);
20211
- busyRef.current = false;
20212
- setTurnStartedAt(null);
20213
- setTurnPhase("waiting");
20214
- setCurrentToolName(null);
20215
- setLastActivityAt(null);
20559
+ endTurn();
20216
20560
  activeAsstIdRef.current = null;
20217
20561
  activeScopeRef.current = null;
20218
- isAbortingRef.current = false;
20219
20562
  clearPermissionResolveRef();
20220
20563
  limitResolveRef.current = null;
20221
20564
  loopResolveRef.current = null;
@@ -20223,7 +20566,7 @@ ${wcagWarnings.join("\n")}` }
20223
20566
  pendingToolCallsRef.current.clear();
20224
20567
  }
20225
20568
  }, [cfg, busy, updateAssistant, updateTool, updateGatewayMeta]);
20226
- const handleThemePick = useCallback6(
20569
+ const handleThemePick = useCallback8(
20227
20570
  (picked) => {
20228
20571
  setShowThemePicker(false);
20229
20572
  if (!picked) return;
@@ -20241,100 +20584,7 @@ ${wcagWarnings.join("\n")}` }
20241
20584
  },
20242
20585
  []
20243
20586
  );
20244
- const doResumeSession = useCallback6(
20245
- async (filePath, checkpointId) => {
20246
- try {
20247
- const file = checkpointId ? (await loadSessionFromCheckpoint(filePath, checkpointId)).file : await loadSession(filePath);
20248
- messagesRef.current = file.messages;
20249
- sessionIdRef.current = file.id;
20250
- sessionCreatedAtRef.current = file.createdAt;
20251
- if (file.sessionState && compiledContextRef.current) {
20252
- sessionStateRef.current = file.sessionState;
20253
- }
20254
- if (file.artifactStore) {
20255
- artifactStoreRef.current = deserializeArtifactStore(file.artifactStore);
20256
- } else {
20257
- artifactStoreRef.current = new ArtifactStore();
20258
- }
20259
- const manager = memoryManagerRef.current;
20260
- if (manager) {
20261
- try {
20262
- const cwd = process.cwd();
20263
- const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
20264
- if (results.length > 0) {
20265
- const text = await manager.synthesizeRecalled(results);
20266
- const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
20267
- const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
20268
- messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
20269
- }
20270
- } catch {
20271
- }
20272
- }
20273
- const msg = checkpointId ? `resumed session ${file.id} from checkpoint` : `resumed session ${file.id} (${file.messages.filter((m) => m.role !== "system").length} msgs)`;
20274
- setEvents([{ kind: "info", key: mkKey(), text: msg }]);
20275
- const userMsgs = file.messages.filter((m) => m.role === "user" && m.content).map((m) => {
20276
- if (!m.content) return "";
20277
- if (typeof m.content === "string") return m.content;
20278
- const textPart = m.content.find((p) => p.type === "text");
20279
- return textPart?.text ?? "";
20280
- }).filter((text) => text.length > 0);
20281
- if (userMsgs.length > 0) setHistory(userMsgs);
20282
- setUsage(null);
20283
- setSessionUsage(null);
20284
- gatewayMetaRef.current = null;
20285
- setGatewayMeta(null);
20286
- void getCostReport(file.id).then((report) => setSessionUsage(report.session));
20287
- } catch (e) {
20288
- setEvents((es) => [
20289
- ...es,
20290
- { kind: "error", key: mkKey(), text: `failed to load session: ${e.message}` }
20291
- ]);
20292
- }
20293
- },
20294
- []
20295
- );
20296
- const handleResumePick = useCallback6(
20297
- async (picked) => {
20298
- setResumeSessions(null);
20299
- if (!picked) return;
20300
- if (picked.checkpointCount > 0) {
20301
- try {
20302
- const file = await loadSession(picked.filePath);
20303
- setCheckpointList(file.checkpoints ?? []);
20304
- setCheckpointSession(picked);
20305
- } catch (e) {
20306
- setEvents((es) => [
20307
- ...es,
20308
- { kind: "error", key: mkKey(), text: `failed to load checkpoints: ${e.message}` }
20309
- ]);
20310
- await doResumeSession(picked.filePath);
20311
- }
20312
- return;
20313
- }
20314
- await doResumeSession(picked.filePath);
20315
- },
20316
- [doResumeSession]
20317
- );
20318
- const handleCheckpointPick = useCallback6(
20319
- async (checkpointId) => {
20320
- const session = checkpointSession;
20321
- setCheckpointSession(null);
20322
- setCheckpointList([]);
20323
- if (!session || !checkpointId) {
20324
- if (session) {
20325
- setResumeSessions(await listSessions(200, process.cwd()));
20326
- }
20327
- return;
20328
- }
20329
- if (checkpointId === "__start__") {
20330
- await doResumeSession(session.filePath);
20331
- return;
20332
- }
20333
- await doResumeSession(session.filePath, checkpointId);
20334
- },
20335
- [checkpointSession, doResumeSession]
20336
- );
20337
- const handleSlash = useCallback6(
20587
+ const handleSlash = useCallback8(
20338
20588
  (cmd) => {
20339
20589
  const raw = cmd.trim();
20340
20590
  const [head, ...rest] = raw.split(/\s+/);
@@ -20354,11 +20604,7 @@ ${wcagWarnings.join("\n")}` }
20354
20604
  } else {
20355
20605
  messagesRef.current = [messagesRef.current[0]];
20356
20606
  }
20357
- sessionIdRef.current = null;
20358
- sessionCreatedAtRef.current = null;
20359
- sessionTitleRef.current = null;
20360
- sessionStateRef.current = emptySessionState();
20361
- artifactStoreRef.current = new ArtifactStore();
20607
+ resetSession();
20362
20608
  executorRef.current.clearArtifacts();
20363
20609
  if (flushTimeoutRef.current) {
20364
20610
  clearTimeout(flushTimeoutRef.current);
@@ -20374,15 +20620,13 @@ ${wcagWarnings.join("\n")}` }
20374
20620
  setSessionUsage(null);
20375
20621
  gatewayMetaRef.current = null;
20376
20622
  setGatewayMeta(null);
20377
- setTasks([]);
20378
- setTasksStartedAt(null);
20379
- setTasksStartTokens(0);
20623
+ clearTaskTracking();
20380
20624
  compactSuggestedRef.current = false;
20381
20625
  updateNudgedRef.current = false;
20382
20626
  return true;
20383
20627
  }
20384
20628
  if (c === "/reasoning") {
20385
- setShowReasoning((s) => {
20629
+ turn.setShowReasoning((s) => {
20386
20630
  const next = !s;
20387
20631
  setEvents((e) => [
20388
20632
  ...e,
@@ -21341,7 +21585,7 @@ ${lines.join("\n")}` }]);
21341
21585
  },
21342
21586
  [cfg, exit, usage, theme, mode, openResumePicker, runCompact, runInit, initMcp, setCfg, setShowRemoteDashboard, setSelectedRemoteSession]
21343
21587
  );
21344
- const handleCommandSave = useCallback6(
21588
+ const handleCommandSave = useCallback8(
21345
21589
  async (opts2) => {
21346
21590
  setCommandWizard(null);
21347
21591
  try {
@@ -21363,7 +21607,7 @@ ${lines.join("\n")}` }]);
21363
21607
  },
21364
21608
  [commandWizard, reloadCustomCommands, setEvents]
21365
21609
  );
21366
- const handleCommandDelete = useCallback6(
21610
+ const handleCommandDelete = useCallback8(
21367
21611
  async (cmd) => {
21368
21612
  setCommandToDelete(null);
21369
21613
  try {
@@ -21382,7 +21626,7 @@ ${lines.join("\n")}` }]);
21382
21626
  },
21383
21627
  [reloadCustomCommands, setEvents, setCommandToDelete]
21384
21628
  );
21385
- const handleLspSave = useCallback6(
21629
+ const handleLspSave = useCallback8(
21386
21630
  (servers, enabled, scope) => {
21387
21631
  setCfg((c) => c ? { ...c, lspEnabled: enabled, lspServers: servers } : c);
21388
21632
  setLspScope(scope);
@@ -21411,7 +21655,7 @@ ${lines.join("\n")}` }]);
21411
21655
  },
21412
21656
  [cfg, setCfg, setEvents, setShowLspWizard]
21413
21657
  );
21414
- const handleRemoteCancel = useCallback6(
21658
+ const handleRemoteCancel = useCallback8(
21415
21659
  async (session) => {
21416
21660
  try {
21417
21661
  const { cancelRemoteSession: cancelRemoteSession2 } = await Promise.resolve().then(() => (init_worker_client(), worker_client_exports));
@@ -21431,7 +21675,7 @@ ${lines.join("\n")}` }]);
21431
21675
  },
21432
21676
  [setEvents, setShowRemoteDashboard]
21433
21677
  );
21434
- const processMessage = useCallback6(
21678
+ const processMessage = useCallback8(
21435
21679
  async (text, displayText, opts2) => {
21436
21680
  if (!cfg) return;
21437
21681
  let trimmed = text.trim();
@@ -21534,11 +21778,9 @@ ${lines.join("\n")}` }]);
21534
21778
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps KimiFlare stay accurate as your project evolves." }
21535
21779
  ]);
21536
21780
  }
21537
- setBusy(true);
21538
- busyRef.current = true;
21781
+ beginTurn();
21539
21782
  gatewayMetaRef.current = null;
21540
21783
  setGatewayMeta(null);
21541
- setTurnStartedAt(Date.now());
21542
21784
  const classification = classifyIntent(trimmed);
21543
21785
  setIntentTier(classification.tier);
21544
21786
  if (!sessionTitleRef.current) {
@@ -21716,24 +21958,15 @@ ${lines.join("\n")}` }]);
21716
21958
  setCodeMode(false);
21717
21959
  const asstId = activeAsstIdRef.current;
21718
21960
  if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
21719
- setBusy(false);
21720
- busyRef.current = false;
21721
- setTurnStartedAt(null);
21722
- setTurnPhase("waiting");
21723
- setCurrentToolName(null);
21724
- setLastActivityAt(null);
21961
+ endTurn();
21725
21962
  activeAsstIdRef.current = null;
21726
21963
  activeScopeRef.current = null;
21727
- isAbortingRef.current = false;
21728
21964
  clearPermissionResolveRef();
21729
21965
  limitResolveRef.current = null;
21730
21966
  loopResolveRef.current = null;
21731
21967
  setLoopModal(null);
21732
21968
  pendingToolCallsRef.current.clear();
21733
- setTasks([]);
21734
- setTasksStartedAt(null);
21735
- setTasksStartTokens(0);
21736
- tasksRef.current = [];
21969
+ clearTaskTracking();
21737
21970
  setEvents(
21738
21971
  (evts) => evts.map((e) => e.kind === "tool" && e.status === "running" ? { ...e, status: "error", result: "(stopped)" } : e)
21739
21972
  );
@@ -21955,7 +22188,7 @@ ${lines.join("\n")}` }]);
21955
22188
  processMessage(next.full, next.display, { queuedKey: next.key });
21956
22189
  }
21957
22190
  }, [busy, queue, processMessage]);
21958
- const submit = useCallback6(
22191
+ const submit = useCallback8(
21959
22192
  (full, display) => {
21960
22193
  const trimmedFull = full.trim();
21961
22194
  if (!trimmedFull) return;
@@ -22210,7 +22443,6 @@ var init_app = __esm({
22210
22443
  "src/app.tsx"() {
22211
22444
  "use strict";
22212
22445
  init_loop();
22213
- init_supervisor();
22214
22446
  init_system_prompt();
22215
22447
  init_llm_summarize();
22216
22448
  init_artifact_compaction();
@@ -22268,6 +22500,9 @@ var init_app = __esm({
22268
22500
  init_use_picker_controller();
22269
22501
  init_use_modal_host();
22270
22502
  init_modal_host();
22503
+ init_use_session_manager();
22504
+ init_use_turn_controller();
22505
+ init_input_handlers();
22271
22506
  MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
22272
22507
  FEEDBACK_WORKER_URL2 = "https://hello.kimiflare.com";
22273
22508
  CONTEXT_LIMIT = 262e3;