@kernelonpanic/kitcode 1.3.3 → 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,11 @@ 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),
80
+ // Manual override for the model's context window. Unset means the window
81
+ // comes from the provider catalogue or the endpoint's model list.
82
+ contextWindow: z.number().int().min(1e3).max(1e7).optional(),
78
83
  maxTokens: z.number().int().positive().max(2e5).default(64e3),
79
84
  budget: budgetSchema.default({
80
85
  maxTokensPerTurn: 1e7,
@@ -1396,15 +1401,39 @@ async function clearProjectMemory(workspace) {
1396
1401
  }
1397
1402
 
1398
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");
1399
1405
  function resolveEffort(effort, messages) {
1400
1406
  if (effort !== "auto") return effort;
1401
1407
  const latest = messages.findLast((message) => message.role === "user" && message.content.some((block) => block.type === "text"));
1402
1408
  const text = latest?.content.filter((block) => block.type === "text").map((block) => block.text).join("\n") ?? "";
1403
- 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;
1404
1428
  }
1405
1429
  function openAiEffort(model, effort) {
1406
- const id = model.split("/").at(-1) ?? model;
1407
- 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
+ }
1408
1437
  if (/^(?:o[134](?:-|$)|gpt-5(?:-(?:mini|nano|\d{4})|$)|gpt-5\.1(?:-|$))/.test(id)) {
1409
1438
  return effort === "max" || effort === "xhigh" ? "high" : effort;
1410
1439
  }
@@ -1487,9 +1516,10 @@ async function loadSession(id) {
1487
1516
  async function listSessions(limit = 20) {
1488
1517
  const files = await filesByRecency();
1489
1518
  const summaries = [];
1490
- for (const file of files.slice(0, limit)) {
1519
+ for (const file of files) {
1520
+ if (summaries.length >= limit) break;
1491
1521
  const state = readState(await readFile4(file, "utf8").catch(() => ""));
1492
- if (state) {
1522
+ if (state && state.messages.length > 0) {
1493
1523
  summaries.push({
1494
1524
  id: state.id,
1495
1525
  title: state.title,
@@ -1846,6 +1876,17 @@ async function runTurn(cfg, history, hooks, signal) {
1846
1876
  hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
1847
1877
  return messages;
1848
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
+ }
1849
1890
  const request = async () => {
1850
1891
  if (signal.aborted) return { content: [], stopReason: "aborted" };
1851
1892
  const budgetDecision = cfg.budget?.beforeRequest({
@@ -1920,13 +1961,19 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1920
1961
  let outcome;
1921
1962
  let text = "";
1922
1963
  let thinking = "";
1964
+ let outputTokens = 0;
1965
+ const startedAt = Date.now();
1966
+ let firstTokenAt;
1923
1967
  for await (const event of streamWithRetry(cfg.provider, request, cfg.modelRef, signal)) {
1924
1968
  switch (event.type) {
1925
1969
  case "text_delta":
1970
+ firstTokenAt ??= Date.now();
1926
1971
  text += event.text;
1927
1972
  hooks.onEvent({ type: "text_delta", text: event.text });
1928
1973
  break;
1929
1974
  case "thinking_delta":
1975
+ firstTokenAt ??= Date.now();
1976
+ if (!cfg.thinking) break;
1930
1977
  thinking += event.text;
1931
1978
  hooks.onEvent({ type: "thinking_delta", text: event.text });
1932
1979
  break;
@@ -1934,6 +1981,7 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1934
1981
  cfg.budget?.record(cfg.modelRef, event.usage);
1935
1982
  cfg.usage.record(cfg.modelRef, event.usage);
1936
1983
  hooks.onEvent({ type: "usage", model: cfg.modelRef, usage: event.usage });
1984
+ outputTokens = event.usage.output;
1937
1985
  break;
1938
1986
  case "rate_limits":
1939
1987
  hooks.onEvent({ type: "rate_limits", model: cfg.modelRef, limits: event.limits });
@@ -1945,7 +1993,13 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
1945
1993
  break;
1946
1994
  }
1947
1995
  }
1948
- 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
+ }
1949
2003
  if (signal.aborted) {
1950
2004
  const content = [];
1951
2005
  if (thinking) content.push({ type: "thinking", text: thinking });
@@ -2066,23 +2120,21 @@ async function runToolCalls(cfg, content, hooks, signal) {
2066
2120
  const operation = () => runOneCall(cfg, call, hooks, signal);
2067
2121
  return call.name !== "task" && cfg.executionLane ? cfg.executionLane.run(operation) : operation();
2068
2122
  };
2069
- for (let index = 0; index < calls.length; ) {
2070
- const call = calls[index];
2071
- if (call.name !== "task") {
2072
- results.push(await run(call));
2073
- index += 1;
2074
- continue;
2075
- }
2076
- const batch = [];
2077
- while (calls[index]?.name === "task") {
2078
- batch.push(run(calls[index]));
2079
- index += 1;
2080
- }
2081
- const settled = await Promise.allSettled(batch);
2082
- for (const result of settled) {
2083
- if (result.status === "rejected") throw result.reason;
2084
- results.push(result.value);
2085
- }
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());
2086
2138
  }
2087
2139
  return results;
2088
2140
  }
@@ -2099,6 +2151,7 @@ async function runOneCall(cfg, call, hooks, signal) {
2099
2151
  requestPermission: (request) => hooks.requestPermission(request),
2100
2152
  checkpoint: cfg.checkpoint,
2101
2153
  onEvent: (event) => hooks.onEvent(event),
2154
+ onProgress: (text) => hooks.onEvent({ type: "tool_progress", id: call.id, text }),
2102
2155
  confirm: async (question) => await hooks.requestPermission({
2103
2156
  toolName: tool.name,
2104
2157
  summary: question,
@@ -2409,13 +2462,13 @@ function beginCheckpoint(options) {
2409
2462
  async function undoLatestCheckpoint(options) {
2410
2463
  assertSessionId2(options.sessionId);
2411
2464
  const root = workspaceRoot(options.cwd);
2412
- const sessionDir = path6.join(options.storageDir ?? checkpointsDir, options.sessionId);
2413
- const file = await newestCheckpoint(sessionDir);
2465
+ const storageDir = options.storageDir ?? checkpointsDir;
2414
2466
  const empty = { found: false, restored: [], removed: [], conflicts: [], failed: [] };
2467
+ const file = await newestCheckpoint(path6.join(storageDir, options.sessionId)) ?? await newestWorkspaceCheckpoint(storageDir, root);
2415
2468
  if (!file) return empty;
2416
2469
  const checkpoint = await readCheckpoint(file);
2417
- if (checkpoint.sessionId !== options.sessionId || checkpoint.cwd !== root) {
2418
- 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.");
2419
2472
  }
2420
2473
  const result = { ...empty, found: true };
2421
2474
  const retry = [];
@@ -2585,6 +2638,27 @@ function decodeSnapshot(snapshot) {
2585
2638
  function validBase64(value) {
2586
2639
  return value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
2587
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
+ }
2588
2662
  async function newestCheckpoint(sessionDir) {
2589
2663
  let names;
2590
2664
  try {
@@ -2623,6 +2697,7 @@ var DEFAULT_TIMEOUT = 12e4;
2623
2697
  var MAX_TIMEOUT = 6e5;
2624
2698
  var MAX_OUTPUT = 1e5;
2625
2699
  var MAX_COMMAND_CHARS = 2e4;
2700
+ var PROGRESS_INTERVAL_MS = 250;
2626
2701
  var bashTool = {
2627
2702
  name: "bash",
2628
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.",
@@ -2686,6 +2761,19 @@ var bashTool = {
2686
2761
  windowsHide: true
2687
2762
  });
2688
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
+ };
2689
2777
  let captured = 0;
2690
2778
  let truncated = false;
2691
2779
  const collect = (buf) => {
@@ -2696,6 +2784,7 @@ var bashTool = {
2696
2784
  } catch {
2697
2785
  text = buf.toString();
2698
2786
  }
2787
+ progress(text);
2699
2788
  if (text.length > MAX_OUTPUT - captured) {
2700
2789
  chunks.push(text.slice(0, MAX_OUTPUT - captured));
2701
2790
  truncated = true;
@@ -2909,7 +2998,7 @@ async function isFile2(file) {
2909
2998
 
2910
2999
  // src/core/attachments.ts
2911
3000
  import { execFile } from "child_process";
2912
- 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";
2913
3002
  import os2 from "os";
2914
3003
  import path8 from "path";
2915
3004
  import { fileURLToPath } from "url";
@@ -3004,14 +3093,15 @@ async function loadClipboardImage(platform = process.platform, runner = runClipb
3004
3093
  if (data.length > MAX_IMAGE_BYTES) {
3005
3094
  throw new Error(`Clipboard image is larger than ${formatBytes(MAX_IMAGE_BYTES)}.`);
3006
3095
  }
3096
+ const fitted = await fitImage(data, mediaType, platform);
3007
3097
  return {
3008
3098
  type: "image",
3009
- mediaType,
3010
- data: data.toString("base64"),
3011
- name: clipboardImageName(mediaType)
3099
+ mediaType: fitted.mediaType,
3100
+ data: fitted.data.toString("base64"),
3101
+ name: clipboardImageName(fitted.mediaType)
3012
3102
  };
3013
3103
  } catch (error) {
3014
- if (error instanceof Error && error.message.startsWith("Clipboard image is larger")) {
3104
+ if (error instanceof Error && /^(?:Clipboard image|Image) is larger/.test(error.message)) {
3015
3105
  throw error;
3016
3106
  }
3017
3107
  }
@@ -3028,10 +3118,11 @@ async function loadResolvedAttachment(resolved, size) {
3028
3118
  const data = await readFile7(resolved);
3029
3119
  const mediaType = detectImage(data);
3030
3120
  if (mediaType) {
3121
+ const fitted = await fitImage(data, mediaType);
3031
3122
  return {
3032
3123
  path: resolved,
3033
- bytes: data.length,
3034
- 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 }
3035
3126
  };
3036
3127
  }
3037
3128
  if (data.length > MAX_TEXT_BYTES) {
@@ -3221,6 +3312,85 @@ try {
3221
3312
  $image.Dispose()
3222
3313
  }
3223
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
+ `;
3224
3394
  function textMime(file) {
3225
3395
  const extension = path8.extname(file).toLowerCase();
3226
3396
  const known = {
@@ -3249,13 +3419,27 @@ function normalizeInputPath(value) {
3249
3419
  return value.slice(1, -1);
3250
3420
  }
3251
3421
  }
3422
+ if (WINDOWS_PATH.test(value)) return value.replace(/\\ /g, " ");
3252
3423
  return value.replace(/\\([\\ "'()\[\]&;#$!])/g, (_match, character) => character);
3253
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
+ }
3254
3438
  function safeName(value) {
3255
3439
  return value.replace(/[\r\n\0-\x1f\x7f]/g, "").slice(0, 200) || "attachment";
3256
3440
  }
3257
3441
  function formatBytes(value) {
3258
- return `${Math.round(value / 1024 / 1024)} MB`;
3442
+ return `${(value / 1024 / 1024).toFixed(1).replace(/\.0$/, "")} MB`;
3259
3443
  }
3260
3444
 
3261
3445
  // src/core/compact.ts
@@ -3271,11 +3455,15 @@ var MAX_FILE_BLOCK_CHARS = 2e3;
3271
3455
  var MAX_TOOL_RESULT_CHARS = 1200;
3272
3456
  var MAX_TOOL_ERROR_CHARS = 2e3;
3273
3457
  var MAX_TOOL_INPUT_CHARS = 800;
3274
- var SINGLE_MAX_TOKENS = 768;
3275
- var CHUNK_MAX_TOKENS = 512;
3276
- 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;
3277
3465
  var CHUNK_CONCURRENCY = 3;
3278
- 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.`;
3279
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.";
3280
3468
  async function compactHistory(options) {
3281
3469
  const cut = compactCutIndex(options.history);
@@ -3348,7 +3536,7 @@ async function compactHistory(options) {
3348
3536
  const prefix = [
3349
3537
  {
3350
3538
  role: "user",
3351
- content: [{ type: "text", text: `[Earlier conversation summary]
3539
+ content: [{ type: "text", text: `${SUMMARY_MARKER}
3352
3540
  ${summary2}` }]
3353
3541
  },
3354
3542
  {
@@ -3356,14 +3544,70 @@ ${summary2}` }]
3356
3544
  content: [{ type: "text", text: "Earlier context loaded. I will continue from this summary." }]
3357
3545
  }
3358
3546
  ];
3547
+ const kept = clearOldToolResults(options.history.slice(cut));
3359
3548
  return {
3360
- history: [...prefix, ...options.history.slice(cut)],
3549
+ history: [...prefix, ...withoutThinkingReplay(kept.history)],
3361
3550
  compacted: true,
3362
3551
  removedMessages: cut,
3552
+ clearedToolResults: kept.cleared,
3363
3553
  usage,
3364
3554
  rateLimits
3365
3555
  };
3366
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
+ }
3367
3611
  function shouldAutoCompact(used, window) {
3368
3612
  return window !== null && window > 0 && used / window >= 0.8;
3369
3613
  }
@@ -3407,7 +3651,7 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3407
3651
  function isConversationUser(message) {
3408
3652
  if (message.role !== "user") return false;
3409
3653
  return message.content.some(
3410
- (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)
3411
3655
  );
3412
3656
  }
3413
3657
  function chunkRenderedMessages(messages, targetChars, overlapMessages, overlapMaxChars) {
@@ -3613,6 +3857,9 @@ ${blocks.join("\n")}`] : [];
3613
3857
  function renderBlock(block, role) {
3614
3858
  switch (block.type) {
3615
3859
  case "text":
3860
+ if (role === "user" && block.text.startsWith(SUMMARY_MARKER)) {
3861
+ return truncate(block.text, MAX_PREVIOUS_SUMMARY_CHARS);
3862
+ }
3616
3863
  return truncate(
3617
3864
  block.text,
3618
3865
  role === "user" ? MAX_USER_TEXT_CHARS : MAX_ASSISTANT_TEXT_CHARS
@@ -3689,7 +3936,7 @@ function buildSystemPrompt(args) {
3689
3936
 
3690
3937
  // src/core/subagent.ts
3691
3938
  var TASK_TOOL_NAME = "task";
3692
- var MAX_SUBAGENT_STEPS = 12;
3939
+ var MAX_SUBAGENT_STEPS = 30;
3693
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.";
3694
3941
  var SUBAGENT_CANCELLED = "The subagent was cancelled before it produced anything.";
3695
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.`;
@@ -3704,12 +3951,12 @@ var SUBAGENT_SYSTEM = [
3704
3951
  ].join("\n");
3705
3952
  function createSubagentRunner(makeConfig, tools) {
3706
3953
  const permissionLane = createExecutionLane();
3707
- const executionLane = createExecutionLane();
3708
- const modelRequestLane = createExecutionLane();
3709
3954
  return {
3710
3955
  async run(request) {
3711
3956
  if (request.signal.aborted) return SUBAGENT_CANCELLED;
3712
3957
  let steps = 0;
3958
+ const executionLane = createExecutionLane();
3959
+ const modelRequestLane = createExecutionLane();
3713
3960
  const base = makeConfig(SUBAGENT_SYSTEM, tools);
3714
3961
  const cfg = {
3715
3962
  ...base,
@@ -3721,9 +3968,12 @@ function createSubagentRunner(makeConfig, tools) {
3721
3968
  kind: base.provider.kind,
3722
3969
  listModels: () => base.provider.listModels(),
3723
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.
3724
3975
  stream: (chat) => base.provider.stream(steps === MAX_SUBAGENT_STEPS ? {
3725
3976
  ...chat,
3726
- tools: [],
3727
3977
  system: `${chat.system}
3728
3978
 
3729
3979
  This is your last model call. Do not call tools. Return your findings now, clearly stating any unfinished work.`
@@ -3854,9 +4104,10 @@ function formatUsageBreakdown(tracker) {
3854
4104
  lines.push("", `total ${formatUsageCompact(tracker)}`);
3855
4105
  return lines.join("\n");
3856
4106
  }
3857
- function tokens(count) {
3858
- if (count < 1e3) return String(count);
3859
- 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"];
3860
4111
  return `${value.toFixed(1).replace(/\.0$/, "")}${suffix}`;
3861
4112
  }
3862
4113
  function dollars(amount) {
@@ -3940,7 +4191,7 @@ function clip(value) {
3940
4191
  // package.json
3941
4192
  var package_default = {
3942
4193
  name: "@kernelonpanic/kitcode",
3943
- version: "1.3.3",
4194
+ version: "1.4.1",
3944
4195
  description: "Terminal coding agent with a config you never have to write by hand",
3945
4196
  type: "module",
3946
4197
  license: "MIT",
@@ -3973,6 +4224,7 @@ var package_default = {
3973
4224
  dependencies: {
3974
4225
  "@anthropic-ai/sdk": "^0.115.0",
3975
4226
  "@modelcontextprotocol/sdk": "^1.30.0",
4227
+ build: "^0.1.4",
3976
4228
  chalk: "^6.0.0",
3977
4229
  commander: "^15.0.0",
3978
4230
  "fast-glob": "^3.3.3",
@@ -4008,7 +4260,7 @@ var package_default = {
4008
4260
 
4009
4261
  // src/version.ts
4010
4262
  var KITCODE_VERSION = package_default.version;
4011
- var KITCODE_COMMIT = true ? "81f72aca6f23448aa2239b7bd9175da0f9e5862c" : "development";
4263
+ var KITCODE_COMMIT = true ? "be753d255e7675a850d0ca1ee64f790f68d3fd93" : "development";
4012
4264
 
4013
4265
  // src/mcp/client.ts
4014
4266
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -4393,7 +4645,7 @@ async function limitedResponseText(response, limit) {
4393
4645
  }
4394
4646
 
4395
4647
  // src/providers/catalog.ts
4396
- 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";
4397
4649
  import path9 from "path";
4398
4650
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
4399
4651
  var CACHE_VERSION = 3;
@@ -4455,7 +4707,7 @@ async function writeCache(file, models) {
4455
4707
  const payload = { version: CACHE_VERSION, fetchedAt: Date.now(), models };
4456
4708
  try {
4457
4709
  await ensureDir(path9.dirname(file));
4458
- await writeFile6(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
4710
+ await writeFile7(file, JSON.stringify(payload), { encoding: "utf8", mode: 384 });
4459
4711
  await chmod4(file, 384);
4460
4712
  } catch {
4461
4713
  return;
@@ -4603,8 +4855,11 @@ async function* streamTurn(client, providerId, apiKey2, req) {
4603
4855
  messages: toMessageParams(req.messages),
4604
4856
  tools: toToolParams(req.tools)
4605
4857
  };
4606
- if (req.thinking) params.thinking = { type: "adaptive", display: "summarized" };
4607
- 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);
4608
4863
  if (effort) params.output_config = { effort };
4609
4864
  let thinking = "";
4610
4865
  let text = "";
@@ -4683,6 +4938,11 @@ function toMessageParams(messages) {
4683
4938
  const content = message.content.flatMap(toBlockParams);
4684
4939
  if (content.length > 0) params.push({ role: message.role, content });
4685
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
+ }
4686
4946
  return params;
4687
4947
  }
4688
4948
  function toBlockParams(block) {
@@ -4752,6 +5012,15 @@ function toUsage(usage) {
4752
5012
  cacheRead: usage.cache_read_input_tokens ?? 0
4753
5013
  };
4754
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
+ }
4755
5024
  function toRefusal(details) {
4756
5025
  return { category: details?.category ?? null, explanation: details?.explanation ?? null };
4757
5026
  }
@@ -4784,6 +5053,57 @@ function createOpenAiProvider(args) {
4784
5053
  };
4785
5054
  }
4786
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) {
4787
5107
  let text = "";
4788
5108
  let thinking = "";
4789
5109
  let finishReason = null;
@@ -4793,13 +5113,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
4793
5113
  let recognised = 0;
4794
5114
  const calls = /* @__PURE__ */ new Map();
4795
5115
  const capture = captureResponseHead();
4796
- const effort = openAiEffort(req.model, resolveEffort(req.effort, req.messages));
4797
5116
  try {
4798
5117
  const stream = await client.withOptions({ fetch: capture.fetch }).chat.completions.create(
4799
5118
  {
4800
5119
  model: req.model,
4801
- max_tokens: req.maxTokens,
5120
+ [tokenField]: req.maxTokens,
4802
5121
  ...effort ? { reasoning_effort: effort } : {},
5122
+ ...thinkingOff,
4803
5123
  messages: toChatMessages(req.system, req.messages),
4804
5124
  ...req.tools.length > 0 ? { tools: toChatTools(req.tools) } : {},
4805
5125
  stream: true,
@@ -4989,14 +5309,22 @@ function toUsage2(usage) {
4989
5309
  return normalizeOpenAiUsage(usage);
4990
5310
  }
4991
5311
  function normalizeOpenAiUsage(usage) {
4992
- 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));
4993
5318
  return {
4994
- input: Math.max(0, (usage.prompt_tokens ?? 0) - cacheRead),
4995
- output: usage.completion_tokens ?? 0,
4996
- cacheWrite: 0,
5319
+ input: prompt - cacheRead - cacheWrite,
5320
+ output: count(usage.completion_tokens),
5321
+ cacheWrite,
4997
5322
  cacheRead
4998
5323
  };
4999
5324
  }
5325
+ function count(value) {
5326
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
5327
+ }
5000
5328
 
5001
5329
  // src/providers/registry.ts
5002
5330
  function createRegistry(config, auth) {
@@ -5045,6 +5373,7 @@ var PLAN_REFUSAL = "Plan mode is on: this tool changes state and is unavailable.
5045
5373
  function createPermissionEngine(configPermissions) {
5046
5374
  const sessionGrants = /* @__PURE__ */ new Set();
5047
5375
  let bypassEnabled = false;
5376
+ let bypassUnlocked = false;
5048
5377
  let current = "normal";
5049
5378
  const resolve3 = (tool, requested) => configPermissions[tool.name] ?? requested ?? tool.defaultPermission;
5050
5379
  return {
@@ -5071,9 +5400,12 @@ function createPermissionEngine(configPermissions) {
5071
5400
  bypass: {
5072
5401
  enable() {
5073
5402
  bypassEnabled = true;
5403
+ bypassUnlocked = true;
5404
+ current = "normal";
5074
5405
  },
5075
5406
  disable() {
5076
5407
  bypassEnabled = false;
5408
+ bypassUnlocked = false;
5077
5409
  },
5078
5410
  isEnabled() {
5079
5411
  return bypassEnabled;
@@ -5085,7 +5417,18 @@ function createPermissionEngine(configPermissions) {
5085
5417
  current = mode;
5086
5418
  },
5087
5419
  cycle() {
5088
- 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;
5089
5432
  return current;
5090
5433
  }
5091
5434
  }
@@ -5153,7 +5496,7 @@ import { readFile as readFile9, stat as stat6 } from "fs/promises";
5153
5496
 
5154
5497
  // src/tools/safe-write.ts
5155
5498
  import { randomUUID as randomUUID3 } from "crypto";
5156
- 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";
5157
5500
  import { basename as basename2, dirname as dirname2, join as join2 } from "path";
5158
5501
  var UnsafeFileChangeError = class extends Error {
5159
5502
  constructor(message) {
@@ -5216,7 +5559,7 @@ async function atomicWriteSafeFile(file, data, snapshot) {
5216
5559
  await rename5(temp, file);
5217
5560
  } catch (error) {
5218
5561
  await handle?.close().catch(() => void 0);
5219
- await rm4(temp, { force: true }).catch(() => void 0);
5562
+ await rm5(temp, { force: true }).catch(() => void 0);
5220
5563
  throw error;
5221
5564
  }
5222
5565
  }
@@ -5279,12 +5622,12 @@ async function lstatMaybe(file) {
5279
5622
  var MAX_FILE_BYTES2 = 5e6;
5280
5623
  function countOccurrences(haystack, needle) {
5281
5624
  if (needle === "") return 0;
5282
- let count = 0;
5625
+ let count2 = 0;
5283
5626
  let from = 0;
5284
5627
  for (; ; ) {
5285
5628
  const at = haystack.indexOf(needle, from);
5286
- if (at === -1) return count;
5287
- count += 1;
5629
+ if (at === -1) return count2;
5630
+ count2 += 1;
5288
5631
  from = at + needle.length;
5289
5632
  }
5290
5633
  }
@@ -5343,16 +5686,16 @@ var editTool = {
5343
5686
  return { content: `Cannot edit ${path15}: it is a binary file, not text.`, isError: true };
5344
5687
  }
5345
5688
  const before = beforeBuffer.toString("utf8");
5346
- const count = countOccurrences(before, oldString);
5347
- if (count === 0) {
5689
+ const count2 = countOccurrences(before, oldString);
5690
+ if (count2 === 0) {
5348
5691
  return {
5349
5692
  content: `oldString was not found in ${path15}. Read the file again and match the text exactly, including whitespace.`,
5350
5693
  isError: true
5351
5694
  };
5352
5695
  }
5353
- if (count > 1 && !replaceAll) {
5696
+ if (count2 > 1 && !replaceAll) {
5354
5697
  return {
5355
- 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.`,
5356
5699
  isError: true
5357
5700
  };
5358
5701
  }
@@ -5376,14 +5719,14 @@ var editTool = {
5376
5719
  return { content: `Failed to write ${path15}: ${error.message}`, isError: true };
5377
5720
  }
5378
5721
  return {
5379
- content: `Edited ${path15} (${count} ${count === 1 ? "replacement" : "replacements"})`,
5722
+ content: `Edited ${path15} (${count2} ${count2 === 1 ? "replacement" : "replacements"})`,
5380
5723
  display: { kind: "diff", path: path15, before, after }
5381
5724
  };
5382
5725
  }
5383
5726
  };
5384
5727
  function replacement(before, oldString, newString, replaceAll) {
5385
- const count = countOccurrences(before, oldString);
5386
- if (count === 0 || count > 1 && !replaceAll) return null;
5728
+ const count2 = countOccurrences(before, oldString);
5729
+ if (count2 === 0 || count2 > 1 && !replaceAll) return null;
5387
5730
  if (replaceAll) return before.split(oldString).join(newString);
5388
5731
  const at = before.indexOf(oldString);
5389
5732
  return before.slice(0, at) + newString + before.slice(at + oldString.length);
@@ -5815,7 +6158,7 @@ function createToolRegistry(tools) {
5815
6158
  }
5816
6159
 
5817
6160
  // src/prompts/library.ts
5818
- 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";
5819
6162
  import path11 from "path";
5820
6163
  async function savePrompt(input) {
5821
6164
  const slug = slugify(input.name);
@@ -5829,7 +6172,7 @@ async function savePrompt(input) {
5829
6172
  };
5830
6173
  await ensureDir(promptsDir);
5831
6174
  const file = path11.join(promptsDir, `${slug}.md`);
5832
- await writeFile7(file, serialize(prompt), {
6175
+ await writeFile8(file, serialize(prompt), {
5833
6176
  encoding: "utf8",
5834
6177
  mode: 384
5835
6178
  });
@@ -6372,7 +6715,10 @@ function createTaskTool(runner) {
6372
6715
  required: ["description", "prompt"],
6373
6716
  additionalProperties: false
6374
6717
  },
6375
- 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,
6376
6722
  summarize(input) {
6377
6723
  return `task(${brief(field(input, "description"))})`;
6378
6724
  },
@@ -6546,6 +6892,7 @@ async function readJsonBounded(response) {
6546
6892
 
6547
6893
  // src/app/runtime.ts
6548
6894
  var PREFERRED2 = ["claude-opus-5", "claude-sonnet-5", "gpt-5", "claude-opus-4-8"];
6895
+ var MID_TURN_TRIM_RATIO = 0.8;
6549
6896
  async function boot(options) {
6550
6897
  const loadedConfig = await loadRuntimeConfig(options.cwd);
6551
6898
  const config = loadedConfig.config;
@@ -6619,6 +6966,7 @@ async function boot(options) {
6619
6966
  const notifyContext = () => {
6620
6967
  for (const listener of contextListeners) listener();
6621
6968
  };
6969
+ const resolveContextWindow = (ref, discovered) => config.contextWindow ?? discovered ?? knownContextWindow(ref);
6622
6970
  const knownContextWindow = (ref) => {
6623
6971
  const parsed = parseModelRef(ref);
6624
6972
  if (!parsed) return null;
@@ -6635,27 +6983,27 @@ async function boot(options) {
6635
6983
  modelRef = ref;
6636
6984
  if (latestRateLimits?.model !== ref) latestRateLimits = void 0;
6637
6985
  contextUsage = restoredContext?.model === ref ? restoredContext : void 0;
6638
- modelContextWindow = discoveredWindow ?? knownContextWindow(ref);
6986
+ modelContextWindow = resolveContextWindow(ref, discoveredWindow);
6639
6987
  notifyContext();
6640
6988
  };
6641
- modelContextWindow = knownContextWindow(modelRef);
6989
+ modelContextWindow = resolveContextWindow(modelRef);
6642
6990
  async function refreshModelContextWindow() {
6643
6991
  const targetRef = modelRef;
6644
6992
  const parsed = parseModelRef(targetRef);
6645
6993
  if (!parsed) {
6646
6994
  if (modelRef === targetRef) {
6647
- modelContextWindow = null;
6995
+ modelContextWindow = config.contextWindow ?? null;
6648
6996
  notifyContext();
6649
6997
  }
6650
6998
  return;
6651
6999
  }
6652
- let next = modelContextWindow ?? knownContextWindow(targetRef);
7000
+ let next = resolveContextWindow(targetRef);
6653
7001
  try {
6654
7002
  const provider = registry.get(parsed.provider);
6655
7003
  const models = await loadModels(provider);
6656
7004
  rememberModels(parsed.provider, models);
6657
7005
  const found = models.find((m) => m.id === parsed.model);
6658
- next = found?.contextWindow ?? next;
7006
+ next = resolveContextWindow(targetRef, found?.contextWindow);
6659
7007
  } catch {
6660
7008
  }
6661
7009
  if (modelRef === targetRef) {
@@ -6678,9 +7026,16 @@ async function boot(options) {
6678
7026
  effort: config.effort,
6679
7027
  thinking: config.thinking,
6680
7028
  budget: activeBudget,
6681
- checkpoint: activeCheckpoint
7029
+ checkpoint: activeCheckpoint,
7030
+ trimContext: (messages) => trimForWindow(resolved.provider.kind, messages)
6682
7031
  };
6683
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
+ };
6684
7039
  let activeBudget;
6685
7040
  let activeCheckpoint;
6686
7041
  let activeAgentCount = 0;
@@ -6727,8 +7082,22 @@ async function boot(options) {
6727
7082
  return save;
6728
7083
  };
6729
7084
  const compactWithBudget = async (history, signal, budget) => {
7085
+ const tokensBefore = estimateHistoryTokens(history);
6730
7086
  if (compactCutIndex(history) <= 0) {
6731
- 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
+ };
6732
7101
  }
6733
7102
  const resolved = registry.resolve(modelRef);
6734
7103
  const estimate = estimateCompactBudget(history, config.maxTokens);
@@ -6756,7 +7125,7 @@ async function boot(options) {
6756
7125
  session.context = void 0;
6757
7126
  notifyContext();
6758
7127
  }
6759
- return result;
7128
+ return { ...result, tokensBefore, tokensAfter: estimateHistoryTokens(result.history) };
6760
7129
  };
6761
7130
  const runtime = {
6762
7131
  cwd: options.cwd,
@@ -6983,26 +7352,50 @@ async function boot(options) {
6983
7352
  effortDescription(history) {
6984
7353
  if (!modelRef) return "unavailable";
6985
7354
  const { provider, modelId } = registry.resolve(modelRef);
6986
- const requested = resolveEffort(config.effort, history);
6987
- const effective = provider.kind === "openai" ? openAiEffort(modelId, requested) : requested;
6988
- 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`;
6989
7357
  },
6990
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
+ },
6991
7368
  async setEffort(effort) {
6992
7369
  await persistConfig((draft) => {
6993
7370
  draft.effort = effort;
6994
7371
  });
6995
7372
  },
7373
+ getBell: () => config.bell,
7374
+ async setBell(enabled) {
7375
+ await persistConfig((draft) => {
7376
+ draft.bell = enabled;
7377
+ });
7378
+ },
6996
7379
  getThinking: () => config.thinking,
6997
7380
  async setThinking(enabled) {
6998
7381
  await persistConfig((draft) => {
6999
7382
  draft.thinking = enabled;
7000
7383
  });
7001
7384
  },
7385
+ getContextWindowOverride: () => config.contextWindow,
7386
+ async setContextWindowOverride(tokens2) {
7387
+ await persistConfig((draft) => {
7388
+ if (tokens2 === void 0) delete draft.contextWindow;
7389
+ else draft.contextWindow = tokens2;
7390
+ });
7391
+ modelContextWindow = config.contextWindow ?? resolveContextWindow(modelRef);
7392
+ notifyContext();
7393
+ void refreshModelContextWindow();
7394
+ },
7002
7395
  getMaxTokensPerTurn: () => config.budget.maxTokensPerTurn,
7003
- async setMaxTokensPerTurn(tokens3) {
7396
+ async setMaxTokensPerTurn(tokens2) {
7004
7397
  await persistConfig((draft) => {
7005
- draft.budget.maxTokensPerTurn = tokens3;
7398
+ draft.budget.maxTokensPerTurn = tokens2;
7006
7399
  draft.budget.maxCostUsdPerTurn = 0;
7007
7400
  });
7008
7401
  },
@@ -7257,7 +7650,7 @@ ${lines.join("\n")}` : null;
7257
7650
  hooks.onEvent({
7258
7651
  type: "notice",
7259
7652
  level: "info",
7260
- text: `Compacted older context; kept the latest conversation (${result.removedMessages} messages replaced).`
7653
+ text: describeCompaction(result)
7261
7654
  });
7262
7655
  }
7263
7656
  } catch (error) {
@@ -7276,10 +7669,6 @@ ${lines.join("\n")}` : null;
7276
7669
  const trackedHooks = {
7277
7670
  ...hooks,
7278
7671
  onEvent(event) {
7279
- if (event.type === "turn_start") {
7280
- contextUsage = void 0;
7281
- notifyContext();
7282
- }
7283
7672
  if (event.type === "usage") {
7284
7673
  contextUsage = { model: requestModelRef, usage: event.usage };
7285
7674
  notifyContext();
@@ -7333,6 +7722,7 @@ ${lines.join("\n")}` : null;
7333
7722
  context: contextUsage
7334
7723
  };
7335
7724
  session = snapshot;
7725
+ if (history.length === 0) return;
7336
7726
  const save = persistQueue.catch(() => void 0).then(() => saveSession(snapshot));
7337
7727
  persistQueue = save;
7338
7728
  await save;
@@ -7370,6 +7760,13 @@ function preferredDetectedModel(detected) {
7370
7760
  }
7371
7761
  return candidates[0];
7372
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
+ }
7373
7770
 
7374
7771
  // src/app/ask.ts
7375
7772
  var RU_YES = /* @__PURE__ */ new Set(["y", "\u043D"]);
@@ -7556,8 +7953,8 @@ function validateKey(value) {
7556
7953
  import { render } from "ink";
7557
7954
 
7558
7955
  // src/ui/App.tsx
7559
- import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useStdout as useStdout2 } from "ink";
7560
- 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";
7561
7958
 
7562
7959
  // src/ui/terminal-size.ts
7563
7960
  import { useStdout } from "ink";
@@ -7631,37 +8028,74 @@ function stdioServer(name, command, args) {
7631
8028
  return parsed.success ? { ok: true, name, config: parsed.data } : { ok: false, error: "usage" };
7632
8029
  }
7633
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
+
7634
8065
  // src/ui/commands.ts
8066
+ var BUSY_SAFE = { runsWhileBusy: true };
7635
8067
  var COMMANDS = [
7636
- { name: "model" },
8068
+ { name: "model", ...BUSY_SAFE },
7637
8069
  { name: "login" },
7638
8070
  { name: "provider" },
7639
8071
  { name: "logout", args: "[provider]" },
7640
8072
  { name: "key", args: "[provider]" },
7641
8073
  { name: "refresh", args: "[provider]" },
7642
- { name: "effort" },
7643
- { name: "thinking" },
7644
- { name: "budget", args: "[tokens]" },
7645
- { name: "theme", args: "[color]" },
7646
- { name: "lang" },
8074
+ { name: "effort", ...BUSY_SAFE },
8075
+ { name: "thinking", ...BUSY_SAFE },
8076
+ { name: "bell", args: "[on|off]", ...BUSY_SAFE },
8077
+ { name: "context", args: "[tokens|auto]", ...BUSY_SAFE },
8078
+ { name: "budget", args: "[tokens]", ...BUSY_SAFE },
8079
+ { name: "theme", args: "[color]", ...BUSY_SAFE },
8080
+ { name: "lang", ...BUSY_SAFE },
7647
8081
  { name: "prompt", args: "[save <name>|delete <name>]" },
7648
8082
  { name: "skills", args: "[install <source>]" },
7649
8083
  { name: "bypass" },
7650
- { name: "usage" },
8084
+ { name: "usage", ...BUSY_SAFE },
7651
8085
  { name: "mcp", args: "[add|list|delete|enable|disable]" },
7652
8086
  { name: "attach", args: "<path|clipboard|clear>" },
7653
8087
  { name: "memory", args: "[show|set <text>|clear]" },
7654
8088
  { name: "compact" },
7655
8089
  { name: "update" },
7656
- { name: "checker" },
8090
+ { name: "checker", ...BUSY_SAFE },
7657
8091
  { name: "sessions", args: "[list|rename|delete [all]|export]" },
7658
- { name: "config" },
8092
+ { name: "config", ...BUSY_SAFE },
7659
8093
  { name: "resume" },
7660
8094
  { name: "undo" },
7661
8095
  { name: "clear" },
7662
- { name: "help" },
7663
- { name: "exit" },
7664
- { name: "subagents" }
8096
+ { name: "subagents", ...BUSY_SAFE },
8097
+ { name: "help", ...BUSY_SAFE },
8098
+ { name: "exit", ...BUSY_SAFE }
7665
8099
  ];
7666
8100
  var SUBCOMMANDS = [
7667
8101
  { name: "mcp add", args: "<name> <url|-- command>" },
@@ -7675,6 +8109,10 @@ var SUBCOMMANDS = [
7675
8109
  { name: "sessions delete all" },
7676
8110
  { name: "sessions export", args: "[id] [path]" }
7677
8111
  ];
8112
+ function commandRunsWhileBusy(name) {
8113
+ if (name === "quit") return true;
8114
+ return COMMANDS.some((command) => command.name === name && command.runsWhileBusy === true);
8115
+ }
7678
8116
  function matchCommands(line) {
7679
8117
  if (!line.startsWith("/")) return [];
7680
8118
  const token = line.slice(1);
@@ -7712,17 +8150,23 @@ function isSubsequence2(needle, haystack) {
7712
8150
  return needle.length === 0;
7713
8151
  }
7714
8152
  function editDistance(a, b) {
8153
+ let beforePrevious = [];
7715
8154
  let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
7716
8155
  for (let i = 1; i <= a.length; i += 1) {
7717
8156
  const current = [i];
7718
8157
  for (let j = 1; j <= b.length; j += 1) {
7719
8158
  const cost = a[i - 1] === b[j - 1] ? 0 : 1;
7720
- current[j] = Math.min(
8159
+ let best = Math.min(
7721
8160
  (current[j - 1] ?? 0) + 1,
7722
8161
  (previous[j] ?? 0) + 1,
7723
8162
  (previous[j - 1] ?? 0) + cost
7724
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;
7725
8168
  }
8169
+ beforePrevious = previous;
7726
8170
  previous = current;
7727
8171
  }
7728
8172
  return previous[b.length] ?? 0;
@@ -7783,11 +8227,19 @@ var PRESETS = {
7783
8227
  white: "#e5e7eb"
7784
8228
  };
7785
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
+ }
7786
8238
  function resolveAccent(value) {
7787
8239
  if (!value) return DEFAULT_ACCENT;
7788
- const preset = PRESETS[value.toLowerCase()];
7789
- if (preset) return preset;
7790
- 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;
7791
8243
  }
7792
8244
  function makeTheme(accent) {
7793
8245
  return {
@@ -7851,10 +8303,12 @@ var en = {
7851
8303
  placeholder: "ask, or / for commands",
7852
8304
  working: "working\u2026",
7853
8305
  suggestHelp: "\u2191\u2193 move \xB7 tab complete \xB7 enter run",
7854
- more: (count) => `\u2026 ${count} more`,
8306
+ more: (count2) => `\u2026 ${count2} more`,
7855
8307
  allowOnce: "allow once",
7856
8308
  always: "always",
7857
8309
  deny: "deny",
8310
+ dangerousCommand: "risky command",
8311
+ permissionKeys: "\u2191/\u2193 choose \xB7 enter confirm \xB7 y/a/n \xB7 esc deny",
7858
8312
  yes: "yes",
7859
8313
  noDefault: "no (default)",
7860
8314
  noMatches: "no matches",
@@ -7869,7 +8323,7 @@ var en = {
7869
8323
  titleModelRefresh: "Reload models for a provider",
7870
8324
  titleSessions: "Resume a session",
7871
8325
  titleSessionAction: "Session action",
7872
- resumed: (id, count) => `Resumed ${id} \u2014 ${count} messages restored`,
8326
+ resumed: (id, count2) => `Resumed ${id} \u2014 ${count2} messages restored`,
7873
8327
  sessionsEmpty: "No saved sessions yet.",
7874
8328
  providerSet: (id, model) => `Provider: ${id} \xB7 model ${model}`,
7875
8329
  providersEmpty: "No providers configured. Use /login to add one.",
@@ -7877,11 +8331,15 @@ var en = {
7877
8331
  providerBalance: (balances) => `provider balance: ${balances}`,
7878
8332
  providerKeyRemaining: (balances) => `API key limit remaining: ${balances}`,
7879
8333
  noThink: "no-think",
7880
- mcpFailed: (count) => `mcp:${count} failed`,
8334
+ mcpFailed: (count2) => `mcp:${count2} failed`,
7881
8335
  escCancel: "esc to cancel",
8336
+ escClear: "esc clears the input \xB7 esc again cancels the answer",
7882
8337
  escHelp: "esc \u2014 cancel the running turn",
7883
8338
  cancelled: "Cancelled.",
7884
- 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",
7885
8343
  configAt: (path15) => `config: ${path15}`,
7886
8344
  skillsEmpty: "No skills installed. Drop a folder with a SKILL.md into ~/.kitcode/skills or ./.kitcode/skills",
7887
8345
  skillsInstallUsage: "Usage: /skills install <github-url|npm-package|local-path>",
@@ -7900,7 +8358,7 @@ var en = {
7900
8358
  mcpDeleted: (name) => `MCP "${name}" disconnected and removed.`,
7901
8359
  mcpDeleteFailed: (name, error) => `Could not remove MCP "${name}": ${error}`,
7902
8360
  mcpNotFound: (name) => `MCP server "${name}" was not found.`,
7903
- mcpTools: (count) => `${count} tools`,
8361
+ mcpTools: (count2) => `${count2} tools`,
7904
8362
  mcpEnabled: (name, tools) => `MCP "${name}" enabled \xB7 ${tools} tools`,
7905
8363
  mcpDisabled: (name) => `MCP "${name}" disabled.`,
7906
8364
  mcpToggleFailed: (name, error) => `Could not change MCP "${name}": ${error}`,
@@ -7910,14 +8368,24 @@ var en = {
7910
8368
  error: "error",
7911
8369
  disabled: "disabled"
7912
8370
  },
7913
- activeAgents: (count) => `agents: ${count}`,
7914
- 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",
7915
8378
  attachUsage: "Usage: /attach <image-or-text-file> \xB7 /attach clipboard \xB7 /attach clear",
7916
8379
  attachmentAdded: (name) => `Attached for the next message: ${name}`,
7917
8380
  attachmentsCleared: "Pending attachments cleared.",
7918
8381
  compactNothing: "Not enough older conversation to compact yet.",
7919
8382
  compactStarting: "Compacting context\u2026",
7920
- 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 ") + ".",
7921
8389
  sessionRenameUsage: "Type the title after the command: /sessions rename <id> <title>",
7922
8390
  sessionRenamed: (title) => `Session renamed to "${title}".`,
7923
8391
  sessionDeleteAsk: "The saved chat file will be deleted. This cannot be undone.",
@@ -7926,7 +8394,7 @@ var en = {
7926
8394
  sessionDeleteAllAskBody: "All KitCode sessions from every workspace will be permanently deleted.",
7927
8395
  sessionDeleteAllFinalAsk: "Final confirmation: delete all chats?",
7928
8396
  sessionDeleteAllFinalBody: "This cannot be undone. Press y only if you want to erase the complete session history.",
7929
- sessionsDeletedAll: (count) => `All saved chats deleted \xB7 ${count} sessions removed.`,
8397
+ sessionsDeletedAll: (count2) => `All saved chats deleted \xB7 ${count2} sessions removed.`,
7930
8398
  sessionsDeleteAllFailed: (deleted, failed) => `Session cleanup was incomplete \xB7 ${deleted} removed, ${failed} failed.`,
7931
8399
  sessionExported: (path15) => `Session exported to ${path15}`,
7932
8400
  sessionActionResume: "resume",
@@ -7939,14 +8407,18 @@ ${url}`,
7939
8407
  updateCurrent: (version) => `KitCode ${version} is up to date.`,
7940
8408
  updateFailed: (reason) => `Could not check for updates: ${reason}.`,
7941
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",
7942
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.",
7943
8415
  effortSet: (value) => `Effort: ${value}`,
7944
- budgetCurrent: (tokens3) => `Token budget: ${tokens3.toLocaleString()} per turn`,
7945
- 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`,
7946
8418
  budgetUnlimited: "Token budget: unlimited (0). Set /budget <tokens> to restore a limit.",
7947
8419
  budgetInvalid: "Invalid budget: must be between 1,000 and 10,000,000",
7948
8420
  modelSet: (value) => `Model: ${value}`,
7949
- modelsRefreshed: (provider, count) => `${provider}: ${count} models reloaded from the endpoint.`,
8421
+ modelsRefreshed: (provider, count2) => `${provider}: ${count2} models reloaded from the endpoint.`,
7950
8422
  noModels: "No models available. Use /login to add a provider.",
7951
8423
  bypassOff: "Approval prompts are back on.",
7952
8424
  bypassAsk1: "Disable approval prompts for this session?",
@@ -7973,10 +8445,17 @@ ${url}`,
7973
8445
  promptsEmpty: "No saved prompts. Use /prompt save <name>.",
7974
8446
  unknownCommand: (name) => `Unknown command /${name}. Try /help`,
7975
8447
  didYouMean: (typed, guess) => `Unknown command /${typed}. Did you mean /${guess}?`,
7976
- undoNothing: "Nothing to undo in this session.",
8448
+ undoNothing: "Nothing to undo: no file changes made by kitcode in this folder.",
7977
8449
  undoResult: (restored, removed) => `Undo complete: ${restored} restored, ${removed} newly created removed.`,
7978
8450
  undoConflicts: (paths) => `Kept newer changes in: ${paths.join(", ")}`,
7979
8451
  undoFailed: (failures) => `Could not restore: ${failures.join("; ")}`,
8452
+ titleContext: "Context window",
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).`,
8456
+ contextUnknown: "Context window: unknown. Set it with /context 256k, /context 512k or /context 1m.",
8457
+ contextInvalid: "Usage: /context 256k | 512k | 1m | <tokens> | auto",
8458
+ contextCurrentLabel: "current",
7980
8459
  titleAccent: "Accent colour",
7981
8460
  titleEffort: "Reasoning depth",
7982
8461
  titleModel: "Model",
@@ -7986,7 +8465,7 @@ ${url}`,
7986
8465
  titleMcpDelete: "Remove an MCP server",
7987
8466
  titleMcpEnable: "Enable an MCP server",
7988
8467
  titleMcpDisable: "Disable an MCP server",
7989
- 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" },
7990
8469
  modeChanged: (label) => `Mode: ${label} (shift+tab to cycle)`,
7991
8470
  cmd: {
7992
8471
  model: "switch model",
@@ -7997,6 +8476,8 @@ ${url}`,
7997
8476
  refresh: "reload the model list for a provider",
7998
8477
  effort: "set reasoning depth",
7999
8478
  thinking: "toggle reasoning",
8479
+ bell: "toggle the sound for finished answers and permission prompts",
8480
+ context: "set or show the model context window",
8000
8481
  budget: "set token budget per turn",
8001
8482
  theme: "change the accent colour",
8002
8483
  lang: "change the interface language",
@@ -8029,7 +8510,7 @@ ${url}`,
8029
8510
  clear: "start a fresh session",
8030
8511
  help: "list commands",
8031
8512
  exit: "quit",
8032
- subagents: "show active sub-agents and what they are doing"
8513
+ subagents: "open a sub-agent and watch its chat live"
8033
8514
  }
8034
8515
  };
8035
8516
  var ru = {
@@ -8043,10 +8524,12 @@ var ru = {
8043
8524
  placeholder: "\u0441\u043F\u0440\u043E\u0441\u0438, \u0438\u043B\u0438 / \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434",
8044
8525
  working: "\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u2026",
8045
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",
8046
- more: (count) => `\u2026 \u0435\u0449\u0451 ${count}`,
8527
+ more: (count2) => `\u2026 \u0435\u0449\u0451 ${count2}`,
8047
8528
  allowOnce: "\u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u043E\u0434\u0438\u043D \u0440\u0430\u0437",
8048
8529
  always: "\u0432\u0441\u0435\u0433\u0434\u0430",
8049
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",
8050
8533
  yes: "\u0434\u0430",
8051
8534
  noDefault: "\u043D\u0435\u0442 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E)",
8052
8535
  noMatches: "\u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
@@ -8061,7 +8544,7 @@ var ru = {
8061
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",
8062
8545
  titleSessions: "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E",
8063
8546
  titleSessionAction: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439",
8064
- 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}`,
8065
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.",
8066
8549
  providerSet: (id, model) => `\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440: ${id} \xB7 \u043C\u043E\u0434\u0435\u043B\u044C ${model}`,
8067
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.",
@@ -8069,11 +8552,15 @@ var ru = {
8069
8552
  providerBalance: (balances) => `\u0431\u0430\u043B\u0430\u043D\u0441 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430: ${balances}`,
8070
8553
  providerKeyRemaining: (balances) => `\u043E\u0441\u0442\u0430\u0442\u043E\u043A \u043B\u0438\u043C\u0438\u0442\u0430 API-\u043A\u043B\u044E\u0447\u0430: ${balances}`,
8071
8554
  noThink: "\u0431\u0435\u0437 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8072
- 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`,
8073
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",
8074
8558
  escHelp: "esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0445\u043E\u0434",
8075
8559
  cancelled: "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E.",
8076
- 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",
8077
8564
  configAt: (path15) => `\u043A\u043E\u043D\u0444\u0438\u0433: ${path15}`,
8078
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",
8079
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>",
@@ -8092,7 +8579,7 @@ var ru = {
8092
8579
  mcpDeleted: (name) => `MCP \xAB${name}\xBB \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u0438 \u0443\u0434\u0430\u043B\u0451\u043D.`,
8093
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}`,
8094
8581
  mcpNotFound: (name) => `MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \xAB${name}\xBB \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`,
8095
- 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}`,
8096
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}`,
8097
8584
  mcpDisabled: (name) => `MCP \xAB${name}\xBB \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D.`,
8098
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}`,
@@ -8102,14 +8589,24 @@ var ru = {
8102
8589
  error: "\u043E\u0448\u0438\u0431\u043A\u0430",
8103
8590
  disabled: "\u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D"
8104
8591
  },
8105
- activeAgents: (count) => `\u0430\u0433\u0435\u043D\u0442\u043E\u0432: ${count}`,
8106
- 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",
8107
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",
8108
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}`,
8109
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.",
8110
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.",
8111
8603
  compactStarting: "\u0421\u0436\u0430\u0442\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430\u2026",
8112
- 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 ") + ".",
8113
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>",
8114
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.`,
8115
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.",
@@ -8118,7 +8615,7 @@ var ru = {
8118
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.",
8119
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?",
8120
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.",
8121
- 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}.`,
8122
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}.`,
8123
8620
  sessionExported: (path15) => `\u0421\u0435\u0441\u0441\u0438\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0430: ${path15}`,
8124
8621
  sessionActionResume: "\u043E\u0442\u043A\u0440\u044B\u0442\u044C",
@@ -8131,14 +8628,18 @@ ${url}`,
8131
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}.`,
8132
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}.`,
8133
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",
8134
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.",
8135
8636
  effortSet: (value) => `\u0413\u043B\u0443\u0431\u0438\u043D\u0430: ${value}`,
8136
- budgetCurrent: (tokens3) => `\u041B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432: ${tokens3.toLocaleString()} \u0437\u0430 \u0445\u043E\u0434`,
8137
- 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`,
8138
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.",
8139
8640
  budgetInvalid: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043B\u0438\u043C\u0438\u0442: \u043E\u0442 1 000 \u0434\u043E 10 000 000",
8140
8641
  modelSet: (value) => `\u041C\u043E\u0434\u0435\u043B\u044C: ${value}`,
8141
- 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}.`,
8142
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.",
8143
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.",
8144
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?",
@@ -8165,10 +8666,17 @@ ${url}`,
8165
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>.",
8166
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`,
8167
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}?`,
8168
- 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.",
8169
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}.`,
8170
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(", ")}`,
8171
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("; ")}`,
8673
+ titleContext: "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\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).`,
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.",
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",
8679
+ contextCurrentLabel: "\u0442\u0435\u043A\u0443\u0449\u0435\u0435",
8172
8680
  titleAccent: "\u0426\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
8173
8681
  titleEffort: "\u0413\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8174
8682
  titleModel: "\u041C\u043E\u0434\u0435\u043B\u044C",
@@ -8178,7 +8686,7 @@ ${url}`,
8178
8686
  titleMcpDelete: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8179
8687
  titleMcpEnable: "\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8180
8688
  titleMcpDisable: "\u0412\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
8181
- 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" },
8182
8690
  modeChanged: (label) => `\u0420\u0435\u0436\u0438\u043C: ${label} (shift+tab \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C)`,
8183
8691
  cmd: {
8184
8692
  model: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u044C",
@@ -8189,6 +8697,8 @@ ${url}`,
8189
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",
8190
8698
  effort: "\u0433\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8191
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",
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",
8192
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)",
8193
8703
  theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
8194
8704
  lang: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430",
@@ -8220,7 +8730,7 @@ ${url}`,
8220
8730
  clear: "\u043D\u0430\u0447\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0441\u0435\u0441\u0441\u0438\u044E",
8221
8731
  help: "\u0441\u043F\u0438\u0441\u043E\u043A \u043A\u043E\u043C\u0430\u043D\u0434",
8222
8732
  exit: "\u0432\u044B\u0445\u043E\u0434",
8223
- 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"
8224
8734
  }
8225
8735
  };
8226
8736
  var DICTIONARIES = { en, ru };
@@ -8431,9 +8941,11 @@ function Onboarding({ onSubmit }) {
8431
8941
 
8432
8942
  // src/ui/components/PermissionPrompt.tsx
8433
8943
  import { Box as Box6, Text as Text6 } from "ink";
8944
+ import { useEffect, useRef as useRef2, useState as useState3 } from "react";
8434
8945
 
8435
8946
  // src/ui/components/Diff.tsx
8436
8947
  import { Box as Box5, Text as Text5 } from "ink";
8948
+ import stringWidth from "string-width";
8437
8949
 
8438
8950
  // src/ui/diff.ts
8439
8951
  var MAX_RENDER_DIFF_CHARS = 5e5;
@@ -8460,28 +8972,54 @@ function diffBody(before, after) {
8460
8972
  let added = 0;
8461
8973
  let removed = 0;
8462
8974
  for (let i = Math.max(0, start - 2); i < start; i += 1) {
8463
- hunk.push({ kind: "ctx", text: a[i] ?? "" });
8975
+ hunk.push({ kind: "ctx", text: a[i] ?? "", line: i + 1 });
8464
8976
  }
8977
+ const dels = [];
8465
8978
  for (let i = start; i < endA; i += 1) {
8466
- hunk.push({ kind: "del", text: a[i] ?? "" });
8979
+ dels.push({ kind: "del", text: a[i] ?? "", line: i + 1 });
8467
8980
  removed += 1;
8468
8981
  }
8982
+ const adds = [];
8469
8983
  for (let i = start; i < endB; i += 1) {
8470
- hunk.push({ kind: "add", text: b[i] ?? "" });
8984
+ adds.push({ kind: "add", text: b[i] ?? "", line: i + 1 });
8471
8985
  added += 1;
8472
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);
8473
8998
  for (let i = endA; i < Math.min(a.length, endA + 2); i += 1) {
8474
- hunk.push({ kind: "ctx", text: a[i] ?? "" });
8999
+ hunk.push({ kind: "ctx", text: a[i] ?? "", line: i + 1 });
8475
9000
  }
8476
9001
  return { added, removed, hunk };
8477
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
+ }
8478
9012
  function truncate2(text, width) {
8479
9013
  return text.length <= width ? text : `${text.slice(0, width - 1)}\u2026`;
8480
9014
  }
8481
9015
 
8482
9016
  // src/ui/components/Diff.tsx
8483
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
8484
- 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 }) {
8485
9023
  if (!canRenderDiff(before, after)) {
8486
9024
  return /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
8487
9025
  "Diff preview omitted: it exceeds ",
@@ -8490,77 +9028,152 @@ function Diff({ before, after }) {
8490
9028
  ] });
8491
9029
  }
8492
9030
  const { hunk, hidden } = diffLines(sanitizeTerminalText(before), sanitizeTerminalText(after));
8493
- return /* @__PURE__ */ jsx5(DiffHunk, { lines: hunk, hidden });
9031
+ return /* @__PURE__ */ jsx5(DiffHunk, { lines: hunk, hidden, indent });
8494
9032
  }
8495
- 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);
8496
9037
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
8497
- lines.map((line, index) => /* @__PURE__ */ jsxs5(
8498
- Text5,
8499
- {
8500
- color: line.kind === "add" ? "green" : line.kind === "del" ? "red" : "gray",
8501
- dimColor: line.kind === "ctx",
8502
- children: [
8503
- line.kind === "add" ? "+ " : line.kind === "del" ? "- " : " ",
8504
- truncate2(sanitizeTerminalText(line.text), 110)
8505
- ]
8506
- },
8507
- index
8508
- )),
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
+ }),
8509
9067
  hidden > 0 && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
8510
- " \u2026 ",
9068
+ " ".repeat(gutter),
9069
+ " \u2026 ",
8511
9070
  hidden,
8512
9071
  " more lines"
8513
9072
  ] })
8514
9073
  ] });
8515
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
+ }
8516
9084
 
8517
9085
  // src/ui/components/PermissionPrompt.tsx
8518
- 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";
8519
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
+ }
8520
9104
  function PermissionPrompt({ request, onDecide }) {
8521
9105
  const theme = useTheme();
8522
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]);
8523
9120
  useTerminalInput((char, key) => {
8524
9121
  const c = normalizeHotkey(char);
8525
- if (c === "y") onDecide("once");
8526
- else if (c === "a" && request.allowAlways !== false) onDecide("always");
8527
- 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
+ }
8528
9131
  });
8529
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: theme.warn, paddingX: 1, children: [
8530
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.warn, children: sanitizeTerminalText(request.summary) }),
8531
- request.display?.kind === "diff" && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
8532
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.path) }),
8533
- /* @__PURE__ */ jsx6(Diff, { before: request.display.before, after: request.display.after })
8534
- ] }),
8535
- request.display?.kind === "text" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: previewText(request.display.text) }),
8536
- request.display?.kind === "list" && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
8537
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: sanitizeTerminalText(request.display.title) }),
8538
- request.display.items.slice(0, 100).map((item, index) => /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
8539
- "\u2022 ",
8540
- previewText(item, 1e3)
8541
- ] }, 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)
8542
9137
  ] }),
8543
- !request.display && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: printInput(request.input) }),
8544
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
8545
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "y" }),
8546
- " ",
8547
- strings.allowOnce,
8548
- " \xB7",
8549
- " ",
8550
- request.allowAlways !== false && /* @__PURE__ */ jsxs6(Fragment, { children: [
8551
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "a" }),
8552
- " ",
8553
- strings.always,
8554
- " (",
8555
- sanitizeTerminalText(request.toolName),
8556
- ") \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 })
8557
9145
  ] }),
8558
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "n" }),
8559
- " ",
8560
- strings.deny
8561
- ] }) })
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
+ ] })
8562
9171
  ] });
8563
9172
  }
9173
+ function commandOf(input) {
9174
+ const command = input?.command;
9175
+ return typeof command === "string" && command.trim() !== "" ? command : void 0;
9176
+ }
8564
9177
  function printInput(input) {
8565
9178
  try {
8566
9179
  return previewText(JSON.stringify(input, null, 2));
@@ -8576,15 +9189,15 @@ function previewText(value, limit = MAX_PREVIEW_CHARS) {
8576
9189
 
8577
9190
  // src/ui/components/Picker.tsx
8578
9191
  import { Box as Box7, Text as Text7 } from "ink";
8579
- import { useMemo as useMemo2, useState as useState3 } from "react";
9192
+ import { useMemo as useMemo2, useState as useState4 } from "react";
8580
9193
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8581
9194
  var WINDOW = 10;
8582
9195
  function Picker({ title, items, onSelect, onCancel }) {
8583
9196
  const theme = useTheme();
8584
9197
  const { columns } = useTerminalSize();
8585
9198
  const strings = useStrings();
8586
- const [query, setQuery] = useState3("");
8587
- const [cursor, setCursor] = useState3(0);
9199
+ const [query, setQuery] = useState4("");
9200
+ const [cursor, setCursor] = useState4(0);
8588
9201
  const matches = useMemo2(() => {
8589
9202
  const needle = query.toLowerCase();
8590
9203
  if (needle === "") return items;
@@ -8636,7 +9249,8 @@ function Picker({ title, items, onSelect, onCancel }) {
8636
9249
 
8637
9250
  // src/ui/components/PromptInput.tsx
8638
9251
  import { Box as Box8, Text as Text8 } from "ink";
8639
- 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";
8640
9254
 
8641
9255
  // src/ui/history.ts
8642
9256
  function moveInputHistory(history, index, value, draft, direction) {
@@ -8677,6 +9291,10 @@ var CAT_FRAMES = [
8677
9291
  "/\u1420\uFF61\uA788\uFF61\u141F\\ \u223E"
8678
9292
  ];
8679
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" };
8680
9298
  var PromptInput = memo(function PromptInput2({
8681
9299
  value,
8682
9300
  onChange,
@@ -8688,37 +9306,48 @@ var PromptInput = memo(function PromptInput2({
8688
9306
  hint,
8689
9307
  history,
8690
9308
  attachments = [],
8691
- onListFiles
9309
+ onListFiles,
9310
+ mode = "normal",
9311
+ bypass = false,
9312
+ queued = [],
9313
+ onNavigateQueue
8692
9314
  }) {
8693
9315
  const theme = useTheme();
8694
- const { columns, rows } = useTerminalSize();
9316
+ const { columns } = useTerminalSize();
8695
9317
  const panelWidth = Math.max(2, columns - 1);
8696
- const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, rows - 16));
9318
+ const inputRows = MAX_INPUT_ROWS;
8697
9319
  const strings = useStrings();
8698
- const [catFrame, setCatFrame] = useState4(0);
8699
- useEffect(() => {
9320
+ const [catFrame, setCatFrame] = useState5(0);
9321
+ useEffect2(() => {
8700
9322
  if (!disabled) return;
8701
9323
  const timer = setInterval(() => setCatFrame((frame) => (frame + 1) % CAT_FRAMES.length), CAT_FRAME_MS);
8702
9324
  return () => clearInterval(timer);
8703
9325
  }, [disabled]);
8704
- 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 : "";
8705
9329
  const fullTitle = disabled ? `KitCode ${CAT_FRAMES[catFrame]}` : "KitCode";
8706
9330
  const panelTitle = fullTitle.slice(0, Math.max(0, panelWidth - 5));
8707
9331
  const safeValue = sanitizeTerminalText(value);
8708
- const [selectionCursor, setSelectionCursor] = useState4(0);
8709
- const [inputCursor, setInputCursor] = useState4(() => characters2(safeValue).length);
8710
- const [historyIndex, setHistoryIndex] = useState4(null);
8711
- const historyDraft = useRef2("");
8712
- const pendingValue = useRef2(null);
8713
- const valueRef = useRef2(safeValue);
8714
- 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);
8715
9339
  valueRef.current = safeValue;
8716
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} ` : "";
8717
9346
  const commandSuggestions = matchCommands(safeValue);
8718
9347
  const mention = onListFiles ? mentionSpan(safeValue, inputCursor) : null;
8719
- const [files, setFiles] = useState4([]);
9348
+ const [files, setFiles] = useState5([]);
8720
9349
  const mentionQuery = mention?.query ?? null;
8721
- useEffect(() => {
9350
+ useEffect2(() => {
8722
9351
  if (mentionQuery === null || !onListFiles) {
8723
9352
  setFiles([]);
8724
9353
  return;
@@ -8736,10 +9365,10 @@ var PromptInput = memo(function PromptInput2({
8736
9365
  const open3 = suggestions.length > 0 || showFiles;
8737
9366
  const optionCount = showFiles ? files.length : suggestions.length;
8738
9367
  const active2 = Math.min(selectionCursor, Math.max(0, optionCount - 1));
8739
- useEffect(() => {
9368
+ useEffect2(() => {
8740
9369
  setSelectionCursor(0);
8741
9370
  }, [safeValue]);
8742
- useEffect(() => {
9371
+ useEffect2(() => {
8743
9372
  const length = characters2(safeValue).length;
8744
9373
  if (pendingValue.current === safeValue) {
8745
9374
  pendingValue.current = null;
@@ -8759,28 +9388,39 @@ var PromptInput = memo(function PromptInput2({
8759
9388
  historyDraft.current = next;
8760
9389
  onChange(next);
8761
9390
  };
9391
+ const pastes = useRef3(/* @__PURE__ */ new Map());
9392
+ const pasteCounter = useRef3(0);
8762
9393
  const submit = (next) => {
8763
9394
  setHistoryIndex(null);
8764
9395
  historyDraft.current = "";
8765
- onSubmit(next);
9396
+ const expanded = expandPastes(next, pastes.current);
9397
+ pastes.current = /* @__PURE__ */ new Map();
9398
+ onSubmit(expanded);
8766
9399
  };
8767
9400
  const insertPastedText = (pasted) => {
8768
9401
  const inserted = characters2(sanitizeTerminalText(pasted));
8769
9402
  if (inserted.length === 0) return;
8770
9403
  const valueCharacters = characters2(valueRef.current);
8771
9404
  const cursor = clamp2(cursorRef.current, 0, valueCharacters.length);
8772
- valueCharacters.splice(cursor, 0, ...inserted);
8773
- 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);
8774
9414
  };
8775
9415
  useTerminalPaste((pasted) => {
8776
9416
  const safePaste = sanitizeTerminalText(pasted);
8777
9417
  if (onPastePath && looksLikeAttachmentPath(safePaste)) {
8778
9418
  void onPastePath(safePaste).then((consumed) => {
8779
- if (!consumed) insertPastedText(safePaste);
9419
+ if (!consumed) insertPastedText(collapsePaste(safePaste));
8780
9420
  });
8781
9421
  return;
8782
9422
  }
8783
- insertPastedText(safePaste);
9423
+ insertPastedText(collapsePaste(safePaste));
8784
9424
  });
8785
9425
  useTerminalInput((input, key) => {
8786
9426
  if (onPasteImage && (key.ctrl || key.meta || key.super) && (input.toLowerCase() === "v" || input === "")) {
@@ -8813,6 +9453,13 @@ var PromptInput = memo(function PromptInput2({
8813
9453
  if (chosen) submit(`/${chosen.name}`);
8814
9454
  return;
8815
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
+ }
8816
9463
  if (!open3 && (key.upArrow || key.downArrow)) {
8817
9464
  const moved = moveInputHistory(
8818
9465
  history,
@@ -8867,6 +9514,21 @@ var PromptInput = memo(function PromptInput2({
8867
9514
  const visible = suggestions.slice(start, start + WINDOW2);
8868
9515
  const fileStart = Math.max(0, Math.min(active2 - WINDOW2 + 2, files.length - WINDOW2));
8869
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
+ ] }),
8870
9532
  /* @__PURE__ */ jsxs8(Box8, { width: "100%", flexShrink: 0, children: [
8871
9533
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u256D" }) }),
8872
9534
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsxs8(Text8, { color: panelColor, wrap: "truncate-end", children: [
@@ -8892,12 +9554,8 @@ var PromptInput = memo(function PromptInput2({
8892
9554
  /* @__PURE__ */ jsx8(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u256E" }) })
8893
9555
  ] }),
8894
9556
  /* @__PURE__ */ jsxs8(Box8, { width: "100%", paddingX: 1, maxHeight: inputRows, overflowY: "hidden", children: [
8895
- /* @__PURE__ */ jsx8(Text8, { color: panelColor, children: "\u203A " }),
8896
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }) }),
8897
- pending && pending > 0 ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
8898
- " \xB7 ",
8899
- strings.queued(pending)
8900
- ] }) : 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 }) })
8901
9559
  ] }),
8902
9560
  attachments.length > 0 && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
8903
9561
  " ",
@@ -8921,7 +9579,8 @@ var PromptInput = memo(function PromptInput2({
8921
9579
  borderRight: false
8922
9580
  }
8923
9581
  ),
8924
- /* @__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" }) })
8925
9584
  ] }),
8926
9585
  disabled && hint && /* @__PURE__ */ jsxs8(Text8, { color: theme.accent, children: [
8927
9586
  " ",
@@ -8958,25 +9617,81 @@ var PromptInput = memo(function PromptInput2({
8958
9617
  ] })
8959
9618
  ] });
8960
9619
  });
8961
- function EditableText({
8962
- value,
8963
- cursor,
8964
- placeholder
8965
- }) {
8966
- const valueCharacters = characters2(value);
8967
- const at = clamp2(cursor, 0, valueCharacters.length);
8968
- 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) {
8969
9675
  const placeholderCharacters = characters2(sanitizeTerminalText(placeholder));
8970
9676
  return /* @__PURE__ */ jsxs8(Text8, { children: [
8971
9677
  /* @__PURE__ */ jsx8(Text8, { inverse: true, children: placeholderCharacters[0] ?? " " }),
8972
9678
  /* @__PURE__ */ jsx8(Text8, { color: "gray", children: placeholderCharacters.slice(1).join("") })
8973
9679
  ] });
8974
9680
  }
8975
- return /* @__PURE__ */ jsxs8(Text8, { children: [
8976
- valueCharacters.slice(0, at).join(""),
8977
- /* @__PURE__ */ jsx8(Text8, { inverse: true, children: valueCharacters[at] ?? " " }),
8978
- valueCharacters.slice(at + 1).join("")
8979
- ] });
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);
8980
9695
  }
8981
9696
  function nextCursor(cursor, length, key) {
8982
9697
  if (key.home) return 0;
@@ -8994,7 +9709,6 @@ function clamp2(value, min, max) {
8994
9709
 
8995
9710
  // src/ui/components/StatusBar.tsx
8996
9711
  import { Box as Box9, Text as Text9 } from "ink";
8997
- import { Fragment as Fragment2 } from "react";
8998
9712
 
8999
9713
  // src/ui/time.ts
9000
9714
  function formatDuration(ms) {
@@ -9010,32 +9724,31 @@ function formatDuration(ms) {
9010
9724
  // src/ui/components/StatusBar.tsx
9011
9725
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9012
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
+ }
9013
9733
  function StatusBar({ status }) {
9014
9734
  const theme = useTheme();
9015
9735
  const strings = useStrings();
9016
- const primary = [];
9017
9736
  const details = [];
9018
- primary.push(/* @__PURE__ */ jsx9(ModelBadge, { ref: status.modelRef }, "model"));
9019
- 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"));
9020
9739
  if (!status.thinking) {
9021
9740
  details.push(
9022
9741
  /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: strings.noThink }, "think")
9023
9742
  );
9024
9743
  }
9025
- if (status.mode !== "normal") {
9026
- const modeColor = status.mode === "accept" ? "yellow" : status.mode === "plan" ? "blue" : theme.accent;
9027
- details.push(
9028
- /* @__PURE__ */ jsx9(Text9, { color: modeColor, children: strings.modeLabel[status.mode] ?? status.mode }, "mode")
9029
- );
9030
- }
9031
- if (status.usage) {
9744
+ if (status.usage && status.usage.totalTokens > 0) {
9032
9745
  const u = status.usage;
9033
9746
  details.push(
9034
9747
  /* @__PURE__ */ jsxs9(Text9, { children: [
9035
9748
  /* @__PURE__ */ jsx9(Text9, { color: "white", bold: true, children: u.input }),
9036
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \u2191 " }),
9749
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191 " }),
9037
9750
  /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: u.output }),
9038
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \u2193" }),
9751
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2193" }),
9039
9752
  u.cache && /* @__PURE__ */ jsxs9(Fragment3, { children: [
9040
9753
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (" }),
9041
9754
  /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
@@ -9051,6 +9764,15 @@ function StatusBar({ status }) {
9051
9764
  ] }, "usage")
9052
9765
  );
9053
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
+ }
9054
9776
  details.push(
9055
9777
  /* @__PURE__ */ jsx9(
9056
9778
  Text9,
@@ -9062,14 +9784,14 @@ function StatusBar({ status }) {
9062
9784
  "elapsed"
9063
9785
  )
9064
9786
  );
9065
- if (status.bypass) {
9066
- details.push(
9067
- /* @__PURE__ */ jsxs9(Text9, { color: "black", backgroundColor: theme.warn, bold: true, children: [
9068
- " ",
9069
- "BYPASS",
9070
- " "
9071
- ] }, "bypass")
9072
- );
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"));
9073
9795
  }
9074
9796
  return /* @__PURE__ */ jsxs9(
9075
9797
  Box9,
@@ -9089,59 +9811,28 @@ function StatusBar({ status }) {
9089
9811
  flexDirection: "column",
9090
9812
  children: [
9091
9813
  /* @__PURE__ */ jsxs9(Box9, { width: "100%", alignItems: "flex-start", children: [
9092
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, flexShrink: 1, flexWrap: "wrap", children: /* @__PURE__ */ jsx9(Segments, { items: primary }) }),
9093
- /* @__PURE__ */ jsxs9(
9094
- Box9,
9095
- {
9096
- flexShrink: 0,
9097
- marginLeft: 2,
9098
- flexDirection: "column",
9099
- alignItems: "flex-end",
9100
- children: [
9101
- /* @__PURE__ */ jsx9(ContextMeter, { context: status.context }),
9102
- (status.mcp.connected > 0 || status.mcp.failed > 0) && /* @__PURE__ */ jsxs9(Text9, { children: [
9103
- status.mcp.connected > 0 && /* @__PURE__ */ jsxs9(Text9, { color: theme.ok, children: [
9104
- "mcp:",
9105
- status.mcp.connected
9106
- ] }),
9107
- status.mcp.connected > 0 && status.mcp.failed > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
9108
- status.mcp.failed > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme.error, children: strings.mcpFailed(status.mcp.failed) })
9109
- ] }),
9110
- status.activeAgents > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: strings.activeAgents(status.activeAgents) })
9111
- ]
9112
- }
9113
- )
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 }) })
9114
9819
  ] }),
9115
- details.length > 0 && /* @__PURE__ */ jsx9(Box9, { flexWrap: "wrap", children: details.map((item, index) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
9116
- index > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
9117
- item
9118
- ] }, 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
+ ] })
9119
9827
  ]
9120
9828
  }
9121
9829
  );
9122
9830
  }
9123
- function Segments({ items }) {
9124
- return items.flatMap((item, index) => {
9125
- const elements = [];
9126
- if (index > 0) {
9127
- elements.push(
9128
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }) }, `sep-${index}`)
9129
- );
9130
- }
9131
- elements.push(/* @__PURE__ */ jsx9(Fragment2, { children: item }, `item-${index}`));
9132
- return elements;
9133
- });
9134
- }
9135
9831
  function shortModel(ref) {
9136
9832
  const safe = sanitizeTerminalText(ref);
9137
9833
  const slash = safe.lastIndexOf("/");
9138
9834
  return slash === -1 ? safe : safe.slice(slash + 1);
9139
9835
  }
9140
- function ModelBadge({ ref: modelRef }) {
9141
- const theme = useTheme();
9142
- const name = shortModel(modelRef);
9143
- return /* @__PURE__ */ jsx9(Box9, { borderColor: theme.accent, borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: name }) });
9144
- }
9145
9836
  var EFFORT_STYLES = {
9146
9837
  auto: { label: "auto", color: "cyan" },
9147
9838
  max: { label: "max", color: "red", bold: true },
@@ -9150,36 +9841,30 @@ var EFFORT_STYLES = {
9150
9841
  medium: { label: "medium", color: "blue" },
9151
9842
  low: { label: "low", color: "gray", dim: true }
9152
9843
  };
9153
- function EffortLabel({ effort }) {
9844
+ function EffortLabel({ effort, effective }) {
9154
9845
  const style = EFFORT_STYLES[effort];
9155
- return /* @__PURE__ */ jsx9(Box9, { borderColor: style.color ?? "gray", borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx9(
9156
- Text9,
9157
- {
9158
- color: style.color,
9159
- bold: style.bold,
9160
- dimColor: style.dim,
9161
- children: style.label
9162
- }
9163
- ) });
9164
- }
9165
- function tokens2(count) {
9166
- if (count < 1e3) return String(count);
9167
- const [value, suffix] = count < 1e6 ? [count / 1e3, "k"] : [count / 1e6, "M"];
9168
- 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
+ ] });
9169
9857
  }
9170
9858
  function ContextMeter({
9171
9859
  context
9172
9860
  }) {
9173
9861
  const theme = useTheme();
9174
- const { window: win, used, exact } = context;
9175
- 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) {
9176
9865
  return /* @__PURE__ */ jsxs9(Text9, { children: [
9177
9866
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "ctx " }),
9178
- /* @__PURE__ */ jsx9(Text9, { color: "gray", children: exact ? tokens2(used) : "?" }),
9179
- win && win > 0 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9180
- "/",
9181
- tokens2(win)
9182
- ] })
9867
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: tokens(used) })
9183
9868
  ] });
9184
9869
  }
9185
9870
  const pct = Math.min(Math.max(used / win, 0), 1);
@@ -9197,107 +9882,306 @@ function ContextMeter({
9197
9882
  /* @__PURE__ */ jsx9(Text9, { color, children: "\u258C" }),
9198
9883
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
9199
9884
  " ",
9200
- tokens2(used),
9885
+ tokens(used),
9201
9886
  "/",
9202
- tokens2(win)
9887
+ tokens(win)
9203
9888
  ] })
9204
9889
  ] });
9205
9890
  }
9206
9891
 
9207
- // src/ui/components/TerminalViewport.tsx
9208
- import { Box as Box10 } from "ink";
9209
- import { jsx as jsx10 } from "react/jsx-runtime";
9210
- function interactiveViewportRows(rows) {
9211
- if (!Number.isFinite(rows)) return 22;
9212
- return Math.max(1, Math.floor(rows) - 2);
9213
- }
9214
- var INTERACTIVE_CHROME_ROWS = 14;
9215
- function liveTranscriptRows(rows) {
9216
- return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
9217
- }
9218
- function TerminalViewport({ children, rows }) {
9219
- const { columns } = useTerminalSize();
9220
- return /* @__PURE__ */ jsx10(
9221
- Box10,
9222
- {
9223
- width: columns,
9224
- maxWidth: "100%",
9225
- flexDirection: "column",
9226
- maxHeight: interactiveViewportRows(rows),
9227
- overflowY: "hidden",
9228
- children
9229
- }
9230
- );
9231
- }
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";
9232
9895
 
9233
9896
  // src/ui/components/Transcript.tsx
9234
- import { Box as Box12, Static, Text as Text11 } from "ink";
9235
- 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";
9236
9899
  import Spinner2 from "ink-spinner";
9237
- import stringWidth2 from "string-width";
9900
+ import stringWidth4 from "string-width";
9238
9901
 
9239
9902
  // src/ui/markdown.tsx
9240
- import { Box as Box11, Text as InkText } from "ink";
9241
- import { createContext as createContext3, Fragment as Fragment4, useContext as useContext3, useMemo as useMemo3 } from "react";
9242
- import stringWidth from "string-width";
9243
- 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";
9244
10101
  var DimContext = createContext3(false);
9245
- function Text10({ dimColor, color, ...props }) {
10102
+ function Text10({ dimColor, color, bold, ...props }) {
9246
10103
  const dim = useContext3(DimContext);
9247
- return /* @__PURE__ */ jsx11(InkText, { ...props, color: dim ? void 0 : color, dimColor: dimColor ?? dim });
10104
+ return /* @__PURE__ */ jsx10(
10105
+ InkText,
10106
+ {
10107
+ ...props,
10108
+ color: dim ? void 0 : color,
10109
+ bold: dim ? void 0 : bold,
10110
+ dimColor: dimColor ?? dim
10111
+ }
10112
+ );
9248
10113
  }
9249
10114
  function Markdown({ children, dim = false }) {
9250
- const blocks = useMemo3(() => extractBlocks(children), [children]);
9251
- 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)) }) });
9252
10117
  }
9253
10118
  function BlockView({ block }) {
9254
10119
  switch (block.type) {
9255
10120
  case "heading": {
9256
- 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 ?? "") }) });
9257
10122
  }
9258
10123
  case "blockquote":
9259
- 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)) });
9260
10125
  case "ul":
9261
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
9262
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text?.match(/^[☑☐] /) ? "" : "\u2022 " }),
9263
- /* @__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 }) })
9264
10129
  ] }, i)) });
9265
10130
  case "ol":
9266
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
9267
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: `${(block.start ?? 1) + i}. ` }),
9268
- /* @__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 }) })
9269
10134
  ] }, i)) });
9270
10135
  case "paragraph":
9271
10136
  default: {
9272
- const content = /* @__PURE__ */ jsx11(Text10, { children: inline(block.text ?? "") });
10137
+ const content = /* @__PURE__ */ jsx10(Text10, { children: inline(block.text ?? "") });
9273
10138
  if (!block.children?.length) return content;
9274
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
10139
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
9275
10140
  content,
9276
- /* @__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)) })
9277
10142
  ] });
9278
10143
  }
9279
10144
  case "table":
9280
- return /* @__PURE__ */ jsx11(TableView, { block });
10145
+ return /* @__PURE__ */ jsx10(TableView, { block });
9281
10146
  case "code":
9282
- return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
10147
+ return /* @__PURE__ */ jsx10(CodeBlock, { code: block.text ?? "", lang: block.lang });
9283
10148
  case "hr":
9284
- return /* @__PURE__ */ jsx11(Box11, { height: 1 });
10149
+ return /* @__PURE__ */ jsx10(Box10, { height: 1 });
9285
10150
  }
9286
10151
  }
9287
- function CodeBlock({ code }) {
9288
- const lines = code.split("\n");
9289
- 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)) });
9290
10174
  }
9291
10175
  function TableView({ block }) {
9292
10176
  const { columns } = useTerminalSize();
9293
10177
  const headers = block.headers ?? [];
9294
10178
  const rows = block.rows ?? [];
9295
10179
  if (headers.length === 0 && rows.length === 0) {
9296
- return /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "(empty table)" });
10180
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
9297
10181
  }
9298
10182
  const colCount = Math.max(headers.length, ...rows.map((r) => r?.length ?? 0));
9299
10183
  if (colCount === 0) {
9300
- return /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "(empty table)" });
10184
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
9301
10185
  }
9302
10186
  const allRows = [headers, ...rows].map(
9303
10187
  (row) => Array.from({ length: colCount }, (_, index) => inlineDisplayText(row[index] ?? ""))
@@ -9306,7 +10190,7 @@ function TableView({ block }) {
9306
10190
  for (const row of allRows) {
9307
10191
  for (let i = 0; i < colCount; i++) {
9308
10192
  const cell = row[i] ?? "";
9309
- naturalWidths[i] = Math.max(naturalWidths[i] ?? 1, stringWidth(cell));
10193
+ naturalWidths[i] = Math.max(naturalWidths[i] ?? 1, stringWidth3(cell));
9310
10194
  }
9311
10195
  }
9312
10196
  const colWidths = fitColumnWidths(naturalWidths, Math.max(1, columns));
@@ -9322,7 +10206,7 @@ function TableView({ block }) {
9322
10206
  const align = block.colAligns?.[i] ?? "left";
9323
10207
  const width = colWidths[i] ?? 1;
9324
10208
  const fitted = truncateToWidth(cell, width);
9325
- const visualWidth = stringWidth(fitted);
10209
+ const visualWidth = stringWidth3(fitted);
9326
10210
  const totalPad = Math.max(0, width - visualWidth);
9327
10211
  let padded;
9328
10212
  if (align === "right") {
@@ -9336,13 +10220,13 @@ function TableView({ block }) {
9336
10220
  cells.push(padded);
9337
10221
  }
9338
10222
  lines.push(
9339
- /* @__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}`)
9340
10224
  );
9341
10225
  if (rowIdx === 0) {
9342
- 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}`));
9343
10227
  }
9344
10228
  });
9345
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: lines });
10229
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines });
9346
10230
  }
9347
10231
  function fitColumnWidths(natural, maxLineWidth) {
9348
10232
  const widths = natural.map((width) => Math.max(1, width));
@@ -9365,11 +10249,11 @@ function fitColumnWidths(natural, maxLineWidth) {
9365
10249
  }
9366
10250
  var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
9367
10251
  function truncateToWidth(text, maxWidth) {
9368
- if (stringWidth(text) <= maxWidth) return text;
10252
+ if (stringWidth3(text) <= maxWidth) return text;
9369
10253
  if (maxWidth <= 1) return "\u2026";
9370
10254
  let result = "";
9371
10255
  for (const { segment } of graphemeSegmenter.segment(text)) {
9372
- if (stringWidth(result + segment) > maxWidth - 1) break;
10256
+ if (stringWidth3(result + segment) > maxWidth - 1) break;
9373
10257
  result += segment;
9374
10258
  }
9375
10259
  return `${result}\u2026`;
@@ -9730,7 +10614,7 @@ function inline(text, depth = 0) {
9730
10614
  handler: (m) => {
9731
10615
  const fragmentKey = key++;
9732
10616
  return /* @__PURE__ */ jsxs10(Fragment4, { children: [
9733
- /* @__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) }),
9734
10618
  /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
9735
10619
  "(",
9736
10620
  restoreInlineEscapes(m[3]),
@@ -9741,31 +10625,31 @@ function inline(text, depth = 0) {
9741
10625
  },
9742
10626
  {
9743
10627
  re: /(`+)([^`]+?)\1/,
9744
- 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++)
9745
10629
  },
9746
10630
  {
9747
10631
  re: /(\*\*\*|___)(?=\S)(.*?\S)\1/,
9748
- 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++)
9749
10633
  },
9750
10634
  {
9751
10635
  re: /(\*\*)(?=\S)(.*?\S)\1/,
9752
- 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++)
9753
10637
  },
9754
10638
  {
9755
10639
  re: /(__)(?=\S)(.*?\S)\1/,
9756
- 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++)
9757
10641
  },
9758
10642
  {
9759
10643
  re: /(~~)(?=\S)(.*?\S)\1/,
9760
- 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++)
9761
10645
  },
9762
10646
  {
9763
10647
  re: /(\*)(?=\S)(.*?\S)\1/,
9764
- 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++)
9765
10649
  },
9766
10650
  {
9767
10651
  re: /(?<!\w)(_)(?=\S)(.*?\S)\1(?!\w)/,
9768
- 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++)
9769
10653
  }
9770
10654
  ];
9771
10655
  while (rest.length > 0) {
@@ -9789,7 +10673,7 @@ function inline(text, depth = 0) {
9789
10673
  }
9790
10674
 
9791
10675
  // src/ui/components/Transcript.tsx
9792
- 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";
9793
10677
  var HEADER = { kind: "header" };
9794
10678
  function assistantThinkingForFrame(bubble, frozenThinking) {
9795
10679
  return bubble.streaming && bubble.text !== "" ? frozenThinking ?? bubble.thinking : bubble.thinking;
@@ -9806,7 +10690,7 @@ var Transcript = memo2(function Transcript2({
9806
10690
  }) {
9807
10691
  const liveAt = firstMutableBubbleIndex(bubbles);
9808
10692
  const stableCount = liveAt === -1 ? bubbles.length : liveAt;
9809
- const stableRef = useRef3([]);
10693
+ const stableRef = useRef4([]);
9810
10694
  if (stableRef.current.length < stableCount) {
9811
10695
  stableRef.current = [
9812
10696
  ...stableRef.current,
@@ -9820,7 +10704,7 @@ var Transcript = memo2(function Transcript2({
9820
10704
  const agents = live.filter((bubble) => bubble.kind === "subagent");
9821
10705
  const groupedAgents = agents.length > 1;
9822
10706
  return /* @__PURE__ */ jsxs11(
9823
- Box12,
10707
+ Box11,
9824
10708
  {
9825
10709
  flexDirection: "column",
9826
10710
  marginBottom: 1,
@@ -9828,9 +10712,9 @@ var Transcript = memo2(function Transcript2({
9828
10712
  minHeight: 0,
9829
10713
  overflowY: "hidden",
9830
10714
  children: [
9831
- /* @__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) }),
9832
10716
  /* @__PURE__ */ jsxs11(
9833
- Box12,
10717
+ Box11,
9834
10718
  {
9835
10719
  flexDirection: "column",
9836
10720
  flexShrink: 1,
@@ -9839,8 +10723,8 @@ var Transcript = memo2(function Transcript2({
9839
10723
  overflowY: "hidden",
9840
10724
  justifyContent: "flex-end",
9841
10725
  children: [
9842
- live.filter((bubble) => !groupedAgents || bubble.kind !== "subagent" && !(bubble.kind === "tool" && bubble.name === "task")).map((bubble) => /* @__PURE__ */ jsx12(BubbleView, { bubble, maxRows: maxLiveRows }, bubble.id)),
9843
- 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 })
9844
10728
  ]
9845
10729
  }
9846
10730
  )
@@ -9854,22 +10738,22 @@ var BubbleView = memo2(function BubbleView2({
9854
10738
  }) {
9855
10739
  const theme = useTheme();
9856
10740
  if (bubble.kind === "user") {
9857
- 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: [
9858
10742
  "\u203A ",
9859
10743
  bubble.text
9860
10744
  ] }) });
9861
10745
  }
9862
10746
  if (bubble.kind === "notice") {
9863
10747
  const color = bubble.level === "error" ? theme.error : bubble.level === "warn" ? theme.warn : theme.accent;
9864
- 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 }) });
9865
10749
  }
9866
10750
  if (bubble.kind === "assistant") {
9867
- return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
10751
+ return /* @__PURE__ */ jsx11(AssistantView, { bubble, maxRows });
9868
10752
  }
9869
10753
  if (bubble.kind === "subagent") {
9870
- return /* @__PURE__ */ jsx12(SubagentView, { bubble, maxRows });
10754
+ return /* @__PURE__ */ jsx11(SubagentView, { bubble, maxRows });
9871
10755
  }
9872
- return /* @__PURE__ */ jsx12(ToolView, { bubble });
10756
+ return /* @__PURE__ */ jsx11(ToolView, { bubble });
9873
10757
  });
9874
10758
  function splitThinkingPrefix(text, streaming) {
9875
10759
  const opening = /^\s*<think(?:ing)?>/i.exec(text);
@@ -9885,7 +10769,7 @@ function AssistantView({ bubble: source, maxRows }) {
9885
10769
  const tagged = splitThinkingPrefix(source.text, source.streaming);
9886
10770
  const bubble = { ...source, text: tagged.text, thinking: [source.thinking, tagged.thinking].filter(Boolean).join("\n") };
9887
10771
  const { columns } = useTerminalSize();
9888
- const frozenThinking = useRef3(void 0);
10772
+ const frozenThinking = useRef4(void 0);
9889
10773
  const answering = bubble.streaming && bubble.text !== "";
9890
10774
  if (!answering) frozenThinking.current = void 0;
9891
10775
  if (answering && frozenThinking.current === void 0) {
@@ -9900,11 +10784,11 @@ function AssistantView({ bubble: source, maxRows }) {
9900
10784
  const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
9901
10785
  const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
9902
10786
  const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
9903
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
9904
- frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { dim: true, children: frameThinking }) }),
9905
- 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 }),
9906
10790
  bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
9907
- /* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
10791
+ /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
9908
10792
  " thinking"
9909
10793
  ] })
9910
10794
  ] });
@@ -9923,7 +10807,7 @@ function wrapVisualLine(line, columns) {
9923
10807
  let current = "";
9924
10808
  let currentWidth = 0;
9925
10809
  for (const character of line) {
9926
- const width = Math.max(1, stringWidth2(character));
10810
+ const width = Math.max(1, stringWidth4(character));
9927
10811
  if (current !== "" && currentWidth + width > columns) {
9928
10812
  rows.push(current);
9929
10813
  current = "";
@@ -9940,23 +10824,23 @@ function ToolView({ bubble }) {
9940
10824
  const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
9941
10825
  const color = bubble.state === "error" ? theme.error : bubble.state === "ok" ? theme.ok : theme.warn;
9942
10826
  const display = bubble.display;
9943
- const diff = useMemo4(
10827
+ const diff = useMemo5(
9944
10828
  () => display?.kind === "diff" ? diffLines(display.before, display.after) : null,
9945
10829
  [display]
9946
10830
  );
9947
- if (!diff) return /* @__PURE__ */ jsx12(NonDiffToolView, { bubble, mark, color });
10831
+ if (!diff) return /* @__PURE__ */ jsx11(NonDiffToolView, { bubble, mark, color });
9948
10832
  const hasChanges = diff.added > 0 || diff.removed > 0;
9949
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
10833
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
9950
10834
  /* @__PURE__ */ jsxs11(Text11, { color, children: [
9951
10835
  mark,
9952
10836
  " ",
9953
10837
  bubble.summary,
9954
10838
  hasChanges && /* @__PURE__ */ jsxs11(Fragment5, { children: [
9955
- diff.added > 0 && /* @__PURE__ */ jsx12(Text11, { color: theme.ok, bold: true, children: ` +${diff.added}` }),
9956
- 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}` })
9957
10841
  ] })
9958
10842
  ] }),
9959
- /* @__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 }) })
9960
10844
  ] });
9961
10845
  }
9962
10846
  function NonDiffToolView({
@@ -9964,13 +10848,32 @@ function NonDiffToolView({
9964
10848
  mark,
9965
10849
  color
9966
10850
  }) {
9967
- 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: [
9968
10853
  /* @__PURE__ */ jsxs11(Text11, { color, children: [
9969
10854
  mark,
9970
10855
  " ",
9971
- bubble.summary
10856
+ bubble.summary,
10857
+ running && bubble.startedAt !== void 0 && /* @__PURE__ */ jsx11(Elapsed, { since: bubble.startedAt })
9972
10858
  ] }),
9973
- 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)
9974
10877
  ] });
9975
10878
  }
9976
10879
  function previewLines(content) {
@@ -9982,7 +10885,7 @@ var VISIBLE_SUBAGENTS = 3;
9982
10885
  function SubagentSummary({ agents }) {
9983
10886
  const theme = useTheme();
9984
10887
  const running = agents.filter((agent) => agent.state === "running");
9985
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10888
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
9986
10889
  /* @__PURE__ */ jsxs11(Text11, { color: theme.accent, wrap: "truncate-end", children: [
9987
10890
  "Subagents: ",
9988
10891
  running.length,
@@ -9999,7 +10902,7 @@ function SubagentSummary({ agents }) {
9999
10902
  progress.replace(/\s+/g, " ").trim() || "waiting"
10000
10903
  ] }, agent.id);
10001
10904
  }),
10002
- /* @__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" })
10003
10906
  ] });
10004
10907
  }
10005
10908
  function SubagentView({ bubble, maxRows }) {
@@ -10009,16 +10912,99 @@ function SubagentView({ bubble, maxRows }) {
10009
10912
  const color = bubble.state === "running" ? theme.warn : theme.ok;
10010
10913
  const latest = bubble.bubbles.at(-1);
10011
10914
  const progress = latest?.kind === "assistant" ? sanitizeThinkingText(latest.text || latest.thinking, latest.streaming) : latest?.kind === "tool" ? latest.summary : "";
10012
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10915
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
10013
10916
  /* @__PURE__ */ jsxs11(Text11, { color, bold: true, wrap: "truncate-end", children: [
10014
10917
  mark,
10015
10918
  " subagent: ",
10016
10919
  bubble.description
10017
10920
  ] }),
10018
- /* @__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 })
10922
+ ] });
10923
+ }
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
+ ] })
10019
10980
  ] });
10020
10981
  }
10021
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
+
10022
11008
  // src/ui/transcript.ts
10023
11009
  function emptyTranscript() {
10024
11010
  return { bubbles: [], seq: 0 };
@@ -10059,11 +11045,19 @@ function applyEvent(state, event) {
10059
11045
  name: sanitizeTerminalText(event.name),
10060
11046
  summary: sanitizeTerminalText(event.summary),
10061
11047
  state: "running",
10062
- content: ""
11048
+ content: "",
11049
+ startedAt: Date.now()
10063
11050
  }
10064
11051
  ],
10065
11052
  seq: state.seq + 1
10066
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
+ };
10067
11061
  case "tool_end":
10068
11062
  return {
10069
11063
  ...state,
@@ -10120,6 +11114,7 @@ function applyEvent(state, event) {
10120
11114
  case "turn_start":
10121
11115
  case "usage":
10122
11116
  case "rate_limits":
11117
+ case "speed":
10123
11118
  return state;
10124
11119
  }
10125
11120
  }
@@ -10256,10 +11251,25 @@ function sanitizeDisplay(display) {
10256
11251
  }
10257
11252
 
10258
11253
  // src/ui/App.tsx
10259
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
11254
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
10260
11255
  var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
10261
11256
  var STREAM_FRAME_MS = 50;
11257
+ var CUSTOM_ACCENT = "__custom__";
11258
+ var BELL_AFTER_MS = 15e3;
11259
+ var SUBAGENT_CHAT_EXTRA_ROWS = 4;
10262
11260
  var MAX_ATTACHMENTS = 8;
11261
+ var CONTEXT_PRESETS = [256e3, 512e3, 1e6];
11262
+ function formatContextTokens(tokens2) {
11263
+ if (tokens2 >= 1e6) return `${tokens2 / 1e6}M`;
11264
+ return `${Math.round(tokens2 / 1e3)}k`;
11265
+ }
11266
+ function parseContextTokens(input) {
11267
+ const match = /^(\d+(?:\.\d+)?)\s*([km])?$/i.exec(input.trim());
11268
+ if (!match) return null;
11269
+ const scale = match[2]?.toLowerCase() === "m" ? 1e6 : match[2]?.toLowerCase() === "k" ? 1e3 : 1;
11270
+ const tokens2 = Math.round(Number(match[1]) * scale);
11271
+ return tokens2 >= 1e3 && tokens2 <= 1e7 ? tokens2 : null;
11272
+ }
10263
11273
  function App({
10264
11274
  runtime,
10265
11275
  initialHistory,
@@ -10268,40 +11278,53 @@ function App({
10268
11278
  const { exit, suspendTerminal } = useApp();
10269
11279
  const { stdout } = useStdout2();
10270
11280
  const { rows } = useTerminalSize();
10271
- const [transcript, setTranscript] = useState5(
11281
+ const [transcript, setTranscript] = useState8(
10272
11282
  () => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
10273
11283
  );
10274
- const [transcriptRevision, setTranscriptRevision] = useState5(0);
10275
- const [input, setInput] = useState5("");
10276
- 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(
10277
11289
  () => inputHistoryFromMessages(initialHistory)
10278
11290
  );
10279
- const [busy, setBusy] = useState5(false);
10280
- const busyRef = useRef4(false);
10281
- const [pendingCount, setPendingCount] = useState5(0);
10282
- const queueRef = useRef4([]);
10283
- const slashQueueRef = useRef4(Promise.resolve());
10284
- const slashPendingRef = useRef4(0);
10285
- const drainQueueRef = useRef4(() => void 0);
10286
- const [attachments, setAttachments] = useState5([]);
10287
- const attachmentsRef = useRef4([]);
10288
- const attachmentGeneration = useRef4(0);
10289
- const automaticAttachmentTask = useRef4(null);
10290
- const clipboardPasteTask = useRef4(null);
10291
- const [overlay, setOverlay] = useState5({ kind: "none" });
10292
- const [accent, setAccent] = useState5(runtime.getAccent());
10293
- const [lang, setLang] = useState5(runtime.getLang());
10294
- const [setup, setSetup] = useState5(runtime.needsSetup());
10295
- const [, forceRender] = useState5(0);
10296
- const history = useRef4(initialHistory);
10297
- const abort = useRef4(null);
10298
- const sessionStart = useRef4(Date.now());
10299
- const turns = useRef4(0);
10300
- const [turnStart, setTurnStart] = useState5(null);
10301
- const [, tick] = useState5(0);
10302
- const [context, setContext] = useState5(() => runtime.modelContext());
10303
- const transcriptEvents = useRef4([]);
10304
- 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]);
10305
11328
  const clearScreen = useCallback(() => {
10306
11329
  if (stdout.isTTY) stdout.write("\x1B[2J\x1B[3J\x1B[H");
10307
11330
  }, [stdout]);
@@ -10328,6 +11351,7 @@ function App({
10328
11351
  }, []);
10329
11352
  const queueTranscriptEvent = useCallback(
10330
11353
  (event) => {
11354
+ recordSpeed(speedMeter.current, event);
10331
11355
  transcriptEvents.current.push(event);
10332
11356
  if (event.type !== "text_delta" && event.type !== "thinking_delta") {
10333
11357
  flushTranscriptEvents();
@@ -10337,24 +11361,24 @@ function App({
10337
11361
  },
10338
11362
  [flushTranscriptEvents]
10339
11363
  );
10340
- useEffect2(
11364
+ useEffect4(
10341
11365
  () => () => {
10342
11366
  if (transcriptTimer.current !== null) clearTimeout(transcriptTimer.current);
10343
11367
  transcriptEvents.current = [];
10344
11368
  },
10345
11369
  []
10346
11370
  );
10347
- useEffect2(
11371
+ useEffect4(
10348
11372
  () => runtime.subscribeContext(() => setContext(runtime.modelContext())),
10349
11373
  [runtime]
10350
11374
  );
10351
- useEffect2(() => {
11375
+ useEffect4(() => {
10352
11376
  if (turnStart === null) return;
10353
11377
  const timer = setInterval(() => tick((n) => n + 1), 1e3);
10354
11378
  return () => clearInterval(timer);
10355
11379
  }, [turnStart]);
10356
- const theme = useMemo5(() => makeTheme(accent), [accent]);
10357
- const strings = useMemo5(() => stringsFor(lang), [lang]);
11380
+ const theme = useMemo6(() => makeTheme(accent), [accent]);
11381
+ const strings = useMemo6(() => stringsFor(lang), [lang]);
10358
11382
  const notice = useCallback(
10359
11383
  (level, text) => {
10360
11384
  flushTranscriptEvents();
@@ -10362,7 +11386,7 @@ function App({
10362
11386
  },
10363
11387
  [flushTranscriptEvents]
10364
11388
  );
10365
- useEffect2(() => {
11389
+ useEffect4(() => {
10366
11390
  const check = runtime.startupUpdateCheck();
10367
11391
  let active2 = true;
10368
11392
  void check.then((result) => {
@@ -10402,7 +11426,8 @@ function App({
10402
11426
  abort.current = controller;
10403
11427
  busyRef.current = true;
10404
11428
  setBusy(true);
10405
- setTurnStart(Date.now());
11429
+ const startedAt = Date.now();
11430
+ setTurnStart(startedAt);
10406
11431
  turns.current += 1;
10407
11432
  const hooks = {
10408
11433
  onEvent: queueTranscriptEvent,
@@ -10425,7 +11450,11 @@ function App({
10425
11450
  return;
10426
11451
  }
10427
11452
  controller.signal.addEventListener("abort", cancel, { once: true });
10428
- 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
+ });
10429
11458
  })
10430
11459
  };
10431
11460
  try {
@@ -10439,6 +11468,7 @@ function App({
10439
11468
  } catch (error) {
10440
11469
  notice("error", error instanceof Error ? error.message : String(error));
10441
11470
  } finally {
11471
+ if (!controller.signal.aborted && Date.now() - startedAt >= BELL_AFTER_MS) bell();
10442
11472
  flushTranscriptEvents();
10443
11473
  abort.current = null;
10444
11474
  busyRef.current = false;
@@ -10446,9 +11476,14 @@ function App({
10446
11476
  setTurnStart(null);
10447
11477
  drainQueueRef.current();
10448
11478
  }
10449
- }, [flushTranscriptEvents, notice, queueTranscriptEvent, runtime]);
11479
+ }, [bell, flushTranscriptEvents, notice, queueTranscriptEvent, runtime]);
10450
11480
  const applyAccent = useCallback(
10451
- async (value) => {
11481
+ async (requested) => {
11482
+ const value = parseAccent(requested);
11483
+ if (!value) {
11484
+ notice("warn", strings.accentInvalid(requested));
11485
+ return;
11486
+ }
10452
11487
  await runtime.setAccent(value);
10453
11488
  setAccent(value);
10454
11489
  notice("info", strings.accentSet(value, resolveAccent(value)));
@@ -10652,6 +11687,10 @@ function App({
10652
11687
  notice("info", strings.undoNothing);
10653
11688
  return;
10654
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
+ }
10655
11694
  const lines = [strings.undoResult(result.restored.length, result.removed.length)];
10656
11695
  if (result.conflicts.length > 0) {
10657
11696
  lines.push(strings.undoConflicts(result.conflicts));
@@ -10776,8 +11815,8 @@ ${strings.modelSet(result.nextModel)}` : ""}`;
10776
11815
  return;
10777
11816
  }
10778
11817
  try {
10779
- const count = await runtime.refreshProviderModels(providerId);
10780
- notice("info", strings.modelsRefreshed(providerId, count));
11818
+ const count2 = await runtime.refreshProviderModels(providerId);
11819
+ notice("info", strings.modelsRefreshed(providerId, count2));
10781
11820
  } catch (error) {
10782
11821
  notice("error", error instanceof Error ? error.message : String(error));
10783
11822
  }
@@ -11077,7 +12116,7 @@ ${strings.mcpAddUsage}`
11077
12116
  history.current = result.history;
11078
12117
  setPromptHistory(inputHistoryFromMessages(result.history));
11079
12118
  setTranscript(
11080
- 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))
11081
12120
  );
11082
12121
  setTranscriptRevision((revision) => revision + 1);
11083
12122
  await runtime.persist(result.history);
@@ -11096,17 +12135,27 @@ ${strings.mcpAddUsage}`
11096
12135
  return;
11097
12136
  }
11098
12137
  case "subagents": {
11099
- const agents = runtime.activeAgentsList();
12138
+ const agents = transcriptRef.current.bubbles.filter(
12139
+ (bubble) => bubble.kind === "subagent"
12140
+ );
11100
12141
  if (agents.length === 0) {
11101
- notice("info", strings.subagentsStatus(0));
12142
+ notice("info", strings.noSubagents);
11102
12143
  return;
11103
12144
  }
11104
- const lines = agents.map((a, i) => {
11105
- const label = a.description || `subagent ${i + 1}`;
11106
- const progress = a.progress.length > 0 ? ` \xB7 ${a.progress[a.progress.length - 1]}` : "";
11107
- return `${label}${progress}`;
11108
- });
11109
- 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 });
11110
12159
  return;
11111
12160
  }
11112
12161
  case "checker":
@@ -11124,9 +12173,21 @@ ${strings.mcpAddUsage}`
11124
12173
  }
11125
12174
  const choice = await pick(
11126
12175
  strings.titleAccent,
11127
- 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
+ ]
11128
12180
  );
11129
- 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));
11130
12191
  return;
11131
12192
  }
11132
12193
  case "thinking": {
@@ -11163,16 +12224,16 @@ ${strings.mcpAddUsage}`
11163
12224
  case "budget": {
11164
12225
  const current = runtime.getMaxTokensPerTurn();
11165
12226
  if (rest[0]) {
11166
- const tokens3 = Number(rest[0]);
11167
- if (tokens3 === 0) {
12227
+ const tokens2 = Number(rest[0]);
12228
+ if (tokens2 === 0) {
11168
12229
  await runtime.setMaxTokensPerTurn(0);
11169
12230
  notice("info", strings.budgetUnlimited);
11170
12231
  forceRender((n) => n + 1);
11171
- } else if (!Number.isInteger(tokens3) || tokens3 < 1e3 || tokens3 > 1e7) {
12232
+ } else if (!Number.isInteger(tokens2) || tokens2 < 1e3 || tokens2 > 1e7) {
11172
12233
  notice("warn", strings.budgetInvalid);
11173
12234
  } else {
11174
- await runtime.setMaxTokensPerTurn(tokens3);
11175
- notice("info", strings.budgetSet(tokens3));
12235
+ await runtime.setMaxTokensPerTurn(tokens2);
12236
+ notice("info", strings.budgetSet(tokens2));
11176
12237
  forceRender((n) => n + 1);
11177
12238
  }
11178
12239
  } else {
@@ -11180,6 +12241,57 @@ ${strings.mcpAddUsage}`
11180
12241
  }
11181
12242
  return;
11182
12243
  }
12244
+ case "context": {
12245
+ const override = runtime.getContextWindowOverride();
12246
+ const current = runtime.modelContext();
12247
+ const requested = rest[0]?.toLowerCase();
12248
+ if (requested !== void 0 && requested !== "") {
12249
+ if (requested === "auto") {
12250
+ await runtime.setContextWindowOverride(void 0);
12251
+ notice("info", strings.contextAuto(runtime.modelContext().window));
12252
+ forceRender((n) => n + 1);
12253
+ return;
12254
+ }
12255
+ const tokens2 = parseContextTokens(requested);
12256
+ if (tokens2 === null) {
12257
+ notice("warn", strings.contextInvalid);
12258
+ return;
12259
+ }
12260
+ await runtime.setContextWindowOverride(tokens2);
12261
+ notice("info", strings.contextSet(tokens2));
12262
+ forceRender((n) => n + 1);
12263
+ return;
12264
+ }
12265
+ const effective = override ?? current.window;
12266
+ notice("info", effective === null ? strings.contextUnknown : strings.contextCurrent(effective));
12267
+ const choice = await pick(
12268
+ strings.titleContext,
12269
+ CONTEXT_PRESETS.map((preset) => ({
12270
+ key: String(preset),
12271
+ label: formatContextTokens(preset),
12272
+ hint: current.window === preset ? strings.contextCurrentLabel : void 0
12273
+ })).concat([
12274
+ {
12275
+ key: "auto",
12276
+ label: "auto",
12277
+ hint: override === void 0 ? strings.contextCurrentLabel : void 0
12278
+ }
12279
+ ])
12280
+ );
12281
+ if (!choice) return;
12282
+ const chosen = CONTEXT_PRESETS.find((preset) => String(preset) === choice);
12283
+ if (choice === "auto") {
12284
+ await runtime.setContextWindowOverride(void 0);
12285
+ notice("info", strings.contextAuto(runtime.modelContext().window));
12286
+ } else if (chosen !== void 0) {
12287
+ await runtime.setContextWindowOverride(chosen);
12288
+ notice("info", strings.contextSet(chosen));
12289
+ } else {
12290
+ return;
12291
+ }
12292
+ forceRender((n) => n + 1);
12293
+ return;
12294
+ }
11183
12295
  case "model": {
11184
12296
  const items = await runtime.listModelItems();
11185
12297
  if (items.length === 0) {
@@ -11329,7 +12441,7 @@ Rename the file if you want a different name.`);
11329
12441
  return;
11330
12442
  }
11331
12443
  setTranscript((state) => pushUser(state, userDisplay(item.text, item.content)));
11332
- history.current = [...history.current, { role: "user", content: item.content }];
12444
+ history.current = [...history.current, { role: "user", content: withUndoNote(item.content) }];
11333
12445
  void runAgent();
11334
12446
  },
11335
12447
  [runAgent, runSlash]
@@ -11346,6 +12458,49 @@ Rename the file if you want a different name.`);
11346
12458
  queueMicrotask(() => enqueueNext(next));
11347
12459
  }, [enqueueNext]);
11348
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
+ };
11349
12504
  const tryQueueAutomaticAttachment = useCallback(
11350
12505
  (requestedPath) => {
11351
12506
  if (!looksLikeAttachmentPath(requestedPath)) return Promise.resolve(false);
@@ -11356,19 +12511,30 @@ Rename the file if you want a different name.`);
11356
12511
  notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
11357
12512
  return Promise.resolve(true);
11358
12513
  }
11359
- const task = runtime.loadAutomaticAttachment(requestedPath).then((block) => {
11360
- if (automaticAttachmentTask.current !== task) return true;
11361
- if (!block) return false;
11362
- if (!appendAttachment(block)) {
11363
- notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
11364
- 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
+ }
11365
12535
  }
11366
- notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
11367
- return true;
11368
- }).catch(() => {
11369
- if (automaticAttachmentTask.current !== task) return true;
11370
- return false;
11371
- });
12536
+ return added > 0;
12537
+ })();
11372
12538
  automaticAttachmentTask.current = task;
11373
12539
  void task.finally(() => {
11374
12540
  if (automaticAttachmentTask.current === task) automaticAttachmentTask.current = null;
@@ -11420,12 +12586,11 @@ Rename the file if you want a different name.`);
11420
12586
  const submitSlash = () => {
11421
12587
  if (!detachedInput) setInput("");
11422
12588
  const command = text.slice(1).trim().split(/\s+/)[0]?.toLowerCase();
11423
- const liveCommand = ["help", "usage", "subagents", "config", "exit", "quit"].includes(command ?? "");
11424
- if (busyRef.current && !liveCommand || slashPendingRef.current > 0) {
11425
- queueRef.current = [...queueRef.current, { kind: "command", line: text }];
11426
- setPendingCount(queueRef.current.length);
12589
+ if (busyRef.current && !commandRunsWhileBusy(command) || slashPendingRef.current > 0) {
12590
+ enqueue({ kind: "command", line: text });
11427
12591
  return;
11428
12592
  }
12593
+ checkedOut.current = null;
11429
12594
  runSlash(text);
11430
12595
  };
11431
12596
  if (isKnownSlashCommand(text)) {
@@ -11463,14 +12628,14 @@ Rename the file if you want a different name.`);
11463
12628
  ...mentioned
11464
12629
  ];
11465
12630
  replaceAttachments([]);
11466
- const item = { kind: "message", text, content };
12631
+ const item = { kind: "message", text, content, attachments: queuedAttachments };
11467
12632
  if (busyRef.current || slashPendingRef.current > 0) {
11468
- queueRef.current = [...queueRef.current, item];
11469
- setPendingCount(queueRef.current.length);
12633
+ enqueue(item);
11470
12634
  return;
11471
12635
  }
12636
+ checkedOut.current = null;
11472
12637
  setTranscript((state) => pushUser(state, userDisplay(text, content)));
11473
- history.current = [...history.current, { role: "user", content }];
12638
+ history.current = [...history.current, { role: "user", content: withUndoNote(content) }];
11474
12639
  void runAgent();
11475
12640
  },
11476
12641
  [replaceAttachments, runAgent, runSlash, tryQueueAutomaticAttachment]
@@ -11483,14 +12648,25 @@ Rename the file if you want a different name.`);
11483
12648
  return;
11484
12649
  }
11485
12650
  if (!key.escape) return;
11486
- 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")) {
11487
12663
  if (!abort.current.signal.aborted) {
11488
12664
  abort.current.abort();
11489
12665
  notice("warn", strings.cancelled);
11490
12666
  }
11491
12667
  return;
11492
12668
  }
11493
- if (input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current || clipboardPasteTask.current) {
12669
+ if (hasDraft) {
11494
12670
  attachmentGeneration.current += 1;
11495
12671
  setInput("");
11496
12672
  replaceAttachments([]);
@@ -11515,10 +12691,10 @@ Rename the file if you want a different name.`);
11515
12691
  }
11516
12692
  setInput("");
11517
12693
  });
11518
- 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 }) });
11519
12695
  if (lang === void 0) {
11520
12696
  return shell(
11521
- /* @__PURE__ */ jsx13(
12697
+ /* @__PURE__ */ jsx14(
11522
12698
  LanguagePicker,
11523
12699
  {
11524
12700
  onPick: (chosen) => {
@@ -11531,7 +12707,7 @@ Rename the file if you want a different name.`);
11531
12707
  }
11532
12708
  if (setup) {
11533
12709
  return shell(
11534
- /* @__PURE__ */ jsx13(
12710
+ /* @__PURE__ */ jsx14(
11535
12711
  Onboarding,
11536
12712
  {
11537
12713
  onSubmit: async (url, key) => {
@@ -11546,17 +12722,17 @@ ${strings.configAt(runtime.configPath())}`);
11546
12722
  );
11547
12723
  }
11548
12724
  return shell(
11549
- /* @__PURE__ */ jsxs12(TerminalViewport, { rows, children: [
11550
- /* @__PURE__ */ jsx13(
12725
+ /* @__PURE__ */ jsxs13(TerminalViewport, { rows, children: [
12726
+ /* @__PURE__ */ jsx14(
11551
12727
  Transcript,
11552
12728
  {
11553
12729
  bubbles: transcript.bubbles,
11554
12730
  workspace: runtime.cwd,
11555
- maxLiveRows: liveTranscriptRows(rows)
12731
+ maxLiveRows: overlay.kind === "subagent" ? 0 : liveTranscriptRows(rows)
11556
12732
  },
11557
12733
  transcriptRevision
11558
12734
  ),
11559
- overlay.kind === "permission" && /* @__PURE__ */ jsx13(
12735
+ overlay.kind === "permission" && /* @__PURE__ */ jsx14(
11560
12736
  PermissionPrompt,
11561
12737
  {
11562
12738
  request: overlay.request,
@@ -11566,7 +12742,7 @@ ${strings.configAt(runtime.configPath())}`);
11566
12742
  }
11567
12743
  }
11568
12744
  ),
11569
- overlay.kind === "picker" && /* @__PURE__ */ jsx13(
12745
+ overlay.kind === "picker" && /* @__PURE__ */ jsx14(
11570
12746
  Picker,
11571
12747
  {
11572
12748
  title: overlay.title,
@@ -11581,7 +12757,17 @@ ${strings.configAt(runtime.configPath())}`);
11581
12757
  }
11582
12758
  }
11583
12759
  ),
11584
- 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(
11585
12771
  Confirm,
11586
12772
  {
11587
12773
  title: overlay.title,
@@ -11593,7 +12779,7 @@ ${strings.configAt(runtime.configPath())}`);
11593
12779
  }
11594
12780
  }
11595
12781
  ),
11596
- overlay.kind === "textinput" && /* @__PURE__ */ jsx13(
12782
+ overlay.kind === "textinput" && /* @__PURE__ */ jsx14(
11597
12783
  TextInputOverlay,
11598
12784
  {
11599
12785
  title: overlay.title,
@@ -11608,7 +12794,7 @@ ${strings.configAt(runtime.configPath())}`);
11608
12794
  }
11609
12795
  }
11610
12796
  ),
11611
- overlay.kind === "none" && /* @__PURE__ */ jsx13(
12797
+ overlay.kind === "none" && /* @__PURE__ */ jsx14(
11612
12798
  PromptInput,
11613
12799
  {
11614
12800
  value: input,
@@ -11619,7 +12805,11 @@ ${strings.configAt(runtime.configPath())}`);
11619
12805
  onListFiles: listWorkspaceFiles,
11620
12806
  disabled: busy,
11621
12807
  pending: pendingCount,
11622
- 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(),
11623
12813
  history: promptHistory,
11624
12814
  attachments: attachments.flatMap((block) => {
11625
12815
  const label = attachmentLabel(block);
@@ -11627,21 +12817,23 @@ ${strings.configAt(runtime.configPath())}`);
11627
12817
  })
11628
12818
  }
11629
12819
  ),
11630
- /* @__PURE__ */ jsx13(
12820
+ /* @__PURE__ */ jsx14(
11631
12821
  StatusBar,
11632
12822
  {
11633
12823
  status: {
11634
12824
  modelRef: runtime.getModelRef() || strings.noModel,
11635
12825
  effort: runtime.getEffort(),
12826
+ effectiveEffort: runtime.getEffectiveEffort(history.current),
11636
12827
  thinking: runtime.getThinking(),
11637
12828
  usage: runtime.usageParts(),
12829
+ speed: readSpeed(speedMeter.current, busy),
11638
12830
  mcp: runtime.mcpSummary(),
11639
12831
  mode: runtime.getMode(),
11640
12832
  bypass: runtime.isBypassEnabled(),
11641
12833
  busy,
11642
12834
  sessionMs: Date.now() - sessionStart.current,
11643
12835
  turnMs: turnStart === null ? null : Date.now() - turnStart,
11644
- activeAgents: runtime.activeAgentsCount(),
12836
+ agents: runtime.activeAgentsList().map((agent) => agent.description),
11645
12837
  context
11646
12838
  }
11647
12839
  }
@@ -11655,17 +12847,17 @@ function TextInputOverlay({
11655
12847
  onAnswer,
11656
12848
  onCancel
11657
12849
  }) {
11658
- const [value, setValue] = useState5("");
12850
+ const [value, setValue] = useState8("");
11659
12851
  const theme = useTheme();
11660
12852
  const submit = useCallback(() => {
11661
12853
  onAnswer(value || null);
11662
12854
  }, [value, onAnswer]);
11663
- useInput2((_input, key) => {
12855
+ useInput3((_input, key) => {
11664
12856
  if (key.escape) onCancel();
11665
12857
  });
11666
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
11667
- /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: title }),
11668
- /* @__PURE__ */ jsx13(
12858
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
12859
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: title }),
12860
+ /* @__PURE__ */ jsx14(
11669
12861
  TextInput2,
11670
12862
  {
11671
12863
  value,
@@ -11674,9 +12866,24 @@ function TextInputOverlay({
11674
12866
  mask: mask ? "\u2022" : void 0
11675
12867
  }
11676
12868
  ),
11677
- /* @__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" }) })
11678
12870
  ] });
11679
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
+ }
11680
12887
  function isKnownSlashCommand(line) {
11681
12888
  if (!line.startsWith("/")) return false;
11682
12889
  const [name = ""] = line.slice(1).trim().toLowerCase().split(/\s+/);
@@ -11747,7 +12954,7 @@ function unquoteArg(value) {
11747
12954
  }
11748
12955
 
11749
12956
  // src/app/tui.tsx
11750
- import { jsx as jsx14 } from "react/jsx-runtime";
12957
+ import { jsx as jsx15 } from "react/jsx-runtime";
11751
12958
  function clearTerminal() {
11752
12959
  if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
11753
12960
  }
@@ -11767,7 +12974,7 @@ async function startTui(options) {
11767
12974
  });
11768
12975
  clearTerminal();
11769
12976
  const instance = render(
11770
- /* @__PURE__ */ jsx14(App, { runtime, initialHistory: history, warnings }),
12977
+ /* @__PURE__ */ jsx15(App, { runtime, initialHistory: history, warnings }),
11771
12978
  TUI_RENDER_OPTIONS
11772
12979
  );
11773
12980
  try {