@kernelonpanic/kitcode 1.4.0 → 1.4.1

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
@@ -75,6 +75,8 @@ var configSchema = z.object({
75
75
  theme: themeSchema.default({ accent: "#a78bfa" }),
76
76
  effort: effortSchema.default("xhigh"),
77
77
  thinking: z.boolean().default(true),
78
+ // Terminal bell when a long answer finishes or a permission prompt appears.
79
+ bell: z.boolean().default(true),
78
80
  // Manual override for the model's context window. Unset means the window
79
81
  // comes from the provider catalogue or the endpoint's model list.
80
82
  contextWindow: z.number().int().min(1e3).max(1e7).optional(),
@@ -1399,15 +1401,39 @@ async function clearProjectMemory(workspace) {
1399
1401
  }
1400
1402
 
1401
1403
  // src/providers/effort.ts
1404
+ var HARD_TASK = new RegExp("(?<!\\p{L})(?:bug|fix|debug|test|refactor|implement|\u0431\u0430\u0433|\u0444\u0438\u043A\u0441|\u0438\u0441\u043F\u0440\u0430\u0432|\u0442\u0435\u0441\u0442|\u0440\u0435\u0444\u0430\u043A\u0442\u043E\u0440|\u0440\u0435\u0430\u043B\u0438\u0437)", "iu");
1402
1405
  function resolveEffort(effort, messages) {
1403
1406
  if (effort !== "auto") return effort;
1404
1407
  const latest = messages.findLast((message) => message.role === "user" && message.content.some((block) => block.type === "text"));
1405
1408
  const text = latest?.content.filter((block) => block.type === "text").map((block) => block.text).join("\n") ?? "";
1406
- return text.length > 500 || /bug|fix|debug|test|refactor|implement|баг|фикс|исправ|тест|рефактор|реализ/i.test(text) ? "high" : "medium";
1409
+ return text.length > 500 || HARD_TASK.test(text) ? "high" : "medium";
1410
+ }
1411
+ var rejected = /* @__PURE__ */ new Set();
1412
+ function markEffortRejected(providerId, model) {
1413
+ rejected.add(`${providerId}/${model}`);
1414
+ }
1415
+ function effectiveEffort(kind, model, effort, messages, providerId) {
1416
+ if (providerId !== void 0 && rejected.has(`${providerId}/${model}`)) return void 0;
1417
+ const requested = resolveEffort(effort, messages);
1418
+ return kind === "openai" ? openAiEffort(model, requested) : anthropicEffort(model, requested);
1419
+ }
1420
+ function anthropicEffort(model, effort) {
1421
+ if (!effort) return void 0;
1422
+ const id = (model.split("/").at(-1) ?? model).replace(/^(?:[a-z]{2}\.)?anthropic\./, "");
1423
+ if (!id.startsWith("claude-")) return void 0;
1424
+ if (/^claude-(?:3|[a-z]+-3|haiku-4|sonnet-4(?:-[0-5])?(?:-\d{8}|@|$)|opus-4(?:-[01])?(?:-\d{8}|@|$))/.test(id)) return void 0;
1425
+ if (/^claude-opus-4-5(?:-|@|$)/.test(id)) return effort === "xhigh" || effort === "max" ? "high" : effort;
1426
+ if (/^claude-(?:opus|sonnet)-4-6(?:-|@|$)/.test(id)) return effort === "xhigh" ? "high" : effort;
1427
+ return effort;
1407
1428
  }
1408
1429
  function openAiEffort(model, effort) {
1409
- const id = model.split("/").at(-1) ?? model;
1410
- if (!/^(?:o[134](?:-|$)|gpt-5(?:[.-]|$))/.test(id)) return void 0;
1430
+ const id = (model.split("/").at(-1) ?? model).toLowerCase();
1431
+ if (/^(?:gpt-oss|gemini-(?:2\.5|[3-9])|grok-3-mini)/.test(id)) {
1432
+ return effort === "max" || effort === "xhigh" ? "high" : effort;
1433
+ }
1434
+ if (!/^(?:o[134](?:-|$)|gpt-5(?:[.-]|$))/.test(id)) {
1435
+ return effort === "max" || effort === "xhigh" ? "high" : effort;
1436
+ }
1411
1437
  if (/^(?:o[134](?:-|$)|gpt-5(?:-(?:mini|nano|\d{4})|$)|gpt-5\.1(?:-|$))/.test(id)) {
1412
1438
  return effort === "max" || effort === "xhigh" ? "high" : effort;
1413
1439
  }
@@ -1490,9 +1516,10 @@ async function loadSession(id) {
1490
1516
  async function listSessions(limit = 20) {
1491
1517
  const files = await filesByRecency();
1492
1518
  const summaries = [];
1493
- for (const file of files.slice(0, limit)) {
1519
+ for (const file of files) {
1520
+ if (summaries.length >= limit) break;
1494
1521
  const state = readState(await readFile4(file, "utf8").catch(() => ""));
1495
- if (state) {
1522
+ if (state && state.messages.length > 0) {
1496
1523
  summaries.push({
1497
1524
  id: state.id,
1498
1525
  title: state.title,
@@ -1849,6 +1876,17 @@ async function runTurn(cfg, history, hooks, signal) {
1849
1876
  hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
1850
1877
  return messages;
1851
1878
  }
1879
+ if (steps > 1) {
1880
+ const trimmed = cfg.trimContext?.(messages);
1881
+ if (trimmed) {
1882
+ messages = trimmed;
1883
+ hooks.onEvent({
1884
+ type: "notice",
1885
+ level: "info",
1886
+ text: "Context is nearly full: cleared older tool output to keep going."
1887
+ });
1888
+ }
1889
+ }
1852
1890
  const request = async () => {
1853
1891
  if (signal.aborted) return { content: [], stopReason: "aborted" };
1854
1892
  const budgetDecision = cfg.budget?.beforeRequest({
@@ -1923,13 +1961,19 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1923
1961
  let outcome;
1924
1962
  let text = "";
1925
1963
  let thinking = "";
1964
+ let outputTokens = 0;
1965
+ const startedAt = Date.now();
1966
+ let firstTokenAt;
1926
1967
  for await (const event of streamWithRetry(cfg.provider, request, cfg.modelRef, signal)) {
1927
1968
  switch (event.type) {
1928
1969
  case "text_delta":
1970
+ firstTokenAt ??= Date.now();
1929
1971
  text += event.text;
1930
1972
  hooks.onEvent({ type: "text_delta", text: event.text });
1931
1973
  break;
1932
1974
  case "thinking_delta":
1975
+ firstTokenAt ??= Date.now();
1976
+ if (!cfg.thinking) break;
1933
1977
  thinking += event.text;
1934
1978
  hooks.onEvent({ type: "thinking_delta", text: event.text });
1935
1979
  break;
@@ -1937,6 +1981,7 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1937
1981
  cfg.budget?.record(cfg.modelRef, event.usage);
1938
1982
  cfg.usage.record(cfg.modelRef, event.usage);
1939
1983
  hooks.onEvent({ type: "usage", model: cfg.modelRef, usage: event.usage });
1984
+ outputTokens = event.usage.output;
1940
1985
  break;
1941
1986
  case "rate_limits":
1942
1987
  hooks.onEvent({ type: "rate_limits", model: cfg.modelRef, limits: event.limits });
@@ -1948,7 +1993,13 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1948
1993
  break;
1949
1994
  }
1950
1995
  }
1951
- if (outcome) return outcome;
1996
+ if (outcome) {
1997
+ const ms = Date.now() - (firstTokenAt ?? startedAt);
1998
+ if (outcome.stopReason !== "aborted" && outputTokens > 0 && ms > 0) {
1999
+ hooks.onEvent({ type: "speed", model: cfg.modelRef, outputTokens, ms });
2000
+ }
2001
+ return outcome;
2002
+ }
1952
2003
  if (signal.aborted) {
1953
2004
  const content = [];
1954
2005
  if (thinking) content.push({ type: "thinking", text: thinking });
@@ -2069,23 +2120,21 @@ async function runToolCalls(cfg, content, hooks, signal) {
2069
2120
  const operation = () => runOneCall(cfg, call, hooks, signal);
2070
2121
  return call.name !== "task" && cfg.executionLane ? cfg.executionLane.run(operation) : operation();
2071
2122
  };
2072
- for (let index = 0; index < calls.length; ) {
2073
- const call = calls[index];
2074
- if (call.name !== "task") {
2075
- results.push(await run(call));
2076
- index += 1;
2077
- continue;
2078
- }
2079
- const batch = [];
2080
- while (calls[index]?.name === "task") {
2081
- batch.push(run(calls[index]));
2082
- index += 1;
2083
- }
2084
- const settled = await Promise.allSettled(batch);
2085
- for (const result of settled) {
2086
- if (result.status === "rejected") throw result.reason;
2087
- results.push(result.value);
2088
- }
2123
+ const tasks = calls.filter((call) => call.name === "task").map(run);
2124
+ const others = (async () => {
2125
+ const out = [];
2126
+ for (const call of calls) if (call.name !== "task") out.push(await run(call));
2127
+ return out;
2128
+ })();
2129
+ const [settledOthers] = await Promise.allSettled([others]);
2130
+ const settledTasks = await Promise.allSettled(tasks);
2131
+ for (const result of [settledOthers, ...settledTasks]) {
2132
+ if (result.status === "rejected") throw result.reason;
2133
+ }
2134
+ const otherResults = settledOthers.status === "fulfilled" ? settledOthers.value : [];
2135
+ const taskResults = settledTasks.map((result) => result.value);
2136
+ for (const call of calls) {
2137
+ results.push((call.name === "task" ? taskResults : otherResults).shift());
2089
2138
  }
2090
2139
  return results;
2091
2140
  }
@@ -2102,6 +2151,7 @@ async function runOneCall(cfg, call, hooks, signal) {
2102
2151
  requestPermission: (request) => hooks.requestPermission(request),
2103
2152
  checkpoint: cfg.checkpoint,
2104
2153
  onEvent: (event) => hooks.onEvent(event),
2154
+ onProgress: (text) => hooks.onEvent({ type: "tool_progress", id: call.id, text }),
2105
2155
  confirm: async (question) => await hooks.requestPermission({
2106
2156
  toolName: tool.name,
2107
2157
  summary: question,
@@ -2412,13 +2462,13 @@ function beginCheckpoint(options) {
2412
2462
  async function undoLatestCheckpoint(options) {
2413
2463
  assertSessionId2(options.sessionId);
2414
2464
  const root = workspaceRoot(options.cwd);
2415
- const sessionDir = path6.join(options.storageDir ?? checkpointsDir, options.sessionId);
2416
- const file = await newestCheckpoint(sessionDir);
2465
+ const storageDir = options.storageDir ?? checkpointsDir;
2417
2466
  const empty = { found: false, restored: [], removed: [], conflicts: [], failed: [] };
2467
+ const file = await newestCheckpoint(path6.join(storageDir, options.sessionId)) ?? await newestWorkspaceCheckpoint(storageDir, root);
2418
2468
  if (!file) return empty;
2419
2469
  const checkpoint = await readCheckpoint(file);
2420
- if (checkpoint.sessionId !== options.sessionId || checkpoint.cwd !== root) {
2421
- throw new Error("The latest undo checkpoint belongs to a different workspace or session.");
2470
+ if (checkpoint.cwd !== root) {
2471
+ throw new Error("The latest undo checkpoint belongs to a different workspace.");
2422
2472
  }
2423
2473
  const result = { ...empty, found: true };
2424
2474
  const retry = [];
@@ -2588,6 +2638,27 @@ function decodeSnapshot(snapshot) {
2588
2638
  function validBase64(value) {
2589
2639
  return value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
2590
2640
  }
2641
+ async function newestWorkspaceCheckpoint(storageDir, root) {
2642
+ let sessions;
2643
+ try {
2644
+ sessions = await readdir2(storageDir);
2645
+ } catch (error) {
2646
+ if (error.code === "ENOENT") return null;
2647
+ throw error;
2648
+ }
2649
+ const candidates = [];
2650
+ for (const session of sessions) {
2651
+ if (!/^[A-Za-z0-9_-]{1,240}$/.test(session)) continue;
2652
+ const file = await newestCheckpoint(path6.join(storageDir, session)).catch(() => null);
2653
+ if (file) candidates.push({ name: path6.basename(file), file });
2654
+ }
2655
+ candidates.sort((a, b) => b.name.localeCompare(a.name));
2656
+ for (const candidate of candidates) {
2657
+ const checkpoint = await readCheckpoint(candidate.file).catch(() => null);
2658
+ if (checkpoint?.cwd === root) return candidate.file;
2659
+ }
2660
+ return null;
2661
+ }
2591
2662
  async function newestCheckpoint(sessionDir) {
2592
2663
  let names;
2593
2664
  try {
@@ -2626,6 +2697,7 @@ var DEFAULT_TIMEOUT = 12e4;
2626
2697
  var MAX_TIMEOUT = 6e5;
2627
2698
  var MAX_OUTPUT = 1e5;
2628
2699
  var MAX_COMMAND_CHARS = 2e4;
2700
+ var PROGRESS_INTERVAL_MS = 250;
2629
2701
  var bashTool = {
2630
2702
  name: "bash",
2631
2703
  description: "Run a shell command in the workspace root. stdout and stderr are merged. Avoid commands that wait for interactive input. Set background: true to keep a long command (dev server, test watcher, build) running while you carry on with other work, then collect its output with bash_output.",
@@ -2689,6 +2761,19 @@ var bashTool = {
2689
2761
  windowsHide: true
2690
2762
  });
2691
2763
  const chunks = [];
2764
+ let partial = "";
2765
+ let lastProgress = 0;
2766
+ const progress = (text) => {
2767
+ if (!ctx.onProgress) return;
2768
+ const lines = (partial + text).split(/\r?\n|\r/);
2769
+ partial = lines.at(-1) ?? "";
2770
+ const now = Date.now();
2771
+ if (now - lastProgress < PROGRESS_INTERVAL_MS) return;
2772
+ const latest = [...lines].reverse().find((line) => line.trim() !== "");
2773
+ if (latest === void 0) return;
2774
+ lastProgress = now;
2775
+ ctx.onProgress(latest.trim());
2776
+ };
2692
2777
  let captured = 0;
2693
2778
  let truncated = false;
2694
2779
  const collect = (buf) => {
@@ -2699,6 +2784,7 @@ var bashTool = {
2699
2784
  } catch {
2700
2785
  text = buf.toString();
2701
2786
  }
2787
+ progress(text);
2702
2788
  if (text.length > MAX_OUTPUT - captured) {
2703
2789
  chunks.push(text.slice(0, MAX_OUTPUT - captured));
2704
2790
  truncated = true;
@@ -2912,7 +2998,7 @@ async function isFile2(file) {
2912
2998
 
2913
2999
  // src/core/attachments.ts
2914
3000
  import { execFile } from "child_process";
2915
- import { lstat as lstat2, readdir as readdir3, readFile as readFile7, stat as stat5 } from "fs/promises";
3001
+ import { lstat as lstat2, mkdtemp, readdir as readdir3, readFile as readFile7, rm as rm4, stat as stat5, writeFile as writeFile6 } from "fs/promises";
2916
3002
  import os2 from "os";
2917
3003
  import path8 from "path";
2918
3004
  import { fileURLToPath } from "url";
@@ -3007,14 +3093,15 @@ async function loadClipboardImage(platform = process.platform, runner = runClipb
3007
3093
  if (data.length > MAX_IMAGE_BYTES) {
3008
3094
  throw new Error(`Clipboard image is larger than ${formatBytes(MAX_IMAGE_BYTES)}.`);
3009
3095
  }
3096
+ const fitted = await fitImage(data, mediaType, platform);
3010
3097
  return {
3011
3098
  type: "image",
3012
- mediaType,
3013
- data: data.toString("base64"),
3014
- name: clipboardImageName(mediaType)
3099
+ mediaType: fitted.mediaType,
3100
+ data: fitted.data.toString("base64"),
3101
+ name: clipboardImageName(fitted.mediaType)
3015
3102
  };
3016
3103
  } catch (error) {
3017
- if (error instanceof Error && error.message.startsWith("Clipboard image is larger")) {
3104
+ if (error instanceof Error && /^(?:Clipboard image|Image) is larger/.test(error.message)) {
3018
3105
  throw error;
3019
3106
  }
3020
3107
  }
@@ -3031,10 +3118,11 @@ async function loadResolvedAttachment(resolved, size) {
3031
3118
  const data = await readFile7(resolved);
3032
3119
  const mediaType = detectImage(data);
3033
3120
  if (mediaType) {
3121
+ const fitted = await fitImage(data, mediaType);
3034
3122
  return {
3035
3123
  path: resolved,
3036
- bytes: data.length,
3037
- block: { type: "image", mediaType, data: data.toString("base64"), name }
3124
+ bytes: fitted.data.length,
3125
+ block: { type: "image", mediaType: fitted.mediaType, data: fitted.data.toString("base64"), name }
3038
3126
  };
3039
3127
  }
3040
3128
  if (data.length > MAX_TEXT_BYTES) {
@@ -3224,6 +3312,85 @@ try {
3224
3312
  $image.Dispose()
3225
3313
  }
3226
3314
  `;
3315
+ var MAX_SENT_IMAGE_BYTES = 37e5;
3316
+ var SHRINK_LONG_EDGE = 2e3;
3317
+ async function fitImage(data, mediaType, platform = process.platform, shrink = shrinkWithSystemTools) {
3318
+ if (data.length <= MAX_SENT_IMAGE_BYTES) return { data, mediaType };
3319
+ const dir = await mkdtemp(path8.join(os2.tmpdir(), "kitcode-image-"));
3320
+ try {
3321
+ const input = path8.join(dir, `in${extensionFor(mediaType)}`);
3322
+ const output = path8.join(dir, "out.jpg");
3323
+ await writeFile6(input, data);
3324
+ try {
3325
+ await shrink(input, output, platform);
3326
+ } catch {
3327
+ throw new Error(
3328
+ `Image is larger than ${formatBytes(MAX_SENT_IMAGE_BYTES)} and could not be scaled down automatically` + (platform === "win32" || platform === "darwin" ? "" : " (install ImageMagick for that)") + ". Save a smaller copy and attach that."
3329
+ );
3330
+ }
3331
+ const shrunk = await readFile7(output);
3332
+ if (shrunk.length > MAX_SENT_IMAGE_BYTES || detectImage(shrunk) !== "image/jpeg") {
3333
+ throw new Error(`Image is larger than ${formatBytes(MAX_SENT_IMAGE_BYTES)} even after scaling it down.`);
3334
+ }
3335
+ return { data: shrunk, mediaType: "image/jpeg" };
3336
+ } finally {
3337
+ await rm4(dir, { recursive: true, force: true }).catch(() => void 0);
3338
+ }
3339
+ }
3340
+ async function shrinkWithSystemTools(input, output, platform) {
3341
+ if (platform === "win32") {
3342
+ await runQuiet("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", WINDOWS_SHRINK_SCRIPT], {
3343
+ KITCODE_IMAGE_IN: input,
3344
+ KITCODE_IMAGE_OUT: output,
3345
+ KITCODE_IMAGE_EDGE: String(SHRINK_LONG_EDGE)
3346
+ });
3347
+ return;
3348
+ }
3349
+ if (platform === "darwin") {
3350
+ await runQuiet("sips", ["-Z", String(SHRINK_LONG_EDGE), "-s", "format", "jpeg", "-s", "formatOptions", "85", input, "--out", output]);
3351
+ return;
3352
+ }
3353
+ const args = [input, "-resize", `${SHRINK_LONG_EDGE}x${SHRINK_LONG_EDGE}>`, "-background", "white", "-flatten", "-quality", "85", output];
3354
+ try {
3355
+ await runQuiet("magick", args);
3356
+ } catch {
3357
+ await runQuiet("convert", args);
3358
+ }
3359
+ }
3360
+ function runQuiet(command, args, env) {
3361
+ return new Promise((resolve3, reject) => {
3362
+ execFile(command, args, { timeout: 2e4, windowsHide: true, env: { ...process.env, ...env } }, (error) => {
3363
+ if (error) reject(error);
3364
+ else resolve3();
3365
+ });
3366
+ });
3367
+ }
3368
+ function extensionFor(mediaType) {
3369
+ return mediaType === "image/jpeg" ? ".jpg" : mediaType === "image/gif" ? ".gif" : mediaType === "image/webp" ? ".webp" : ".png";
3370
+ }
3371
+ var WINDOWS_SHRINK_SCRIPT = String.raw`
3372
+ Add-Type -AssemblyName System.Drawing
3373
+ $source = [System.Drawing.Image]::FromFile($env:KITCODE_IMAGE_IN)
3374
+ try {
3375
+ $edge = [double]$env:KITCODE_IMAGE_EDGE
3376
+ $scale = [Math]::Min(1.0, $edge / [Math]::Max($source.Width, $source.Height))
3377
+ $width = [Math]::Max(1, [int]($source.Width * $scale))
3378
+ $height = [Math]::Max(1, [int]($source.Height * $scale))
3379
+ $bitmap = New-Object System.Drawing.Bitmap $width, $height
3380
+ $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
3381
+ $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
3382
+ $graphics.Clear([System.Drawing.Color]::White)
3383
+ $graphics.DrawImage($source, 0, 0, $width, $height)
3384
+ $codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.MimeType -eq 'image/jpeg' }
3385
+ $parameters = New-Object System.Drawing.Imaging.EncoderParameters 1
3386
+ $parameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, [long]85)
3387
+ $bitmap.Save($env:KITCODE_IMAGE_OUT, $codec, $parameters)
3388
+ $graphics.Dispose()
3389
+ $bitmap.Dispose()
3390
+ } finally {
3391
+ $source.Dispose()
3392
+ }
3393
+ `;
3227
3394
  function textMime(file) {
3228
3395
  const extension = path8.extname(file).toLowerCase();
3229
3396
  const known = {
@@ -3252,13 +3419,27 @@ function normalizeInputPath(value) {
3252
3419
  return value.slice(1, -1);
3253
3420
  }
3254
3421
  }
3422
+ if (WINDOWS_PATH.test(value)) return value.replace(/\\ /g, " ");
3255
3423
  return value.replace(/\\([\\ "'()\[\]&;#$!])/g, (_match, character) => character);
3256
3424
  }
3425
+ var WINDOWS_PATH = /^(?:[A-Za-z]:[\\/]|\\\\)/;
3426
+ function splitAttachmentPaths(value) {
3427
+ const trimmed = value.trim();
3428
+ if (!trimmed || /[\r\n\0]/.test(trimmed)) return null;
3429
+ const tokens2 = [];
3430
+ const pattern = /"([^"]+)"|'([^']+)'|((?:\\ |[^\s"'])+)/g;
3431
+ for (const match of trimmed.matchAll(pattern)) {
3432
+ tokens2.push(match[1] ?? match[2] ?? match[3] ?? "");
3433
+ }
3434
+ if (tokens2.length < 2) return null;
3435
+ const absolute = (token) => /^file:\/\//i.test(token) || WINDOWS_PATH.test(token) || token.startsWith("/") || /^~[\\/]/.test(token);
3436
+ return tokens2.every(absolute) ? tokens2 : null;
3437
+ }
3257
3438
  function safeName(value) {
3258
3439
  return value.replace(/[\r\n\0-\x1f\x7f]/g, "").slice(0, 200) || "attachment";
3259
3440
  }
3260
3441
  function formatBytes(value) {
3261
- return `${Math.round(value / 1024 / 1024)} MB`;
3442
+ return `${(value / 1024 / 1024).toFixed(1).replace(/\.0$/, "")} MB`;
3262
3443
  }
3263
3444
 
3264
3445
  // src/core/compact.ts
@@ -3274,11 +3455,15 @@ var MAX_FILE_BLOCK_CHARS = 2e3;
3274
3455
  var MAX_TOOL_RESULT_CHARS = 1200;
3275
3456
  var MAX_TOOL_ERROR_CHARS = 2e3;
3276
3457
  var MAX_TOOL_INPUT_CHARS = 800;
3277
- var SINGLE_MAX_TOKENS = 768;
3278
- var CHUNK_MAX_TOKENS = 512;
3279
- var MERGE_MAX_TOKENS = 1024;
3458
+ var SINGLE_MAX_TOKENS = 3e3;
3459
+ var CHUNK_MAX_TOKENS = 1200;
3460
+ var MERGE_MAX_TOKENS = 4e3;
3461
+ var MAX_PREVIOUS_SUMMARY_CHARS = 4e4;
3462
+ var SUMMARY_MARKER = "[Earlier conversation summary]";
3463
+ var KEEP_RECENT_TOOL_RESULTS = 6;
3464
+ var CLEARABLE_TOOL_RESULT_CHARS = 300;
3280
3465
  var CHUNK_CONCURRENCY = 3;
3281
- var SYSTEM_SUMMARIZE = "You are summarizing a coding conversation so another agent can continue the work. Extract and preserve ONLY: concrete requirements, user preferences, files changed, commands run and their results, errors encountered, bugs found, architectural decisions, and unfinished work. Be terse and factual. Do NOT include greetings, acknowledgments, chain-of-thought, or step-by-step narration. Use these sections: Requirements; Verified facts (with file paths or tool evidence); Changes made; Verification (exact commands, pass/fail/not run, and unresolved error excerpts); Decisions and assumptions; Remaining work. Keep plans and assumptions separate from verified facts. Never turn an intended command into a completed check. When checks conflict, retain the latest result and mark earlier results as superseded. Preserve explicit user corrections and project constraints. Do not retain credentials or secret values. Return only the summary.";
3466
+ var SYSTEM_SUMMARIZE = `You are summarizing a coding conversation so another agent can continue the work. Extract and preserve ONLY: concrete requirements, user preferences, files changed, commands run and their results, errors encountered, bugs found, architectural decisions, and unfinished work. Be terse and factual. Do NOT include greetings, acknowledgments, chain-of-thought, or step-by-step narration. Use these sections: Requirements; Verified facts (with file paths or tool evidence); Changes made; Verification (exact commands, pass/fail/not run, and unresolved error excerpts); Decisions and assumptions; Remaining work. Keep plans and assumptions separate from verified facts. Never turn an intended command into a completed check. When checks conflict, retain the latest result and mark earlier results as superseded. Preserve explicit user corrections and project constraints. Do not retain credentials or secret values. If the input starts with "${SUMMARY_MARKER}", that is the summary of even older work: carry over everything in it that is still relevant and fold the newer events into the same sections. Return only the summary.`;
3282
3467
  var SYSTEM_MERGE = "Merge these partial summaries of a coding conversation into one coherent summary. Remove duplicates. Preserve: requirements, files changed, commands run, errors, decisions, and unfinished work. Keep the structured sections and evidence from the source summaries. Separate verified facts, assumptions, and planned work; keep exact verification commands and their latest outcomes. Do not resolve conflicting evidence by guessing. Do not retain credentials or secret values. Be terse. Return only the merged summary.";
3283
3468
  async function compactHistory(options) {
3284
3469
  const cut = compactCutIndex(options.history);
@@ -3351,7 +3536,7 @@ async function compactHistory(options) {
3351
3536
  const prefix = [
3352
3537
  {
3353
3538
  role: "user",
3354
- content: [{ type: "text", text: `[Earlier conversation summary]
3539
+ content: [{ type: "text", text: `${SUMMARY_MARKER}
3355
3540
  ${summary2}` }]
3356
3541
  },
3357
3542
  {
@@ -3359,14 +3544,70 @@ ${summary2}` }]
3359
3544
  content: [{ type: "text", text: "Earlier context loaded. I will continue from this summary." }]
3360
3545
  }
3361
3546
  ];
3547
+ const kept = clearOldToolResults(options.history.slice(cut));
3362
3548
  return {
3363
- history: [...prefix, ...options.history.slice(cut)],
3549
+ history: [...prefix, ...withoutThinkingReplay(kept.history)],
3364
3550
  compacted: true,
3365
3551
  removedMessages: cut,
3552
+ clearedToolResults: kept.cleared,
3366
3553
  usage,
3367
3554
  rateLimits
3368
3555
  };
3369
3556
  }
3557
+ function clearOldToolResults(history, keepRecent = KEEP_RECENT_TOOL_RESULTS) {
3558
+ let seen = 0;
3559
+ let cleared = 0;
3560
+ const out = [...history];
3561
+ for (let index = out.length - 1; index >= 0; index -= 1) {
3562
+ const message = out[index];
3563
+ if (!message.content.some((block) => block.type === "tool_result")) continue;
3564
+ let changed = false;
3565
+ const content = [...message.content];
3566
+ for (let at = content.length - 1; at >= 0; at -= 1) {
3567
+ const block = content[at];
3568
+ if (block.type !== "tool_result") continue;
3569
+ seen += 1;
3570
+ if (seen <= keepRecent || block.content.length <= CLEARABLE_TOOL_RESULT_CHARS) continue;
3571
+ if (block.content.startsWith(CLEARED_NOTE_PREFIX)) continue;
3572
+ content[at] = {
3573
+ ...block,
3574
+ content: `${CLEARED_NOTE_PREFIX} ${block.content.length} characters). Run the tool again if you need it.]`
3575
+ };
3576
+ cleared += 1;
3577
+ changed = true;
3578
+ }
3579
+ if (changed) out[index] = { ...message, content };
3580
+ }
3581
+ return { history: cleared > 0 ? out : history, cleared };
3582
+ }
3583
+ var CLEARED_NOTE_PREFIX = "[Older tool output cleared to free context (";
3584
+ function withoutThinkingReplay(history) {
3585
+ let changed = false;
3586
+ const out = history.map((message) => {
3587
+ if (message.role !== "assistant" || !message.content.some((block) => block.type === "thinking" && block.raw)) {
3588
+ return message;
3589
+ }
3590
+ changed = true;
3591
+ return {
3592
+ ...message,
3593
+ content: message.content.map((block) => block.type === "thinking" && block.raw ? { type: "thinking", text: block.text } : block)
3594
+ };
3595
+ });
3596
+ return changed ? out : history;
3597
+ }
3598
+ function estimateHistoryTokens(history) {
3599
+ let chars = 0;
3600
+ for (const message of history) {
3601
+ for (const block of message.content) {
3602
+ if (block.type === "text" || block.type === "thinking") chars += block.text.length;
3603
+ else if (block.type === "tool_result") chars += block.content.length;
3604
+ else if (block.type === "file") chars += block.text.length;
3605
+ else if (block.type === "tool_use") chars += safeJson(block.input).length;
3606
+ else if (block.type === "image") chars += 6e3;
3607
+ }
3608
+ }
3609
+ return Math.round(chars / 4);
3610
+ }
3370
3611
  function shouldAutoCompact(used, window) {
3371
3612
  return window !== null && window > 0 && used / window >= 0.8;
3372
3613
  }
@@ -3410,7 +3651,7 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3410
3651
  function isConversationUser(message) {
3411
3652
  if (message.role !== "user") return false;
3412
3653
  return message.content.some(
3413
- (block) => block.type === "image" || block.type === "file" || block.type === "text" && !block.text.startsWith("[Earlier conversation summary]")
3654
+ (block) => block.type === "image" || block.type === "file" || block.type === "text" && !block.text.startsWith(SUMMARY_MARKER)
3414
3655
  );
3415
3656
  }
3416
3657
  function chunkRenderedMessages(messages, targetChars, overlapMessages, overlapMaxChars) {
@@ -3616,6 +3857,9 @@ ${blocks.join("\n")}`] : [];
3616
3857
  function renderBlock(block, role) {
3617
3858
  switch (block.type) {
3618
3859
  case "text":
3860
+ if (role === "user" && block.text.startsWith(SUMMARY_MARKER)) {
3861
+ return truncate(block.text, MAX_PREVIOUS_SUMMARY_CHARS);
3862
+ }
3619
3863
  return truncate(
3620
3864
  block.text,
3621
3865
  role === "user" ? MAX_USER_TEXT_CHARS : MAX_ASSISTANT_TEXT_CHARS
@@ -3692,7 +3936,7 @@ function buildSystemPrompt(args) {
3692
3936
 
3693
3937
  // src/core/subagent.ts
3694
3938
  var TASK_TOOL_NAME = "task";
3695
- var MAX_SUBAGENT_STEPS = 12;
3939
+ var MAX_SUBAGENT_STEPS = 30;
3696
3940
  var SUBAGENT_NO_ANSWER = "The subagent stopped without writing a final answer \u2014 it was interrupted or ended on a tool call. Nothing was reported back: do the work directly, or delegate it again with a narrower prompt.";
3697
3941
  var SUBAGENT_CANCELLED = "The subagent was cancelled before it produced anything.";
3698
3942
  var STEP_LIMIT_NOTE = `The subagent was stopped after ${MAX_SUBAGENT_STEPS} model calls without finishing, so anything above is partial. Do the work directly or delegate a narrower piece of it.`;
@@ -3724,9 +3968,12 @@ function createSubagentRunner(makeConfig, tools) {
3724
3968
  kind: base.provider.kind,
3725
3969
  listModels: () => base.provider.listModels(),
3726
3970
  knownModels: () => base.provider.knownModels(),
3971
+ // Keep the tool definitions on the last call: the history holds
3972
+ // tool_use/tool_result blocks, and providers reject those without
3973
+ // tools (Anthropic: 400 "must define tools"). Calls made anyway are
3974
+ // refused by tools.get below.
3727
3975
  stream: (chat) => base.provider.stream(steps === MAX_SUBAGENT_STEPS ? {
3728
3976
  ...chat,
3729
- tools: [],
3730
3977
  system: `${chat.system}
3731
3978
 
3732
3979
  This is your last model call. Do not call tools. Return your findings now, clearly stating any unfinished work.`
@@ -3857,9 +4104,10 @@ function formatUsageBreakdown(tracker) {
3857
4104
  lines.push("", `total ${formatUsageCompact(tracker)}`);
3858
4105
  return lines.join("\n");
3859
4106
  }
3860
- function tokens(count) {
3861
- if (count < 1e3) return String(count);
3862
- const [value, suffix] = count < 1e6 ? [count / 1e3, "k"] : [count / 1e6, "M"];
4107
+ function tokens(count2) {
4108
+ if (count2 < 1e3) return String(count2);
4109
+ const k = Number((count2 / 1e3).toFixed(1));
4110
+ const [value, suffix] = k < 1e3 ? [k, "k"] : [count2 / 1e6, "M"];
3863
4111
  return `${value.toFixed(1).replace(/\.0$/, "")}${suffix}`;
3864
4112
  }
3865
4113
  function dollars(amount) {
@@ -3943,7 +4191,7 @@ function clip(value) {
3943
4191
  // package.json
3944
4192
  var package_default = {
3945
4193
  name: "@kernelonpanic/kitcode",
3946
- version: "1.4.0",
4194
+ version: "1.4.1",
3947
4195
  description: "Terminal coding agent with a config you never have to write by hand",
3948
4196
  type: "module",
3949
4197
  license: "MIT",
@@ -4012,7 +4260,7 @@ var package_default = {
4012
4260
 
4013
4261
  // src/version.ts
4014
4262
  var KITCODE_VERSION = package_default.version;
4015
- var KITCODE_COMMIT = true ? "f5ecc5fb8dac2872eeb1a988705c06552b250129" : "development";
4263
+ var KITCODE_COMMIT = true ? "be753d255e7675a850d0ca1ee64f790f68d3fd93" : "development";
4016
4264
 
4017
4265
  // src/mcp/client.ts
4018
4266
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -4397,7 +4645,7 @@ async function limitedResponseText(response, limit) {
4397
4645
  }
4398
4646
 
4399
4647
  // src/providers/catalog.ts
4400
- import { chmod as chmod4, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
4648
+ import { chmod as chmod4, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
4401
4649
  import path9 from "path";
4402
4650
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
4403
4651
  var CACHE_VERSION = 3;
@@ -4459,7 +4707,7 @@ async function writeCache(file, models) {
4459
4707
  const payload = { version: CACHE_VERSION, fetchedAt: Date.now(), models };
4460
4708
  try {
4461
4709
  await ensureDir(path9.dirname(file));
4462
- await writeFile6(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
4710
+ await writeFile7(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
4463
4711
  await chmod4(file, 384);
4464
4712
  } catch {
4465
4713
  return;
@@ -4607,8 +4855,11 @@ async function* streamTurn(client, providerId, apiKey2, req) {
4607
4855
  messages: toMessageParams(req.messages),
4608
4856
  tools: toToolParams(req.tools)
4609
4857
  };
4610
- if (req.thinking) params.thinking = { type: "adaptive", display: "summarized" };
4611
- const effort = resolveEffort(req.effort, req.messages);
4858
+ if (req.thinking) {
4859
+ const thinking2 = thinkingParam(req.model, req.maxTokens);
4860
+ if (thinking2) params.thinking = thinking2;
4861
+ }
4862
+ const effort = effectiveEffort("anthropic", req.model, req.effort, req.messages);
4612
4863
  if (effort) params.output_config = { effort };
4613
4864
  let thinking = "";
4614
4865
  let text = "";
@@ -4687,6 +4938,11 @@ function toMessageParams(messages) {
4687
4938
  const content = message.content.flatMap(toBlockParams);
4688
4939
  if (content.length > 0) params.push({ role: message.role, content });
4689
4940
  }
4941
+ const last = params.at(-1)?.content;
4942
+ if (Array.isArray(last)) {
4943
+ const block = last.findLast((candidate) => candidate.type !== "thinking" && candidate.type !== "redacted_thinking");
4944
+ if (block) block.cache_control = { type: "ephemeral" };
4945
+ }
4690
4946
  return params;
4691
4947
  }
4692
4948
  function toBlockParams(block) {
@@ -4756,6 +5012,15 @@ function toUsage(usage) {
4756
5012
  cacheRead: usage.cache_read_input_tokens ?? 0
4757
5013
  };
4758
5014
  }
5015
+ var BUDGET_THINKING = /^claude-(?:3|[a-z]+-3|haiku-4|(?:sonnet|opus)-4(?:-[0-5])?(?:-\d{8}|@|$))/;
5016
+ var THINKING_BUDGET = 16e3;
5017
+ var MIN_THINKING_BUDGET = 1024;
5018
+ function thinkingParam(model, maxTokens) {
5019
+ const id = (model.split("/").at(-1) ?? model).replace(/^(?:[a-z]{2}\.)?anthropic\./, "");
5020
+ if (!BUDGET_THINKING.test(id)) return { type: "adaptive", display: "summarized" };
5021
+ const budget = Math.min(THINKING_BUDGET, maxTokens - 1);
5022
+ return budget >= MIN_THINKING_BUDGET ? { type: "enabled", budget_tokens: budget } : void 0;
5023
+ }
4759
5024
  function toRefusal(details) {
4760
5025
  return { category: details?.category ?? null, explanation: details?.explanation ?? null };
4761
5026
  }
@@ -4788,6 +5053,57 @@ function createOpenAiProvider(args) {
4788
5053
  };
4789
5054
  }
4790
5055
  async function* streamTurn2(client, providerId, apiKey2, req) {
5056
+ let effort = effectiveEffort("openai", req.model, req.effort, req.messages, providerId);
5057
+ const thinkingKey = `${providerId}/${req.model}`;
5058
+ let thinkingOff = req.thinking === false && !thinkingOffRejected.has(thinkingKey) ? thinkingOffParams(req.model) : void 0;
5059
+ let tokenField = completionTokenModels.has(thinkingKey) || usesCompletionTokens(req.model) ? "max_completion_tokens" : "max_tokens";
5060
+ for (; ; ) {
5061
+ let streamed = false;
5062
+ try {
5063
+ for await (const event of streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField)) {
5064
+ if (event.type !== "rate_limits" && event.type !== "usage") streamed = true;
5065
+ yield event;
5066
+ }
5067
+ return;
5068
+ } catch (error) {
5069
+ if (streamed) throw error;
5070
+ if (tokenField === "max_tokens" && rejectsField(error, /max_completion_tokens/)) {
5071
+ completionTokenModels.add(thinkingKey);
5072
+ tokenField = "max_completion_tokens";
5073
+ continue;
5074
+ }
5075
+ if (effort && rejectsField(error, /reasoning|effort/i)) {
5076
+ markEffortRejected(providerId, req.model);
5077
+ effort = void 0;
5078
+ continue;
5079
+ }
5080
+ if (thinkingOff && rejectsField(error, /thinking/i)) {
5081
+ thinkingOffRejected.add(thinkingKey);
5082
+ thinkingOff = void 0;
5083
+ continue;
5084
+ }
5085
+ throw error;
5086
+ }
5087
+ }
5088
+ }
5089
+ var thinkingOffRejected = /* @__PURE__ */ new Set();
5090
+ var completionTokenModels = /* @__PURE__ */ new Set();
5091
+ function usesCompletionTokens(model) {
5092
+ const id = (model.split("/").at(-1) ?? model).toLowerCase();
5093
+ return /^(?:o[1-9](?:-|$)|gpt-5(?:[.-]|$))/.test(id);
5094
+ }
5095
+ function thinkingOffParams(model) {
5096
+ const id = (model.split("/").at(-1) ?? model).toLowerCase();
5097
+ if (/^(?:glm-4\.[5-9]|glm-[5-9]|kimi-k(?:2\.[5-9]|[3-9]))/.test(id)) return { thinking: { type: "disabled" } };
5098
+ if (/^(?:qwen3|qwq)/.test(id)) return { enable_thinking: false };
5099
+ return void 0;
5100
+ }
5101
+ function rejectsField(error, field2) {
5102
+ if (!(error instanceof ProviderError) || error.status !== 400 && error.status !== 422) return false;
5103
+ const cause = error.cause instanceof Error ? error.cause.message : "";
5104
+ return field2.test(`${error.message} ${cause}`);
5105
+ }
5106
+ async function* streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField) {
4791
5107
  let text = "";
4792
5108
  let thinking = "";
4793
5109
  let finishReason = null;
@@ -4797,13 +5113,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
4797
5113
  let recognised = 0;
4798
5114
  const calls = /* @__PURE__ */ new Map();
4799
5115
  const capture = captureResponseHead();
4800
- const effort = openAiEffort(req.model, resolveEffort(req.effort, req.messages));
4801
5116
  try {
4802
5117
  const stream = await client.withOptions({ fetch: capture.fetch }).chat.completions.create(
4803
5118
  {
4804
5119
  model: req.model,
4805
- max_tokens: req.maxTokens,
5120
+ [tokenField]: req.maxTokens,
4806
5121
  ...effort ? { reasoning_effort: effort } : {},
5122
+ ...thinkingOff,
4807
5123
  messages: toChatMessages(req.system, req.messages),
4808
5124
  ...req.tools.length > 0 ? { tools: toChatTools(req.tools) } : {},
4809
5125
  stream: true,
@@ -4993,14 +5309,22 @@ function toUsage2(usage) {
4993
5309
  return normalizeOpenAiUsage(usage);
4994
5310
  }
4995
5311
  function normalizeOpenAiUsage(usage) {
4996
- const cacheRead = usage.prompt_tokens_details?.cached_tokens ?? 0;
5312
+ const prompt = count(usage.prompt_tokens);
5313
+ const cacheRead = Math.min(
5314
+ prompt,
5315
+ count(usage.prompt_tokens_details?.cached_tokens) || count(usage.prompt_cache_hit_tokens) || count(usage.cached_tokens)
5316
+ );
5317
+ const cacheWrite = Math.min(prompt - cacheRead, count(usage.prompt_tokens_details?.cache_write_tokens));
4997
5318
  return {
4998
- input: Math.max(0, (usage.prompt_tokens ?? 0) - cacheRead),
4999
- output: usage.completion_tokens ?? 0,
5000
- cacheWrite: 0,
5319
+ input: prompt - cacheRead - cacheWrite,
5320
+ output: count(usage.completion_tokens),
5321
+ cacheWrite,
5001
5322
  cacheRead
5002
5323
  };
5003
5324
  }
5325
+ function count(value) {
5326
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
5327
+ }
5004
5328
 
5005
5329
  // src/providers/registry.ts
5006
5330
  function createRegistry(config, auth) {
@@ -5049,6 +5373,7 @@ var PLAN_REFUSAL = "Plan mode is on: this tool changes state and is unavailable.
5049
5373
  function createPermissionEngine(configPermissions) {
5050
5374
  const sessionGrants = /* @__PURE__ */ new Set();
5051
5375
  let bypassEnabled = false;
5376
+ let bypassUnlocked = false;
5052
5377
  let current = "normal";
5053
5378
  const resolve3 = (tool, requested) => configPermissions[tool.name] ?? requested ?? tool.defaultPermission;
5054
5379
  return {
@@ -5075,9 +5400,12 @@ function createPermissionEngine(configPermissions) {
5075
5400
  bypass: {
5076
5401
  enable() {
5077
5402
  bypassEnabled = true;
5403
+ bypassUnlocked = true;
5404
+ current = "normal";
5078
5405
  },
5079
5406
  disable() {
5080
5407
  bypassEnabled = false;
5408
+ bypassUnlocked = false;
5081
5409
  },
5082
5410
  isEnabled() {
5083
5411
  return bypassEnabled;
@@ -5089,7 +5417,18 @@ function createPermissionEngine(configPermissions) {
5089
5417
  current = mode;
5090
5418
  },
5091
5419
  cycle() {
5092
- current = MODES[(MODES.indexOf(current) + 1) % MODES.length] ?? "normal";
5420
+ if (bypassEnabled) {
5421
+ bypassEnabled = false;
5422
+ current = "normal";
5423
+ return current;
5424
+ }
5425
+ const next = MODES[(MODES.indexOf(current) + 1) % MODES.length] ?? "normal";
5426
+ if (next === "normal" && bypassUnlocked) {
5427
+ bypassEnabled = true;
5428
+ current = "normal";
5429
+ return "bypass";
5430
+ }
5431
+ current = next;
5093
5432
  return current;
5094
5433
  }
5095
5434
  }
@@ -5157,7 +5496,7 @@ import { readFile as readFile9, stat as stat6 } from "fs/promises";
5157
5496
 
5158
5497
  // src/tools/safe-write.ts
5159
5498
  import { randomUUID as randomUUID3 } from "crypto";
5160
- import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as rm4 } from "fs/promises";
5499
+ import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as rm5 } from "fs/promises";
5161
5500
  import { basename as basename2, dirname as dirname2, join as join2 } from "path";
5162
5501
  var UnsafeFileChangeError = class extends Error {
5163
5502
  constructor(message) {
@@ -5220,7 +5559,7 @@ async function atomicWriteSafeFile(file, data, snapshot) {
5220
5559
  await rename5(temp, file);
5221
5560
  } catch (error) {
5222
5561
  await handle?.close().catch(() => void 0);
5223
- await rm4(temp, { force: true }).catch(() => void 0);
5562
+ await rm5(temp, { force: true }).catch(() => void 0);
5224
5563
  throw error;
5225
5564
  }
5226
5565
  }
@@ -5283,12 +5622,12 @@ async function lstatMaybe(file) {
5283
5622
  var MAX_FILE_BYTES2 = 5e6;
5284
5623
  function countOccurrences(haystack, needle) {
5285
5624
  if (needle === "") return 0;
5286
- let count = 0;
5625
+ let count2 = 0;
5287
5626
  let from = 0;
5288
5627
  for (; ; ) {
5289
5628
  const at = haystack.indexOf(needle, from);
5290
- if (at === -1) return count;
5291
- count += 1;
5629
+ if (at === -1) return count2;
5630
+ count2 += 1;
5292
5631
  from = at + needle.length;
5293
5632
  }
5294
5633
  }
@@ -5347,16 +5686,16 @@ var editTool = {
5347
5686
  return { content: `Cannot edit ${path15}: it is a binary file, not text.`, isError: true };
5348
5687
  }
5349
5688
  const before = beforeBuffer.toString("utf8");
5350
- const count = countOccurrences(before, oldString);
5351
- if (count === 0) {
5689
+ const count2 = countOccurrences(before, oldString);
5690
+ if (count2 === 0) {
5352
5691
  return {
5353
5692
  content: `oldString was not found in ${path15}. Read the file again and match the text exactly, including whitespace.`,
5354
5693
  isError: true
5355
5694
  };
5356
5695
  }
5357
- if (count > 1 && !replaceAll) {
5696
+ if (count2 > 1 && !replaceAll) {
5358
5697
  return {
5359
- content: `oldString matches ${count} places in ${path15}. Add more surrounding context to identify a single one, or set replaceAll to true.`,
5698
+ content: `oldString matches ${count2} places in ${path15}. Add more surrounding context to identify a single one, or set replaceAll to true.`,
5360
5699
  isError: true
5361
5700
  };
5362
5701
  }
@@ -5380,14 +5719,14 @@ var editTool = {
5380
5719
  return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
5381
5720
  }
5382
5721
  return {
5383
- content: `Edited ${path15} (${count} ${count === 1 ? "replacement" : "replacements"})`,
5722
+ content: `Edited ${path15} (${count2} ${count2 === 1 ? "replacement" : "replacements"})`,
5384
5723
  display: { kind: "diff", path: path15, before, after }
5385
5724
  };
5386
5725
  }
5387
5726
  };
5388
5727
  function replacement(before, oldString, newString, replaceAll) {
5389
- const count = countOccurrences(before, oldString);
5390
- if (count === 0 || count > 1 && !replaceAll) return null;
5728
+ const count2 = countOccurrences(before, oldString);
5729
+ if (count2 === 0 || count2 > 1 && !replaceAll) return null;
5391
5730
  if (replaceAll) return before.split(oldString).join(newString);
5392
5731
  const at = before.indexOf(oldString);
5393
5732
  return before.slice(0, at) + newString + before.slice(at + oldString.length);
@@ -5819,7 +6158,7 @@ function createToolRegistry(tools) {
5819
6158
  }
5820
6159
 
5821
6160
  // src/prompts/library.ts
5822
- import { chmod as chmod6, readFile as readFile13, readdir as readdir4, unlink as unlink3, writeFile as writeFile7 } from "fs/promises";
6161
+ import { chmod as chmod6, readFile as readFile13, readdir as readdir4, unlink as unlink3, writeFile as writeFile8 } from "fs/promises";
5823
6162
  import path11 from "path";
5824
6163
  async function savePrompt(input) {
5825
6164
  const slug = slugify(input.name);
@@ -5833,7 +6172,7 @@ async function savePrompt(input) {
5833
6172
  };
5834
6173
  await ensureDir(promptsDir);
5835
6174
  const file = path11.join(promptsDir, `${slug}.md`);
5836
- await writeFile7(file, serialize(prompt), {
6175
+ await writeFile8(file, serialize(prompt), {
5837
6176
  encoding: "utf8",
5838
6177
  mode: 384
5839
6178
  });
@@ -6376,7 +6715,10 @@ function createTaskTool(runner) {
6376
6715
  required: ["description", "prompt"],
6377
6716
  additionalProperties: false
6378
6717
  },
6379
- defaultPermission: "ask",
6718
+ // Starting a subagent changes nothing by itself: each tool call it makes
6719
+ // goes through the same permission and plan-mode checks as the parent.
6720
+ defaultPermission: "allow",
6721
+ readOnly: true,
6380
6722
  summarize(input) {
6381
6723
  return `task(${brief(field(input, "description"))})`;
6382
6724
  },
@@ -6550,6 +6892,7 @@ async function readJsonBounded(response) {
6550
6892
 
6551
6893
  // src/app/runtime.ts
6552
6894
  var PREFERRED2 = ["claude-opus-5", "claude-sonnet-5", "gpt-5", "claude-opus-4-8"];
6895
+ var MID_TURN_TRIM_RATIO = 0.8;
6553
6896
  async function boot(options) {
6554
6897
  const loadedConfig = await loadRuntimeConfig(options.cwd);
6555
6898
  const config = loadedConfig.config;
@@ -6683,9 +7026,16 @@ async function boot(options) {
6683
7026
  effort: config.effort,
6684
7027
  thinking: config.thinking,
6685
7028
  budget: activeBudget,
6686
- checkpoint: activeCheckpoint
7029
+ checkpoint: activeCheckpoint,
7030
+ trimContext: (messages) => trimForWindow(resolved.provider.kind, messages)
6687
7031
  };
6688
7032
  };
7033
+ const trimForWindow = (kind, messages) => {
7034
+ if (kind !== "openai" || !modelContextWindow) return null;
7035
+ if (estimateHistoryTokens(messages) < modelContextWindow * MID_TURN_TRIM_RATIO) return null;
7036
+ const cleared = clearOldToolResults(messages);
7037
+ return cleared.cleared > 0 ? cleared.history : null;
7038
+ };
6689
7039
  let activeBudget;
6690
7040
  let activeCheckpoint;
6691
7041
  let activeAgentCount = 0;
@@ -6732,8 +7082,22 @@ async function boot(options) {
6732
7082
  return save;
6733
7083
  };
6734
7084
  const compactWithBudget = async (history, signal, budget) => {
7085
+ const tokensBefore = estimateHistoryTokens(history);
6735
7086
  if (compactCutIndex(history) <= 0) {
6736
- return { history, compacted: false, removedMessages: 0 };
7087
+ const cleared = clearOldToolResults(history);
7088
+ if (cleared.cleared === 0) return { history, compacted: false, removedMessages: 0 };
7089
+ const next = withoutThinkingReplay(cleared.history);
7090
+ contextUsage = void 0;
7091
+ session.context = void 0;
7092
+ notifyContext();
7093
+ return {
7094
+ history: next,
7095
+ compacted: true,
7096
+ removedMessages: 0,
7097
+ clearedToolResults: cleared.cleared,
7098
+ tokensBefore,
7099
+ tokensAfter: estimateHistoryTokens(next)
7100
+ };
6737
7101
  }
6738
7102
  const resolved = registry.resolve(modelRef);
6739
7103
  const estimate = estimateCompactBudget(history, config.maxTokens);
@@ -6761,7 +7125,7 @@ async function boot(options) {
6761
7125
  session.context = void 0;
6762
7126
  notifyContext();
6763
7127
  }
6764
- return result;
7128
+ return { ...result, tokensBefore, tokensAfter: estimateHistoryTokens(result.history) };
6765
7129
  };
6766
7130
  const runtime = {
6767
7131
  cwd: options.cwd,
@@ -6988,16 +7352,30 @@ async function boot(options) {
6988
7352
  effortDescription(history) {
6989
7353
  if (!modelRef) return "unavailable";
6990
7354
  const { provider, modelId } = registry.resolve(modelRef);
6991
- const requested = resolveEffort(config.effort, history);
6992
- const effective = provider.kind === "openai" ? openAiEffort(modelId, requested) : requested;
6993
- return effective ? `${config.effort} \u2192 ${effective} (API request)` : `${config.effort} \u2192 not sent: unknown model support`;
7355
+ const effective = effectiveEffort(provider.kind, modelId, config.effort, history, provider.id);
7356
+ return effective ? `${config.effort} \u2192 ${effective} (API request)` : `${config.effort} \u2192 not sent: this model/provider does not accept an effort level`;
6994
7357
  },
6995
7358
  getEffort: () => config.effort,
7359
+ getEffectiveEffort(history) {
7360
+ if (!modelRef) return null;
7361
+ try {
7362
+ const { provider, modelId } = registry.resolve(modelRef);
7363
+ return effectiveEffort(provider.kind, modelId, config.effort, history, provider.id) ?? null;
7364
+ } catch {
7365
+ return null;
7366
+ }
7367
+ },
6996
7368
  async setEffort(effort) {
6997
7369
  await persistConfig((draft) => {
6998
7370
  draft.effort = effort;
6999
7371
  });
7000
7372
  },
7373
+ getBell: () => config.bell,
7374
+ async setBell(enabled) {
7375
+ await persistConfig((draft) => {
7376
+ draft.bell = enabled;
7377
+ });
7378
+ },
7001
7379
  getThinking: () => config.thinking,
7002
7380
  async setThinking(enabled) {
7003
7381
  await persistConfig((draft) => {
@@ -7005,19 +7383,19 @@ async function boot(options) {
7005
7383
  });
7006
7384
  },
7007
7385
  getContextWindowOverride: () => config.contextWindow,
7008
- async setContextWindowOverride(tokens3) {
7386
+ async setContextWindowOverride(tokens2) {
7009
7387
  await persistConfig((draft) => {
7010
- if (tokens3 === void 0) delete draft.contextWindow;
7011
- else draft.contextWindow = tokens3;
7388
+ if (tokens2 === void 0) delete draft.contextWindow;
7389
+ else draft.contextWindow = tokens2;
7012
7390
  });
7013
7391
  modelContextWindow = config.contextWindow ?? resolveContextWindow(modelRef);
7014
7392
  notifyContext();
7015
7393
  void refreshModelContextWindow();
7016
7394
  },
7017
7395
  getMaxTokensPerTurn: () => config.budget.maxTokensPerTurn,
7018
- async setMaxTokensPerTurn(tokens3) {
7396
+ async setMaxTokensPerTurn(tokens2) {
7019
7397
  await persistConfig((draft) => {
7020
- draft.budget.maxTokensPerTurn = tokens3;
7398
+ draft.budget.maxTokensPerTurn = tokens2;
7021
7399
  draft.budget.maxCostUsdPerTurn = 0;
7022
7400
  });
7023
7401
  },
@@ -7272,7 +7650,7 @@ ${lines.join("\n")}` : null;
7272
7650
  hooks.onEvent({
7273
7651
  type: "notice",
7274
7652
  level: "info",
7275
- text: `Compacted older context; kept the latest conversation (${result.removedMessages} messages replaced).`
7653
+ text: describeCompaction(result)
7276
7654
  });
7277
7655
  }
7278
7656
  } catch (error) {
@@ -7291,10 +7669,6 @@ ${lines.join("\n")}` : null;
7291
7669
  const trackedHooks = {
7292
7670
  ...hooks,
7293
7671
  onEvent(event) {
7294
- if (event.type === "turn_start") {
7295
- contextUsage = void 0;
7296
- notifyContext();
7297
- }
7298
7672
  if (event.type === "usage") {
7299
7673
  contextUsage = { model: requestModelRef, usage: event.usage };
7300
7674
  notifyContext();
@@ -7348,6 +7722,7 @@ ${lines.join("\n")}` : null;
7348
7722
  context: contextUsage
7349
7723
  };
7350
7724
  session = snapshot;
7725
+ if (history.length === 0) return;
7351
7726
  const save = persistQueue.catch(() => void 0).then(() => saveSession(snapshot));
7352
7727
  persistQueue = save;
7353
7728
  await save;
@@ -7385,6 +7760,13 @@ function preferredDetectedModel(detected) {
7385
7760
  }
7386
7761
  return candidates[0];
7387
7762
  }
7763
+ function describeCompaction(result) {
7764
+ const parts = [];
7765
+ if (result.removedMessages > 0) parts.push(`${result.removedMessages} older messages summarised`);
7766
+ if (result.clearedToolResults) parts.push(`${result.clearedToolResults} old tool outputs cleared`);
7767
+ const size = result.tokensBefore !== void 0 && result.tokensAfter !== void 0 ? ` \xB7 ~${tokens(result.tokensBefore)} \u2192 ~${tokens(result.tokensAfter)} tokens` : "";
7768
+ return `Context compacted: ${parts.join(", ")}${size}.`;
7769
+ }
7388
7770
 
7389
7771
  // src/app/ask.ts
7390
7772
  var RU_YES = /* @__PURE__ */ new Set(["y", "\u043D"]);
@@ -7571,8 +7953,8 @@ function validateKey(value) {
7571
7953
  import { render } from "ink";
7572
7954
 
7573
7955
  // src/ui/App.tsx
7574
- import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useStdout as useStdout2 } from "ink";
7575
- import { useCallback, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef4, useState as useState5 } from "react";
7956
+ import { Box as Box14, Text as Text13, useApp, useInput as useInput3, useStdout as useStdout2 } from "ink";
7957
+ import { useCallback, useEffect as useEffect4, useMemo as useMemo6, useRef as useRef5, useState as useState8 } from "react";
7576
7958
 
7577
7959
  // src/ui/terminal-size.ts
7578
7960
  import { useStdout } from "ink";
@@ -7646,6 +8028,40 @@ function stdioServer(name, command, args) {
7646
8028
  return parsed.success ? { ok: true, name, config: parsed.data } : { ok: false, error: "usage" };
7647
8029
  }
7648
8030
 
8031
+ // src/ui/speed.ts
8032
+ var CHARS_PER_TOKEN = 4;
8033
+ var MIN_LIVE_MS = 1e3;
8034
+ function createSpeedMeter() {
8035
+ return { streamedChars: 0, firstTokenAt: null, last: null };
8036
+ }
8037
+ function recordSpeed(meter, event, now = Date.now()) {
8038
+ switch (event.type) {
8039
+ case "turn_start":
8040
+ meter.streamedChars = 0;
8041
+ meter.firstTokenAt = null;
8042
+ break;
8043
+ case "text_delta":
8044
+ case "thinking_delta":
8045
+ meter.firstTokenAt ??= now;
8046
+ meter.streamedChars += event.text.length;
8047
+ break;
8048
+ case "speed":
8049
+ meter.last = event.outputTokens / (event.ms / 1e3);
8050
+ meter.streamedChars = 0;
8051
+ meter.firstTokenAt = null;
8052
+ break;
8053
+ }
8054
+ }
8055
+ function readSpeed(meter, busy, now = Date.now()) {
8056
+ if (busy && meter.firstTokenAt !== null && meter.streamedChars > 0) {
8057
+ const ms = now - meter.firstTokenAt;
8058
+ if (ms >= MIN_LIVE_MS) {
8059
+ return { tokensPerSecond: meter.streamedChars / CHARS_PER_TOKEN / (ms / 1e3), live: true };
8060
+ }
8061
+ }
8062
+ return meter.last === null || !Number.isFinite(meter.last) ? null : { tokensPerSecond: meter.last, live: false };
8063
+ }
8064
+
7649
8065
  // src/ui/commands.ts
7650
8066
  var BUSY_SAFE = { runsWhileBusy: true };
7651
8067
  var COMMANDS = [
@@ -7657,6 +8073,7 @@ var COMMANDS = [
7657
8073
  { name: "refresh", args: "[provider]" },
7658
8074
  { name: "effort", ...BUSY_SAFE },
7659
8075
  { name: "thinking", ...BUSY_SAFE },
8076
+ { name: "bell", args: "[on|off]", ...BUSY_SAFE },
7660
8077
  { name: "context", args: "[tokens|auto]", ...BUSY_SAFE },
7661
8078
  { name: "budget", args: "[tokens]", ...BUSY_SAFE },
7662
8079
  { name: "theme", args: "[color]", ...BUSY_SAFE },
@@ -7676,9 +8093,9 @@ var COMMANDS = [
7676
8093
  { name: "resume" },
7677
8094
  { name: "undo" },
7678
8095
  { name: "clear" },
8096
+ { name: "subagents", ...BUSY_SAFE },
7679
8097
  { name: "help", ...BUSY_SAFE },
7680
- { name: "exit", ...BUSY_SAFE },
7681
- { name: "subagents", ...BUSY_SAFE }
8098
+ { name: "exit", ...BUSY_SAFE }
7682
8099
  ];
7683
8100
  var SUBCOMMANDS = [
7684
8101
  { name: "mcp add", args: "<name> <url|-- command>" },
@@ -7733,17 +8150,23 @@ function isSubsequence2(needle, haystack) {
7733
8150
  return needle.length === 0;
7734
8151
  }
7735
8152
  function editDistance(a, b) {
8153
+ let beforePrevious = [];
7736
8154
  let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
7737
8155
  for (let i = 1; i <= a.length; i += 1) {
7738
8156
  const current = [i];
7739
8157
  for (let j = 1; j <= b.length; j += 1) {
7740
8158
  const cost = a[i - 1] === b[j - 1] ? 0 : 1;
7741
- current[j] = Math.min(
8159
+ let best = Math.min(
7742
8160
  (current[j - 1] ?? 0) + 1,
7743
8161
  (previous[j] ?? 0) + 1,
7744
8162
  (previous[j - 1] ?? 0) + cost
7745
8163
  );
8164
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
8165
+ best = Math.min(best, (beforePrevious[j - 2] ?? 0) + 1);
8166
+ }
8167
+ current[j] = best;
7746
8168
  }
8169
+ beforePrevious = previous;
7747
8170
  previous = current;
7748
8171
  }
7749
8172
  return previous[b.length] ?? 0;
@@ -7804,11 +8227,19 @@ var PRESETS = {
7804
8227
  white: "#e5e7eb"
7805
8228
  };
7806
8229
  var DEFAULT_ACCENT = PRESETS.purple;
8230
+ function parseAccent(value) {
8231
+ const trimmed = value.trim().toLowerCase();
8232
+ if (Object.hasOwn(PRESETS, trimmed)) return trimmed;
8233
+ const hex = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/.exec(trimmed)?.[1];
8234
+ if (!hex) return void 0;
8235
+ const full = hex.length === 3 ? [...hex].map((digit) => digit + digit).join("") : hex;
8236
+ return `#${full}`;
8237
+ }
7807
8238
  function resolveAccent(value) {
7808
8239
  if (!value) return DEFAULT_ACCENT;
7809
- const preset = PRESETS[value.toLowerCase()];
7810
- if (preset) return preset;
7811
- return /^#[0-9a-f]{6}$/i.test(value) ? value : DEFAULT_ACCENT;
8240
+ const parsed = parseAccent(value);
8241
+ if (!parsed) return DEFAULT_ACCENT;
8242
+ return PRESETS[parsed] ?? parsed;
7812
8243
  }
7813
8244
  function makeTheme(accent) {
7814
8245
  return {
@@ -7872,10 +8303,12 @@ var en = {
7872
8303
  placeholder: "ask, or / for commands",
7873
8304
  working: "working\u2026",
7874
8305
  suggestHelp: "\u2191\u2193 move \xB7 tab complete \xB7 enter run",
7875
- more: (count) => `\u2026 ${count} more`,
8306
+ more: (count2) => `\u2026 ${count2} more`,
7876
8307
  allowOnce: "allow once",
7877
8308
  always: "always",
7878
8309
  deny: "deny",
8310
+ dangerousCommand: "risky command",
8311
+ permissionKeys: "\u2191/\u2193 choose \xB7 enter confirm \xB7 y/a/n \xB7 esc deny",
7879
8312
  yes: "yes",
7880
8313
  noDefault: "no (default)",
7881
8314
  noMatches: "no matches",
@@ -7890,7 +8323,7 @@ var en = {
7890
8323
  titleModelRefresh: "Reload models for a provider",
7891
8324
  titleSessions: "Resume a session",
7892
8325
  titleSessionAction: "Session action",
7893
- resumed: (id, count) => `Resumed ${id} \u2014 ${count} messages restored`,
8326
+ resumed: (id, count2) => `Resumed ${id} \u2014 ${count2} messages restored`,
7894
8327
  sessionsEmpty: "No saved sessions yet.",
7895
8328
  providerSet: (id, model) => `Provider: ${id} \xB7 model ${model}`,
7896
8329
  providersEmpty: "No providers configured. Use /login to add one.",
@@ -7898,11 +8331,15 @@ var en = {
7898
8331
  providerBalance: (balances) => `provider balance: ${balances}`,
7899
8332
  providerKeyRemaining: (balances) => `API key limit remaining: ${balances}`,
7900
8333
  noThink: "no-think",
7901
- mcpFailed: (count) => `mcp:${count} failed`,
8334
+ mcpFailed: (count2) => `mcp:${count2} failed`,
7902
8335
  escCancel: "esc to cancel",
8336
+ escClear: "esc clears the input \xB7 esc again cancels the answer",
7903
8337
  escHelp: "esc \u2014 cancel the running turn",
7904
8338
  cancelled: "Cancelled.",
7905
- queued: (count) => `queued: ${count}`,
8339
+ queued: (count2) => `queued: ${count2}`,
8340
+ pasteToken: (id, lines, chars) => `[paste #${id} \xB7 ${lines > 1 ? `${lines} lines` : `${chars} chars`}]`,
8341
+ inputRows: (from, to, total) => `lines ${from}\u2013${to} of ${total}`,
8342
+ queueHint: "waiting for the current answer \xB7 \u2191/\u2193 pick one to edit \xB7 enter puts it back in its place",
7906
8343
  configAt: (path15) => `config: ${path15}`,
7907
8344
  skillsEmpty: "No skills installed. Drop a folder with a SKILL.md into ~/.kitcode/skills or ./.kitcode/skills",
7908
8345
  skillsInstallUsage: "Usage: /skills install <github-url|npm-package|local-path>",
@@ -7921,7 +8358,7 @@ var en = {
7921
8358
  mcpDeleted: (name) => `MCP "${name}" disconnected and removed.`,
7922
8359
  mcpDeleteFailed: (name, error) => `Could not remove MCP "${name}": ${error}`,
7923
8360
  mcpNotFound: (name) => `MCP server "${name}" was not found.`,
7924
- mcpTools: (count) => `${count} tools`,
8361
+ mcpTools: (count2) => `${count2} tools`,
7925
8362
  mcpEnabled: (name, tools) => `MCP "${name}" enabled \xB7 ${tools} tools`,
7926
8363
  mcpDisabled: (name) => `MCP "${name}" disabled.`,
7927
8364
  mcpToggleFailed: (name, error) => `Could not change MCP "${name}": ${error}`,
@@ -7931,14 +8368,24 @@ var en = {
7931
8368
  error: "error",
7932
8369
  disabled: "disabled"
7933
8370
  },
7934
- activeAgents: (count) => `agents: ${count}`,
7935
- subagentsStatus: (count) => count > 0 ? `Active sub-agents: ${count}` : "No active sub-agents.",
8371
+ moreAgents: (count2) => `+${count2} more`,
8372
+ noSubagents: "No sub-agents in this session yet.",
8373
+ subagentsTitle: "Open a sub-agent chat",
8374
+ subagentRunning: "running",
8375
+ subagentDone: "done",
8376
+ subagentWaiting: "waiting for the first step\u2026",
8377
+ subagentBack: "esc back",
7936
8378
  attachUsage: "Usage: /attach <image-or-text-file> \xB7 /attach clipboard \xB7 /attach clear",
7937
8379
  attachmentAdded: (name) => `Attached for the next message: ${name}`,
7938
8380
  attachmentsCleared: "Pending attachments cleared.",
7939
8381
  compactNothing: "Not enough older conversation to compact yet.",
7940
8382
  compactStarting: "Compacting context\u2026",
7941
- compactDone: (count) => `Context compacted \xB7 ${count} older messages replaced with a summary.`,
8383
+ compactDone: (summarised, cleared, before, after) => [
8384
+ "Context compacted",
8385
+ summarised > 0 ? `${summarised} older messages replaced with a summary` : "",
8386
+ cleared > 0 ? `${cleared} old tool outputs cleared` : "",
8387
+ before !== void 0 && after !== void 0 ? `~${tokens(before)} \u2192 ~${tokens(after)} tokens` : ""
8388
+ ].filter(Boolean).join(" \xB7 ") + ".",
7942
8389
  sessionRenameUsage: "Type the title after the command: /sessions rename <id> <title>",
7943
8390
  sessionRenamed: (title) => `Session renamed to "${title}".`,
7944
8391
  sessionDeleteAsk: "The saved chat file will be deleted. This cannot be undone.",
@@ -7947,7 +8394,7 @@ var en = {
7947
8394
  sessionDeleteAllAskBody: "All KitCode sessions from every workspace will be permanently deleted.",
7948
8395
  sessionDeleteAllFinalAsk: "Final confirmation: delete all chats?",
7949
8396
  sessionDeleteAllFinalBody: "This cannot be undone. Press y only if you want to erase the complete session history.",
7950
- sessionsDeletedAll: (count) => `All saved chats deleted \xB7 ${count} sessions removed.`,
8397
+ sessionsDeletedAll: (count2) => `All saved chats deleted \xB7 ${count2} sessions removed.`,
7951
8398
  sessionsDeleteAllFailed: (deleted, failed) => `Session cleanup was incomplete \xB7 ${deleted} removed, ${failed} failed.`,
7952
8399
  sessionExported: (path15) => `Session exported to ${path15}`,
7953
8400
  sessionActionResume: "resume",
@@ -7960,14 +8407,18 @@ ${url}`,
7960
8407
  updateCurrent: (version) => `KitCode ${version} is up to date.`,
7961
8408
  updateFailed: (reason) => `Could not check for updates: ${reason}.`,
7962
8409
  accentSet: (name, hex) => `Accent: ${name} (${hex})`,
8410
+ accentInvalid: (value) => `"${value}" is not a colour. Use a preset name or a hex code like #ff8800 or #f80.`,
8411
+ customAccent: "custom colour\u2026",
8412
+ customAccentPrompt: "Hex colour, e.g. #ff8800 or #f80",
7963
8413
  reasoning: (on) => `Reasoning ${on ? "on" : "off"}`,
8414
+ bellState: (on) => on ? "Sound on: a beep when a long answer finishes or a permission is needed." : "Sound off.",
7964
8415
  effortSet: (value) => `Effort: ${value}`,
7965
- budgetCurrent: (tokens3) => `Token budget: ${tokens3.toLocaleString()} per turn`,
7966
- budgetSet: (tokens3) => `Token budget set to ${tokens3.toLocaleString()} per turn`,
8416
+ budgetCurrent: (tokens2) => `Token budget: ${tokens2.toLocaleString()} per turn`,
8417
+ budgetSet: (tokens2) => `Token budget set to ${tokens2.toLocaleString()} per turn`,
7967
8418
  budgetUnlimited: "Token budget: unlimited (0). Set /budget <tokens> to restore a limit.",
7968
8419
  budgetInvalid: "Invalid budget: must be between 1,000 and 10,000,000",
7969
8420
  modelSet: (value) => `Model: ${value}`,
7970
- modelsRefreshed: (provider, count) => `${provider}: ${count} models reloaded from the endpoint.`,
8421
+ modelsRefreshed: (provider, count2) => `${provider}: ${count2} models reloaded from the endpoint.`,
7971
8422
  noModels: "No models available. Use /login to add a provider.",
7972
8423
  bypassOff: "Approval prompts are back on.",
7973
8424
  bypassAsk1: "Disable approval prompts for this session?",
@@ -7994,14 +8445,14 @@ ${url}`,
7994
8445
  promptsEmpty: "No saved prompts. Use /prompt save <name>.",
7995
8446
  unknownCommand: (name) => `Unknown command /${name}. Try /help`,
7996
8447
  didYouMean: (typed, guess) => `Unknown command /${typed}. Did you mean /${guess}?`,
7997
- undoNothing: "Nothing to undo in this session.",
8448
+ undoNothing: "Nothing to undo: no file changes made by kitcode in this folder.",
7998
8449
  undoResult: (restored, removed) => `Undo complete: ${restored} restored, ${removed} newly created removed.`,
7999
8450
  undoConflicts: (paths) => `Kept newer changes in: ${paths.join(", ")}`,
8000
8451
  undoFailed: (failures) => `Could not restore: ${failures.join("; ")}`,
8001
8452
  titleContext: "Context window",
8002
- contextCurrent: (tokens3) => `Context window: ${tokens3.toLocaleString()} tokens`,
8003
- contextSet: (tokens3) => `Context window set to ${tokens3.toLocaleString()} tokens. Auto-compact triggers at 80%.`,
8004
- contextAuto: (tokens3) => tokens3 === null ? "Context window: unknown \u2014 using whatever the provider reports." : `Context window: auto (${tokens3.toLocaleString()} tokens from the provider).`,
8453
+ contextCurrent: (tokens2) => `Context window: ${tokens2.toLocaleString()} tokens`,
8454
+ contextSet: (tokens2) => `Context window set to ${tokens2.toLocaleString()} tokens. Auto-compact triggers at 80%.`,
8455
+ contextAuto: (tokens2) => tokens2 === null ? "Context window: unknown \u2014 using whatever the provider reports." : `Context window: auto (${tokens2.toLocaleString()} tokens from the provider).`,
8005
8456
  contextUnknown: "Context window: unknown. Set it with /context 256k, /context 512k or /context 1m.",
8006
8457
  contextInvalid: "Usage: /context 256k | 512k | 1m | <tokens> | auto",
8007
8458
  contextCurrentLabel: "current",
@@ -8014,7 +8465,7 @@ ${url}`,
8014
8465
  titleMcpDelete: "Remove an MCP server",
8015
8466
  titleMcpEnable: "Enable an MCP server",
8016
8467
  titleMcpDisable: "Disable an MCP server",
8017
- modeLabel: { normal: "normal", accept: "auto-accept edits", plan: "plan only" },
8468
+ modeLabel: { normal: "normal", accept: "auto-accept edits", plan: "plan only", bypass: "BYPASS \u2014 no permission prompts" },
8018
8469
  modeChanged: (label) => `Mode: ${label} (shift+tab to cycle)`,
8019
8470
  cmd: {
8020
8471
  model: "switch model",
@@ -8025,6 +8476,7 @@ ${url}`,
8025
8476
  refresh: "reload the model list for a provider",
8026
8477
  effort: "set reasoning depth",
8027
8478
  thinking: "toggle reasoning",
8479
+ bell: "toggle the sound for finished answers and permission prompts",
8028
8480
  context: "set or show the model context window",
8029
8481
  budget: "set token budget per turn",
8030
8482
  theme: "change the accent colour",
@@ -8058,7 +8510,7 @@ ${url}`,
8058
8510
  clear: "start a fresh session",
8059
8511
  help: "list commands",
8060
8512
  exit: "quit",
8061
- subagents: "show active sub-agents and what they are doing"
8513
+ subagents: "open a sub-agent and watch its chat live"
8062
8514
  }
8063
8515
  };
8064
8516
  var ru = {
@@ -8072,10 +8524,12 @@ var ru = {
8072
8524
  placeholder: "\u0441\u043F\u0440\u043E\u0441\u0438, \u0438\u043B\u0438 / \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434",
8073
8525
  working: "\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u2026",
8074
8526
  suggestHelp: "\u2191\u2193 \u0432\u044B\u0431\u043E\u0440 \xB7 tab \u2014 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u044C \xB7 enter \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C",
8075
- more: (count) => `\u2026 \u0435\u0449\u0451 ${count}`,
8527
+ more: (count2) => `\u2026 \u0435\u0449\u0451 ${count2}`,
8076
8528
  allowOnce: "\u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u043E\u0434\u0438\u043D \u0440\u0430\u0437",
8077
8529
  always: "\u0432\u0441\u0435\u0433\u0434\u0430",
8078
8530
  deny: "\u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C",
8531
+ dangerousCommand: "\u0440\u0438\u0441\u043A\u043E\u0432\u0430\u043D\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430",
8532
+ permissionKeys: "\u2191/\u2193 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \xB7 enter \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \xB7 y/a/n \xB7 esc \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C",
8079
8533
  yes: "\u0434\u0430",
8080
8534
  noDefault: "\u043D\u0435\u0442 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E)",
8081
8535
  noMatches: "\u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
@@ -8090,7 +8544,7 @@ var ru = {
8090
8544
  titleModelRefresh: "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u0438 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
8091
8545
  titleSessions: "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E",
8092
8546
  titleSessionAction: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439",
8093
- resumed: (id, count) => `\u0421\u0435\u0441\u0441\u0438\u044F ${id} \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439: ${count}`,
8547
+ resumed: (id, count2) => `\u0421\u0435\u0441\u0441\u0438\u044F ${id} \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439: ${count2}`,
8094
8548
  sessionsEmpty: "\u0421\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442.",
8095
8549
  providerSet: (id, model) => `\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440: ${id} \xB7 \u043C\u043E\u0434\u0435\u043B\u044C ${model}`,
8096
8550
  providersEmpty: "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432 \u043D\u0435\u0442. \u0414\u043E\u0431\u0430\u0432\u044C \u0447\u0435\u0440\u0435\u0437 /login.",
@@ -8098,11 +8552,15 @@ var ru = {
8098
8552
  providerBalance: (balances) => `\u0431\u0430\u043B\u0430\u043D\u0441 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430: ${balances}`,
8099
8553
  providerKeyRemaining: (balances) => `\u043E\u0441\u0442\u0430\u0442\u043E\u043A \u043B\u0438\u043C\u0438\u0442\u0430 API-\u043A\u043B\u044E\u0447\u0430: ${balances}`,
8100
8554
  noThink: "\u0431\u0435\u0437 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8101
- mcpFailed: (count) => `mcp: ${count} \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439`,
8555
+ mcpFailed: (count2) => `mcp: ${count2} \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439`,
8102
8556
  escCancel: "esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C",
8557
+ escClear: "esc \u2014 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043B\u0435 \xB7 \u0435\u0449\u0451 \u0440\u0430\u0437 esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u043E\u0442\u0432\u0435\u0442",
8103
8558
  escHelp: "esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0445\u043E\u0434",
8104
8559
  cancelled: "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E.",
8105
- queued: (count) => `\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438: ${count}`,
8560
+ queued: (count2) => `\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438: ${count2}`,
8561
+ pasteToken: (id, lines, chars) => `[\u0432\u0441\u0442\u0430\u0432\u043A\u0430 #${id} \xB7 ${lines > 1 ? `${lines} \u0441\u0442\u0440\u043E\u043A` : `${chars} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432`}]`,
8562
+ inputRows: (from, to, total) => `\u0441\u0442\u0440\u043E\u043A\u0438 ${from}\u2013${to} \u0438\u0437 ${total}`,
8563
+ queueHint: "\u0436\u0434\u0443\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043E\u0442\u0432\u0435\u0442\u0430 \xB7 \u2191/\u2193 \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u043E\u0434\u043D\u043E \u0434\u043B\u044F \u043F\u0440\u0430\u0432\u043A\u0438 \xB7 enter \u0432\u0435\u0440\u043D\u0451\u0442 \u0435\u0433\u043E \u043D\u0430 \u043C\u0435\u0441\u0442\u043E",
8106
8564
  configAt: (path15) => `\u043A\u043E\u043D\u0444\u0438\u0433: ${path15}`,
8107
8565
  skillsEmpty: "\u0421\u043A\u0438\u043B\u043B\u043E\u0432 \u043D\u0435\u0442. \u041F\u043E\u043B\u043E\u0436\u0438 \u043F\u0430\u043F\u043A\u0443 \u0441 \u0444\u0430\u0439\u043B\u043E\u043C SKILL.md \u0432 ~/.kitcode/skills \u0438\u043B\u0438 ./.kitcode/skills",
8108
8566
  skillsInstallUsage: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /skills install <github-url|npm-\u043F\u0430\u043A\u0435\u0442|\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439-\u043F\u0443\u0442\u044C>",
@@ -8121,7 +8579,7 @@ var ru = {
8121
8579
  mcpDeleted: (name) => `MCP \xAB${name}\xBB \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u0438 \u0443\u0434\u0430\u043B\u0451\u043D.`,
8122
8580
  mcpDeleteFailed: (name, error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0434\u0430\u043B\u0438\u0442\u044C MCP \xAB${name}\xBB: ${error}`,
8123
8581
  mcpNotFound: (name) => `MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \xAB${name}\xBB \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`,
8124
- mcpTools: (count) => `\u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${count}`,
8582
+ mcpTools: (count2) => `\u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${count2}`,
8125
8583
  mcpEnabled: (name, tools) => `MCP \xAB${name}\xBB \u0432\u043A\u043B\u044E\u0447\u0451\u043D \xB7 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${tools}`,
8126
8584
  mcpDisabled: (name) => `MCP \xAB${name}\xBB \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D.`,
8127
8585
  mcpToggleFailed: (name, error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C MCP \xAB${name}\xBB: ${error}`,
@@ -8131,14 +8589,24 @@ var ru = {
8131
8589
  error: "\u043E\u0448\u0438\u0431\u043A\u0430",
8132
8590
  disabled: "\u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D"
8133
8591
  },
8134
- activeAgents: (count) => `\u0430\u0433\u0435\u043D\u0442\u043E\u0432: ${count}`,
8135
- subagentsStatus: (count) => count > 0 ? `\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u043E\u0432: ${count}` : "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u043E\u0432.",
8592
+ moreAgents: (count2) => `+${count2} \u0435\u0449\u0451`,
8593
+ noSubagents: "\u0412 \u044D\u0442\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0435\u0449\u0451 \u043D\u0435 \u0431\u044B\u043B\u043E \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u043E\u0432.",
8594
+ subagentsTitle: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0447\u0430\u0442 \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u0430",
8595
+ subagentRunning: "\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442",
8596
+ subagentDone: "\u0433\u043E\u0442\u043E\u0432\u043E",
8597
+ subagentWaiting: "\u0436\u0434\u0451\u043C \u043F\u0435\u0440\u0432\u044B\u0439 \u0448\u0430\u0433\u2026",
8598
+ subagentBack: "esc \u2014 \u043D\u0430\u0437\u0430\u0434",
8136
8599
  attachUsage: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /attach <\u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0430-\u0438\u043B\u0438-\u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0439-\u0444\u0430\u0439\u043B> \xB7 /attach clipboard \xB7 /attach clear",
8137
8600
  attachmentAdded: (name) => `\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u043E \u043A \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C\u0443 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044E: ${name}`,
8138
8601
  attachmentsCleared: "\u041E\u0436\u0438\u0434\u0430\u044E\u0449\u0438\u0435 \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0443\u0431\u0440\u0430\u043D\u044B.",
8139
8602
  compactNothing: "\u041F\u043E\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0434\u043B\u044F \u0441\u0436\u0430\u0442\u0438\u044F \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430.",
8140
8603
  compactStarting: "\u0421\u0436\u0430\u0442\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430\u2026",
8141
- compactDone: (count) => `\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442 \xB7 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u043C\u0435\u043D\u0435\u043D\u043E \u0441\u0432\u043E\u0434\u043A\u043E\u0439: ${count}.`,
8604
+ compactDone: (summarised, cleared, before, after) => [
8605
+ "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442",
8606
+ summarised > 0 ? `\u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u043C\u0435\u043D\u0435\u043D\u043E \u0441\u0432\u043E\u0434\u043A\u043E\u0439: ${summarised}` : "",
8607
+ cleared > 0 ? `\u043E\u0447\u0438\u0449\u0435\u043D\u043E \u0441\u0442\u0430\u0440\u044B\u0445 \u0432\u044B\u0432\u043E\u0434\u043E\u0432 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432: ${cleared}` : "",
8608
+ before !== void 0 && after !== void 0 ? `~${tokens(before)} \u2192 ~${tokens(after)} \u0442\u043E\u043A\u0435\u043D\u043E\u0432` : ""
8609
+ ].filter(Boolean).join(" \xB7 ") + ".",
8142
8610
  sessionRenameUsage: "\u0414\u043E\u043F\u0438\u0448\u0438 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435: /sessions rename <id> <\u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435>",
8143
8611
  sessionRenamed: (title) => `\u0421\u0435\u0441\u0441\u0438\u044F \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0430 \u0432 \xAB${title}\xBB.`,
8144
8612
  sessionDeleteAsk: "\u0424\u0430\u0439\u043B \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u043E\u0433\u043E \u0447\u0430\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0451\u043D \u0431\u0435\u0437 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u0438 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F.",
@@ -8147,7 +8615,7 @@ var ru = {
8147
8615
  sessionDeleteAllAskBody: "\u0411\u0443\u0434\u0443\u0442 \u043D\u0430\u0432\u0441\u0435\u0433\u0434\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u044B \u0432\u0441\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 KitCode \u0438\u0437 \u0432\u0441\u0435\u0445 \u0440\u0430\u0431\u043E\u0447\u0438\u0445 \u043F\u0430\u043F\u043E\u043A.",
8148
8616
  sessionDeleteAllFinalAsk: "\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435: \u0442\u043E\u0447\u043D\u043E \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u0441\u0435 \u0447\u0430\u0442\u044B?",
8149
8617
  sessionDeleteAllFinalBody: "\u042D\u0442\u043E \u043D\u0435\u043B\u044C\u0437\u044F \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C. \u041D\u0430\u0436\u0438\u043C\u0430\u0439 y \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0445\u043E\u0447\u0435\u0448\u044C \u0441\u0442\u0435\u0440\u0435\u0442\u044C \u0432\u0441\u044E \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0435\u0441\u0441\u0438\u0439.",
8150
- sessionsDeletedAll: (count) => `\u0412\u0441\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u0447\u0430\u0442\u044B \u0443\u0434\u0430\u043B\u0435\u043D\u044B \xB7 \u0441\u0435\u0441\u0441\u0438\u0439: ${count}.`,
8618
+ sessionsDeletedAll: (count2) => `\u0412\u0441\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u0447\u0430\u0442\u044B \u0443\u0434\u0430\u043B\u0435\u043D\u044B \xB7 \u0441\u0435\u0441\u0441\u0438\u0439: ${count2}.`,
8151
8619
  sessionsDeleteAllFailed: (deleted, failed) => `\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u043D\u0435 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \xB7 \u0443\u0434\u0430\u043B\u0435\u043D\u043E: ${deleted}, \u043E\u0448\u0438\u0431\u043E\u043A: ${failed}.`,
8152
8620
  sessionExported: (path15) => `\u0421\u0435\u0441\u0441\u0438\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0430: ${path15}`,
8153
8621
  sessionActionResume: "\u043E\u0442\u043A\u0440\u044B\u0442\u044C",
@@ -8160,14 +8628,18 @@ ${url}`,
8160
8628
  updateCurrent: (version) => `\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F KitCode ${version}.`,
8161
8629
  updateFailed: (reason) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F: ${reason}.`,
8162
8630
  accentSet: (name, hex) => `\u0426\u0432\u0435\u0442: ${name} (${hex})`,
8631
+ accentInvalid: (value) => `\xAB${value}\xBB \u2014 \u043D\u0435 \u0446\u0432\u0435\u0442. \u0423\u043A\u0430\u0436\u0438 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0438\u043B\u0438 hex-\u043A\u043E\u0434, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 #ff8800 \u0438\u043B\u0438 #f80.`,
8632
+ customAccent: "\u0441\u0432\u043E\u0439 \u0446\u0432\u0435\u0442\u2026",
8633
+ customAccentPrompt: "Hex-\u043A\u043E\u0434 \u0446\u0432\u0435\u0442\u0430, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 #ff8800 \u0438\u043B\u0438 #f80",
8163
8634
  reasoning: (on) => `\u0420\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F ${on ? "\u0432\u043A\u043B\u044E\u0447\u0435\u043D\u044B" : "\u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u044B"}`,
8635
+ bellState: (on) => on ? "\u0417\u0432\u0443\u043A \u0432\u043A\u043B\u044E\u0447\u0451\u043D: \u0441\u0438\u0433\u043D\u0430\u043B, \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u043B\u0433\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u0433\u043E\u0442\u043E\u0432 \u0438\u043B\u0438 \u043D\u0443\u0436\u043D\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435." : "\u0417\u0432\u0443\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D.",
8164
8636
  effortSet: (value) => `\u0413\u043B\u0443\u0431\u0438\u043D\u0430: ${value}`,
8165
- budgetCurrent: (tokens3) => `\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432: ${tokens3.toLocaleString()} \u0437\u0430 \u0445\u043E\u0434`,
8166
- budgetSet: (tokens3) => `\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${tokens3.toLocaleString()} \u0437\u0430 \u0445\u043E\u0434`,
8637
+ budgetCurrent: (tokens2) => `\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432: ${tokens2.toLocaleString()} \u0437\u0430 \u0445\u043E\u0434`,
8638
+ budgetSet: (tokens2) => `\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${tokens2.toLocaleString()} \u0437\u0430 \u0445\u043E\u0434`,
8167
8639
  budgetUnlimited: "\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432: \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442\u043D\u044B\u0439 (0). \u0412\u0432\u0435\u0434\u0438 /budget <\u0447\u0438\u0441\u043B\u043E> \u0447\u0442\u043E\u0431\u044B \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043B\u0438\u043C\u0438\u0442.",
8168
8640
  budgetInvalid: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442: \u043E\u0442 1 000 \u0434\u043E 10 000 000",
8169
8641
  modelSet: (value) => `\u041C\u043E\u0434\u0435\u043B\u044C: ${value}`,
8170
- modelsRefreshed: (provider, count) => `${provider}: \u0441 \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 ${count}.`,
8642
+ modelsRefreshed: (provider, count2) => `${provider}: \u0441 \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 ${count2}.`,
8171
8643
  noModels: "\u041C\u043E\u0434\u0435\u043B\u0435\u0439 \u043D\u0435\u0442. \u0414\u043E\u0431\u0430\u0432\u044C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 /login.",
8172
8644
  bypassOff: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u043D\u043E\u0432\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u044B.",
8173
8645
  bypassAsk1: "\u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043D\u0430 \u044D\u0442\u0443 \u0441\u0435\u0441\u0441\u0438\u044E?",
@@ -8194,14 +8666,14 @@ ${url}`,
8194
8666
  promptsEmpty: "\u0421\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0445 \u043F\u0440\u043E\u043C\u0442\u043E\u0432 \u043D\u0435\u0442. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 /prompt save <\u0438\u043C\u044F>.",
8195
8667
  unknownCommand: (name) => `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 /${name}. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439 /help`,
8196
8668
  didYouMean: (typed, guess) => `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 /${typed}. \u041C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C /${guess}?`,
8197
- undoNothing: "\u0412 \u044D\u0442\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u043F\u043E\u043A\u0430 \u043D\u0435\u0447\u0435\u0433\u043E \u043E\u0442\u043A\u0430\u0442\u044B\u0432\u0430\u0442\u044C.",
8669
+ undoNothing: "\u041D\u0435\u0447\u0435\u0433\u043E \u043E\u0442\u043A\u0430\u0442\u044B\u0432\u0430\u0442\u044C: kitcode \u0435\u0449\u0451 \u043D\u0435 \u043C\u0435\u043D\u044F\u043B \u0444\u0430\u0439\u043B\u044B \u0432 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0435.",
8198
8670
  undoResult: (restored, removed) => `\u041E\u0442\u043A\u0430\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D: \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E ${restored}, \u0443\u0434\u0430\u043B\u0435\u043D\u043E \u043D\u043E\u0432\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432 ${removed}.`,
8199
8671
  undoConflicts: (paths) => `\u0411\u043E\u043B\u0435\u0435 \u0441\u0432\u0435\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u0432: ${paths.join(", ")}`,
8200
8672
  undoFailed: (failures) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C: ${failures.join("; ")}`,
8201
8673
  titleContext: "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430",
8202
- contextCurrent: (tokens3) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432`,
8203
- contextSet: (tokens3) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432. \u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 80%.`,
8204
- contextAuto: (tokens3) => tokens3 === null ? "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E \u2014 \u0431\u0435\u0440\u0451\u043C \u0442\u043E, \u0447\u0442\u043E \u0441\u043E\u043E\u0431\u0449\u0430\u0435\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440." : `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u0430\u0432\u0442\u043E (${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430).`,
8674
+ contextCurrent: (tokens2) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens2.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432`,
8675
+ contextSet: (tokens2) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens2.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432. \u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 80%.`,
8676
+ contextAuto: (tokens2) => tokens2 === null ? "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E \u2014 \u0431\u0435\u0440\u0451\u043C \u0442\u043E, \u0447\u0442\u043E \u0441\u043E\u043E\u0431\u0449\u0430\u0435\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440." : `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u0430\u0432\u0442\u043E (${tokens2.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430).`,
8205
8677
  contextUnknown: "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E. \u0417\u0430\u0434\u0430\u0439 \u0435\u0433\u043E: /context 256k, /context 512k \u0438\u043B\u0438 /context 1m.",
8206
8678
  contextInvalid: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /context 256k | 512k | 1m | <\u0442\u043E\u043A\u0435\u043D\u044B> | auto",
8207
8679
  contextCurrentLabel: "\u0442\u0435\u043A\u0443\u0449\u0435\u0435",
@@ -8214,7 +8686,7 @@ ${url}`,
8214
8686
  titleMcpDelete: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8215
8687
  titleMcpEnable: "\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8216
8688
  titleMcpDisable: "\u0412\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8217
- modeLabel: { normal: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439", accept: "\u043F\u0440\u0430\u0432\u043A\u0438 \u0431\u0435\u0437 \u0441\u043F\u0440\u043E\u0441\u0430", plan: "\u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043B\u0430\u043D" },
8689
+ modeLabel: { normal: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439", accept: "\u043F\u0440\u0430\u0432\u043A\u0438 \u0431\u0435\u0437 \u0441\u043F\u0440\u043E\u0441\u0430", plan: "\u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043B\u0430\u043D", bypass: "BYPASS \u2014 \u0431\u0435\u0437 \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0439" },
8218
8690
  modeChanged: (label) => `\u0420\u0435\u0436\u0438\u043C: ${label} (shift+tab \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C)`,
8219
8691
  cmd: {
8220
8692
  model: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u044C",
@@ -8225,6 +8697,7 @@ ${url}`,
8225
8697
  refresh: "\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
8226
8698
  effort: "\u0433\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8227
8699
  thinking: "\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F",
8700
+ bell: "\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0437\u0432\u0443\u043A \u043E \u0433\u043E\u0442\u043E\u0432\u043E\u043C \u043E\u0442\u0432\u0435\u0442\u0435 \u0438 \u0437\u0430\u043F\u0440\u043E\u0441\u0430\u0445 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0439",
8228
8701
  context: "\u0437\u0430\u0434\u0430\u0442\u044C \u0438\u043B\u0438 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u043C\u043E\u0434\u0435\u043B\u0438",
8229
8702
  budget: "\u043B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0437\u0430 \u0445\u043E\u0434 (0 = \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442)",
8230
8703
  theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
@@ -8257,7 +8730,7 @@ ${url}`,
8257
8730
  clear: "\u043D\u0430\u0447\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0441\u0435\u0441\u0441\u0438\u044E",
8258
8731
  help: "\u0441\u043F\u0438\u0441\u043E\u043A \u043A\u043E\u043C\u0430\u043D\u0434",
8259
8732
  exit: "\u0432\u044B\u0445\u043E\u0434",
8260
- subagents: "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u043E\u0432 \u0438 \u0447\u0435\u043C \u0437\u0430\u043D\u044F\u0442\u044B"
8733
+ subagents: "\u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0443\u0431-\u0430\u0433\u0435\u043D\u0442\u0430 \u0438 \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0435\u0433\u043E \u0447\u0430\u0442 \u0432\u0436\u0438\u0432\u0443\u044E"
8261
8734
  }
8262
8735
  };
8263
8736
  var DICTIONARIES = { en, ru };
@@ -8468,9 +8941,11 @@ function Onboarding({ onSubmit }) {
8468
8941
 
8469
8942
  // src/ui/components/PermissionPrompt.tsx
8470
8943
  import { Box as Box6, Text as Text6 } from "ink";
8944
+ import { useEffect, useRef as useRef2, useState as useState3 } from "react";
8471
8945
 
8472
8946
  // src/ui/components/Diff.tsx
8473
8947
  import { Box as Box5, Text as Text5 } from "ink";
8948
+ import stringWidth from "string-width";
8474
8949
 
8475
8950
  // src/ui/diff.ts
8476
8951
  var MAX_RENDER_DIFF_CHARS = 5e5;
@@ -8497,28 +8972,54 @@ function diffBody(before, after) {
8497
8972
  let added = 0;
8498
8973
  let removed = 0;
8499
8974
  for (let i = Math.max(0, start - 2); i < start; i += 1) {
8500
- hunk.push({ kind: "ctx", text: a[i] ?? "" });
8975
+ hunk.push({ kind: "ctx", text: a[i] ?? "", line: i + 1 });
8501
8976
  }
8977
+ const dels = [];
8502
8978
  for (let i = start; i < endA; i += 1) {
8503
- hunk.push({ kind: "del", text: a[i] ?? "" });
8979
+ dels.push({ kind: "del", text: a[i] ?? "", line: i + 1 });
8504
8980
  removed += 1;
8505
8981
  }
8982
+ const adds = [];
8506
8983
  for (let i = start; i < endB; i += 1) {
8507
- hunk.push({ kind: "add", text: b[i] ?? "" });
8984
+ adds.push({ kind: "add", text: b[i] ?? "", line: i + 1 });
8508
8985
  added += 1;
8509
8986
  }
8987
+ if (dels.length === adds.length) {
8988
+ dels.forEach((del, index) => {
8989
+ const add = adds[index];
8990
+ const span = changedSpans(del.text, add.text);
8991
+ if (span) {
8992
+ del.changed = span.before;
8993
+ add.changed = span.after;
8994
+ }
8995
+ });
8996
+ }
8997
+ hunk.push(...dels, ...adds);
8510
8998
  for (let i = endA; i < Math.min(a.length, endA + 2); i += 1) {
8511
- hunk.push({ kind: "ctx", text: a[i] ?? "" });
8999
+ hunk.push({ kind: "ctx", text: a[i] ?? "", line: i + 1 });
8512
9000
  }
8513
9001
  return { added, removed, hunk };
8514
9002
  }
9003
+ function changedSpans(before, after) {
9004
+ if (before === after) return null;
9005
+ let head = 0;
9006
+ while (head < before.length && head < after.length && before[head] === after[head]) head += 1;
9007
+ let tail2 = 0;
9008
+ while (tail2 < before.length - head && tail2 < after.length - head && before[before.length - 1 - tail2] === after[after.length - 1 - tail2]) tail2 += 1;
9009
+ if (head + tail2 < Math.min(before.length, after.length) / 3) return null;
9010
+ return { before: [head, before.length - tail2], after: [head, after.length - tail2] };
9011
+ }
8515
9012
  function truncate2(text, width) {
8516
9013
  return text.length <= width ? text : `${text.slice(0, width - 1)}\u2026`;
8517
9014
  }
8518
9015
 
8519
9016
  // src/ui/components/Diff.tsx
8520
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
8521
- function Diff({ before, after }) {
9017
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
9018
+ var STYLE = {
9019
+ add: { sign: "+", fg: "#86efac", bg: "#173822", strong: "#2c6a40" },
9020
+ del: { sign: "-", fg: "#fca5a5", bg: "#3f1c1c", strong: "#83302d" }
9021
+ };
9022
+ function Diff({ before, after, indent }) {
8522
9023
  if (!canRenderDiff(before, after)) {
8523
9024
  return /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
8524
9025
  "Diff preview omitted: it exceeds ",
@@ -8527,77 +9028,152 @@ function Diff({ before, after }) {
8527
9028
  ] });
8528
9029
  }
8529
9030
  const { hunk, hidden } = diffLines(sanitizeTerminalText(before), sanitizeTerminalText(after));
8530
- return /* @__PURE__ */ jsx5(DiffHunk, { lines: hunk, hidden });
9031
+ return /* @__PURE__ */ jsx5(DiffHunk, { lines: hunk, hidden, indent });
8531
9032
  }
8532
- function DiffHunk({ lines, hidden }) {
9033
+ function DiffHunk({ lines, hidden, indent = 2 }) {
9034
+ const { columns } = useTerminalSize();
9035
+ const gutter = Math.max(1, ...lines.map((line) => String(line.line ?? "").length));
9036
+ const width = Math.max(10, (Number.isFinite(columns) ? columns : 80) - indent - gutter - 4);
8533
9037
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
8534
- lines.map((line, index) => /* @__PURE__ */ jsxs5(
8535
- Text5,
8536
- {
8537
- color: line.kind === "add" ? "green" : line.kind === "del" ? "red" : "gray",
8538
- dimColor: line.kind === "ctx",
8539
- children: [
8540
- line.kind === "add" ? "+ " : line.kind === "del" ? "- " : " ",
8541
- truncate2(sanitizeTerminalText(line.text), 110)
8542
- ]
8543
- },
8544
- index
8545
- )),
9038
+ lines.map((line, index) => {
9039
+ const text = sanitizeTerminalText(line.text).replace(/\t/g, " ").replace(/\r$/, "");
9040
+ const number = String(line.line ?? "").padStart(gutter);
9041
+ if (line.kind === "ctx") {
9042
+ return /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
9043
+ number,
9044
+ " ",
9045
+ clip2(text, width)
9046
+ ] }, index);
9047
+ }
9048
+ const style = STYLE[line.kind];
9049
+ const shown = clip2(text, width);
9050
+ const span = line.changed && shown === text && text === line.text ? line.changed : void 0;
9051
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
9052
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
9053
+ number,
9054
+ " "
9055
+ ] }),
9056
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, backgroundColor: style.bg, children: /* @__PURE__ */ jsxs5(Text5, { color: style.fg, wrap: "truncate-end", children: [
9057
+ style.sign,
9058
+ " ",
9059
+ span ? /* @__PURE__ */ jsxs5(Fragment, { children: [
9060
+ shown.slice(0, span[0]),
9061
+ /* @__PURE__ */ jsx5(Text5, { backgroundColor: style.strong, bold: true, children: shown.slice(span[0], span[1]) }),
9062
+ shown.slice(span[1])
9063
+ ] }) : shown
9064
+ ] }) })
9065
+ ] }, index);
9066
+ }),
8546
9067
  hidden > 0 && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
8547
- " \u2026 ",
9068
+ " ".repeat(gutter),
9069
+ " \u2026 ",
8548
9070
  hidden,
8549
9071
  " more lines"
8550
9072
  ] })
8551
9073
  ] });
8552
9074
  }
9075
+ function clip2(text, width) {
9076
+ if (stringWidth(text) <= width) return text;
9077
+ let out = "";
9078
+ for (const character of text) {
9079
+ if (stringWidth(out + character) > width - 1) break;
9080
+ out += character;
9081
+ }
9082
+ return `${out}\u2026`;
9083
+ }
8553
9084
 
8554
9085
  // src/ui/components/PermissionPrompt.tsx
8555
- import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
9086
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8556
9087
  var MAX_PREVIEW_CHARS = 2e4;
9088
+ var ENTER_GRACE_MS = 400;
9089
+ var DANGEROUS = [
9090
+ /\brm\s+(?:-[a-z]*[rf][a-z]*\s+)+/i,
9091
+ /\b(?:rmdir|del|erase|rd)\s+\/[sq]/i,
9092
+ /\bRemove-Item\b.*-Recurse/i,
9093
+ /\bgit\s+(?:push\s+.*(?:--force|-f\b)|reset\s+--hard|clean\s+-[a-z]*f|checkout\s+--\s|branch\s+-D)/i,
9094
+ /\b(?:sudo|doas|runas)\b/i,
9095
+ /\b(?:curl|wget|iwr|Invoke-WebRequest)\b[^|]*\|\s*(?:sh|bash|zsh|iex|Invoke-Expression|python)/i,
9096
+ /\b(?:mkfs|format|diskpart|dd\s+if=)/i,
9097
+ /\bchmod\s+(?:-R\s+)?777\b/i,
9098
+ />\s*\/dev\/(?:sd|nvme|disk)/i,
9099
+ /\bDROP\s+(?:TABLE|DATABASE)\b/i
9100
+ ];
9101
+ function isDangerousCommand(command) {
9102
+ return DANGEROUS.some((pattern) => pattern.test(command));
9103
+ }
8557
9104
  function PermissionPrompt({ request, onDecide }) {
8558
9105
  const theme = useTheme();
8559
9106
  const strings = useStrings();
9107
+ const options = [
9108
+ { decision: "once", label: strings.allowOnce, key: "y" },
9109
+ ...request.allowAlways !== false ? [{ decision: "always", label: `${strings.always} (${sanitizeTerminalText(request.toolName)})`, key: "a" }] : [],
9110
+ { decision: "deny", label: strings.deny, key: "n" }
9111
+ ];
9112
+ const command = commandOf(request.input);
9113
+ const dangerous = command !== void 0 && isDangerousCommand(command);
9114
+ const [selected, setSelected] = useState3(dangerous ? options.length - 1 : 0);
9115
+ const shownAt = useRef2(Date.now());
9116
+ useEffect(() => {
9117
+ shownAt.current = Date.now();
9118
+ setSelected(dangerous ? options.length - 1 : 0);
9119
+ }, [request]);
8560
9120
  useTerminalInput((char, key) => {
8561
9121
  const c = normalizeHotkey(char);
8562
- if (c === "y") onDecide("once");
8563
- else if (c === "a" && request.allowAlways !== false) onDecide("always");
8564
- else if (c === "n" || key.escape || key.return) onDecide("deny");
9122
+ if (key.upArrow) setSelected((index) => (index - 1 + options.length) % options.length);
9123
+ else if (key.downArrow || key.tab) setSelected((index) => (index + 1) % options.length);
9124
+ else if (key.escape) onDecide("deny");
9125
+ else if (key.return) {
9126
+ if (Date.now() - shownAt.current >= ENTER_GRACE_MS) onDecide(options[selected].decision);
9127
+ } else {
9128
+ const byKey = options.find((option) => option.key === c);
9129
+ if (byKey) onDecide(byKey.decision);
9130
+ }
8565
9131
  });
8566
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: theme.warn, paddingX: 1, children: [
8567
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.warn, children: sanitizeTerminalText(request.summary) }),
8568
- request.display?.kind === "diff" && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
8569
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.path) }),
8570
- /* @__PURE__ */ jsx6(Diff, { before: request.display.before, after: request.display.after })
8571
- ] }),
8572
- request.display?.kind === "text" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: previewText(request.display.text) }),
8573
- request.display?.kind === "list" && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
8574
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.title) }),
8575
- request.display.items.slice(0, 100).map((item, index) => /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
8576
- "\u2022 ",
8577
- previewText(item, 1e3)
8578
- ] }, index))
9132
+ const frame = dangerous ? theme.error : theme.warn;
9133
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: frame, paddingX: 1, children: [
9134
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: frame, children: [
9135
+ dangerous ? `\u26A0 ${strings.dangerousCommand} \xB7 ` : "",
9136
+ sanitizeTerminalText(request.summary)
8579
9137
  ] }),
8580
- !request.display && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: printInput(request.input) }),
8581
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
8582
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "y" }),
8583
- " ",
8584
- strings.allowOnce,
8585
- " \xB7",
8586
- " ",
8587
- request.allowAlways !== false && /* @__PURE__ */ jsxs6(Fragment, { children: [
8588
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "a" }),
8589
- " ",
8590
- strings.always,
8591
- " (",
8592
- sanitizeTerminalText(request.toolName),
8593
- ") \xB7 "
9138
+ command !== void 0 ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: dangerous ? theme.error : void 0, children: [
9139
+ "$ ",
9140
+ previewText(command, 2e3)
9141
+ ] }) }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
9142
+ request.display?.kind === "diff" && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
9143
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.path) }),
9144
+ /* @__PURE__ */ jsx6(Diff, { before: request.display.before, after: request.display.after, indent: 4 })
8594
9145
  ] }),
8595
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "n" }),
8596
- " ",
8597
- strings.deny
8598
- ] }) })
9146
+ request.display?.kind === "text" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: previewText(request.display.text) }),
9147
+ request.display?.kind === "list" && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9148
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.title) }),
9149
+ request.display.items.slice(0, 100).map((item, index) => /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
9150
+ "\u2022 ",
9151
+ previewText(item, 1e3)
9152
+ ] }, index))
9153
+ ] }),
9154
+ !request.display && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: printInput(request.input) })
9155
+ ] }),
9156
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
9157
+ options.map((option, index) => {
9158
+ const active2 = index === selected;
9159
+ return /* @__PURE__ */ jsxs6(Text6, { color: active2 ? frame : void 0, bold: active2, dimColor: !active2, children: [
9160
+ active2 ? "\u276F " : " ",
9161
+ option.label,
9162
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
9163
+ " (",
9164
+ option.key,
9165
+ ")"
9166
+ ] })
9167
+ ] }, option.decision);
9168
+ }),
9169
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: strings.permissionKeys })
9170
+ ] })
8599
9171
  ] });
8600
9172
  }
9173
+ function commandOf(input) {
9174
+ const command = input?.command;
9175
+ return typeof command === "string" && command.trim() !== "" ? command : void 0;
9176
+ }
8601
9177
  function printInput(input) {
8602
9178
  try {
8603
9179
  return previewText(JSON.stringify(input, null, 2));
@@ -8613,15 +9189,15 @@ function previewText(value, limit = MAX_PREVIEW_CHARS) {
8613
9189
 
8614
9190
  // src/ui/components/Picker.tsx
8615
9191
  import { Box as Box7, Text as Text7 } from "ink";
8616
- import { useMemo as useMemo2, useState as useState3 } from "react";
9192
+ import { useMemo as useMemo2, useState as useState4 } from "react";
8617
9193
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8618
9194
  var WINDOW = 10;
8619
9195
  function Picker({ title, items, onSelect, onCancel }) {
8620
9196
  const theme = useTheme();
8621
9197
  const { columns } = useTerminalSize();
8622
9198
  const strings = useStrings();
8623
- const [query, setQuery] = useState3("");
8624
- const [cursor, setCursor] = useState3(0);
9199
+ const [query, setQuery] = useState4("");
9200
+ const [cursor, setCursor] = useState4(0);
8625
9201
  const matches = useMemo2(() => {
8626
9202
  const needle = query.toLowerCase();
8627
9203
  if (needle === "") return items;
@@ -8673,7 +9249,8 @@ function Picker({ title, items, onSelect, onCancel }) {
8673
9249
 
8674
9250
  // src/ui/components/PromptInput.tsx
8675
9251
  import { Box as Box8, Text as Text8 } from "ink";
8676
- import { memo, useEffect, useRef as useRef2, useState as useState4 } from "react";
9252
+ import { memo, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
9253
+ import stringWidth2 from "string-width";
8677
9254
 
8678
9255
  // src/ui/history.ts
8679
9256
  function moveInputHistory(history, index, value, draft, direction) {
@@ -8714,6 +9291,10 @@ var CAT_FRAMES = [
8714
9291
  "/\u1420\uFF61\uA788\uFF61\u141F\\ \u223E"
8715
9292
  ];
8716
9293
  var MAX_INPUT_ROWS = 6;
9294
+ var PASTE_COLLAPSE_CHARS = 1e3;
9295
+ var PASTE_COLLAPSE_LINES = 10;
9296
+ var VISIBLE_QUEUED = 3;
9297
+ var MODE_COLORS = { plan: "#60a5fa", accept: "#fbbf24" };
8717
9298
  var PromptInput = memo(function PromptInput2({
8718
9299
  value,
8719
9300
  onChange,
@@ -8725,37 +9306,48 @@ var PromptInput = memo(function PromptInput2({
8725
9306
  hint,
8726
9307
  history,
8727
9308
  attachments = [],
8728
- onListFiles
9309
+ onListFiles,
9310
+ mode = "normal",
9311
+ bypass = false,
9312
+ queued = [],
9313
+ onNavigateQueue
8729
9314
  }) {
8730
9315
  const theme = useTheme();
8731
9316
  const { columns } = useTerminalSize();
8732
9317
  const panelWidth = Math.max(2, columns - 1);
8733
9318
  const inputRows = MAX_INPUT_ROWS;
8734
9319
  const strings = useStrings();
8735
- const [catFrame, setCatFrame] = useState4(0);
8736
- useEffect(() => {
9320
+ const [catFrame, setCatFrame] = useState5(0);
9321
+ useEffect2(() => {
8737
9322
  if (!disabled) return;
8738
9323
  const timer = setInterval(() => setCatFrame((frame) => (frame + 1) % CAT_FRAMES.length), CAT_FRAME_MS);
8739
9324
  return () => clearInterval(timer);
8740
9325
  }, [disabled]);
8741
- const panelColor = disabled ? theme.warn : theme.accent;
9326
+ const modeColor = bypass ? theme.error : mode === "plan" ? MODE_COLORS.plan : mode === "accept" ? MODE_COLORS.accept : void 0;
9327
+ const panelColor = disabled ? theme.warn : modeColor ?? theme.accent;
9328
+ const modeTitle = bypass ? "BYPASS" : mode !== "normal" ? strings.modeLabel[mode] ?? mode : "";
8742
9329
  const fullTitle = disabled ? `KitCode ${CAT_FRAMES[catFrame]}` : "KitCode";
8743
9330
  const panelTitle = fullTitle.slice(0, Math.max(0, panelWidth - 5));
8744
9331
  const safeValue = sanitizeTerminalText(value);
8745
- const [selectionCursor, setSelectionCursor] = useState4(0);
8746
- const [inputCursor, setInputCursor] = useState4(() => characters2(safeValue).length);
8747
- const [historyIndex, setHistoryIndex] = useState4(null);
8748
- const historyDraft = useRef2("");
8749
- const pendingValue = useRef2(null);
8750
- const valueRef = useRef2(safeValue);
8751
- const cursorRef = useRef2(inputCursor);
9332
+ const [selectionCursor, setSelectionCursor] = useState5(0);
9333
+ const [inputCursor, setInputCursor] = useState5(() => characters2(safeValue).length);
9334
+ const [historyIndex, setHistoryIndex] = useState5(null);
9335
+ const historyDraft = useRef3("");
9336
+ const pendingValue = useRef3(null);
9337
+ const valueRef = useRef3(safeValue);
9338
+ const cursorRef = useRef3(inputCursor);
8752
9339
  valueRef.current = safeValue;
8753
9340
  cursorRef.current = inputCursor;
9341
+ const valueChars = useMemo3(() => characters2(safeValue), [safeValue]);
9342
+ const inputView = valueChars.length === 0 ? null : inputWindow(valueChars, clamp2(inputCursor, 0, valueChars.length), panelWidth - 4, inputRows);
9343
+ const position = inputView && inputView.total > inputRows ? strings.inputRows(inputView.first + 1, inputView.first + inputView.rows.length, inputView.total) : "";
9344
+ const footerText = [modeTitle && `${modeTitle} \xB7 shift+tab`, position].filter(Boolean).join(" \xB7 ");
9345
+ const footer = footerText ? `\u2500 ${footerText} ` : "";
8754
9346
  const commandSuggestions = matchCommands(safeValue);
8755
9347
  const mention = onListFiles ? mentionSpan(safeValue, inputCursor) : null;
8756
- const [files, setFiles] = useState4([]);
9348
+ const [files, setFiles] = useState5([]);
8757
9349
  const mentionQuery = mention?.query ?? null;
8758
- useEffect(() => {
9350
+ useEffect2(() => {
8759
9351
  if (mentionQuery === null || !onListFiles) {
8760
9352
  setFiles([]);
8761
9353
  return;
@@ -8773,10 +9365,10 @@ var PromptInput = memo(function PromptInput2({
8773
9365
  const open3 = suggestions.length > 0 || showFiles;
8774
9366
  const optionCount = showFiles ? files.length : suggestions.length;
8775
9367
  const active2 = Math.min(selectionCursor, Math.max(0, optionCount - 1));
8776
- useEffect(() => {
9368
+ useEffect2(() => {
8777
9369
  setSelectionCursor(0);
8778
9370
  }, [safeValue]);
8779
- useEffect(() => {
9371
+ useEffect2(() => {
8780
9372
  const length = characters2(safeValue).length;
8781
9373
  if (pendingValue.current === safeValue) {
8782
9374
  pendingValue.current = null;
@@ -8796,28 +9388,39 @@ var PromptInput = memo(function PromptInput2({
8796
9388
  historyDraft.current = next;
8797
9389
  onChange(next);
8798
9390
  };
9391
+ const pastes = useRef3(/* @__PURE__ */ new Map());
9392
+ const pasteCounter = useRef3(0);
8799
9393
  const submit = (next) => {
8800
9394
  setHistoryIndex(null);
8801
9395
  historyDraft.current = "";
8802
- onSubmit(next);
9396
+ const expanded = expandPastes(next, pastes.current);
9397
+ pastes.current = /* @__PURE__ */ new Map();
9398
+ onSubmit(expanded);
8803
9399
  };
8804
9400
  const insertPastedText = (pasted) => {
8805
9401
  const inserted = characters2(sanitizeTerminalText(pasted));
8806
9402
  if (inserted.length === 0) return;
8807
9403
  const valueCharacters = characters2(valueRef.current);
8808
9404
  const cursor = clamp2(cursorRef.current, 0, valueCharacters.length);
8809
- valueCharacters.splice(cursor, 0, ...inserted);
8810
- change(valueCharacters.join(""), cursor + inserted.length);
9405
+ const next = valueCharacters.slice(0, cursor).concat(inserted, valueCharacters.slice(cursor));
9406
+ change(next.join(""), cursor + inserted.length);
9407
+ };
9408
+ const collapsePaste = (text) => {
9409
+ const lines = text.split("\n").length;
9410
+ if (text.length < PASTE_COLLAPSE_CHARS && lines <= PASTE_COLLAPSE_LINES) return text;
9411
+ pasteCounter.current += 1;
9412
+ pastes.current.set(pasteCounter.current, text);
9413
+ return strings.pasteToken(pasteCounter.current, lines, text.length);
8811
9414
  };
8812
9415
  useTerminalPaste((pasted) => {
8813
9416
  const safePaste = sanitizeTerminalText(pasted);
8814
9417
  if (onPastePath && looksLikeAttachmentPath(safePaste)) {
8815
9418
  void onPastePath(safePaste).then((consumed) => {
8816
- if (!consumed) insertPastedText(safePaste);
9419
+ if (!consumed) insertPastedText(collapsePaste(safePaste));
8817
9420
  });
8818
9421
  return;
8819
9422
  }
8820
- insertPastedText(safePaste);
9423
+ insertPastedText(collapsePaste(safePaste));
8821
9424
  });
8822
9425
  useTerminalInput((input, key) => {
8823
9426
  if (onPasteImage && (key.ctrl || key.meta || key.super) && (input.toLowerCase() === "v" || input === "")) {
@@ -8850,6 +9453,13 @@ var PromptInput = memo(function PromptInput2({
8850
9453
  if (chosen) submit(`/${chosen.name}`);
8851
9454
  return;
8852
9455
  }
9456
+ if (!open3 && (key.upArrow || key.downArrow) && onNavigateQueue) {
9457
+ const next = onNavigateQueue(key.upArrow ? "previous" : "next", safeValue);
9458
+ if (next !== null) {
9459
+ change(next);
9460
+ return;
9461
+ }
9462
+ }
8853
9463
  if (!open3 && (key.upArrow || key.downArrow)) {
8854
9464
  const moved = moveInputHistory(
8855
9465
  history,
@@ -8904,6 +9514,21 @@ var PromptInput = memo(function PromptInput2({
8904
9514
  const visible = suggestions.slice(start, start + WINDOW2);
8905
9515
  const fileStart = Math.max(0, Math.min(active2 - WINDOW2 + 2, files.length - WINDOW2));
8906
9516
  return /* @__PURE__ */ jsxs8(Box8, { width: panelWidth, maxWidth: "100%", flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
9517
+ queued.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
9518
+ queued.slice(0, VISIBLE_QUEUED).map((text, index) => /* @__PURE__ */ jsxs8(Text8, { dimColor: true, wrap: "truncate-end", children: [
9519
+ " ",
9520
+ "\u23F3 ",
9521
+ sanitizeTerminalText(text).replace(/\s+/g, " ").trim()
9522
+ ] }, index)),
9523
+ queued.length > VISIBLE_QUEUED && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
9524
+ " ",
9525
+ strings.more(queued.length - VISIBLE_QUEUED)
9526
+ ] }),
9527
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
9528
+ " ",
9529
+ strings.queueHint
9530
+ ] })
9531
+ ] }),
8907
9532
  /* @__PURE__ */ jsxs8(Box8, { width: "100%", flexShrink: 0, children: [
8908
9533
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u256D" }) }),
8909
9534
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsxs8(Text8, { color: panelColor, wrap: "truncate-end", children: [
@@ -8929,12 +9554,8 @@ var PromptInput = memo(function PromptInput2({
8929
9554
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u256E" }) })
8930
9555
  ] }),
8931
9556
  /* @__PURE__ */ jsxs8(Box8, { width: "100%", paddingX: 1, maxHeight: inputRows, overflowY: "hidden", children: [
8932
- /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u203A " }),
8933
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }) }),
8934
- pending && pending > 0 ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
8935
- " \xB7 ",
8936
- strings.queued(pending)
8937
- ] }) : null
9557
+ /* @__PURE__ */ jsx8(Text8, { color: panelColor, bold: true, children: "\u203A " }),
9558
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(EditableText, { window: inputView, placeholder: strings.placeholder }) })
8938
9559
  ] }),
8939
9560
  attachments.length > 0 && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
8940
9561
  " ",
@@ -8958,7 +9579,8 @@ var PromptInput = memo(function PromptInput2({
8958
9579
  borderRight: false
8959
9580
  }
8960
9581
  ),
8961
- /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u256F" }) })
9582
+ footer && /* @__PURE__ */ jsx8(Box8, { flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, bold: true, wrap: "truncate-start", children: footer.slice(0, Math.max(0, panelWidth - 6)) }) }),
9583
+ /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: footer ? "\u2500\u256F" : "\u256F" }) })
8962
9584
  ] }),
8963
9585
  disabled && hint && /* @__PURE__ */ jsxs8(Text8, { color: theme.accent, children: [
8964
9586
  " ",
@@ -8995,25 +9617,81 @@ var PromptInput = memo(function PromptInput2({
8995
9617
  ] })
8996
9618
  ] });
8997
9619
  });
8998
- function EditableText({
8999
- value,
9000
- cursor,
9001
- placeholder
9002
- }) {
9003
- const valueCharacters = characters2(value);
9004
- const at = clamp2(cursor, 0, valueCharacters.length);
9005
- if (valueCharacters.length === 0) {
9620
+ function inputWindow(chars, cursor, width, maxRows) {
9621
+ const limit = Math.max(1, width - 1);
9622
+ const rows = [{ chars: [], start: 0 }];
9623
+ let rowWidth = 0;
9624
+ let cursorRow = -1;
9625
+ let cursorCol = 0;
9626
+ for (let index = 0; index <= chars.length; index += 1) {
9627
+ const row = rows[rows.length - 1];
9628
+ if (index === cursor) {
9629
+ cursorRow = rows.length - 1;
9630
+ cursorCol = row.chars.length;
9631
+ }
9632
+ if (index === chars.length) break;
9633
+ const character = chars[index];
9634
+ if (character === "\n") {
9635
+ rows.push({ chars: [], start: index + 1 });
9636
+ rowWidth = 0;
9637
+ continue;
9638
+ }
9639
+ const cells = charWidth(character);
9640
+ if (rowWidth + cells > limit && row.chars.length > 0) {
9641
+ rows.push({ chars: [character], start: index });
9642
+ rowWidth = cells;
9643
+ if (cursorRow === rows.length - 2 && cursorCol === row.chars.length) {
9644
+ cursorRow = rows.length - 1;
9645
+ cursorCol = 0;
9646
+ }
9647
+ continue;
9648
+ }
9649
+ row.chars.push(character);
9650
+ rowWidth += cells;
9651
+ }
9652
+ if (cursorRow === -1) {
9653
+ cursorRow = rows.length - 1;
9654
+ cursorCol = rows[cursorRow].chars.length;
9655
+ }
9656
+ let first2 = Math.max(0, rows.length - maxRows);
9657
+ if (cursorRow < first2) first2 = cursorRow;
9658
+ if (cursorRow >= first2 + maxRows) first2 = cursorRow - maxRows + 1;
9659
+ return {
9660
+ rows: rows.slice(first2, first2 + maxRows).map((row, offset) => ({
9661
+ text: row.chars.join(""),
9662
+ ...first2 + offset === cursorRow ? { cursor: cursorCol } : {}
9663
+ })),
9664
+ first: first2,
9665
+ total: rows.length
9666
+ };
9667
+ }
9668
+ function charWidth(character) {
9669
+ const code = character.codePointAt(0) ?? 0;
9670
+ if (code >= 32 && code < 768 || code >= 880 && code < 4352) return 1;
9671
+ return stringWidth2(character);
9672
+ }
9673
+ function EditableText({ window, placeholder }) {
9674
+ if (!window) {
9006
9675
  const placeholderCharacters = characters2(sanitizeTerminalText(placeholder));
9007
9676
  return /* @__PURE__ */ jsxs8(Text8, { children: [
9008
9677
  /* @__PURE__ */ jsx8(Text8, { inverse: true, children: placeholderCharacters[0] ?? " " }),
9009
9678
  /* @__PURE__ */ jsx8(Text8, { color: "gray", children: placeholderCharacters.slice(1).join("") })
9010
9679
  ] });
9011
9680
  }
9012
- return /* @__PURE__ */ jsxs8(Text8, { children: [
9013
- valueCharacters.slice(0, at).join(""),
9014
- /* @__PURE__ */ jsx8(Text8, { inverse: true, children: valueCharacters[at] ?? " " }),
9015
- valueCharacters.slice(at + 1).join("")
9016
- ] });
9681
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: window.rows.map((row, index) => {
9682
+ if (row.cursor === void 0) return /* @__PURE__ */ jsx8(Text8, { children: row.text || " " }, index);
9683
+ const chars = characters2(row.text);
9684
+ return /* @__PURE__ */ jsxs8(Text8, { children: [
9685
+ chars.slice(0, row.cursor).join(""),
9686
+ /* @__PURE__ */ jsx8(Text8, { inverse: true, children: chars[row.cursor] ?? " " }),
9687
+ chars.slice(row.cursor + 1).join("")
9688
+ ] }, index);
9689
+ }) });
9690
+ }
9691
+ var PASTE_TOKEN = /\[(?:paste|вставка) #(\d+)[^\]\n]*\]/g;
9692
+ function expandPastes(text, pastes) {
9693
+ if (pastes.size === 0) return text;
9694
+ return text.replace(PASTE_TOKEN, (token, id) => pastes.get(Number(id)) ?? token);
9017
9695
  }
9018
9696
  function nextCursor(cursor, length, key) {
9019
9697
  if (key.home) return 0;
@@ -9031,7 +9709,6 @@ function clamp2(value, min, max) {
9031
9709
 
9032
9710
  // src/ui/components/StatusBar.tsx
9033
9711
  import { Box as Box9, Text as Text9 } from "ink";
9034
- import { Fragment as Fragment2 } from "react";
9035
9712
 
9036
9713
  // src/ui/time.ts
9037
9714
  function formatDuration(ms) {
@@ -9047,32 +9724,31 @@ function formatDuration(ms) {
9047
9724
  // src/ui/components/StatusBar.tsx
9048
9725
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9049
9726
  var BAR_WIDTH = 12;
9727
+ var VISIBLE_AGENTS = 3;
9728
+ var AGENT_LABEL_WIDTH = 28;
9729
+ function shortTask(task) {
9730
+ const label = sanitizeTerminalText(task).replace(/\s+/g, " ").trim() || "subagent";
9731
+ return label.length <= AGENT_LABEL_WIDTH ? label : `${label.slice(0, AGENT_LABEL_WIDTH - 1)}\u2026`;
9732
+ }
9050
9733
  function StatusBar({ status }) {
9051
9734
  const theme = useTheme();
9052
9735
  const strings = useStrings();
9053
- const primary = [];
9054
9736
  const details = [];
9055
- primary.push(/* @__PURE__ */ jsx9(ModelBadge, { ref: status.modelRef }, "model"));
9056
- primary.push(/* @__PURE__ */ jsx9(EffortLabel, { effort: status.effort }, "effort"));
9737
+ details.push(/* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: shortModel(status.modelRef) }, "model"));
9738
+ details.push(/* @__PURE__ */ jsx9(EffortLabel, { effort: status.effort, effective: status.effectiveEffort }, "effort"));
9057
9739
  if (!status.thinking) {
9058
9740
  details.push(
9059
9741
  /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: strings.noThink }, "think")
9060
9742
  );
9061
9743
  }
9062
- if (status.mode !== "normal") {
9063
- const modeColor = status.mode === "accept" ? "yellow" : status.mode === "plan" ? "blue" : theme.accent;
9064
- details.push(
9065
- /* @__PURE__ */ jsx9(Text9, { color: modeColor, children: strings.modeLabel[status.mode] ?? status.mode }, "mode")
9066
- );
9067
- }
9068
- if (status.usage) {
9744
+ if (status.usage && status.usage.totalTokens > 0) {
9069
9745
  const u = status.usage;
9070
9746
  details.push(
9071
9747
  /* @__PURE__ */ jsxs9(Text9, { children: [
9072
9748
  /* @__PURE__ */ jsx9(Text9, { color: "white", bold: true, children: u.input }),
9073
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \u2191 " }),
9749
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191 " }),
9074
9750
  /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: u.output }),
9075
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \u2193" }),
9751
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2193" }),
9076
9752
  u.cache && /* @__PURE__ */ jsxs9(Fragment3, { children: [
9077
9753
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (" }),
9078
9754
  /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
@@ -9088,6 +9764,15 @@ function StatusBar({ status }) {
9088
9764
  ] }, "usage")
9089
9765
  );
9090
9766
  }
9767
+ if (status.speed) {
9768
+ const rate = status.speed.tokensPerSecond;
9769
+ details.push(
9770
+ /* @__PURE__ */ jsxs9(Text9, { children: [
9771
+ /* @__PURE__ */ jsx9(Text9, { color: status.speed.live ? theme.accent : "white", bold: !status.speed.live, children: `${status.speed.live ? "~" : ""}${rate < 10 ? rate.toFixed(1) : Math.round(rate)}` }),
9772
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " tok/s" })
9773
+ ] }, "speed")
9774
+ );
9775
+ }
9091
9776
  details.push(
9092
9777
  /* @__PURE__ */ jsx9(
9093
9778
  Text9,
@@ -9099,14 +9784,14 @@ function StatusBar({ status }) {
9099
9784
  "elapsed"
9100
9785
  )
9101
9786
  );
9102
- if (status.bypass) {
9103
- details.push(
9104
- /* @__PURE__ */ jsxs9(Text9, { color: "black", backgroundColor: theme.warn, bold: true, children: [
9105
- " ",
9106
- "BYPASS",
9107
- " "
9108
- ] }, "bypass")
9109
- );
9787
+ if (status.mcp.connected > 0) {
9788
+ details.push(/* @__PURE__ */ jsxs9(Text9, { color: theme.ok, children: [
9789
+ "mcp:",
9790
+ status.mcp.connected
9791
+ ] }, "mcp"));
9792
+ }
9793
+ if (status.mcp.failed > 0) {
9794
+ details.push(/* @__PURE__ */ jsx9(Text9, { color: theme.error, children: strings.mcpFailed(status.mcp.failed) }, "mcp-failed"));
9110
9795
  }
9111
9796
  return /* @__PURE__ */ jsxs9(
9112
9797
  Box9,
@@ -9126,59 +9811,28 @@ function StatusBar({ status }) {
9126
9811
  flexDirection: "column",
9127
9812
  children: [
9128
9813
  /* @__PURE__ */ jsxs9(Box9, { width: "100%", alignItems: "flex-start", children: [
9129
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, flexShrink: 1, flexWrap: "wrap", children: /* @__PURE__ */ jsx9(Segments, { items: primary }) }),
9130
- /* @__PURE__ */ jsxs9(
9131
- Box9,
9132
- {
9133
- flexShrink: 0,
9134
- marginLeft: 2,
9135
- flexDirection: "column",
9136
- alignItems: "flex-end",
9137
- children: [
9138
- /* @__PURE__ */ jsx9(ContextMeter, { context: status.context }),
9139
- (status.mcp.connected > 0 || status.mcp.failed > 0) && /* @__PURE__ */ jsxs9(Text9, { children: [
9140
- status.mcp.connected > 0 && /* @__PURE__ */ jsxs9(Text9, { color: theme.ok, children: [
9141
- "mcp:",
9142
- status.mcp.connected
9143
- ] }),
9144
- status.mcp.connected > 0 && status.mcp.failed > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
9145
- status.mcp.failed > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme.error, children: strings.mcpFailed(status.mcp.failed) })
9146
- ] }),
9147
- status.activeAgents > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: strings.activeAgents(status.activeAgents) })
9148
- ]
9149
- }
9150
- )
9814
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, flexShrink: 1, flexWrap: "wrap", children: details.map((item, index) => /* @__PURE__ */ jsxs9(Text9, { children: [
9815
+ index > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
9816
+ item
9817
+ ] }, index)) }),
9818
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, marginLeft: 2, children: /* @__PURE__ */ jsx9(ContextMeter, { context: status.context }) })
9151
9819
  ] }),
9152
- details.length > 0 && /* @__PURE__ */ jsx9(Box9, { flexWrap: "wrap", children: details.map((item, index) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
9153
- index > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
9154
- item
9155
- ] }, index)) })
9820
+ status.agents.length > 0 && /* @__PURE__ */ jsxs9(Box9, { width: "100%", flexDirection: "column", alignItems: "flex-end", children: [
9821
+ status.agents.slice(0, VISIBLE_AGENTS).map((task, index) => /* @__PURE__ */ jsxs9(Text9, { color: theme.accent, wrap: "truncate-end", children: [
9822
+ "\u25CC ",
9823
+ shortTask(task)
9824
+ ] }, index)),
9825
+ status.agents.length > VISIBLE_AGENTS && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: strings.moreAgents(status.agents.length - VISIBLE_AGENTS) })
9826
+ ] })
9156
9827
  ]
9157
9828
  }
9158
9829
  );
9159
9830
  }
9160
- function Segments({ items }) {
9161
- return items.flatMap((item, index) => {
9162
- const elements = [];
9163
- if (index > 0) {
9164
- elements.push(
9165
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }) }, `sep-${index}`)
9166
- );
9167
- }
9168
- elements.push(/* @__PURE__ */ jsx9(Fragment2, { children: item }, `item-${index}`));
9169
- return elements;
9170
- });
9171
- }
9172
9831
  function shortModel(ref) {
9173
9832
  const safe = sanitizeTerminalText(ref);
9174
9833
  const slash = safe.lastIndexOf("/");
9175
9834
  return slash === -1 ? safe : safe.slice(slash + 1);
9176
9835
  }
9177
- function ModelBadge({ ref: modelRef }) {
9178
- const theme = useTheme();
9179
- const name = shortModel(modelRef);
9180
- return /* @__PURE__ */ jsx9(Box9, { borderColor: theme.accent, borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: name }) });
9181
- }
9182
9836
  var EFFORT_STYLES = {
9183
9837
  auto: { label: "auto", color: "cyan" },
9184
9838
  max: { label: "max", color: "red", bold: true },
@@ -9187,36 +9841,30 @@ var EFFORT_STYLES = {
9187
9841
  medium: { label: "medium", color: "blue" },
9188
9842
  low: { label: "low", color: "gray", dim: true }
9189
9843
  };
9190
- function EffortLabel({ effort }) {
9844
+ function EffortLabel({ effort, effective }) {
9191
9845
  const style = EFFORT_STYLES[effort];
9192
- return /* @__PURE__ */ jsx9(Box9, { borderColor: style.color ?? "gray", borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(
9193
- Text9,
9194
- {
9195
- color: style.color,
9196
- bold: style.bold,
9197
- dimColor: style.dim,
9198
- children: style.label
9199
- }
9200
- ) });
9201
- }
9202
- function tokens2(count) {
9203
- if (count < 1e3) return String(count);
9204
- const [value, suffix] = count < 1e6 ? [count / 1e3, "k"] : [count / 1e6, "M"];
9205
- return `${value.toFixed(1).replace(/\.0$/, "")}${suffix}`;
9846
+ if (effective === null) {
9847
+ return /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, strikethrough: true, children: style.label });
9848
+ }
9849
+ const sent = effective !== effort ? EFFORT_STYLES[effective] : void 0;
9850
+ return /* @__PURE__ */ jsxs9(Text9, { children: [
9851
+ /* @__PURE__ */ jsx9(Text9, { color: style.color, bold: style.bold, dimColor: style.dim, children: style.label }),
9852
+ sent && /* @__PURE__ */ jsxs9(Fragment3, { children: [
9853
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: effort === "auto" ? "\xB7" : "\u2192" }),
9854
+ /* @__PURE__ */ jsx9(Text9, { color: sent.color, bold: sent.bold, dimColor: sent.dim, children: sent.label })
9855
+ ] })
9856
+ ] });
9206
9857
  }
9207
9858
  function ContextMeter({
9208
9859
  context
9209
9860
  }) {
9210
9861
  const theme = useTheme();
9211
- const { window: win, used, exact } = context;
9212
- if (!exact || win === null || win <= 0) {
9862
+ const { window: win, exact } = context;
9863
+ const used = exact ? context.used : 0;
9864
+ if (win === null || win <= 0) {
9213
9865
  return /* @__PURE__ */ jsxs9(Text9, { children: [
9214
9866
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "ctx " }),
9215
- /* @__PURE__ */ jsx9(Text9, { color: "gray", children: exact ? tokens2(used) : "?" }),
9216
- win && win > 0 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9217
- "/",
9218
- tokens2(win)
9219
- ] })
9867
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: tokens(used) })
9220
9868
  ] });
9221
9869
  }
9222
9870
  const pct = Math.min(Math.max(used / win, 0), 1);
@@ -9234,53 +9882,226 @@ function ContextMeter({
9234
9882
  /* @__PURE__ */ jsx9(Text9, { color, children: "\u258C" }),
9235
9883
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9236
9884
  " ",
9237
- tokens2(used),
9885
+ tokens(used),
9238
9886
  "/",
9239
- tokens2(win)
9887
+ tokens(win)
9240
9888
  ] })
9241
9889
  ] });
9242
9890
  }
9243
9891
 
9244
- // src/ui/components/TerminalViewport.tsx
9245
- import { Box as Box10 } from "ink";
9246
- import { jsx as jsx10 } from "react/jsx-runtime";
9247
- var INTERACTIVE_CHROME_ROWS = 14;
9248
- var MAX_LIVE_ROWS = 8;
9249
- var MIN_LIVE_ROWS = 4;
9250
- function liveTranscriptRows(rows) {
9251
- if (!Number.isFinite(rows)) return MAX_LIVE_ROWS;
9252
- return Math.max(MIN_LIVE_ROWS, Math.min(MAX_LIVE_ROWS, Math.floor(rows) - INTERACTIVE_CHROME_ROWS - 2));
9253
- }
9254
- function TerminalViewport({ children, rows }) {
9255
- const { columns } = useTerminalSize();
9256
- return /* @__PURE__ */ jsx10(
9257
- Box10,
9258
- {
9259
- width: columns,
9260
- maxWidth: "100%",
9261
- flexDirection: "column",
9262
- maxHeight: liveTranscriptRows(rows) + INTERACTIVE_CHROME_ROWS,
9263
- overflowY: "hidden",
9264
- children
9265
- }
9266
- );
9267
- }
9892
+ // src/ui/components/SubagentChat.tsx
9893
+ import { Box as Box12, Text as Text12, useInput as useInput2 } from "ink";
9894
+ import { useState as useState7 } from "react";
9268
9895
 
9269
9896
  // src/ui/components/Transcript.tsx
9270
- import { Box as Box12, Static, Text as Text11 } from "ink";
9271
- import { memo as memo2, useMemo as useMemo4, useRef as useRef3 } from "react";
9897
+ import { Box as Box11, Static, Text as Text11 } from "ink";
9898
+ import { memo as memo2, useEffect as useEffect3, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
9272
9899
  import Spinner2 from "ink-spinner";
9273
- import stringWidth2 from "string-width";
9900
+ import stringWidth4 from "string-width";
9274
9901
 
9275
9902
  // src/ui/markdown.tsx
9276
- import { Box as Box11, Text as InkText } from "ink";
9277
- import { createContext as createContext3, Fragment as Fragment4, useContext as useContext3, useMemo as useMemo3 } from "react";
9278
- import stringWidth from "string-width";
9279
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
9903
+ import { Box as Box10, Text as InkText } from "ink";
9904
+ import { createContext as createContext3, Fragment as Fragment4, useContext as useContext3, useMemo as useMemo4 } from "react";
9905
+ import stringWidth3 from "string-width";
9906
+
9907
+ // src/ui/highlight.ts
9908
+ var words = (list) => new Set(list.split(/\s+/).filter(Boolean));
9909
+ var JS_KEYWORDS = words(`
9910
+ async await break case catch class const continue debugger default delete do else enum export
9911
+ extends finally for from function get if implements import in instanceof interface let new of
9912
+ package private protected public readonly return satisfies set static super switch this throw
9913
+ try type typeof var void while with yield as declare abstract keyof namespace`);
9914
+ var GRAMMARS = {
9915
+ js: { keywords: JS_KEYWORDS, literals: words("true false null undefined NaN Infinity"), lineComment: ["//"], block: ["/*", "*/"], types: true },
9916
+ py: {
9917
+ keywords: words(`and as assert async await break class continue def del elif else except finally for
9918
+ from global if import in is lambda nonlocal not or pass raise return try while with yield match case self`),
9919
+ literals: words("True False None"),
9920
+ lineComment: ["#"],
9921
+ triple: true,
9922
+ types: true
9923
+ },
9924
+ go: {
9925
+ keywords: words(`break case chan const continue default defer else fallthrough for func go goto if import
9926
+ interface map package range return select struct switch type var`),
9927
+ literals: words("true false nil iota"),
9928
+ lineComment: ["//"],
9929
+ block: ["/*", "*/"],
9930
+ types: true
9931
+ },
9932
+ rust: {
9933
+ keywords: words(`as async await break const continue crate dyn else enum extern fn for if impl in let loop
9934
+ match mod move mut pub ref return self Self static struct super trait type unsafe use where while`),
9935
+ literals: words("true false None Some Ok Err"),
9936
+ lineComment: ["//"],
9937
+ block: ["/*", "*/"],
9938
+ types: true
9939
+ },
9940
+ c: {
9941
+ keywords: words(`auto break case char class const continue default delete do double else enum extern float
9942
+ for goto if inline int long namespace new private protected public return short signed sizeof static
9943
+ struct switch template this throw try typedef typename union unsigned using virtual void volatile while
9944
+ boolean byte extends final finally implements import instanceof interface package super synchronized throws var`),
9945
+ literals: words("true false null nullptr NULL"),
9946
+ lineComment: ["//"],
9947
+ block: ["/*", "*/"],
9948
+ types: true
9949
+ },
9950
+ sh: {
9951
+ keywords: words(`if then else elif fi for while until do done case esac in function return export local
9952
+ readonly set unset shift exit echo cd source alias`),
9953
+ literals: words("true false"),
9954
+ lineComment: ["#"]
9955
+ },
9956
+ json: { keywords: /* @__PURE__ */ new Set(), literals: words("true false null"), lineComment: [] },
9957
+ sql: {
9958
+ keywords: words(`select from where insert into values update set delete create table drop alter index join
9959
+ left right inner outer on group by order having limit offset as and or not null is in distinct union all
9960
+ primary key foreign references default SELECT FROM WHERE INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE
9961
+ DROP ALTER INDEX JOIN LEFT RIGHT INNER OUTER ON GROUP BY ORDER HAVING LIMIT OFFSET AS AND OR NOT NULL IS IN
9962
+ DISTINCT UNION ALL PRIMARY KEY FOREIGN REFERENCES DEFAULT`),
9963
+ literals: words("true false TRUE FALSE"),
9964
+ lineComment: ["--"],
9965
+ block: ["/*", "*/"]
9966
+ }
9967
+ };
9968
+ var ALIASES = {
9969
+ js: "js",
9970
+ javascript: "js",
9971
+ jsx: "js",
9972
+ mjs: "js",
9973
+ cjs: "js",
9974
+ ts: "js",
9975
+ typescript: "js",
9976
+ tsx: "js",
9977
+ py: "py",
9978
+ python: "py",
9979
+ go: "go",
9980
+ golang: "go",
9981
+ rs: "rust",
9982
+ rust: "rust",
9983
+ c: "c",
9984
+ h: "c",
9985
+ cpp: "c",
9986
+ "c++": "c",
9987
+ cc: "c",
9988
+ hpp: "c",
9989
+ cs: "c",
9990
+ csharp: "c",
9991
+ java: "c",
9992
+ kotlin: "c",
9993
+ kt: "c",
9994
+ swift: "c",
9995
+ php: "c",
9996
+ sh: "sh",
9997
+ bash: "sh",
9998
+ zsh: "sh",
9999
+ shell: "sh",
10000
+ console: "sh",
10001
+ powershell: "sh",
10002
+ ps1: "sh",
10003
+ ps: "sh",
10004
+ json: "json",
10005
+ jsonc: "json",
10006
+ json5: "json",
10007
+ sql: "sql",
10008
+ postgres: "sql",
10009
+ mysql: "sql",
10010
+ sqlite: "sql"
10011
+ };
10012
+ function grammarFor(lang) {
10013
+ return lang ? ALIASES[lang.toLowerCase()] : void 0;
10014
+ }
10015
+ function highlightCode(code, lang) {
10016
+ const grammar = GRAMMARS[grammarFor(lang) ?? ""];
10017
+ const lines = code.split("\n");
10018
+ if (!grammar) return lines.map((line) => [{ kind: "plain", text: line }]);
10019
+ const state = {};
10020
+ return lines.map((line) => highlightLine(line, grammar, state));
10021
+ }
10022
+ function highlightLine(line, grammar, state) {
10023
+ const tokens2 = [];
10024
+ const push = (kind, text) => {
10025
+ if (text === "") return;
10026
+ const last = tokens2.at(-1);
10027
+ if (last && last.kind === kind) last.text += text;
10028
+ else tokens2.push({ kind, text });
10029
+ };
10030
+ let i = 0;
10031
+ if (state.open) {
10032
+ const end = line.indexOf(state.open.close);
10033
+ if (end === -1) {
10034
+ push(state.open.kind, line);
10035
+ return tokens2;
10036
+ }
10037
+ push(state.open.kind, line.slice(0, end + state.open.close.length));
10038
+ i = end + state.open.close.length;
10039
+ state.open = void 0;
10040
+ }
10041
+ while (i < line.length) {
10042
+ const rest = line.slice(i);
10043
+ const lineComment = grammar.lineComment.find((marker) => rest.startsWith(marker));
10044
+ if (lineComment) {
10045
+ push("comment", rest);
10046
+ break;
10047
+ }
10048
+ if (grammar.block && rest.startsWith(grammar.block[0])) {
10049
+ const end = rest.indexOf(grammar.block[1], grammar.block[0].length);
10050
+ if (end === -1) {
10051
+ push("comment", rest);
10052
+ state.open = { kind: "comment", close: grammar.block[1] };
10053
+ break;
10054
+ }
10055
+ push("comment", rest.slice(0, end + grammar.block[1].length));
10056
+ i += end + grammar.block[1].length;
10057
+ continue;
10058
+ }
10059
+ if (grammar.triple && (rest.startsWith('"""') || rest.startsWith("'''"))) {
10060
+ const quote = rest.slice(0, 3);
10061
+ const end = rest.indexOf(quote, 3);
10062
+ if (end === -1) {
10063
+ push("string", rest);
10064
+ state.open = { kind: "string", close: quote };
10065
+ break;
10066
+ }
10067
+ push("string", rest.slice(0, end + 3));
10068
+ i += end + 3;
10069
+ continue;
10070
+ }
10071
+ const character = line[i];
10072
+ if (character === '"' || character === "'" || character === "`") {
10073
+ let j = i + 1;
10074
+ while (j < line.length && line[j] !== character) j += line[j] === "\\" ? 2 : 1;
10075
+ push("string", line.slice(i, Math.min(line.length, j + 1)));
10076
+ i = j + 1;
10077
+ continue;
10078
+ }
10079
+ const number = /^(?:0x[0-9a-f_]+|\d[\d_]*(?:\.\d+)?(?:e[+-]?\d+)?)/i.exec(rest);
10080
+ if (number && !/[\w$]/.test(line[i - 1] ?? "")) {
10081
+ push("number", number[0]);
10082
+ i += number[0].length;
10083
+ continue;
10084
+ }
10085
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
10086
+ if (word) {
10087
+ const text = word[0];
10088
+ const kind = grammar.keywords.has(text) ? "keyword" : grammar.literals.has(text) ? "literal" : grammar.types && /^[A-Z][a-z0-9]/.test(text) ? "type" : "plain";
10089
+ push(kind, text);
10090
+ i += text.length;
10091
+ continue;
10092
+ }
10093
+ push("plain", character);
10094
+ i += 1;
10095
+ }
10096
+ return tokens2;
10097
+ }
10098
+
10099
+ // src/ui/markdown.tsx
10100
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
9280
10101
  var DimContext = createContext3(false);
9281
10102
  function Text10({ dimColor, color, bold, ...props }) {
9282
10103
  const dim = useContext3(DimContext);
9283
- return /* @__PURE__ */ jsx11(
10104
+ return /* @__PURE__ */ jsx10(
9284
10105
  InkText,
9285
10106
  {
9286
10107
  ...props,
@@ -9291,57 +10112,76 @@ function Text10({ dimColor, color, bold, ...props }) {
9291
10112
  );
9292
10113
  }
9293
10114
  function Markdown({ children, dim = false }) {
9294
- const blocks = useMemo3(() => extractBlocks(children), [children]);
9295
- return /* @__PURE__ */ jsx11(DimContext.Provider, { value: dim, children: /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) }) });
10115
+ const blocks = useMemo4(() => extractBlocks(children), [children]);
10116
+ return /* @__PURE__ */ jsx10(DimContext.Provider, { value: dim, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx10(BlockView, { block }, i)) }) });
9296
10117
  }
9297
10118
  function BlockView({ block }) {
9298
10119
  switch (block.type) {
9299
10120
  case "heading": {
9300
- return /* @__PURE__ */ jsx11(Box11, { marginTop: block.level === 1 ? 1 : 0, children: /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(block.text ?? "") }) });
10121
+ return /* @__PURE__ */ jsx10(Box10, { marginTop: block.level === 1 ? 1 : 0, children: /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(block.text ?? "") }) });
9301
10122
  }
9302
10123
  case "blockquote":
9303
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, italic: true, children: inline(line) }, i)) });
10124
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: (block.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx10(Text10, { dimColor: true, italic: true, children: inline(line) }, i)) });
9304
10125
  case "ul":
9305
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
9306
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text?.match(/^[☑☐] /) ? "" : "\u2022 " }),
9307
- /* @__PURE__ */ jsx11(Box11, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx11(BlockView, { block: item }) })
10126
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
10127
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: item.text?.match(/^[☑☐] /) ? "" : "\u2022 " }),
10128
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx10(BlockView, { block: item }) })
9308
10129
  ] }, i)) });
9309
10130
  case "ol":
9310
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
9311
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: `${(block.start ?? 1) + i}. ` }),
9312
- /* @__PURE__ */ jsx11(Box11, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx11(BlockView, { block: item }) })
10131
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
10132
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: `${(block.start ?? 1) + i}. ` }),
10133
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx10(BlockView, { block: item }) })
9313
10134
  ] }, i)) });
9314
10135
  case "paragraph":
9315
10136
  default: {
9316
- const content = /* @__PURE__ */ jsx11(Text10, { children: inline(block.text ?? "") });
10137
+ const content = /* @__PURE__ */ jsx10(Text10, { children: inline(block.text ?? "") });
9317
10138
  if (!block.children?.length) return content;
9318
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
10139
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
9319
10140
  content,
9320
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: block.children.map((child, index) => /* @__PURE__ */ jsx11(BlockView, { block: child }, index)) })
10141
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: block.children.map((child, index) => /* @__PURE__ */ jsx10(BlockView, { block: child }, index)) })
9321
10142
  ] });
9322
10143
  }
9323
10144
  case "table":
9324
- return /* @__PURE__ */ jsx11(TableView, { block });
10145
+ return /* @__PURE__ */ jsx10(TableView, { block });
9325
10146
  case "code":
9326
- return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
10147
+ return /* @__PURE__ */ jsx10(CodeBlock, { code: block.text ?? "", lang: block.lang });
9327
10148
  case "hr":
9328
- return /* @__PURE__ */ jsx11(Box11, { height: 1 });
10149
+ return /* @__PURE__ */ jsx10(Box10, { height: 1 });
9329
10150
  }
9330
10151
  }
9331
- function CodeBlock({ code }) {
9332
- const lines = code.split("\n");
9333
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) });
10152
+ var TOKEN_COLORS = {
10153
+ plain: void 0,
10154
+ keyword: "#c4a7ff",
10155
+ string: "#86efac",
10156
+ comment: void 0,
10157
+ number: "#fbbf24",
10158
+ literal: "#fb923c",
10159
+ type: "#67e8f9"
10160
+ };
10161
+ function CodeBlock({ code, lang }) {
10162
+ const lines = useMemo4(() => highlightCode(code.replace(/\t/g, " "), lang), [code, lang]);
10163
+ const plain = grammarFor(lang) ? void 0 : "cyan";
10164
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", marginTop: 1, children: lines.map((tokens2, i) => /* @__PURE__ */ jsx10(Text10, { children: tokens2.map((token, j) => /* @__PURE__ */ jsx10(
10165
+ Text10,
10166
+ {
10167
+ color: TOKEN_COLORS[token.kind] ?? (token.kind === "plain" ? plain : void 0),
10168
+ dimColor: token.kind === "comment" ? true : void 0,
10169
+ italic: token.kind === "comment",
10170
+ children: token.text
10171
+ },
10172
+ j
10173
+ )) }, i)) });
9334
10174
  }
9335
10175
  function TableView({ block }) {
9336
10176
  const { columns } = useTerminalSize();
9337
10177
  const headers = block.headers ?? [];
9338
10178
  const rows = block.rows ?? [];
9339
10179
  if (headers.length === 0 && rows.length === 0) {
9340
- return /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "(empty table)" });
10180
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
9341
10181
  }
9342
10182
  const colCount = Math.max(headers.length, ...rows.map((r) => r?.length ?? 0));
9343
10183
  if (colCount === 0) {
9344
- return /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "(empty table)" });
10184
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
9345
10185
  }
9346
10186
  const allRows = [headers, ...rows].map(
9347
10187
  (row) => Array.from({ length: colCount }, (_, index) => inlineDisplayText(row[index] ?? ""))
@@ -9350,7 +10190,7 @@ function TableView({ block }) {
9350
10190
  for (const row of allRows) {
9351
10191
  for (let i = 0; i < colCount; i++) {
9352
10192
  const cell = row[i] ?? "";
9353
- naturalWidths[i] = Math.max(naturalWidths[i] ?? 1, stringWidth(cell));
10193
+ naturalWidths[i] = Math.max(naturalWidths[i] ?? 1, stringWidth3(cell));
9354
10194
  }
9355
10195
  }
9356
10196
  const colWidths = fitColumnWidths(naturalWidths, Math.max(1, columns));
@@ -9366,7 +10206,7 @@ function TableView({ block }) {
9366
10206
  const align = block.colAligns?.[i] ?? "left";
9367
10207
  const width = colWidths[i] ?? 1;
9368
10208
  const fitted = truncateToWidth(cell, width);
9369
- const visualWidth = stringWidth(fitted);
10209
+ const visualWidth = stringWidth3(fitted);
9370
10210
  const totalPad = Math.max(0, width - visualWidth);
9371
10211
  let padded;
9372
10212
  if (align === "right") {
@@ -9380,13 +10220,13 @@ function TableView({ block }) {
9380
10220
  cells.push(padded);
9381
10221
  }
9382
10222
  lines.push(
9383
- /* @__PURE__ */ jsx11(Text10, { bold: rowIdx === 0, wrap: "truncate-end", children: cells.join(" \u2502 ") }, `row-${rowIdx}`)
10223
+ /* @__PURE__ */ jsx10(Text10, { bold: rowIdx === 0, wrap: "truncate-end", children: cells.join(" \u2502 ") }, `row-${rowIdx}`)
9384
10224
  );
9385
10225
  if (rowIdx === 0) {
9386
- lines.push(/* @__PURE__ */ jsx11(Text10, { dimColor: true, wrap: "truncate-end", children: separator }, `sep-${rowIdx}`));
10226
+ lines.push(/* @__PURE__ */ jsx10(Text10, { dimColor: true, wrap: "truncate-end", children: separator }, `sep-${rowIdx}`));
9387
10227
  }
9388
10228
  });
9389
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: lines });
10229
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines });
9390
10230
  }
9391
10231
  function fitColumnWidths(natural, maxLineWidth) {
9392
10232
  const widths = natural.map((width) => Math.max(1, width));
@@ -9409,11 +10249,11 @@ function fitColumnWidths(natural, maxLineWidth) {
9409
10249
  }
9410
10250
  var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
9411
10251
  function truncateToWidth(text, maxWidth) {
9412
- if (stringWidth(text) <= maxWidth) return text;
10252
+ if (stringWidth3(text) <= maxWidth) return text;
9413
10253
  if (maxWidth <= 1) return "\u2026";
9414
10254
  let result = "";
9415
10255
  for (const { segment } of graphemeSegmenter.segment(text)) {
9416
- if (stringWidth(result + segment) > maxWidth - 1) break;
10256
+ if (stringWidth3(result + segment) > maxWidth - 1) break;
9417
10257
  result += segment;
9418
10258
  }
9419
10259
  return `${result}\u2026`;
@@ -9774,7 +10614,7 @@ function inline(text, depth = 0) {
9774
10614
  handler: (m) => {
9775
10615
  const fragmentKey = key++;
9776
10616
  return /* @__PURE__ */ jsxs10(Fragment4, { children: [
9777
- /* @__PURE__ */ jsx11(Text10, { color: "cyan", underline: true, children: inline(m[2], depth + 1) }),
10617
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", underline: true, children: inline(m[2], depth + 1) }),
9778
10618
  /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
9779
10619
  "(",
9780
10620
  restoreInlineEscapes(m[3]),
@@ -9785,31 +10625,31 @@ function inline(text, depth = 0) {
9785
10625
  },
9786
10626
  {
9787
10627
  re: /(`+)([^`]+?)\1/,
9788
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, color: "cyan", children: restoreInlineEscapes(m[2]) }, key++)
10628
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: restoreInlineEscapes(m[2]) }, key++)
9789
10629
  },
9790
10630
  {
9791
10631
  re: /(\*\*\*|___)(?=\S)(.*?\S)\1/,
9792
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, italic: true, children: inline(m[2], depth + 1) }, key++)
10632
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, italic: true, children: inline(m[2], depth + 1) }, key++)
9793
10633
  },
9794
10634
  {
9795
10635
  re: /(\*\*)(?=\S)(.*?\S)\1/,
9796
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
10636
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
9797
10637
  },
9798
10638
  {
9799
10639
  re: /(__)(?=\S)(.*?\S)\1/,
9800
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
10640
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
9801
10641
  },
9802
10642
  {
9803
10643
  re: /(~~)(?=\S)(.*?\S)\1/,
9804
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { strikethrough: true, children: inline(m[2], depth + 1) }, key++)
10644
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { strikethrough: true, children: inline(m[2], depth + 1) }, key++)
9805
10645
  },
9806
10646
  {
9807
10647
  re: /(\*)(?=\S)(.*?\S)\1/,
9808
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
10648
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
9809
10649
  },
9810
10650
  {
9811
10651
  re: /(?<!\w)(_)(?=\S)(.*?\S)\1(?!\w)/,
9812
- handler: (m) => /* @__PURE__ */ jsx11(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
10652
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
9813
10653
  }
9814
10654
  ];
9815
10655
  while (rest.length > 0) {
@@ -9833,7 +10673,7 @@ function inline(text, depth = 0) {
9833
10673
  }
9834
10674
 
9835
10675
  // src/ui/components/Transcript.tsx
9836
- import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
10676
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
9837
10677
  var HEADER = { kind: "header" };
9838
10678
  function assistantThinkingForFrame(bubble, frozenThinking) {
9839
10679
  return bubble.streaming && bubble.text !== "" ? frozenThinking ?? bubble.thinking : bubble.thinking;
@@ -9850,7 +10690,7 @@ var Transcript = memo2(function Transcript2({
9850
10690
  }) {
9851
10691
  const liveAt = firstMutableBubbleIndex(bubbles);
9852
10692
  const stableCount = liveAt === -1 ? bubbles.length : liveAt;
9853
- const stableRef = useRef3([]);
10693
+ const stableRef = useRef4([]);
9854
10694
  if (stableRef.current.length < stableCount) {
9855
10695
  stableRef.current = [
9856
10696
  ...stableRef.current,
@@ -9864,7 +10704,7 @@ var Transcript = memo2(function Transcript2({
9864
10704
  const agents = live.filter((bubble) => bubble.kind === "subagent");
9865
10705
  const groupedAgents = agents.length > 1;
9866
10706
  return /* @__PURE__ */ jsxs11(
9867
- Box12,
10707
+ Box11,
9868
10708
  {
9869
10709
  flexDirection: "column",
9870
10710
  marginBottom: 1,
@@ -9872,9 +10712,9 @@ var Transcript = memo2(function Transcript2({
9872
10712
  minHeight: 0,
9873
10713
  overflowY: "hidden",
9874
10714
  children: [
9875
- /* @__PURE__ */ jsx12(Static, { items: staticItems, children: (item) => item.kind === "header" ? /* @__PURE__ */ jsx12(Logo, { workspace }, "kitcode-header") : /* @__PURE__ */ jsx12(BubbleView, { bubble: item }, item.id) }),
10715
+ /* @__PURE__ */ jsx11(Static, { items: staticItems, children: (item) => item.kind === "header" ? /* @__PURE__ */ jsx11(Logo, { workspace }, "kitcode-header") : /* @__PURE__ */ jsx11(BubbleView, { bubble: item }, item.id) }),
9876
10716
  /* @__PURE__ */ jsxs11(
9877
- Box12,
10717
+ Box11,
9878
10718
  {
9879
10719
  flexDirection: "column",
9880
10720
  flexShrink: 1,
@@ -9883,8 +10723,8 @@ var Transcript = memo2(function Transcript2({
9883
10723
  overflowY: "hidden",
9884
10724
  justifyContent: "flex-end",
9885
10725
  children: [
9886
- live.filter((bubble) => !groupedAgents || bubble.kind !== "subagent" && !(bubble.kind === "tool" && bubble.name === "task")).map((bubble) => /* @__PURE__ */ jsx12(BubbleView, { bubble, maxRows: maxLiveRows }, bubble.id)),
9887
- groupedAgents && /* @__PURE__ */ jsx12(SubagentSummary, { agents })
10726
+ live.filter((bubble) => !groupedAgents || bubble.kind !== "subagent" && !(bubble.kind === "tool" && bubble.name === "task")).map((bubble) => /* @__PURE__ */ jsx11(BubbleView, { bubble, maxRows: maxLiveRows }, bubble.id)),
10727
+ groupedAgents && /* @__PURE__ */ jsx11(SubagentSummary, { agents })
9888
10728
  ]
9889
10729
  }
9890
10730
  )
@@ -9898,22 +10738,22 @@ var BubbleView = memo2(function BubbleView2({
9898
10738
  }) {
9899
10739
  const theme = useTheme();
9900
10740
  if (bubble.kind === "user") {
9901
- return /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { color: theme.accent, bold: true, children: [
10741
+ return /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { color: theme.accent, bold: true, children: [
9902
10742
  "\u203A ",
9903
10743
  bubble.text
9904
10744
  ] }) });
9905
10745
  }
9906
10746
  if (bubble.kind === "notice") {
9907
10747
  const color = bubble.level === "error" ? theme.error : bubble.level === "warn" ? theme.warn : theme.accent;
9908
- return /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { color, children: bubble.text }) });
10748
+ return /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color, children: bubble.text }) });
9909
10749
  }
9910
10750
  if (bubble.kind === "assistant") {
9911
- return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
10751
+ return /* @__PURE__ */ jsx11(AssistantView, { bubble, maxRows });
9912
10752
  }
9913
10753
  if (bubble.kind === "subagent") {
9914
- return /* @__PURE__ */ jsx12(SubagentView, { bubble, maxRows });
10754
+ return /* @__PURE__ */ jsx11(SubagentView, { bubble, maxRows });
9915
10755
  }
9916
- return /* @__PURE__ */ jsx12(ToolView, { bubble });
10756
+ return /* @__PURE__ */ jsx11(ToolView, { bubble });
9917
10757
  });
9918
10758
  function splitThinkingPrefix(text, streaming) {
9919
10759
  const opening = /^\s*<think(?:ing)?>/i.exec(text);
@@ -9929,7 +10769,7 @@ function AssistantView({ bubble: source, maxRows }) {
9929
10769
  const tagged = splitThinkingPrefix(source.text, source.streaming);
9930
10770
  const bubble = { ...source, text: tagged.text, thinking: [source.thinking, tagged.thinking].filter(Boolean).join("\n") };
9931
10771
  const { columns } = useTerminalSize();
9932
- const frozenThinking = useRef3(void 0);
10772
+ const frozenThinking = useRef4(void 0);
9933
10773
  const answering = bubble.streaming && bubble.text !== "";
9934
10774
  if (!answering) frozenThinking.current = void 0;
9935
10775
  if (answering && frozenThinking.current === void 0) {
@@ -9944,11 +10784,11 @@ function AssistantView({ bubble: source, maxRows }) {
9944
10784
  const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
9945
10785
  const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
9946
10786
  const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
9947
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
9948
- frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { dim: true, children: frameThinking }) }),
9949
- bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
10787
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
10788
+ frameThinking !== "" && /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx11(Markdown, { dim: true, children: frameThinking }) }),
10789
+ bubble.streaming ? /* @__PURE__ */ jsx11(Text11, { children: frameText }) : /* @__PURE__ */ jsx11(Markdown, { children: bubble.text }),
9950
10790
  bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9951
- /* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
10791
+ /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
9952
10792
  " thinking"
9953
10793
  ] })
9954
10794
  ] });
@@ -9967,7 +10807,7 @@ function wrapVisualLine(line, columns) {
9967
10807
  let current = "";
9968
10808
  let currentWidth = 0;
9969
10809
  for (const character of line) {
9970
- const width = Math.max(1, stringWidth2(character));
10810
+ const width = Math.max(1, stringWidth4(character));
9971
10811
  if (current !== "" && currentWidth + width > columns) {
9972
10812
  rows.push(current);
9973
10813
  current = "";
@@ -9984,23 +10824,23 @@ function ToolView({ bubble }) {
9984
10824
  const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
9985
10825
  const color = bubble.state === "error" ? theme.error : bubble.state === "ok" ? theme.ok : theme.warn;
9986
10826
  const display = bubble.display;
9987
- const diff = useMemo4(
10827
+ const diff = useMemo5(
9988
10828
  () => display?.kind === "diff" ? diffLines(display.before, display.after) : null,
9989
10829
  [display]
9990
10830
  );
9991
- if (!diff) return /* @__PURE__ */ jsx12(NonDiffToolView, { bubble, mark, color });
10831
+ if (!diff) return /* @__PURE__ */ jsx11(NonDiffToolView, { bubble, mark, color });
9992
10832
  const hasChanges = diff.added > 0 || diff.removed > 0;
9993
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
10833
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
9994
10834
  /* @__PURE__ */ jsxs11(Text11, { color, children: [
9995
10835
  mark,
9996
10836
  " ",
9997
10837
  bubble.summary,
9998
10838
  hasChanges && /* @__PURE__ */ jsxs11(Fragment5, { children: [
9999
- diff.added > 0 && /* @__PURE__ */ jsx12(Text11, { color: theme.ok, bold: true, children: ` +${diff.added}` }),
10000
- diff.removed > 0 && /* @__PURE__ */ jsx12(Text11, { color: theme.error, bold: true, children: ` -${diff.removed}` })
10839
+ diff.added > 0 && /* @__PURE__ */ jsx11(Text11, { color: theme.ok, bold: true, children: ` +${diff.added}` }),
10840
+ diff.removed > 0 && /* @__PURE__ */ jsx11(Text11, { color: theme.error, bold: true, children: ` -${diff.removed}` })
10001
10841
  ] })
10002
10842
  ] }),
10003
- /* @__PURE__ */ jsx12(Box12, { marginLeft: 2, children: /* @__PURE__ */ jsx12(DiffHunk, { lines: diff.hunk, hidden: diff.hidden }) })
10843
+ /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, children: /* @__PURE__ */ jsx11(DiffHunk, { lines: diff.hunk, hidden: diff.hidden }) })
10004
10844
  ] });
10005
10845
  }
10006
10846
  function NonDiffToolView({
@@ -10008,13 +10848,32 @@ function NonDiffToolView({
10008
10848
  mark,
10009
10849
  color
10010
10850
  }) {
10011
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
10851
+ const running = bubble.state === "running";
10852
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
10012
10853
  /* @__PURE__ */ jsxs11(Text11, { color, children: [
10013
10854
  mark,
10014
10855
  " ",
10015
- bubble.summary
10856
+ bubble.summary,
10857
+ running && bubble.startedAt !== void 0 && /* @__PURE__ */ jsx11(Elapsed, { since: bubble.startedAt })
10016
10858
  ] }),
10017
- bubble.state !== "running" && bubble.content.trim() !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", marginLeft: 2, children: previewLines(bubble.content).map((line, index) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: truncate2(line, 110) }, index)) })
10859
+ running && bubble.progress && /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, wrap: "truncate-end", children: [
10860
+ "\u2514 ",
10861
+ bubble.progress
10862
+ ] }) }),
10863
+ !running && bubble.content.trim() !== "" && /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginLeft: 2, children: previewLines(bubble.content).map((line, index) => /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: truncate2(line, 110) }, index)) })
10864
+ ] });
10865
+ }
10866
+ var ELAPSED_AFTER_MS = 2e3;
10867
+ function Elapsed({ since }) {
10868
+ const [now, setNow] = useState6(() => Date.now());
10869
+ useEffect3(() => {
10870
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
10871
+ return () => clearInterval(timer);
10872
+ }, []);
10873
+ const ms = now - since;
10874
+ return ms < ELAPSED_AFTER_MS ? null : /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
10875
+ " \xB7 ",
10876
+ formatDuration(ms)
10018
10877
  ] });
10019
10878
  }
10020
10879
  function previewLines(content) {
@@ -10026,7 +10885,7 @@ var VISIBLE_SUBAGENTS = 3;
10026
10885
  function SubagentSummary({ agents }) {
10027
10886
  const theme = useTheme();
10028
10887
  const running = agents.filter((agent) => agent.state === "running");
10029
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10888
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10030
10889
  /* @__PURE__ */ jsxs11(Text11, { color: theme.accent, wrap: "truncate-end", children: [
10031
10890
  "Subagents: ",
10032
10891
  running.length,
@@ -10043,7 +10902,7 @@ function SubagentSummary({ agents }) {
10043
10902
  progress.replace(/\s+/g, " ").trim() || "waiting"
10044
10903
  ] }, agent.id);
10045
10904
  }),
10046
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "/subagents \xB7 all active tasks" })
10905
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "/subagents \xB7 open a sub-agent chat" })
10047
10906
  ] });
10048
10907
  }
10049
10908
  function SubagentView({ bubble, maxRows }) {
@@ -10053,16 +10912,99 @@ function SubagentView({ bubble, maxRows }) {
10053
10912
  const color = bubble.state === "running" ? theme.warn : theme.ok;
10054
10913
  const latest = bubble.bubbles.at(-1);
10055
10914
  const progress = latest?.kind === "assistant" ? sanitizeThinkingText(latest.text || latest.thinking, latest.streaming) : latest?.kind === "tool" ? latest.summary : "";
10056
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10915
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10057
10916
  /* @__PURE__ */ jsxs11(Text11, { color, bold: true, wrap: "truncate-end", children: [
10058
10917
  mark,
10059
10918
  " subagent: ",
10060
10919
  bubble.description
10061
10920
  ] }),
10062
- /* @__PURE__ */ jsx12(Box12, { marginLeft: 2, flexDirection: "column", minWidth: 0, children: bubble.state === "running" ? /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: Math.max(1, (maxRows ?? 6) - 2), overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: clipTextToRows(progress, Math.max(1, (maxRows ?? 6) - 2), Math.max(1, columns - 2)) }) }) : bubble.result ? /* @__PURE__ */ jsx12(Markdown, { children: sanitizeThinkingText(bubble.result) }) : null })
10921
+ /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, flexDirection: "column", minWidth: 0, children: bubble.state === "running" ? /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", maxHeight: Math.max(1, (maxRows ?? 6) - 2), overflowY: "hidden", children: /* @__PURE__ */ jsx11(Markdown, { children: clipTextToRows(progress, Math.max(1, (maxRows ?? 6) - 2), Math.max(1, columns - 2)) }) }) : bubble.result ? /* @__PURE__ */ jsx11(Markdown, { children: sanitizeThinkingText(bubble.result) }) : null })
10063
10922
  ] });
10064
10923
  }
10065
10924
 
10925
+ // src/ui/components/SubagentChat.tsx
10926
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
10927
+ function SubagentChat({
10928
+ bubble,
10929
+ rows,
10930
+ onClose
10931
+ }) {
10932
+ const theme = useTheme();
10933
+ const strings = useStrings();
10934
+ const [offset, setOffset] = useState7(0);
10935
+ const total = bubble?.bubbles.length ?? 0;
10936
+ useInput2((_input, key) => {
10937
+ if (key.escape) onClose();
10938
+ else if (key.upArrow) setOffset((value) => Math.min(Math.max(0, total - 1), value + 1));
10939
+ else if (key.downArrow) setOffset((value) => Math.max(0, value - 1));
10940
+ });
10941
+ if (!bubble) {
10942
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
10943
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: strings.noSubagents }),
10944
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: strings.subagentBack })
10945
+ ] });
10946
+ }
10947
+ const running = bubble.state === "running";
10948
+ const shown = bubble.bubbles.slice(0, total - offset);
10949
+ const bodyRows = Math.max(3, rows - 2);
10950
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", minWidth: 0, children: [
10951
+ /* @__PURE__ */ jsxs12(Text12, { color: running ? theme.warn : theme.ok, bold: true, wrap: "truncate-end", children: [
10952
+ running ? "\u25CC" : "\u25CF",
10953
+ " ",
10954
+ bubble.description,
10955
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, bold: false, children: [
10956
+ " \xB7 ",
10957
+ running ? strings.subagentRunning : strings.subagentDone
10958
+ ] })
10959
+ ] }),
10960
+ /* @__PURE__ */ jsxs12(
10961
+ Box12,
10962
+ {
10963
+ flexDirection: "column",
10964
+ height: bodyRows,
10965
+ overflowY: "hidden",
10966
+ justifyContent: "flex-end",
10967
+ minWidth: 0,
10968
+ children: [
10969
+ shown.length === 0 && !bubble.result && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: strings.subagentWaiting }),
10970
+ shown.map((inner) => /* @__PURE__ */ jsx12(BubbleView, { bubble: inner, maxRows: bodyRows }, inner.id)),
10971
+ offset === 0 && !running && bubble.result && shown.length === 0 && /* @__PURE__ */ jsx12(Text12, { children: bubble.result })
10972
+ ]
10973
+ }
10974
+ ),
10975
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
10976
+ strings.subagentBack,
10977
+ " \xB7 \u2191/\u2193",
10978
+ offset > 0 ? ` \xB7 \u2212${offset}` : ""
10979
+ ] })
10980
+ ] });
10981
+ }
10982
+
10983
+ // src/ui/components/TerminalViewport.tsx
10984
+ import { Box as Box13 } from "ink";
10985
+ import { jsx as jsx13 } from "react/jsx-runtime";
10986
+ var INTERACTIVE_CHROME_ROWS = 11;
10987
+ var MAX_LIVE_ROWS = 11;
10988
+ var MIN_LIVE_ROWS = 4;
10989
+ function liveTranscriptRows(rows) {
10990
+ if (!Number.isFinite(rows)) return MAX_LIVE_ROWS;
10991
+ return Math.max(MIN_LIVE_ROWS, Math.min(MAX_LIVE_ROWS, Math.floor(rows) - INTERACTIVE_CHROME_ROWS - 2));
10992
+ }
10993
+ function TerminalViewport({ children, rows }) {
10994
+ const { columns } = useTerminalSize();
10995
+ return /* @__PURE__ */ jsx13(
10996
+ Box13,
10997
+ {
10998
+ width: columns,
10999
+ maxWidth: "100%",
11000
+ flexDirection: "column",
11001
+ maxHeight: liveTranscriptRows(rows) + INTERACTIVE_CHROME_ROWS,
11002
+ overflowY: "hidden",
11003
+ children
11004
+ }
11005
+ );
11006
+ }
11007
+
10066
11008
  // src/ui/transcript.ts
10067
11009
  function emptyTranscript() {
10068
11010
  return { bubbles: [], seq: 0 };
@@ -10103,11 +11045,19 @@ function applyEvent(state, event) {
10103
11045
  name: sanitizeTerminalText(event.name),
10104
11046
  summary: sanitizeTerminalText(event.summary),
10105
11047
  state: "running",
10106
- content: ""
11048
+ content: "",
11049
+ startedAt: Date.now()
10107
11050
  }
10108
11051
  ],
10109
11052
  seq: state.seq + 1
10110
11053
  };
11054
+ case "tool_progress":
11055
+ return {
11056
+ ...state,
11057
+ bubbles: state.bubbles.map(
11058
+ (bubble) => bubble.kind === "tool" && bubble.id === event.id && bubble.state === "running" ? { ...bubble, progress: sanitizeTerminalText(event.text) } : bubble
11059
+ )
11060
+ };
10111
11061
  case "tool_end":
10112
11062
  return {
10113
11063
  ...state,
@@ -10164,6 +11114,7 @@ function applyEvent(state, event) {
10164
11114
  case "turn_start":
10165
11115
  case "usage":
10166
11116
  case "rate_limits":
11117
+ case "speed":
10167
11118
  return state;
10168
11119
  }
10169
11120
  }
@@ -10300,21 +11251,24 @@ function sanitizeDisplay(display) {
10300
11251
  }
10301
11252
 
10302
11253
  // src/ui/App.tsx
10303
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
11254
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
10304
11255
  var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
10305
11256
  var STREAM_FRAME_MS = 50;
11257
+ var CUSTOM_ACCENT = "__custom__";
11258
+ var BELL_AFTER_MS = 15e3;
11259
+ var SUBAGENT_CHAT_EXTRA_ROWS = 4;
10306
11260
  var MAX_ATTACHMENTS = 8;
10307
11261
  var CONTEXT_PRESETS = [256e3, 512e3, 1e6];
10308
- function formatContextTokens(tokens3) {
10309
- if (tokens3 >= 1e6) return `${tokens3 / 1e6}M`;
10310
- return `${Math.round(tokens3 / 1e3)}k`;
11262
+ function formatContextTokens(tokens2) {
11263
+ if (tokens2 >= 1e6) return `${tokens2 / 1e6}M`;
11264
+ return `${Math.round(tokens2 / 1e3)}k`;
10311
11265
  }
10312
11266
  function parseContextTokens(input) {
10313
11267
  const match = /^(\d+(?:\.\d+)?)\s*([km])?$/i.exec(input.trim());
10314
11268
  if (!match) return null;
10315
11269
  const scale = match[2]?.toLowerCase() === "m" ? 1e6 : match[2]?.toLowerCase() === "k" ? 1e3 : 1;
10316
- const tokens3 = Math.round(Number(match[1]) * scale);
10317
- return tokens3 >= 1e3 && tokens3 <= 1e7 ? tokens3 : null;
11270
+ const tokens2 = Math.round(Number(match[1]) * scale);
11271
+ return tokens2 >= 1e3 && tokens2 <= 1e7 ? tokens2 : null;
10318
11272
  }
10319
11273
  function App({
10320
11274
  runtime,
@@ -10324,40 +11278,53 @@ function App({
10324
11278
  const { exit, suspendTerminal } = useApp();
10325
11279
  const { stdout } = useStdout2();
10326
11280
  const { rows } = useTerminalSize();
10327
- const [transcript, setTranscript] = useState5(
11281
+ const [transcript, setTranscript] = useState8(
10328
11282
  () => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
10329
11283
  );
10330
- const [transcriptRevision, setTranscriptRevision] = useState5(0);
10331
- const [input, setInput] = useState5("");
10332
- const [promptHistory, setPromptHistory] = useState5(
11284
+ const [transcriptRevision, setTranscriptRevision] = useState8(0);
11285
+ const transcriptRef = useRef5(transcript);
11286
+ transcriptRef.current = transcript;
11287
+ const [input, setInput] = useState8("");
11288
+ const [promptHistory, setPromptHistory] = useState8(
10333
11289
  () => inputHistoryFromMessages(initialHistory)
10334
11290
  );
10335
- const [busy, setBusy] = useState5(false);
10336
- const busyRef = useRef4(false);
10337
- const [pendingCount, setPendingCount] = useState5(0);
10338
- const queueRef = useRef4([]);
10339
- const slashQueueRef = useRef4(Promise.resolve());
10340
- const slashPendingRef = useRef4(0);
10341
- const drainQueueRef = useRef4(() => void 0);
10342
- const [attachments, setAttachments] = useState5([]);
10343
- const attachmentsRef = useRef4([]);
10344
- const attachmentGeneration = useRef4(0);
10345
- const automaticAttachmentTask = useRef4(null);
10346
- const clipboardPasteTask = useRef4(null);
10347
- const [overlay, setOverlay] = useState5({ kind: "none" });
10348
- const [accent, setAccent] = useState5(runtime.getAccent());
10349
- const [lang, setLang] = useState5(runtime.getLang());
10350
- const [setup, setSetup] = useState5(runtime.needsSetup());
10351
- const [, forceRender] = useState5(0);
10352
- const history = useRef4(initialHistory);
10353
- const abort = useRef4(null);
10354
- const sessionStart = useRef4(Date.now());
10355
- const turns = useRef4(0);
10356
- const [turnStart, setTurnStart] = useState5(null);
10357
- const [, tick] = useState5(0);
10358
- const [context, setContext] = useState5(() => runtime.modelContext());
10359
- const transcriptEvents = useRef4([]);
10360
- const transcriptTimer = useRef4(null);
11291
+ const [busy, setBusy] = useState8(false);
11292
+ const busyRef = useRef5(false);
11293
+ const [pendingCount, setPendingCount] = useState8(0);
11294
+ const queueRef = useRef5([]);
11295
+ const slashQueueRef = useRef5(Promise.resolve());
11296
+ const slashPendingRef = useRef5(0);
11297
+ const drainQueueRef = useRef5(() => void 0);
11298
+ const [attachments, setAttachments] = useState8([]);
11299
+ const attachmentsRef = useRef5([]);
11300
+ const attachmentGeneration = useRef5(0);
11301
+ const automaticAttachmentTask = useRef5(null);
11302
+ const clipboardPasteTask = useRef5(null);
11303
+ const [overlay, setOverlay] = useState8({ kind: "none" });
11304
+ const [accent, setAccent] = useState8(runtime.getAccent());
11305
+ const [lang, setLang] = useState8(runtime.getLang());
11306
+ const [setup, setSetup] = useState8(runtime.needsSetup());
11307
+ const [, forceRender] = useState8(0);
11308
+ const history = useRef5(initialHistory);
11309
+ const abort = useRef5(null);
11310
+ const sessionStart = useRef5(Date.now());
11311
+ const turns = useRef5(0);
11312
+ const [turnStart, setTurnStart] = useState8(null);
11313
+ const [, tick] = useState8(0);
11314
+ const [context, setContext] = useState8(() => runtime.modelContext());
11315
+ const speedMeter = useRef5(createSpeedMeter());
11316
+ const undoNote = useRef5(null);
11317
+ const withUndoNote = (content) => {
11318
+ const note = undoNote.current;
11319
+ if (!note) return content;
11320
+ undoNote.current = null;
11321
+ return [{ type: "text", text: note }, ...content];
11322
+ };
11323
+ const transcriptEvents = useRef5([]);
11324
+ const transcriptTimer = useRef5(null);
11325
+ const bell = useCallback(() => {
11326
+ if (stdout.isTTY && runtime.getBell()) stdout.write("\x07");
11327
+ }, [runtime, stdout]);
10361
11328
  const clearScreen = useCallback(() => {
10362
11329
  if (stdout.isTTY) stdout.write("\x1B[2J\x1B[3J\x1B[H");
10363
11330
  }, [stdout]);
@@ -10384,6 +11351,7 @@ function App({
10384
11351
  }, []);
10385
11352
  const queueTranscriptEvent = useCallback(
10386
11353
  (event) => {
11354
+ recordSpeed(speedMeter.current, event);
10387
11355
  transcriptEvents.current.push(event);
10388
11356
  if (event.type !== "text_delta" && event.type !== "thinking_delta") {
10389
11357
  flushTranscriptEvents();
@@ -10393,24 +11361,24 @@ function App({
10393
11361
  },
10394
11362
  [flushTranscriptEvents]
10395
11363
  );
10396
- useEffect2(
11364
+ useEffect4(
10397
11365
  () => () => {
10398
11366
  if (transcriptTimer.current !== null) clearTimeout(transcriptTimer.current);
10399
11367
  transcriptEvents.current = [];
10400
11368
  },
10401
11369
  []
10402
11370
  );
10403
- useEffect2(
11371
+ useEffect4(
10404
11372
  () => runtime.subscribeContext(() => setContext(runtime.modelContext())),
10405
11373
  [runtime]
10406
11374
  );
10407
- useEffect2(() => {
11375
+ useEffect4(() => {
10408
11376
  if (turnStart === null) return;
10409
11377
  const timer = setInterval(() => tick((n) => n + 1), 1e3);
10410
11378
  return () => clearInterval(timer);
10411
11379
  }, [turnStart]);
10412
- const theme = useMemo5(() => makeTheme(accent), [accent]);
10413
- const strings = useMemo5(() => stringsFor(lang), [lang]);
11380
+ const theme = useMemo6(() => makeTheme(accent), [accent]);
11381
+ const strings = useMemo6(() => stringsFor(lang), [lang]);
10414
11382
  const notice = useCallback(
10415
11383
  (level, text) => {
10416
11384
  flushTranscriptEvents();
@@ -10418,7 +11386,7 @@ function App({
10418
11386
  },
10419
11387
  [flushTranscriptEvents]
10420
11388
  );
10421
- useEffect2(() => {
11389
+ useEffect4(() => {
10422
11390
  const check = runtime.startupUpdateCheck();
10423
11391
  let active2 = true;
10424
11392
  void check.then((result) => {
@@ -10458,7 +11426,8 @@ function App({
10458
11426
  abort.current = controller;
10459
11427
  busyRef.current = true;
10460
11428
  setBusy(true);
10461
- setTurnStart(Date.now());
11429
+ const startedAt = Date.now();
11430
+ setTurnStart(startedAt);
10462
11431
  turns.current += 1;
10463
11432
  const hooks = {
10464
11433
  onEvent: queueTranscriptEvent,
@@ -10481,7 +11450,11 @@ function App({
10481
11450
  return;
10482
11451
  }
10483
11452
  controller.signal.addEventListener("abort", cancel, { once: true });
10484
- setOverlay({ kind: "permission", request, resolve: finish });
11453
+ bell();
11454
+ setOverlay((current) => {
11455
+ if (current.kind === "picker") current.select("");
11456
+ return { kind: "permission", request, resolve: finish };
11457
+ });
10485
11458
  })
10486
11459
  };
10487
11460
  try {
@@ -10495,6 +11468,7 @@ function App({
10495
11468
  } catch (error) {
10496
11469
  notice("error", error instanceof Error ? error.message : String(error));
10497
11470
  } finally {
11471
+ if (!controller.signal.aborted && Date.now() - startedAt >= BELL_AFTER_MS) bell();
10498
11472
  flushTranscriptEvents();
10499
11473
  abort.current = null;
10500
11474
  busyRef.current = false;
@@ -10502,9 +11476,14 @@ function App({
10502
11476
  setTurnStart(null);
10503
11477
  drainQueueRef.current();
10504
11478
  }
10505
- }, [flushTranscriptEvents, notice, queueTranscriptEvent, runtime]);
11479
+ }, [bell, flushTranscriptEvents, notice, queueTranscriptEvent, runtime]);
10506
11480
  const applyAccent = useCallback(
10507
- async (value) => {
11481
+ async (requested) => {
11482
+ const value = parseAccent(requested);
11483
+ if (!value) {
11484
+ notice("warn", strings.accentInvalid(requested));
11485
+ return;
11486
+ }
10508
11487
  await runtime.setAccent(value);
10509
11488
  setAccent(value);
10510
11489
  notice("info", strings.accentSet(value, resolveAccent(value)));
@@ -10708,6 +11687,10 @@ function App({
10708
11687
  notice("info", strings.undoNothing);
10709
11688
  return;
10710
11689
  }
11690
+ const reverted = [...result.restored, ...result.removed];
11691
+ if (reverted.length > 0) {
11692
+ undoNote.current = `[kitcode] The user ran /undo. These file changes from the previous turn were reverted and are no longer on disk: ${reverted.join(", ")}. Re-read files before relying on them.`;
11693
+ }
10711
11694
  const lines = [strings.undoResult(result.restored.length, result.removed.length)];
10712
11695
  if (result.conflicts.length > 0) {
10713
11696
  lines.push(strings.undoConflicts(result.conflicts));
@@ -10832,8 +11815,8 @@ ${strings.modelSet(result.nextModel)}` : ""}`;
10832
11815
  return;
10833
11816
  }
10834
11817
  try {
10835
- const count = await runtime.refreshProviderModels(providerId);
10836
- notice("info", strings.modelsRefreshed(providerId, count));
11818
+ const count2 = await runtime.refreshProviderModels(providerId);
11819
+ notice("info", strings.modelsRefreshed(providerId, count2));
10837
11820
  } catch (error) {
10838
11821
  notice("error", error instanceof Error ? error.message : String(error));
10839
11822
  }
@@ -11133,7 +12116,7 @@ ${strings.mcpAddUsage}`
11133
12116
  history.current = result.history;
11134
12117
  setPromptHistory(inputHistoryFromMessages(result.history));
11135
12118
  setTranscript(
11136
- pushNotice(fromHistory(result.history), "info", strings.compactDone(result.removedMessages))
12119
+ pushNotice(fromHistory(result.history), "info", strings.compactDone(result.removedMessages, result.clearedToolResults ?? 0, result.tokensBefore, result.tokensAfter))
11137
12120
  );
11138
12121
  setTranscriptRevision((revision) => revision + 1);
11139
12122
  await runtime.persist(result.history);
@@ -11152,17 +12135,27 @@ ${strings.mcpAddUsage}`
11152
12135
  return;
11153
12136
  }
11154
12137
  case "subagents": {
11155
- const agents = runtime.activeAgentsList();
12138
+ const agents = transcriptRef.current.bubbles.filter(
12139
+ (bubble) => bubble.kind === "subagent"
12140
+ );
11156
12141
  if (agents.length === 0) {
11157
- notice("info", strings.subagentsStatus(0));
12142
+ notice("info", strings.noSubagents);
11158
12143
  return;
11159
12144
  }
11160
- const lines = agents.map((a, i) => {
11161
- const label = a.description || `subagent ${i + 1}`;
11162
- const progress = a.progress.length > 0 ? ` \xB7 ${a.progress[a.progress.length - 1]}` : "";
11163
- return `${label}${progress}`;
11164
- });
11165
- notice("info", lines.join("\n"));
12145
+ const ordered = [
12146
+ ...agents.filter((agent) => agent.state === "running"),
12147
+ ...agents.filter((agent) => agent.state !== "running").reverse()
12148
+ ];
12149
+ const running = ordered.filter((agent) => agent.state === "running");
12150
+ const id = running.length === 1 && rawRest === "" ? running[0].id : await pick(
12151
+ strings.subagentsTitle,
12152
+ ordered.slice(0, 20).map((agent) => ({
12153
+ key: agent.id,
12154
+ label: agent.description || "subagent",
12155
+ hint: agent.state === "running" ? strings.subagentRunning : strings.subagentDone
12156
+ }))
12157
+ );
12158
+ if (id) setOverlay({ kind: "subagent", id });
11166
12159
  return;
11167
12160
  }
11168
12161
  case "checker":
@@ -11180,9 +12173,21 @@ ${strings.mcpAddUsage}`
11180
12173
  }
11181
12174
  const choice = await pick(
11182
12175
  strings.titleAccent,
11183
- Object.entries(PRESETS).map(([name, hex]) => ({ key: name, label: name, hint: hex }))
12176
+ [
12177
+ ...Object.entries(PRESETS).map(([name, hex]) => ({ key: name, label: name, hint: hex })),
12178
+ { key: CUSTOM_ACCENT, label: strings.customAccent, hint: "#rrggbb" }
12179
+ ]
11184
12180
  );
11185
- if (choice) await applyAccent(choice);
12181
+ if (choice === CUSTOM_ACCENT) {
12182
+ const typed = await promptInput(strings.customAccentPrompt);
12183
+ if (typed) await applyAccent(typed);
12184
+ } else if (choice) await applyAccent(choice);
12185
+ return;
12186
+ }
12187
+ case "bell": {
12188
+ const next = rawRest === "on" ? true : rawRest === "off" ? false : !runtime.getBell();
12189
+ await runtime.setBell(next);
12190
+ notice("info", strings.bellState(next));
11186
12191
  return;
11187
12192
  }
11188
12193
  case "thinking": {
@@ -11219,16 +12224,16 @@ ${strings.mcpAddUsage}`
11219
12224
  case "budget": {
11220
12225
  const current = runtime.getMaxTokensPerTurn();
11221
12226
  if (rest[0]) {
11222
- const tokens3 = Number(rest[0]);
11223
- if (tokens3 === 0) {
12227
+ const tokens2 = Number(rest[0]);
12228
+ if (tokens2 === 0) {
11224
12229
  await runtime.setMaxTokensPerTurn(0);
11225
12230
  notice("info", strings.budgetUnlimited);
11226
12231
  forceRender((n) => n + 1);
11227
- } else if (!Number.isInteger(tokens3) || tokens3 < 1e3 || tokens3 > 1e7) {
12232
+ } else if (!Number.isInteger(tokens2) || tokens2 < 1e3 || tokens2 > 1e7) {
11228
12233
  notice("warn", strings.budgetInvalid);
11229
12234
  } else {
11230
- await runtime.setMaxTokensPerTurn(tokens3);
11231
- notice("info", strings.budgetSet(tokens3));
12235
+ await runtime.setMaxTokensPerTurn(tokens2);
12236
+ notice("info", strings.budgetSet(tokens2));
11232
12237
  forceRender((n) => n + 1);
11233
12238
  }
11234
12239
  } else {
@@ -11247,13 +12252,13 @@ ${strings.mcpAddUsage}`
11247
12252
  forceRender((n) => n + 1);
11248
12253
  return;
11249
12254
  }
11250
- const tokens3 = parseContextTokens(requested);
11251
- if (tokens3 === null) {
12255
+ const tokens2 = parseContextTokens(requested);
12256
+ if (tokens2 === null) {
11252
12257
  notice("warn", strings.contextInvalid);
11253
12258
  return;
11254
12259
  }
11255
- await runtime.setContextWindowOverride(tokens3);
11256
- notice("info", strings.contextSet(tokens3));
12260
+ await runtime.setContextWindowOverride(tokens2);
12261
+ notice("info", strings.contextSet(tokens2));
11257
12262
  forceRender((n) => n + 1);
11258
12263
  return;
11259
12264
  }
@@ -11436,7 +12441,7 @@ Rename the file if you want a different name.`);
11436
12441
  return;
11437
12442
  }
11438
12443
  setTranscript((state) => pushUser(state, userDisplay(item.text, item.content)));
11439
- history.current = [...history.current, { role: "user", content: item.content }];
12444
+ history.current = [...history.current, { role: "user", content: withUndoNote(item.content) }];
11440
12445
  void runAgent();
11441
12446
  },
11442
12447
  [runAgent, runSlash]
@@ -11453,6 +12458,49 @@ Rename the file if you want a different name.`);
11453
12458
  queueMicrotask(() => enqueueNext(next));
11454
12459
  }, [enqueueNext]);
11455
12460
  drainQueueRef.current = drainQueue;
12461
+ const checkedOut = useRef5(null);
12462
+ const putBackCheckedOut = (currentValue) => {
12463
+ const out = checkedOut.current;
12464
+ if (!out) return null;
12465
+ checkedOut.current = null;
12466
+ const restored = withEditedText(out.item, currentValue, attachmentsRef.current);
12467
+ replaceAttachments([]);
12468
+ const index = Math.min(out.index, queueRef.current.length);
12469
+ queueRef.current = [...queueRef.current.slice(0, index), restored, ...queueRef.current.slice(index)];
12470
+ return index;
12471
+ };
12472
+ const checkOut = (index) => {
12473
+ const item = queueRef.current[index];
12474
+ queueRef.current = queueRef.current.filter((_, at) => at !== index);
12475
+ checkedOut.current = { index, item };
12476
+ replaceAttachments(item.kind === "message" ? item.attachments : []);
12477
+ return item.kind === "command" ? item.line : item.text;
12478
+ };
12479
+ const navigateQueue = useCallback(
12480
+ (direction, currentValue) => {
12481
+ if (!busyRef.current && slashPendingRef.current === 0) {
12482
+ checkedOut.current = null;
12483
+ return null;
12484
+ }
12485
+ if (!checkedOut.current && (currentValue !== "" || queueRef.current.length === 0)) return null;
12486
+ const from = putBackCheckedOut(currentValue);
12487
+ const length = queueRef.current.length;
12488
+ let value;
12489
+ if (from === null) value = direction === "previous" ? checkOut(length - 1) : "";
12490
+ else if (direction === "previous") value = checkOut(Math.max(0, from - 1));
12491
+ else value = from + 1 < length ? checkOut(from + 1) : "";
12492
+ setPendingCount(queueRef.current.length);
12493
+ return value;
12494
+ },
12495
+ [replaceAttachments]
12496
+ );
12497
+ const enqueue = (item) => {
12498
+ const out = checkedOut.current;
12499
+ checkedOut.current = null;
12500
+ const index = out ? Math.min(out.index, queueRef.current.length) : queueRef.current.length;
12501
+ queueRef.current = [...queueRef.current.slice(0, index), item, ...queueRef.current.slice(index)];
12502
+ setPendingCount(queueRef.current.length);
12503
+ };
11456
12504
  const tryQueueAutomaticAttachment = useCallback(
11457
12505
  (requestedPath) => {
11458
12506
  if (!looksLikeAttachmentPath(requestedPath)) return Promise.resolve(false);
@@ -11463,19 +12511,30 @@ Rename the file if you want a different name.`);
11463
12511
  notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
11464
12512
  return Promise.resolve(true);
11465
12513
  }
11466
- const task = runtime.loadAutomaticAttachment(requestedPath).then((block) => {
11467
- if (automaticAttachmentTask.current !== task) return true;
11468
- if (!block) return false;
11469
- if (!appendAttachment(block)) {
11470
- notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
11471
- return true;
12514
+ const paths = splitAttachmentPaths(requestedPath) ?? [requestedPath];
12515
+ let task;
12516
+ task = (async () => {
12517
+ let added = 0;
12518
+ for (const candidate of paths) {
12519
+ try {
12520
+ const block = await runtime.loadAutomaticAttachment(candidate);
12521
+ if (automaticAttachmentTask.current !== task) return true;
12522
+ if (!block) continue;
12523
+ if (!appendAttachment(block)) {
12524
+ notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
12525
+ return true;
12526
+ }
12527
+ added += 1;
12528
+ notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
12529
+ } catch (error) {
12530
+ if (automaticAttachmentTask.current !== task) return true;
12531
+ if (error instanceof Error && !error.code) {
12532
+ notice("warn", error.message);
12533
+ }
12534
+ }
11472
12535
  }
11473
- notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
11474
- return true;
11475
- }).catch(() => {
11476
- if (automaticAttachmentTask.current !== task) return true;
11477
- return false;
11478
- });
12536
+ return added > 0;
12537
+ })();
11479
12538
  automaticAttachmentTask.current = task;
11480
12539
  void task.finally(() => {
11481
12540
  if (automaticAttachmentTask.current === task) automaticAttachmentTask.current = null;
@@ -11528,10 +12587,10 @@ Rename the file if you want a different name.`);
11528
12587
  if (!detachedInput) setInput("");
11529
12588
  const command = text.slice(1).trim().split(/\s+/)[0]?.toLowerCase();
11530
12589
  if (busyRef.current && !commandRunsWhileBusy(command) || slashPendingRef.current > 0) {
11531
- queueRef.current = [...queueRef.current, { kind: "command", line: text }];
11532
- setPendingCount(queueRef.current.length);
12590
+ enqueue({ kind: "command", line: text });
11533
12591
  return;
11534
12592
  }
12593
+ checkedOut.current = null;
11535
12594
  runSlash(text);
11536
12595
  };
11537
12596
  if (isKnownSlashCommand(text)) {
@@ -11569,14 +12628,14 @@ Rename the file if you want a different name.`);
11569
12628
  ...mentioned
11570
12629
  ];
11571
12630
  replaceAttachments([]);
11572
- const item = { kind: "message", text, content };
12631
+ const item = { kind: "message", text, content, attachments: queuedAttachments };
11573
12632
  if (busyRef.current || slashPendingRef.current > 0) {
11574
- queueRef.current = [...queueRef.current, item];
11575
- setPendingCount(queueRef.current.length);
12633
+ enqueue(item);
11576
12634
  return;
11577
12635
  }
12636
+ checkedOut.current = null;
11578
12637
  setTranscript((state) => pushUser(state, userDisplay(text, content)));
11579
- history.current = [...history.current, { role: "user", content }];
12638
+ history.current = [...history.current, { role: "user", content: withUndoNote(content) }];
11580
12639
  void runAgent();
11581
12640
  },
11582
12641
  [replaceAttachments, runAgent, runSlash, tryQueueAutomaticAttachment]
@@ -11589,14 +12648,25 @@ Rename the file if you want a different name.`);
11589
12648
  return;
11590
12649
  }
11591
12650
  if (!key.escape) return;
11592
- if (busyRef.current && abort.current) {
12651
+ if (overlay.kind === "subagent") return;
12652
+ if (checkedOut.current && overlay.kind === "none") {
12653
+ if (busyRef.current || slashPendingRef.current > 0) {
12654
+ putBackCheckedOut(input);
12655
+ setPendingCount(queueRef.current.length);
12656
+ setInput("");
12657
+ return;
12658
+ }
12659
+ checkedOut.current = null;
12660
+ }
12661
+ const hasDraft = input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current !== null || clipboardPasteTask.current !== null;
12662
+ if (busyRef.current && abort.current && !(hasDraft && overlay.kind === "none")) {
11593
12663
  if (!abort.current.signal.aborted) {
11594
12664
  abort.current.abort();
11595
12665
  notice("warn", strings.cancelled);
11596
12666
  }
11597
12667
  return;
11598
12668
  }
11599
- if (input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current || clipboardPasteTask.current) {
12669
+ if (hasDraft) {
11600
12670
  attachmentGeneration.current += 1;
11601
12671
  setInput("");
11602
12672
  replaceAttachments([]);
@@ -11621,10 +12691,10 @@ Rename the file if you want a different name.`);
11621
12691
  }
11622
12692
  setInput("");
11623
12693
  });
11624
- const shell = (children) => /* @__PURE__ */ jsx13(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ jsx13(StringsContext.Provider, { value: strings, children }) });
12694
+ const shell = (children) => /* @__PURE__ */ jsx14(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ jsx14(StringsContext.Provider, { value: strings, children }) });
11625
12695
  if (lang === void 0) {
11626
12696
  return shell(
11627
- /* @__PURE__ */ jsx13(
12697
+ /* @__PURE__ */ jsx14(
11628
12698
  LanguagePicker,
11629
12699
  {
11630
12700
  onPick: (chosen) => {
@@ -11637,7 +12707,7 @@ Rename the file if you want a different name.`);
11637
12707
  }
11638
12708
  if (setup) {
11639
12709
  return shell(
11640
- /* @__PURE__ */ jsx13(
12710
+ /* @__PURE__ */ jsx14(
11641
12711
  Onboarding,
11642
12712
  {
11643
12713
  onSubmit: async (url, key) => {
@@ -11652,17 +12722,17 @@ ${strings.configAt(runtime.configPath())}`);
11652
12722
  );
11653
12723
  }
11654
12724
  return shell(
11655
- /* @__PURE__ */ jsxs12(TerminalViewport, { rows, children: [
11656
- /* @__PURE__ */ jsx13(
12725
+ /* @__PURE__ */ jsxs13(TerminalViewport, { rows, children: [
12726
+ /* @__PURE__ */ jsx14(
11657
12727
  Transcript,
11658
12728
  {
11659
12729
  bubbles: transcript.bubbles,
11660
12730
  workspace: runtime.cwd,
11661
- maxLiveRows: liveTranscriptRows(rows)
12731
+ maxLiveRows: overlay.kind === "subagent" ? 0 : liveTranscriptRows(rows)
11662
12732
  },
11663
12733
  transcriptRevision
11664
12734
  ),
11665
- overlay.kind === "permission" && /* @__PURE__ */ jsx13(
12735
+ overlay.kind === "permission" && /* @__PURE__ */ jsx14(
11666
12736
  PermissionPrompt,
11667
12737
  {
11668
12738
  request: overlay.request,
@@ -11672,7 +12742,7 @@ ${strings.configAt(runtime.configPath())}`);
11672
12742
  }
11673
12743
  }
11674
12744
  ),
11675
- overlay.kind === "picker" && /* @__PURE__ */ jsx13(
12745
+ overlay.kind === "picker" && /* @__PURE__ */ jsx14(
11676
12746
  Picker,
11677
12747
  {
11678
12748
  title: overlay.title,
@@ -11687,7 +12757,17 @@ ${strings.configAt(runtime.configPath())}`);
11687
12757
  }
11688
12758
  }
11689
12759
  ),
11690
- overlay.kind === "confirm" && /* @__PURE__ */ jsx13(
12760
+ overlay.kind === "subagent" && /* @__PURE__ */ jsx14(
12761
+ SubagentChat,
12762
+ {
12763
+ bubble: transcript.bubbles.find(
12764
+ (bubble) => bubble.kind === "subagent" && bubble.id === overlay.id
12765
+ ),
12766
+ rows: liveTranscriptRows(rows) + SUBAGENT_CHAT_EXTRA_ROWS,
12767
+ onClose: () => setOverlay({ kind: "none" })
12768
+ }
12769
+ ),
12770
+ overlay.kind === "confirm" && /* @__PURE__ */ jsx14(
11691
12771
  Confirm,
11692
12772
  {
11693
12773
  title: overlay.title,
@@ -11699,7 +12779,7 @@ ${strings.configAt(runtime.configPath())}`);
11699
12779
  }
11700
12780
  }
11701
12781
  ),
11702
- overlay.kind === "textinput" && /* @__PURE__ */ jsx13(
12782
+ overlay.kind === "textinput" && /* @__PURE__ */ jsx14(
11703
12783
  TextInputOverlay,
11704
12784
  {
11705
12785
  title: overlay.title,
@@ -11714,7 +12794,7 @@ ${strings.configAt(runtime.configPath())}`);
11714
12794
  }
11715
12795
  }
11716
12796
  ),
11717
- overlay.kind === "none" && /* @__PURE__ */ jsx13(
12797
+ overlay.kind === "none" && /* @__PURE__ */ jsx14(
11718
12798
  PromptInput,
11719
12799
  {
11720
12800
  value: input,
@@ -11725,7 +12805,11 @@ ${strings.configAt(runtime.configPath())}`);
11725
12805
  onListFiles: listWorkspaceFiles,
11726
12806
  disabled: busy,
11727
12807
  pending: pendingCount,
11728
- hint: busy ? strings.escCancel : void 0,
12808
+ queued: queueRef.current.map((item) => item.kind === "command" ? item.line : item.text || attachmentSummary(item.attachments)),
12809
+ onNavigateQueue: navigateQueue,
12810
+ hint: busy ? input !== "" || attachments.length > 0 ? strings.escClear : strings.escCancel : void 0,
12811
+ mode: runtime.getMode(),
12812
+ bypass: runtime.isBypassEnabled(),
11729
12813
  history: promptHistory,
11730
12814
  attachments: attachments.flatMap((block) => {
11731
12815
  const label = attachmentLabel(block);
@@ -11733,21 +12817,23 @@ ${strings.configAt(runtime.configPath())}`);
11733
12817
  })
11734
12818
  }
11735
12819
  ),
11736
- /* @__PURE__ */ jsx13(
12820
+ /* @__PURE__ */ jsx14(
11737
12821
  StatusBar,
11738
12822
  {
11739
12823
  status: {
11740
12824
  modelRef: runtime.getModelRef() || strings.noModel,
11741
12825
  effort: runtime.getEffort(),
12826
+ effectiveEffort: runtime.getEffectiveEffort(history.current),
11742
12827
  thinking: runtime.getThinking(),
11743
12828
  usage: runtime.usageParts(),
12829
+ speed: readSpeed(speedMeter.current, busy),
11744
12830
  mcp: runtime.mcpSummary(),
11745
12831
  mode: runtime.getMode(),
11746
12832
  bypass: runtime.isBypassEnabled(),
11747
12833
  busy,
11748
12834
  sessionMs: Date.now() - sessionStart.current,
11749
12835
  turnMs: turnStart === null ? null : Date.now() - turnStart,
11750
- activeAgents: runtime.activeAgentsCount(),
12836
+ agents: runtime.activeAgentsList().map((agent) => agent.description),
11751
12837
  context
11752
12838
  }
11753
12839
  }
@@ -11761,17 +12847,17 @@ function TextInputOverlay({
11761
12847
  onAnswer,
11762
12848
  onCancel
11763
12849
  }) {
11764
- const [value, setValue] = useState5("");
12850
+ const [value, setValue] = useState8("");
11765
12851
  const theme = useTheme();
11766
12852
  const submit = useCallback(() => {
11767
12853
  onAnswer(value || null);
11768
12854
  }, [value, onAnswer]);
11769
- useInput2((_input, key) => {
12855
+ useInput3((_input, key) => {
11770
12856
  if (key.escape) onCancel();
11771
12857
  });
11772
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
11773
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: title }),
11774
- /* @__PURE__ */ jsx13(
12858
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
12859
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: title }),
12860
+ /* @__PURE__ */ jsx14(
11775
12861
  TextInput2,
11776
12862
  {
11777
12863
  value,
@@ -11780,9 +12866,24 @@ function TextInputOverlay({
11780
12866
  mask: mask ? "\u2022" : void 0
11781
12867
  }
11782
12868
  ),
11783
- /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text12, { dimColor: true, color: theme.accent, children: "enter submit \xB7 esc cancel" }) })
12869
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, color: theme.accent, children: "enter submit \xB7 esc cancel" }) })
11784
12870
  ] });
11785
12871
  }
12872
+ function withEditedText(item, text, attachments) {
12873
+ if (item.kind === "command") {
12874
+ if (text.startsWith("/")) return { kind: "command", line: text };
12875
+ return { kind: "message", text, attachments, content: messageContent(text, attachments, []) };
12876
+ }
12877
+ if (text === item.text && attachments === item.attachments) return item;
12878
+ const mentioned = item.content.filter((block) => block.type !== "text" && !item.attachments.includes(block));
12879
+ return { kind: "message", text, attachments, content: messageContent(text, attachments, mentioned) };
12880
+ }
12881
+ function messageContent(text, attachments, mentioned) {
12882
+ return [...text ? [{ type: "text", text }] : [], ...attachments, ...mentioned];
12883
+ }
12884
+ function attachmentSummary(blocks) {
12885
+ return "\u{1F4CE} " + blocks.map((block) => attachmentLabel(block) ?? "attachment").join(", ");
12886
+ }
11786
12887
  function isKnownSlashCommand(line) {
11787
12888
  if (!line.startsWith("/")) return false;
11788
12889
  const [name = ""] = line.slice(1).trim().toLowerCase().split(/\s+/);
@@ -11853,7 +12954,7 @@ function unquoteArg(value) {
11853
12954
  }
11854
12955
 
11855
12956
  // src/app/tui.tsx
11856
- import { jsx as jsx14 } from "react/jsx-runtime";
12957
+ import { jsx as jsx15 } from "react/jsx-runtime";
11857
12958
  function clearTerminal() {
11858
12959
  if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
11859
12960
  }
@@ -11873,7 +12974,7 @@ async function startTui(options) {
11873
12974
  });
11874
12975
  clearTerminal();
11875
12976
  const instance = render(
11876
- /* @__PURE__ */ jsx14(App, { runtime, initialHistory: history, warnings }),
12977
+ /* @__PURE__ */ jsx15(App, { runtime, initialHistory: history, warnings }),
11877
12978
  TUI_RENDER_OPTIONS
11878
12979
  );
11879
12980
  try {