@moonshot-ai/kimi-code 0.19.1 → 0.19.2

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.
Files changed (74) hide show
  1. package/dist/main.mjs +1476 -789
  2. package/dist-web/assets/CodeBlockNode-DWqdMrZf.js +22 -0
  3. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-C0rAlIZT.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CP8vJ_jY.js} +1 -1
  4. package/dist-web/assets/{Tooltip-BGNN9cLC.js → Tooltip-DnT9F5O4.js} +1 -1
  5. package/dist-web/assets/arc-BTqlVy62.js +1 -0
  6. package/dist-web/assets/{architectureDiagram-3BPJPVTR-CdJj5NzX.js → architectureDiagram-3BPJPVTR-DOs258PA.js} +1 -1
  7. package/dist-web/assets/{blockDiagram-GPEHLZMM-BM14SQiN.js → blockDiagram-GPEHLZMM-YpbL-evQ.js} +1 -1
  8. package/dist-web/assets/{c4Diagram-AAUBKEIU-FYQfQdwd.js → c4Diagram-AAUBKEIU-BQ0g7h08.js} +1 -1
  9. package/dist-web/assets/channel-C9wJxpza.js +1 -0
  10. package/dist-web/assets/{chunk-2J33WTMH-V2R-DrVS.js → chunk-2J33WTMH-nYkFiTji.js} +1 -1
  11. package/dist-web/assets/{chunk-4BX2VUAB-BabgZRBA.js → chunk-4BX2VUAB-CsmhqO62.js} +1 -1
  12. package/dist-web/assets/{chunk-55IACEB6-DkJmDlyu.js → chunk-55IACEB6-CUzTYEsP.js} +1 -1
  13. package/dist-web/assets/{chunk-727SXJPM-CvIYC9nm.js → chunk-727SXJPM-BnqEbbXJ.js} +2 -2
  14. package/dist-web/assets/{chunk-AQP2D5EJ-uL8LYMfy.js → chunk-AQP2D5EJ-DtZARgEl.js} +1 -1
  15. package/dist-web/assets/{chunk-FMBD7UC4-EcbDOlvH.js → chunk-FMBD7UC4-R7lWfBOs.js} +1 -1
  16. package/dist-web/assets/{chunk-ND2GUHAM-C8Qo5Bp8.js → chunk-ND2GUHAM-CZaX1ZXe.js} +1 -1
  17. package/dist-web/assets/{chunk-QZHKN3VN-DqF-jFth.js → chunk-QZHKN3VN-5MXrqfJe.js} +1 -1
  18. package/dist-web/assets/classDiagram-4FO5ZUOK-DqgKlhvP.js +1 -0
  19. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-DqgKlhvP.js +1 -0
  20. package/dist-web/assets/{cose-bilkent-S5V4N54A-BP-HoEg_.js → cose-bilkent-S5V4N54A-4f-2zAe6.js} +1 -1
  21. package/dist-web/assets/{dagre-BM42HDAG-BAqAth0y.js → dagre-BM42HDAG-B29IUQLO.js} +1 -1
  22. package/dist-web/assets/{diagram-2AECGRRQ-CXLHAdSr.js → diagram-2AECGRRQ-BLG8l-kW.js} +1 -1
  23. package/dist-web/assets/{diagram-5GNKFQAL-BoPiyK2f.js → diagram-5GNKFQAL-D49xpQ0V.js} +1 -1
  24. package/dist-web/assets/{diagram-KO2AKTUF-B11ZVuuX.js → diagram-KO2AKTUF-CWBzbQ3X.js} +1 -1
  25. package/dist-web/assets/{diagram-LMA3HP47-B8fTjZXi.js → diagram-LMA3HP47-CAcmX_XH.js} +1 -1
  26. package/dist-web/assets/{diagram-OG6HWLK6-DWzU9uYr.js → diagram-OG6HWLK6-BInwpJw8.js} +1 -1
  27. package/dist-web/assets/{erDiagram-TEJ5UH35-4MqNXbsL.js → erDiagram-TEJ5UH35-Dckcr1cP.js} +1 -1
  28. package/dist-web/assets/{flowDiagram-I6XJVG4X-CCJIZLVz.js → flowDiagram-I6XJVG4X-CjWyYa_0.js} +1 -1
  29. package/dist-web/assets/{ganttDiagram-6RSMTGT7-DNfMlT11.js → ganttDiagram-6RSMTGT7-B8-b8RY7.js} +1 -1
  30. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-IBySLi3i.js → gitGraphDiagram-PVQCEYII-DF8nQ36i.js} +1 -1
  31. package/dist-web/assets/index-CKGltB1b.js +9 -0
  32. package/dist-web/assets/index-Cm9Q4xVi.js +3 -0
  33. package/dist-web/assets/index-DSjuug95.js +272 -0
  34. package/dist-web/assets/{index-CqOt95r5.js → index-Zbw78XRh.js} +1 -1
  35. package/dist-web/assets/index-sXB6HLvC.css +1 -0
  36. package/dist-web/assets/{index10-9hz8xOXN.js → index10-Dh4SYXXK.js} +2 -2
  37. package/dist-web/assets/index11-BIywvtHR.js +9 -0
  38. package/dist-web/assets/{index5-DYhhPUDp.js → index5-C_80WRqf.js} +1 -1
  39. package/dist-web/assets/{index6-DdsuTXIR.js → index6-kJ6dO27T.js} +1 -1
  40. package/dist-web/assets/{index7-CoWnZiYw.js → index7-Zg1vvU3j.js} +1 -1
  41. package/dist-web/assets/index8-Bqtk0wsj.js +2 -0
  42. package/dist-web/assets/{infoDiagram-5YYISTIA-BoRSkIVT.js → infoDiagram-5YYISTIA-m1Ft7ptD.js} +1 -1
  43. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-oMuu2qq0.js → ishikawaDiagram-YF4QCWOH-Cqe15rN3.js} +1 -1
  44. package/dist-web/assets/{journeyDiagram-JHISSGLW-DeuffmUz.js → journeyDiagram-JHISSGLW-2vdQK870.js} +1 -1
  45. package/dist-web/assets/{kanban-definition-UN3LZRKU-DLxFUMop.js → kanban-definition-UN3LZRKU-DWcbYK3Y.js} +1 -1
  46. package/dist-web/assets/{linear-Lxe_18BZ.js → linear-DEB4_JxQ.js} +1 -1
  47. package/dist-web/assets/{mermaid.core-4YTQ-5V5.js → mermaid.core-Bo4PXwhd.js} +5 -5
  48. package/dist-web/assets/{mindmap-definition-RKZ34NQL-Bb4Tm8kX.js → mindmap-definition-RKZ34NQL-xhnv3DGe.js} +1 -1
  49. package/dist-web/assets/{pieDiagram-4H26LBE5-Bl8T3fZ7.js → pieDiagram-4H26LBE5-Kf-4wCvB.js} +2 -2
  50. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-BoEFFDkb.js → quadrantDiagram-W4KKPZXB-CoEDvyN3.js} +1 -1
  51. package/dist-web/assets/{requirementDiagram-4Y6WPE33-B-op7Gg7.js → requirementDiagram-4Y6WPE33-C02xoAKl.js} +1 -1
  52. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-DEMaMFQJ.js → sankeyDiagram-5OEKKPKP-Cta2--lY.js} +1 -1
  53. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-B0FEdEjA.js → sequenceDiagram-3UESZ5HK-BGHuv-fJ.js} +1 -1
  54. package/dist-web/assets/{stateDiagram-AJRCARHV-BbCmT6m3.js → stateDiagram-AJRCARHV-CvYSand1.js} +1 -1
  55. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-CvSckOje.js +1 -0
  56. package/dist-web/assets/{timeline-definition-PNZ67QCA-DsuaxnT6.js → timeline-definition-PNZ67QCA-Puu2_su-.js} +1 -1
  57. package/dist-web/assets/{vennDiagram-CIIHVFJN-CC9doxKp.js → vennDiagram-CIIHVFJN-C5KnFfg1.js} +1 -1
  58. package/dist-web/assets/{vue.runtime.esm-bundler-DPCj1V1e.js → vue.runtime.esm-bundler-Cqjpm82Q.js} +1 -1
  59. package/dist-web/assets/{wardley-L42UT6IY-C1rlxzgy.js → wardley-L42UT6IY-B56jdDEk.js} +1 -1
  60. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-gCZUj4SL.js → wardleyDiagram-YWT4CUSO-C5uByZfn.js} +1 -1
  61. package/dist-web/assets/{xychartDiagram-2RQKCTM6-Bg1yj2-o.js → xychartDiagram-2RQKCTM6-NW-h6Ru2.js} +1 -1
  62. package/dist-web/index.html +2 -2
  63. package/package.json +4 -4
  64. package/dist-web/assets/CodeBlockNode-CEaJG3Ru.js +0 -22
  65. package/dist-web/assets/arc-DZU5jAun.js +0 -1
  66. package/dist-web/assets/channel-y98-FGX8.js +0 -1
  67. package/dist-web/assets/classDiagram-4FO5ZUOK-DIygKdRl.js +0 -1
  68. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-DIygKdRl.js +0 -1
  69. package/dist-web/assets/index-CiC6jpjA.js +0 -268
  70. package/dist-web/assets/index-Cp4cno62.css +0 -1
  71. package/dist-web/assets/index-D9E1vajk.js +0 -9
  72. package/dist-web/assets/index11-jpw4aORI.js +0 -9
  73. package/dist-web/assets/index8-nFDUP4kC.js +0 -2
  74. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-DVIg4v2K.js +0 -1
package/dist/main.mjs CHANGED
@@ -73598,7 +73598,7 @@ const TOOL_ERROR_STATUS$2 = "<system>ERROR: Tool execution failed.</system>";
73598
73598
  const TOOL_EMPTY_STATUS$2 = "<system>Tool output is empty.</system>";
73599
73599
  const TOOL_EMPTY_ERROR_STATUS$2 = "<system>ERROR: Tool execution failed. Tool output is empty.</system>";
73600
73600
  const TOOL_OUTPUT_EMPTY_TEXT$2 = "Tool output is empty.";
73601
- const TOOL_INTERRUPTED_ON_RESUME_OUTPUT = "Tool execution was interrupted before its result was recorded. Do not assume the tool completed successfully.";
73601
+ const TOOL_INTERRUPTED_ON_RESUME_OUTPUT$1 = "Tool execution was interrupted before its result was recorded. Do not assume the tool completed successfully.";
73602
73602
  var ContextMemory = class {
73603
73603
  agent;
73604
73604
  _history = [];
@@ -73770,15 +73770,18 @@ var ContextMemory = class {
73770
73770
  this.pushHistory(...trimTrailingOpenToolExchange(source.project(source.history)));
73771
73771
  }
73772
73772
  finishResume() {
73773
- const interruptedToolCallIds = [...this.pendingToolResultIds];
73774
73773
  this.openSteps.clear();
73775
- if (interruptedToolCallIds.length === 0) return;
73774
+ this.closePendingToolResults();
73775
+ }
73776
+ closePendingToolResults() {
73777
+ if (this.pendingToolResultIds.size === 0) return;
73778
+ const interruptedToolCallIds = [...this.pendingToolResultIds];
73776
73779
  for (const toolCallId of interruptedToolCallIds) this.appendLoopEvent({
73777
73780
  type: "tool.result",
73778
73781
  parentUuid: toolCallId,
73779
73782
  toolCallId,
73780
73783
  result: {
73781
- output: TOOL_INTERRUPTED_ON_RESUME_OUTPUT,
73784
+ output: TOOL_INTERRUPTED_ON_RESUME_OUTPUT$1,
73782
73785
  isError: true
73783
73786
  }
73784
73787
  });
@@ -73790,6 +73793,7 @@ var ContextMemory = class {
73790
73793
  });
73791
73794
  switch (event.type) {
73792
73795
  case "step.begin": {
73796
+ this.closePendingToolResults();
73793
73797
  const message = {
73794
73798
  role: "assistant",
73795
73799
  content: [],
@@ -73835,6 +73839,7 @@ var ContextMemory = class {
73835
73839
  return;
73836
73840
  }
73837
73841
  case "tool.result": {
73842
+ if (!this.pendingToolResultIds.has(event.toolCallId)) return;
73838
73843
  const message = createToolMessage(event.toolCallId, toolResultOutputForModel(event.result));
73839
73844
  this.pushHistory({
73840
73845
  ...message,
@@ -105098,6 +105103,32 @@ function renderLine(entry, lineEndingStyle) {
105098
105103
  function renderedLineBytes(renderedLine, isFirst) {
105099
105104
  return (isFirst ? 0 : 1) + Buffer.byteLength(renderedLine, "utf8");
105100
105105
  }
105106
+ function renderEntries(entries, lineEndingStyle) {
105107
+ const renderedLines = [];
105108
+ const truncatedLineNumbers = [];
105109
+ let bytes = 0;
105110
+ let maxBytesReached = false;
105111
+ for (const entry of entries) {
105112
+ const rendered = renderLine(entry, lineEndingStyle);
105113
+ const lineBytes = renderedLineBytes(rendered.line, renderedLines.length === 0);
105114
+ if (renderedLines.length > 0 && bytes + lineBytes > 102400) {
105115
+ maxBytesReached = true;
105116
+ break;
105117
+ }
105118
+ if (rendered.wasTruncated) truncatedLineNumbers.push(entry.lineNo);
105119
+ renderedLines.push(rendered.line);
105120
+ bytes += lineBytes;
105121
+ if (bytes >= 102400) {
105122
+ maxBytesReached = true;
105123
+ break;
105124
+ }
105125
+ }
105126
+ return {
105127
+ renderedLines,
105128
+ truncatedLineNumbers,
105129
+ maxBytesReached
105130
+ };
105131
+ }
105101
105132
  function isRegularFileMode(stMode) {
105102
105133
  return (stMode & S_IFMT$1) === S_IFREG;
105103
105134
  }
@@ -105198,6 +105229,39 @@ var ReadTool = class {
105198
105229
  }
105199
105230
  }
105200
105231
  async readForward(safePath, displayPath, lineOffset, effectiveLimit, requestedLines) {
105232
+ const rangeKaos = this.kaos;
105233
+ if (rangeKaos.scanTextFile !== void 0 && rangeKaos.readLineRange !== void 0) {
105234
+ const scan = await rangeKaos.scanTextFile(safePath);
105235
+ if (scan.hasNul) return {
105236
+ isError: true,
105237
+ output: notReadableFileOutput(displayPath)
105238
+ };
105239
+ const selectedEntries = [];
105240
+ let lineNo = lineOffset;
105241
+ for await (const rawLine of rangeKaos.readLineRange(safePath, {
105242
+ startLine: lineOffset,
105243
+ maxLines: effectiveLimit,
105244
+ errors: "strict"
105245
+ })) {
105246
+ selectedEntries.push({
105247
+ lineNo,
105248
+ rawContent: stripTrailingLf(rawLine)
105249
+ });
105250
+ lineNo += 1;
105251
+ }
105252
+ const lineEndingStyle = lineEndingStyleFromFlags(scan.lineEndingFlags);
105253
+ const rendered = renderEntries(selectedEntries, lineEndingStyle);
105254
+ return this.finishReadResult({
105255
+ renderedLines: rendered.renderedLines,
105256
+ truncatedLineNumbers: rendered.truncatedLineNumbers,
105257
+ maxLinesReached: effectiveLimit >= 1e3 && lineOffset + 1e3 <= scan.totalLines,
105258
+ maxBytesReached: rendered.maxBytesReached,
105259
+ lineEndingStyle,
105260
+ startLine: selectedEntries.length > 0 ? lineOffset : 0,
105261
+ totalLines: scan.totalLines,
105262
+ requestedLines
105263
+ });
105264
+ }
105201
105265
  const selectedEntries = [];
105202
105266
  const flags = {
105203
105267
  hasCrLf: false,
@@ -105231,38 +105295,45 @@ var ReadTool = class {
105231
105295
  if (selectedEntries.length >= effectiveLimit) collectionClosed = true;
105232
105296
  }
105233
105297
  const lineEndingStyle = lineEndingStyleFromFlags(flags);
105234
- const renderedLines = [];
105235
- const truncatedLineNumbers = [];
105236
- let bytes = 0;
105237
- let maxBytesReached = false;
105238
- for (const entry of selectedEntries) {
105239
- const rendered = renderLine(entry, lineEndingStyle);
105240
- const lineBytes = renderedLineBytes(rendered.line, renderedLines.length === 0);
105241
- if (renderedLines.length > 0 && bytes + lineBytes > 102400) {
105242
- maxBytesReached = true;
105243
- break;
105244
- }
105245
- if (rendered.wasTruncated) truncatedLineNumbers.push(entry.lineNo);
105246
- renderedLines.push(rendered.line);
105247
- bytes += lineBytes;
105248
- if (bytes >= 102400) {
105249
- maxBytesReached = true;
105250
- break;
105251
- }
105252
- }
105298
+ const rendered = renderEntries(selectedEntries, lineEndingStyle);
105253
105299
  return this.finishReadResult({
105254
- renderedLines,
105255
- truncatedLineNumbers,
105300
+ renderedLines: rendered.renderedLines,
105301
+ truncatedLineNumbers: rendered.truncatedLineNumbers,
105256
105302
  maxLinesReached,
105257
- maxBytesReached,
105303
+ maxBytesReached: rendered.maxBytesReached,
105258
105304
  lineEndingStyle,
105259
- startLine: renderedLines.length > 0 ? lineOffset : 0,
105305
+ startLine: selectedEntries.length > 0 ? lineOffset : 0,
105260
105306
  totalLines: currentLineNo,
105261
105307
  requestedLines
105262
105308
  });
105263
105309
  }
105264
105310
  async readTail(safePath, displayPath, lineOffset, effectiveLimit, requestedLines) {
105265
105311
  const tailCount = Math.abs(lineOffset);
105312
+ const rangeKaos = this.kaos;
105313
+ if (rangeKaos.scanTextFile !== void 0 && rangeKaos.readTailLines !== void 0) {
105314
+ const scan = await rangeKaos.scanTextFile(safePath);
105315
+ if (scan.hasNul) return {
105316
+ isError: true,
105317
+ output: notReadableFileOutput(displayPath)
105318
+ };
105319
+ const rawLines = [];
105320
+ for await (const rawLine of rangeKaos.readTailLines(safePath, {
105321
+ tailCount,
105322
+ errors: "strict"
105323
+ })) rawLines.push(rawLine);
105324
+ const startLine = Math.max(1, scan.totalLines - rawLines.length + 1);
105325
+ const entries = rawLines.map((rawLine, index) => ({
105326
+ lineNo: startLine + index,
105327
+ rawContent: stripTrailingLf(rawLine)
105328
+ }));
105329
+ return this.finishTailEntries({
105330
+ entries,
105331
+ lineEndingFlags: scan.lineEndingFlags,
105332
+ effectiveLimit,
105333
+ totalLines: scan.totalLines,
105334
+ requestedLines
105335
+ });
105336
+ }
105266
105337
  const entries = [];
105267
105338
  const flags = {
105268
105339
  hasCrLf: false,
@@ -105283,8 +105354,17 @@ var ReadTool = class {
105283
105354
  });
105284
105355
  if (entries.length > tailCount) entries.shift();
105285
105356
  }
105286
- const lineEndingStyle = lineEndingStyleFromFlags(flags);
105287
- let renderedCandidates = entries.slice(0, effectiveLimit).map((entry) => {
105357
+ return this.finishTailEntries({
105358
+ entries,
105359
+ lineEndingFlags: flags,
105360
+ effectiveLimit,
105361
+ totalLines: currentLineNo,
105362
+ requestedLines
105363
+ });
105364
+ }
105365
+ finishTailEntries(input) {
105366
+ const lineEndingStyle = lineEndingStyleFromFlags(input.lineEndingFlags);
105367
+ let renderedCandidates = input.entries.slice(0, input.effectiveLimit).map((entry) => {
105288
105368
  return {
105289
105369
  entry,
105290
105370
  rendered: renderLine(entry, lineEndingStyle)
@@ -105320,8 +105400,8 @@ var ReadTool = class {
105320
105400
  maxBytesReached,
105321
105401
  lineEndingStyle,
105322
105402
  startLine: renderedCandidates[0]?.entry.lineNo ?? 0,
105323
- totalLines: currentLineNo,
105324
- requestedLines
105403
+ totalLines: input.totalLines,
105404
+ requestedLines: input.requestedLines
105325
105405
  });
105326
105406
  }
105327
105407
  finishReadResult(input) {
@@ -155186,7 +155266,7 @@ function decodeUtf16LeIgnore(data) {
155186
155266
  * byte-to-character mappings so `errors` has no effect.
155187
155267
  * @internal
155188
155268
  */
155189
- function decodeTextWithErrors(data, encoding, errors = "strict") {
155269
+ function decodeTextWithErrors(data, encoding, errors = "strict", ignoreBOM = false) {
155190
155270
  let webLabel;
155191
155271
  switch (encoding) {
155192
155272
  case "utf-8":
@@ -155201,9 +155281,15 @@ function decodeTextWithErrors(data, encoding, errors = "strict") {
155201
155281
  default: webLabel = void 0;
155202
155282
  }
155203
155283
  if (webLabel === void 0) return data.toString(encoding);
155204
- if (errors === "strict") return new TextDecoder(webLabel, { fatal: true }).decode(data);
155284
+ if (errors === "strict") return new TextDecoder(webLabel, {
155285
+ fatal: true,
155286
+ ignoreBOM
155287
+ }).decode(data);
155205
155288
  if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
155206
- return new TextDecoder(webLabel, { fatal: false }).decode(data);
155289
+ return new TextDecoder(webLabel, {
155290
+ fatal: false,
155291
+ ignoreBOM
155292
+ }).decode(data);
155207
155293
  }
155208
155294
  /**
155209
155295
  * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
@@ -155295,6 +155381,7 @@ var BufferedReadable = class extends Readable$1 {
155295
155381
  //#endregion
155296
155382
  //#region ../../packages/kaos/src/local.ts
155297
155383
  const isWindows$1 = process.platform === "win32";
155384
+ const READ_CHUNK_SIZE = 64 * 1024;
155298
155385
  /**
155299
155386
  * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
155300
155387
  * visited set. Returns `null` when `ino` is 0, which Node returns on
@@ -155573,12 +155660,143 @@ var LocalKaos = class LocalKaos {
155573
155660
  const resolved = this._resolvePath(path);
155574
155661
  const encoding = options?.encoding ?? "utf-8";
155575
155662
  const errors = options?.errors ?? "strict";
155576
- const lines = decodeTextWithErrors(await readFile(resolved), encoding, errors).split("\n");
155577
- for (let i = 0; i < lines.length; i++) {
155578
- const line = lines[i];
155579
- if (line === void 0) continue;
155580
- if (i < lines.length - 1) yield line + "\n";
155581
- else if (line !== "") yield line;
155663
+ if (!isUtf8Encoding(encoding)) {
155664
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readFile(resolved), encoding, errors));
155665
+ return;
155666
+ }
155667
+ yield* this._readUtf8Lines(resolved, errors);
155668
+ }
155669
+ async scanTextFile(path) {
155670
+ const fh = await open$1(this._resolvePath(path), "r");
155671
+ try {
155672
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
155673
+ const flags = {
155674
+ hasCrLf: false,
155675
+ hasLf: false,
155676
+ hasLoneCr: false
155677
+ };
155678
+ const validator = createUtf8Validator();
155679
+ let totalLines = 0;
155680
+ let totalBytes = 0;
155681
+ let endsWithNewline = false;
155682
+ let hasNul = false;
155683
+ let prevWasCr = false;
155684
+ while (true) {
155685
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
155686
+ if (bytesRead === 0) break;
155687
+ const chunk = buf.subarray(0, bytesRead);
155688
+ validator.write(chunk);
155689
+ for (let i = 0; i < chunk.length; i += 1) {
155690
+ const byte = chunk[i];
155691
+ if (byte === void 0) continue;
155692
+ if (byte === 0) hasNul = true;
155693
+ if (byte === 10) totalLines += 1;
155694
+ }
155695
+ prevWasCr = updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr);
155696
+ totalBytes += bytesRead;
155697
+ endsWithNewline = chunk[bytesRead - 1] === 10;
155698
+ }
155699
+ if (prevWasCr) flags.hasLoneCr = true;
155700
+ validator.end();
155701
+ if (totalBytes > 0 && !endsWithNewline) totalLines += 1;
155702
+ return {
155703
+ totalLines,
155704
+ endsWithNewline,
155705
+ hasNul,
155706
+ lineEndingFlags: flags
155707
+ };
155708
+ } finally {
155709
+ await fh.close();
155710
+ }
155711
+ }
155712
+ async *readLineRange(path, options) {
155713
+ const resolved = this._resolvePath(path);
155714
+ const errors = options.errors ?? "strict";
155715
+ yield* this._readUtf8Lines(resolved, errors, {
155716
+ startLine: options.startLine,
155717
+ maxLines: options.maxLines
155718
+ });
155719
+ }
155720
+ async *readTailLines(path, options) {
155721
+ if (options.tailCount <= 0) return;
155722
+ const resolved = this._resolvePath(path);
155723
+ const errors = options.errors ?? "strict";
155724
+ const fh = await open$1(resolved, "r");
155725
+ try {
155726
+ const s = await fh.stat();
155727
+ if (s.size === 0) return;
155728
+ let pos = s.size;
155729
+ let foundLf = 0;
155730
+ let startOffset = 0;
155731
+ let needLf = options.tailCount;
155732
+ let sawTailBlock = false;
155733
+ while (pos > 0 && foundLf < needLf) {
155734
+ const readSize = Math.min(READ_CHUNK_SIZE, pos);
155735
+ pos -= readSize;
155736
+ const buf = Buffer.alloc(readSize);
155737
+ await fh.read(buf, 0, readSize, pos);
155738
+ if (!sawTailBlock) {
155739
+ sawTailBlock = true;
155740
+ needLf = buf[readSize - 1] === 10 ? options.tailCount + 1 : options.tailCount;
155741
+ }
155742
+ for (let i = readSize - 1; i >= 0; i -= 1) {
155743
+ if (buf[i] !== 10) continue;
155744
+ foundLf += 1;
155745
+ if (foundLf === needLf) {
155746
+ startOffset = pos + i + 1;
155747
+ break;
155748
+ }
155749
+ }
155750
+ }
155751
+ if (foundLf < needLf) startOffset = 0;
155752
+ yield* splitLinesKeepingTerminator(decodeTextWithErrors(await readRange(fh, startOffset, s.size - startOffset), "utf-8", errors, startOffset !== 0));
155753
+ } finally {
155754
+ await fh.close();
155755
+ }
155756
+ }
155757
+ async *_readUtf8Lines(resolved, errors, range) {
155758
+ const startLine = range?.startLine ?? 1;
155759
+ const maxLines = range?.maxLines ?? Number.POSITIVE_INFINITY;
155760
+ const fh = await open$1(resolved, "r");
155761
+ try {
155762
+ const buf = Buffer.alloc(READ_CHUNK_SIZE);
155763
+ let pending = [];
155764
+ let pendingOffset = 0;
155765
+ let fileOffset = 0;
155766
+ let lineNo = 1;
155767
+ let yielded = 0;
155768
+ while (true) {
155769
+ const { bytesRead } = await fh.read(buf, 0, buf.length, null);
155770
+ if (bytesRead === 0) break;
155771
+ const chunk = buf.subarray(0, bytesRead);
155772
+ let lineStart = 0;
155773
+ for (let i = 0; i < chunk.length; i += 1) {
155774
+ if (chunk[i] !== 10) continue;
155775
+ const piece = chunk.subarray(lineStart, i + 1);
155776
+ const lineOffset = pending.length === 0 ? fileOffset + lineStart : pendingOffset;
155777
+ const line = pending.length === 0 ? piece : Buffer.concat([...pending, piece]);
155778
+ if (lineNo >= startLine) {
155779
+ yield decodeTextWithErrors(line, "utf-8", errors, lineOffset !== 0);
155780
+ yielded += 1;
155781
+ if (yielded >= maxLines) return;
155782
+ }
155783
+ pending = [];
155784
+ lineStart = i + 1;
155785
+ lineNo += 1;
155786
+ }
155787
+ if (lineStart < chunk.length) {
155788
+ const tail = Buffer.from(chunk.subarray(lineStart));
155789
+ if (pending.length === 0) pendingOffset = fileOffset + lineStart;
155790
+ pending.push(tail);
155791
+ }
155792
+ fileOffset += bytesRead;
155793
+ }
155794
+ if (pending.length > 0) {
155795
+ const line = Buffer.concat(pending);
155796
+ if (lineNo >= startLine) yield decodeTextWithErrors(line, "utf-8", errors, pendingOffset !== 0);
155797
+ }
155798
+ } finally {
155799
+ await fh.close();
155582
155800
  }
155583
155801
  }
155584
155802
  async writeBytes(path, data) {
@@ -155640,6 +155858,90 @@ var LocalKaos = class LocalKaos {
155640
155858
  return merged;
155641
155859
  }
155642
155860
  };
155861
+ function isUtf8Encoding(encoding) {
155862
+ return encoding === "utf-8" || encoding === "utf8";
155863
+ }
155864
+ function* splitLinesKeepingTerminator(text) {
155865
+ if (text.length === 0) return;
155866
+ let start = 0;
155867
+ for (let i = 0; i < text.length; i += 1) if (text.codePointAt(i) === 10) {
155868
+ yield text.slice(start, i + 1);
155869
+ start = i + 1;
155870
+ }
155871
+ if (start < text.length) yield text.slice(start);
155872
+ }
155873
+ function updateLineEndingFlagsFromBytes(flags, chunk, prevWasCr) {
155874
+ for (let i = 0; i < chunk.length; i += 1) {
155875
+ const byte = chunk[i];
155876
+ if (byte === void 0) continue;
155877
+ if (byte === 13) {
155878
+ if (prevWasCr) flags.hasLoneCr = true;
155879
+ prevWasCr = true;
155880
+ } else if (byte === 10) {
155881
+ if (prevWasCr) flags.hasCrLf = true;
155882
+ else flags.hasLf = true;
155883
+ prevWasCr = false;
155884
+ } else {
155885
+ if (prevWasCr) flags.hasLoneCr = true;
155886
+ prevWasCr = false;
155887
+ }
155888
+ }
155889
+ return prevWasCr;
155890
+ }
155891
+ function createUtf8Validator() {
155892
+ let needed = 0;
155893
+ let lower = 128;
155894
+ let upper = 191;
155895
+ const fail = () => {
155896
+ throw new TypeError("Invalid UTF-8 data");
155897
+ };
155898
+ return {
155899
+ write(chunk) {
155900
+ for (let i = 0; i < chunk.length; i += 1) {
155901
+ const byte = chunk[i];
155902
+ if (byte === void 0) continue;
155903
+ if (needed === 0) {
155904
+ if (byte <= 127) continue;
155905
+ if (byte >= 194 && byte <= 223) needed = 1;
155906
+ else if (byte === 224) {
155907
+ needed = 2;
155908
+ lower = 160;
155909
+ } else if (byte >= 225 && byte <= 236) needed = 2;
155910
+ else if (byte === 237) {
155911
+ needed = 2;
155912
+ upper = 159;
155913
+ } else if (byte >= 238 && byte <= 239) needed = 2;
155914
+ else if (byte === 240) {
155915
+ needed = 3;
155916
+ lower = 144;
155917
+ } else if (byte >= 241 && byte <= 243) needed = 3;
155918
+ else if (byte === 244) {
155919
+ needed = 3;
155920
+ upper = 143;
155921
+ } else fail();
155922
+ } else {
155923
+ if (byte < lower || byte > upper) fail();
155924
+ lower = 128;
155925
+ upper = 191;
155926
+ needed -= 1;
155927
+ }
155928
+ }
155929
+ },
155930
+ end() {
155931
+ if (needed !== 0) fail();
155932
+ }
155933
+ };
155934
+ }
155935
+ async function readRange(fh, start, length) {
155936
+ const data = Buffer.alloc(length);
155937
+ let offset = 0;
155938
+ while (offset < length) {
155939
+ const { bytesRead } = await fh.read(data, offset, length - offset, start + offset);
155940
+ if (bytesRead === 0) break;
155941
+ offset += bytesRead;
155942
+ }
155943
+ return offset === length ? data : data.subarray(0, offset);
155944
+ }
155643
155945
  function waitForSpawn(child) {
155644
155946
  return new Promise((resolve, reject) => {
155645
155947
  const onSpawn = () => {
@@ -161019,7 +161321,7 @@ function buildItem(item, parentIdx) {
161019
161321
  if (item.header !== void 0) out.header = item.header;
161020
161322
  if (item.body !== void 0) out.body = item.body;
161021
161323
  if (item.multiSelect !== void 0) out.multi_select = item.multiSelect;
161022
- if (item.otherLabel !== void 0 || item.otherDescription !== void 0) out.allow_other = true;
161324
+ out.allow_other = true;
161023
161325
  if (item.otherLabel !== void 0) out.other_label = item.otherLabel;
161024
161326
  if (item.otherDescription !== void 0) out.other_description = item.otherDescription;
161025
161327
  return out;
@@ -162835,9 +163137,9 @@ let FsGitService = class FsGitService extends Disposable {
162835
163137
  filterSet.add(safe.relative);
162836
163138
  }
162837
163139
  }
162838
- const insideRes = await runCommand$1("git", ["rev-parse", "--is-inside-work-tree"], realCwd);
163140
+ const insideRes = await runCommand$2("git", ["rev-parse", "--is-inside-work-tree"], realCwd);
162839
163141
  if (insideRes.exitCode !== 0 || insideRes.stdout.trim() !== "true") throw new FsGitUnavailableError(realCwd, insideRes.stderr.trim() || `git rev-parse exit ${insideRes.exitCode}`);
162840
- const porcRes = await runCommand$1("git", [
163142
+ const porcRes = await runCommand$2("git", [
162841
163143
  "status",
162842
163144
  "--porcelain=v1",
162843
163145
  "--branch"
@@ -162845,13 +163147,13 @@ let FsGitService = class FsGitService extends Disposable {
162845
163147
  if (porcRes.exitCode !== 0) throw new FsGitUnavailableError(realCwd, porcRes.stderr.trim() || `git status exit ${porcRes.exitCode}`);
162846
163148
  const result = parsePorcelain(porcRes.stdout, filterSet);
162847
163149
  if (porcRes.stdout.split("\n").some((line) => line.length > 0 && !line.startsWith("## "))) {
162848
- if ((await runCommand$1("git", [
163150
+ if ((await runCommand$2("git", [
162849
163151
  "rev-parse",
162850
163152
  "--verify",
162851
163153
  "--quiet",
162852
163154
  "HEAD"
162853
163155
  ], realCwd)).exitCode === 0) {
162854
- const numstatRes = await runCommand$1("git", [
163156
+ const numstatRes = await runCommand$2("git", [
162855
163157
  "diff",
162856
163158
  "--no-color",
162857
163159
  "--numstat",
@@ -162872,7 +163174,7 @@ let FsGitService = class FsGitService extends Disposable {
162872
163174
  const cached = this.pullRequestCache.get(cwd);
162873
163175
  const now = Date.now();
162874
163176
  if (cached !== void 0 && now - cached.fetchedAt < PULL_REQUEST_TTL_MS$1) return cached.value;
162875
- const res = await runCommand$1("gh", [
163177
+ const res = await runCommand$2("gh", [
162876
163178
  "pr",
162877
163179
  "view",
162878
163180
  "--json",
@@ -162896,9 +163198,9 @@ let FsGitService = class FsGitService extends Disposable {
162896
163198
  const realCwd = await promises.realpath(cwd);
162897
163199
  const safe = await resolveSafePath(realCwd, req.path);
162898
163200
  const rel = safe.relative;
162899
- const insideRes = await runCommand$1("git", ["rev-parse", "--is-inside-work-tree"], realCwd);
163201
+ const insideRes = await runCommand$2("git", ["rev-parse", "--is-inside-work-tree"], realCwd);
162900
163202
  if (insideRes.exitCode !== 0 || insideRes.stdout.trim() !== "true") throw new FsGitUnavailableError(realCwd, insideRes.stderr.trim() || `git rev-parse exit ${insideRes.exitCode}`);
162901
- const statusRes = await runCommand$1("git", [
163203
+ const statusRes = await runCommand$2("git", [
162902
163204
  "status",
162903
163205
  "--porcelain=v1",
162904
163206
  "--",
@@ -162906,7 +163208,7 @@ let FsGitService = class FsGitService extends Disposable {
162906
163208
  ], realCwd);
162907
163209
  if (statusRes.exitCode !== 0) throw new FsGitUnavailableError(realCwd, statusRes.stderr.trim() || `git status exit ${statusRes.exitCode}`);
162908
163210
  const untracked = statusRes.stdout.startsWith("??");
162909
- const hasHead = (await runCommand$1("git", [
163211
+ const hasHead = (await runCommand$2("git", [
162910
163212
  "rev-parse",
162911
163213
  "--verify",
162912
163214
  "--quiet",
@@ -162914,7 +163216,7 @@ let FsGitService = class FsGitService extends Disposable {
162914
163216
  ], realCwd)).exitCode === 0;
162915
163217
  let diffRes;
162916
163218
  if (untracked || !hasHead) {
162917
- diffRes = await runCommand$1("git", [
163219
+ diffRes = await runCommand$2("git", [
162918
163220
  "diff",
162919
163221
  "--no-color",
162920
163222
  "--no-index",
@@ -162924,7 +163226,7 @@ let FsGitService = class FsGitService extends Disposable {
162924
163226
  ], realCwd);
162925
163227
  if (diffRes.exitCode !== 0 && diffRes.exitCode !== 1) throw new FsGitUnavailableError(realCwd, diffRes.stderr.trim() || `git diff exit ${diffRes.exitCode}`);
162926
163228
  } else {
162927
- diffRes = await runCommand$1("git", [
163229
+ diffRes = await runCommand$2("git", [
162928
163230
  "diff",
162929
163231
  "--no-color",
162930
163232
  "HEAD",
@@ -162946,7 +163248,7 @@ let FsGitService = class FsGitService extends Disposable {
162946
163248
  }
162947
163249
  };
162948
163250
  FsGitService = __decorate([__decorateParam(0, ISessionService)], FsGitService);
162949
- async function runCommand$1(cmd, args, cwd, options = {}) {
163251
+ async function runCommand$2(cmd, args, cwd, options = {}) {
162950
163252
  return new Promise((resolve) => {
162951
163253
  const child = spawn$1(cmd, args, {
162952
163254
  cwd,
@@ -166901,6 +167203,7 @@ const TOOL_ERROR_STATUS$1 = "<system>ERROR: Tool execution failed.</system>";
166901
167203
  const TOOL_EMPTY_STATUS$1 = "<system>Tool output is empty.</system>";
166902
167204
  const TOOL_EMPTY_ERROR_STATUS$1 = "<system>ERROR: Tool execution failed. Tool output is empty.</system>";
166903
167205
  const TOOL_OUTPUT_EMPTY_TEXT$1 = "Tool output is empty.";
167206
+ const TOOL_INTERRUPTED_ON_RESUME_OUTPUT = "Tool execution was interrupted before its result was recorded. Do not assume the tool completed successfully.";
166904
167207
  /**
166905
167208
  * Reduce wire records into the full transcript. Pure (no I/O); exported for
166906
167209
  * tests. Unknown or non-context records are ignored — only `context.*`
@@ -166924,6 +167227,27 @@ function reduceWireRecords(records) {
166924
167227
  push(...deferred);
166925
167228
  deferred = [];
166926
167229
  };
167230
+ const closePendingToolResults = (time) => {
167231
+ if (pendingToolResultIds.size === 0) return;
167232
+ const interruptedToolCallIds = [...pendingToolResultIds];
167233
+ for (const toolCallId of interruptedToolCallIds) {
167234
+ push({
167235
+ message: {
167236
+ role: "tool",
167237
+ content: toolResultContent({
167238
+ output: TOOL_INTERRUPTED_ON_RESUME_OUTPUT,
167239
+ isError: true
167240
+ }),
167241
+ toolCalls: [],
167242
+ toolCallId,
167243
+ isError: true
167244
+ },
167245
+ time
167246
+ });
167247
+ pendingToolResultIds.delete(toolCallId);
167248
+ }
167249
+ flushDeferredIfToolExchangeClosed();
167250
+ };
166927
167251
  const resetOpenState = () => {
166928
167252
  openSteps.clear();
166929
167253
  pendingToolResultIds.clear();
@@ -166932,6 +167256,7 @@ function reduceWireRecords(records) {
166932
167256
  const applyLoopEvent = (event, time) => {
166933
167257
  switch (event.type) {
166934
167258
  case "step.begin": {
167259
+ closePendingToolResults(time);
166935
167260
  const entry = {
166936
167261
  message: {
166937
167262
  role: "assistant",
@@ -166964,6 +167289,7 @@ function reduceWireRecords(records) {
166964
167289
  return;
166965
167290
  }
166966
167291
  case "tool.result":
167292
+ if (!pendingToolResultIds.has(event.toolCallId)) return;
166967
167293
  push({
166968
167294
  message: {
166969
167295
  role: "tool",
@@ -175651,7 +175977,7 @@ function getCapabilities() {
175651
175977
  }
175652
175978
  const KITTY_PREFIX = "\x1B_G";
175653
175979
  const ITERM2_PREFIX = "\x1B]1337;File=";
175654
- function isImageLine(line) {
175980
+ function isImageLine$1(line) {
175655
175981
  if (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) return true;
175656
175982
  return line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX);
175657
175983
  }
@@ -176327,7 +176653,7 @@ var TUI = class TUI extends Container {
176327
176653
  const reset = TUI.SEGMENT_RESET;
176328
176654
  for (let i = 0; i < lines.length; i++) {
176329
176655
  const line = lines[i];
176330
- if (!isImageLine(line)) lines[i] = normalizeTerminalOutput(line) + reset;
176656
+ if (!isImageLine$1(line)) lines[i] = normalizeTerminalOutput(line) + reset;
176331
176657
  }
176332
176658
  return lines;
176333
176659
  }
@@ -176355,7 +176681,7 @@ var TUI = class TUI extends Container {
176355
176681
  }
176356
176682
  /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
176357
176683
  compositeLineAt(baseLine, overlayLine, startCol, overlayWidth, totalWidth) {
176358
- if (isImageLine(baseLine)) return baseLine;
176684
+ if (isImageLine$1(baseLine)) return baseLine;
176359
176685
  const afterStart = startCol + overlayWidth;
176360
176686
  const base = extractSegments(baseLine, startCol, afterStart, totalWidth - afterStart, true);
176361
176687
  const overlay = sliceWithWidth(overlayLine, 0, overlayWidth, true);
@@ -176551,7 +176877,7 @@ var TUI = class TUI extends Container {
176551
176877
  if (i > firstChanged) buffer += "\r\n";
176552
176878
  buffer += "\x1B[2K";
176553
176879
  const line = newLines[i];
176554
- if (!isImageLine(line) && visibleWidth(line) > width) {
176880
+ if (!isImageLine$1(line) && visibleWidth(line) > width) {
176555
176881
  const crashLogPath = path$14.join(nodeOs.homedir(), ".pi", "agent", "pi-crash.log");
176556
176882
  const crashData = [
176557
176883
  `Crash at ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -180552,14 +180878,14 @@ var Markdown = class {
180552
180878
  renderedLines.push(...tokenLines);
180553
180879
  }
180554
180880
  const wrappedLines = [];
180555
- for (const line of renderedLines) if (isImageLine(line)) wrappedLines.push(line);
180881
+ for (const line of renderedLines) if (isImageLine$1(line)) wrappedLines.push(line);
180556
180882
  else wrappedLines.push(...wrapTextWithAnsi(line, contentWidth));
180557
180883
  const leftMargin = " ".repeat(this.paddingX);
180558
180884
  const rightMargin = " ".repeat(this.paddingX);
180559
180885
  const bgFn = this.defaultTextStyle?.bgColor;
180560
180886
  const contentLines = [];
180561
180887
  for (const line of wrappedLines) {
180562
- if (isImageLine(line)) {
180888
+ if (isImageLine$1(line)) {
180563
180889
  contentLines.push(line);
180564
180890
  continue;
180565
180891
  }
@@ -241951,7 +242277,7 @@ function waitForSigint() {
241951
242277
  //#region src/cli/commands.ts
241952
242278
  function createProgram(version, onMain, onMigrate, onPluginNodeRunner = () => {}, onUpgrade = () => {}) {
241953
242279
  const program = new Command(CLI_COMMAND_NAME).description("The Starting Point for Next-Gen Agents").version(version, "-V, --version").allowUnknownOption(false).configureHelp({ helpWidth: 100 }).helpOption("-h, --help", "Show help.").usage("[options] [command]").addHelpText("after", "\nDocumentation: https://moonshotai.github.io/kimi-code/\n");
241954
- program.addOption(new Option("-S, --session [id]", "Resume a session. With ID: resume that session. Without ID: interactively pick.").argParser((val) => val === true ? "" : val)).addOption(new Option("-r, --resume [id]").hideHelp().argParser((val) => val === true ? "" : val)).option("-C, --continue", "Continue the previous session for the working directory.", false).option("-y, --yolo", "Automatically approve all actions.", false).option("--auto", "Start in auto permission mode.", false).addOption(new Option("-m, --model <model>", "LLM model alias to use for this invocation. Defaults to default_model in config.toml.")).addOption(new Option("-p, --prompt <prompt>", "Run one prompt non-interactively and print the response.")).addOption(new Option("--output-format <format>", "Output format for prompt mode. Defaults to text.").choices(["text", "stream-json"])).addOption(new Option("--skills-dir <dir>", "Load skills from this directory instead of auto-discovered user and project directories. Can be repeated.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--add-dir <dir>", "Add an additional workspace directory for this session. Can be repeated.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--yes").hideHelp().default(false)).addOption(new Option("--auto-approve").hideHelp().default(false)).option("--plan", "Start in plan mode.", false);
242280
+ program.addOption(new Option("-S, --session [id]", "Resume a session. With ID: resume that session. Without ID: interactively pick.").argParser((val) => val === true ? "" : val)).addOption(new Option("-r, --resume [id]").hideHelp().argParser((val) => val === true ? "" : val)).option("-c, --continue", "Continue the previous session for the working directory.", false).addOption(new Option("-C").hideHelp().default(false)).option("-y, --yolo", "Automatically approve all actions.", false).option("--auto", "Start in auto permission mode.", false).addOption(new Option("-m, --model <model>", "LLM model alias to use for this invocation. Defaults to default_model in config.toml.")).addOption(new Option("-p, --prompt <prompt>", "Run one prompt non-interactively and print the response.")).addOption(new Option("--output-format <format>", "Output format for prompt mode. Defaults to text.").choices(["text", "stream-json"])).addOption(new Option("--skills-dir <dir>", "Load skills from this directory instead of auto-discovered user and project directories. Can be repeated.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--add-dir <dir>", "Add an additional workspace directory for this session. Can be repeated.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--yes").hideHelp().default(false)).addOption(new Option("--auto-approve").hideHelp().default(false)).option("--plan", "Start in plan mode.", false);
241955
242281
  registerExportCommand(program);
241956
242282
  registerProviderCommand(program);
241957
242283
  registerAcpCommand(program);
@@ -241975,7 +242301,7 @@ function createProgram(version, onMain, onMigrate, onPluginNodeRunner = () => {}
241975
242301
  const autoValue = raw["auto"] === true;
241976
242302
  onMain({
241977
242303
  session: sessionValue,
241978
- continue: raw["continue"],
242304
+ continue: raw["continue"] === true || raw["C"] === true,
241979
242305
  yolo: yoloValue,
241980
242306
  auto: autoValue,
241981
242307
  plan: raw["plan"],
@@ -243330,6 +243656,15 @@ var ModelSelectorComponent = class extends Container {
243330
243656
  alias: selected.alias,
243331
243657
  thinking: effectiveThinking(selected.model, this.draftFor(selected))
243332
243658
  });
243659
+ return;
243660
+ }
243661
+ if (matchesKey(data, Key.alt("s")) && this.opts.onSessionOnlySelect !== void 0) {
243662
+ const selected = this.selectedChoice();
243663
+ if (selected === void 0) return;
243664
+ this.opts.onSessionOnlySelect({
243665
+ alias: selected.alias,
243666
+ thinking: effectiveThinking(selected.model, this.draftFor(selected))
243667
+ });
243333
243668
  }
243334
243669
  }
243335
243670
  render(width) {
@@ -243341,7 +243676,9 @@ var ModelSelectorComponent = class extends Container {
243341
243676
  if (this.opts.providerSwitchHint) hintParts.push("Tab toggle provider");
243342
243677
  hintParts.push("↑↓ navigate");
243343
243678
  if (searchable && view.query.length > 0) hintParts.push("Backspace clear");
243344
- hintParts.push("Enter select", "Esc cancel");
243679
+ hintParts.push("Enter select");
243680
+ if (this.opts.onSessionOnlySelect !== void 0) hintParts.push("Alt+S session-only");
243681
+ hintParts.push("Esc cancel");
243345
243682
  const lines = [
243346
243683
  currentTheme.fg("primary", "─".repeat(width)),
243347
243684
  currentTheme.boldFg("primary", " Select a model") + titleSuffix,
@@ -244044,6 +244381,7 @@ function makeSelector(opts, subset) {
244044
244381
  searchable: true,
244045
244382
  providerSwitchHint: true,
244046
244383
  onSelect: opts.onSelect,
244384
+ onSessionOnlySelect: opts.onSessionOnlySelect,
244047
244385
  onCancel: opts.onCancel
244048
244386
  });
244049
244387
  }
@@ -244912,14 +245250,18 @@ function showModelPicker(host, selectedValue = host.state.appState.model) {
244912
245250
  currentThinking: host.state.appState.thinking,
244913
245251
  onSelect: ({ alias, thinking }) => {
244914
245252
  host.restoreEditor();
244915
- performModelSwitch(host, alias, thinking);
245253
+ performModelSwitch(host, alias, thinking, true);
245254
+ },
245255
+ onSessionOnlySelect: ({ alias, thinking }) => {
245256
+ host.restoreEditor();
245257
+ performModelSwitch(host, alias, thinking, false);
244916
245258
  },
244917
245259
  onCancel: () => {
244918
245260
  host.restoreEditor();
244919
245261
  }
244920
245262
  }));
244921
245263
  }
244922
- async function performModelSwitch(host, alias, thinking) {
245264
+ async function performModelSwitch(host, alias, thinking, persist) {
244923
245265
  if (host.state.appState.streamingPhase !== "idle") {
244924
245266
  host.showError("Cannot switch models while streaming — press Esc or Ctrl-C first.");
244925
245267
  return;
@@ -244949,14 +245291,17 @@ async function performModelSwitch(host, alias, thinking) {
244949
245291
  if (thinking !== prevThinking) host.track("thinking_toggle", { enabled: thinking });
244950
245292
  }
244951
245293
  let persisted = false;
244952
- try {
245294
+ if (persist) try {
244953
245295
  persisted = await persistModelSelection(host, alias, thinking);
244954
245296
  } catch (error) {
244955
245297
  const msg = formatErrorMessage$2(error);
244956
245298
  host.showError(`Switched to ${alias}, but failed to save default: ${msg}`);
244957
245299
  return;
244958
245300
  }
244959
- const status = runtimeChanged ? `Switched to ${alias} with thinking ${level}.` : persisted ? `Saved ${alias} with thinking ${level} as default.` : `Already using ${alias} with thinking ${level}.`;
245301
+ let status;
245302
+ if (runtimeChanged) status = persist ? `Switched to ${alias} with thinking ${level}.` : `Switched to ${alias} with thinking ${level} for this session only.`;
245303
+ else if (persist && persisted) status = `Saved ${alias} with thinking ${level} as default.`;
245304
+ else status = `Already using ${alias} with thinking ${level}.`;
244960
245305
  host.showStatus(status, "success");
244961
245306
  }
244962
245307
  async function persistModelSelection(host, alias, thinking) {
@@ -248872,16 +249217,18 @@ var CompactionComponent = class extends Container {
248872
249217
  ui;
248873
249218
  headerText;
248874
249219
  instruction;
249220
+ tip;
248875
249221
  blinkOn = true;
248876
249222
  blinkTimer = null;
248877
249223
  done = false;
248878
249224
  canceled = false;
248879
249225
  tokensBefore;
248880
249226
  tokensAfter;
248881
- constructor(ui, instruction) {
249227
+ constructor(ui, instruction, tip) {
248882
249228
  super();
248883
249229
  this.ui = ui;
248884
249230
  this.instruction = instruction;
249231
+ this.tip = tip;
248885
249232
  this.addChild(new Spacer(1));
248886
249233
  this.headerText = new Text(this.buildHeader(), 0, 0);
248887
249234
  this.addChild(this.headerText);
@@ -248921,7 +249268,7 @@ var CompactionComponent = class extends Container {
248921
249268
  buildHeader() {
248922
249269
  if (this.done) return `${currentTheme.fg("success", STATUS_BULLET)}${currentTheme.boldFg("success", "Compaction complete")}${this.tokensBefore !== void 0 && this.tokensAfter !== void 0 ? currentTheme.dim(` (${String(this.tokensBefore)} → ${String(this.tokensAfter)} tokens)`) : ""}`;
248923
249270
  if (this.canceled) return `${currentTheme.fg("warning", STATUS_BULLET)}${currentTheme.boldFg("warning", "Compaction cancelled")}`;
248924
- return `${this.blinkOn ? currentTheme.fg("text", STATUS_BULLET) : " "}${currentTheme.boldFg("primary", "Compacting context...")}`;
249271
+ return `${this.blinkOn ? currentTheme.fg("text", STATUS_BULLET) : " "}${currentTheme.boldFg("primary", "Compacting context...")}${this.tip ? currentTheme.fg("textDim", ` · Tip: ${this.tip}`) : ""}`;
248925
249272
  }
248926
249273
  startBlink() {
248927
249274
  this.blinkTimer = setInterval(() => {
@@ -312326,6 +312673,14 @@ var ToolCallComponent = class ToolCallComponent extends Container {
312326
312673
  ...shouldCap ? { maxLines: 10 } : {}
312327
312674
  });
312328
312675
  for (const line of lines) this.addChild(new Text(line, 2, 0));
312676
+ } else if (name === "Bash" && this.result === void 0) {
312677
+ const command = str(this.toolCall.args["command"]);
312678
+ if (command.length === 0) return;
312679
+ this.addChild(new ShellExecutionComponent({
312680
+ command,
312681
+ showCommand: true,
312682
+ commandPreviewLines: this.expanded ? void 0 : 10
312683
+ }));
312329
312684
  }
312330
312685
  }
312331
312686
  /**
@@ -312603,9 +312958,15 @@ var UserMessageComponent = class {
312603
312958
  const imageLines = thumbnail.render(contentWidth);
312604
312959
  for (const line of imageLines) lines.push(" ".repeat(bulletWidth) + line);
312605
312960
  }
312606
- return lines.map((line) => truncateToWidth(line, safeWidth, "…"));
312961
+ return lines.map((line) => {
312962
+ if (isImageLine(line)) return line;
312963
+ return truncateToWidth(line, safeWidth, "…");
312964
+ });
312607
312965
  }
312608
312966
  };
312967
+ function isImageLine(line) {
312968
+ return line.includes("\x1B_G") || line.includes("\x1B]1337;File=");
312969
+ }
312609
312970
  //#endregion
312610
312971
  //#region src/tui/utils/transcript-component-metadata.ts
312611
312972
  const componentEntries = /* @__PURE__ */ new WeakMap();
@@ -314452,6 +314813,8 @@ var MoonLoader = class extends Text {
314452
314813
  colorFn;
314453
314814
  label;
314454
314815
  displayText = "";
314816
+ tip = "";
314817
+ availableWidth = 0;
314455
314818
  constructor(ui, style = "moon", colorFn, label = "") {
314456
314819
  super("", 1, 0);
314457
314820
  this.ui = ui;
@@ -314482,18 +314845,713 @@ var MoonLoader = class extends Text {
314482
314845
  this.colorFn = colorFn;
314483
314846
  this.updateDisplay();
314484
314847
  }
314848
+ setTip(tip) {
314849
+ this.tip = tip;
314850
+ this.updateDisplay();
314851
+ }
314852
+ setAvailableWidth(width) {
314853
+ if (this.availableWidth === width) return;
314854
+ this.availableWidth = width;
314855
+ this.updateDisplay();
314856
+ }
314485
314857
  renderInline() {
314486
314858
  return this.displayText;
314487
314859
  }
314488
314860
  updateDisplay() {
314489
314861
  const frame = this.frames[this.currentFrame];
314490
314862
  const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
314491
- this.displayText = this.label ? `${coloredFrame} ${this.label}` : coloredFrame;
314863
+ const baseText = this.label ? `${coloredFrame} ${this.label}` : coloredFrame;
314864
+ let text = baseText;
314865
+ if (this.tip) {
314866
+ const withTip = baseText + currentTheme.fg("textDim", this.tip);
314867
+ if (this.availableWidth === 0 || visibleWidth(withTip) <= this.availableWidth) text = withTip;
314868
+ }
314869
+ this.displayText = text;
314492
314870
  this.setText(this.displayText);
314493
314871
  this.ui.requestRender();
314494
314872
  }
314495
314873
  };
314496
314874
  //#endregion
314875
+ //#region src/tui/constant/tips.ts
314876
+ /**
314877
+ * Subset of toolbar tips shown behind the composing spinner.
314878
+ */
314879
+ const WORKING_TIPS = [
314880
+ {
314881
+ text: "ctrl-s to add guidance without waiting for the turn to finish",
314882
+ priority: 2,
314883
+ solo: true
314884
+ },
314885
+ {
314886
+ text: "/tasks to check progress and status for background tasks",
314887
+ priority: 2
314888
+ },
314889
+ {
314890
+ text: "/init: generate AGENTS.md",
314891
+ priority: 2
314892
+ },
314893
+ { text: "Try /dance for a hidden Easter egg" },
314894
+ {
314895
+ text: "/plugins: manage plugins — try the \"superpowers\" plugin",
314896
+ solo: true,
314897
+ priority: 3
314898
+ },
314899
+ {
314900
+ text: "/plugins: manage plugins — try the \"Kimi Datasource\" for reliable financial, economic, and academic data",
314901
+ solo: true,
314902
+ priority: 3
314903
+ },
314904
+ {
314905
+ text: "ask Kimi to schedule tasks, e.g. \"remind me at 5pm\"",
314906
+ solo: true,
314907
+ priority: 3
314908
+ },
314909
+ {
314910
+ text: "/sessions to browse and resume earlier sessions",
314911
+ solo: true
314912
+ },
314913
+ {
314914
+ text: "/goal for multi-step work with a clear finish line",
314915
+ priority: 2,
314916
+ solo: true
314917
+ },
314918
+ {
314919
+ text: "/goal next to queue follow-up work while the current goal keeps running",
314920
+ solo: true
314921
+ },
314922
+ {
314923
+ text: "/web: use the Web UI for a better experience",
314924
+ solo: true
314925
+ },
314926
+ {
314927
+ text: "@: mention files",
314928
+ priority: 2
314929
+ }
314930
+ ];
314931
+ const ALL_TIPS = [
314932
+ ...WORKING_TIPS,
314933
+ { text: "shift+enter: newline" },
314934
+ { text: "ctrl+c: cancel" },
314935
+ { text: "/theme to switch the terminal UI theme" },
314936
+ { text: "/auto when you want Kimi to handle approvals and keep going unattended" },
314937
+ { text: "/yolo to skip most approvals for trusted batch work, only use it in repos you trust" },
314938
+ { text: "/help: show commands" },
314939
+ {
314940
+ text: "/compact compresses context when it gets long",
314941
+ priority: 2
314942
+ },
314943
+ {
314944
+ text: "ctrl-o to hide or reveal tool output switching between a clean chat view and full execution details",
314945
+ priority: 2
314946
+ },
314947
+ {
314948
+ text: "shift-tab to Plan mode to review the approach before Kimi edits files.",
314949
+ priority: 2
314950
+ },
314951
+ {
314952
+ text: "/model: switch model",
314953
+ priority: 2
314954
+ }
314955
+ ];
314956
+ //#endregion
314957
+ //#region src/utils/git/git-status.ts
314958
+ /**
314959
+ * Cached git branch + working-tree status for the footer/statusline.
314960
+ *
314961
+ * Branch name refreshes every 5s, porcelain status every 15s. Branch
314962
+ * and status reads stay synchronous with short timeouts. Pull request
314963
+ * lookup uses an async cache so a slow `gh pr view` never blocks
314964
+ * footer rendering.
314965
+ */
314966
+ const BRANCH_TTL_MS = 5e3;
314967
+ const STATUS_TTL_MS = 15e3;
314968
+ const PULL_REQUEST_TTL_MS = 6e4;
314969
+ const SPAWN_TIMEOUT_MS = 500;
314970
+ const PR_SPAWN_TIMEOUT_MS = 5e3;
314971
+ const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
314972
+ function createGitStatusCache(workDir, options = {}) {
314973
+ const isRepo = detectGitRepo(workDir);
314974
+ let branch = {
314975
+ value: null,
314976
+ fetchedAt: 0
314977
+ };
314978
+ let status = {
314979
+ dirty: false,
314980
+ ahead: 0,
314981
+ behind: 0,
314982
+ diffAdded: 0,
314983
+ diffDeleted: 0,
314984
+ fetchedAt: 0
314985
+ };
314986
+ let pullRequest = {
314987
+ value: null,
314988
+ branch: null,
314989
+ fetchedAt: 0,
314990
+ pendingBranch: null,
314991
+ requestId: 0
314992
+ };
314993
+ return { getStatus: () => {
314994
+ if (!isRepo) return null;
314995
+ const now = Date.now();
314996
+ if (now - branch.fetchedAt >= BRANCH_TTL_MS) branch = {
314997
+ value: readBranch(workDir),
314998
+ fetchedAt: now
314999
+ };
315000
+ if (branch.value === null) return null;
315001
+ if (now - status.fetchedAt >= STATUS_TTL_MS) status = {
315002
+ ...readStatus(workDir),
315003
+ fetchedAt: now
315004
+ };
315005
+ refreshPullRequestIfNeeded(branch.value, now);
315006
+ return {
315007
+ branch: branch.value,
315008
+ dirty: status.dirty,
315009
+ ahead: status.ahead,
315010
+ behind: status.behind,
315011
+ diffAdded: status.diffAdded,
315012
+ diffDeleted: status.diffDeleted,
315013
+ pullRequest: pullRequest.branch === branch.value ? pullRequest.value : null
315014
+ };
315015
+ } };
315016
+ function refreshPullRequestIfNeeded(branchName, now) {
315017
+ if (pullRequest.pendingBranch === branchName) return;
315018
+ const fetchedAt = pullRequest.branch === branchName ? pullRequest.fetchedAt : 0;
315019
+ if (now - fetchedAt < PULL_REQUEST_TTL_MS) return;
315020
+ const requestId = pullRequest.requestId + 1;
315021
+ pullRequest = {
315022
+ value: pullRequest.branch === branchName ? pullRequest.value : null,
315023
+ branch: branchName,
315024
+ fetchedAt,
315025
+ pendingBranch: branchName,
315026
+ requestId
315027
+ };
315028
+ readPullRequest(workDir).then((value) => {
315029
+ if (pullRequest.requestId !== requestId) return;
315030
+ const changed = !samePullRequest(pullRequest.branch === branchName ? pullRequest.value : null, value);
315031
+ pullRequest = {
315032
+ value,
315033
+ branch: branchName,
315034
+ fetchedAt: Date.now(),
315035
+ pendingBranch: null,
315036
+ requestId
315037
+ };
315038
+ if (changed) options.onChange?.();
315039
+ });
315040
+ }
315041
+ }
315042
+ function detectGitRepo(workDir) {
315043
+ try {
315044
+ const result = spawnSync("git", [
315045
+ "-C",
315046
+ workDir,
315047
+ "rev-parse",
315048
+ "--is-inside-work-tree"
315049
+ ], {
315050
+ encoding: "utf8",
315051
+ timeout: SPAWN_TIMEOUT_MS
315052
+ });
315053
+ return result.status === 0 && result.stdout.trim() === "true";
315054
+ } catch {
315055
+ return false;
315056
+ }
315057
+ }
315058
+ function readBranch(workDir) {
315059
+ try {
315060
+ const result = spawnSync("git", [
315061
+ "-C",
315062
+ workDir,
315063
+ "branch",
315064
+ "--show-current"
315065
+ ], {
315066
+ encoding: "utf8",
315067
+ timeout: SPAWN_TIMEOUT_MS
315068
+ });
315069
+ if (result.status !== 0) return null;
315070
+ const name = result.stdout.trim();
315071
+ return name.length > 0 ? name : null;
315072
+ } catch {
315073
+ return null;
315074
+ }
315075
+ }
315076
+ function readStatus(workDir) {
315077
+ try {
315078
+ const result = spawnSync("git", [
315079
+ "-C",
315080
+ workDir,
315081
+ "status",
315082
+ "--porcelain",
315083
+ "-b"
315084
+ ], {
315085
+ encoding: "utf8",
315086
+ timeout: SPAWN_TIMEOUT_MS,
315087
+ maxBuffer: 4 * 1024 * 1024
315088
+ });
315089
+ if (result.status !== 0) return {
315090
+ dirty: false,
315091
+ ahead: 0,
315092
+ behind: 0,
315093
+ diffAdded: 0,
315094
+ diffDeleted: 0
315095
+ };
315096
+ let dirty = false;
315097
+ let ahead = 0;
315098
+ let behind = 0;
315099
+ for (const line of result.stdout.split("\n")) if (line.startsWith("## ")) {
315100
+ const m = AHEAD_BEHIND_RE.exec(line);
315101
+ if (m) {
315102
+ ahead = Number.parseInt(m[1] ?? "0", 10) || 0;
315103
+ behind = Number.parseInt(m[2] ?? "0", 10) || 0;
315104
+ }
315105
+ } else if (line.trim().length > 0) dirty = true;
315106
+ const diff = dirty ? readDiffStats(workDir) : {
315107
+ added: 0,
315108
+ deleted: 0
315109
+ };
315110
+ return {
315111
+ dirty,
315112
+ ahead,
315113
+ behind,
315114
+ diffAdded: diff.added,
315115
+ diffDeleted: diff.deleted
315116
+ };
315117
+ } catch {
315118
+ return {
315119
+ dirty: false,
315120
+ ahead: 0,
315121
+ behind: 0,
315122
+ diffAdded: 0,
315123
+ diffDeleted: 0
315124
+ };
315125
+ }
315126
+ }
315127
+ function readDiffStats(workDir) {
315128
+ try {
315129
+ const result = spawnSync("git", [
315130
+ "-C",
315131
+ workDir,
315132
+ "diff",
315133
+ "--numstat",
315134
+ "HEAD",
315135
+ "--"
315136
+ ], {
315137
+ encoding: "utf8",
315138
+ timeout: SPAWN_TIMEOUT_MS,
315139
+ maxBuffer: 4 * 1024 * 1024
315140
+ });
315141
+ if (result.status !== 0) return {
315142
+ added: 0,
315143
+ deleted: 0
315144
+ };
315145
+ let added = 0;
315146
+ let deleted = 0;
315147
+ for (const line of result.stdout.split("\n")) {
315148
+ if (!line) continue;
315149
+ const [addedText, deletedText] = line.split(" ");
315150
+ added += parseDiffNumstatCount(addedText);
315151
+ deleted += parseDiffNumstatCount(deletedText);
315152
+ }
315153
+ return {
315154
+ added,
315155
+ deleted
315156
+ };
315157
+ } catch {
315158
+ return {
315159
+ added: 0,
315160
+ deleted: 0
315161
+ };
315162
+ }
315163
+ }
315164
+ function parseDiffNumstatCount(value) {
315165
+ if (value === void 0 || value === "-") return 0;
315166
+ const n = Number.parseInt(value, 10);
315167
+ return Number.isFinite(n) && n > 0 ? n : 0;
315168
+ }
315169
+ function readPullRequest(workDir) {
315170
+ return new Promise((resolve) => {
315171
+ try {
315172
+ execFile("gh", [
315173
+ "pr",
315174
+ "view",
315175
+ "--json",
315176
+ "number,url"
315177
+ ], {
315178
+ cwd: workDir,
315179
+ encoding: "utf8",
315180
+ env: {
315181
+ ...process.env,
315182
+ GH_NO_UPDATE_NOTIFIER: "1",
315183
+ GH_PROMPT_DISABLED: "1"
315184
+ },
315185
+ timeout: PR_SPAWN_TIMEOUT_MS,
315186
+ maxBuffer: 256 * 1024
315187
+ }, (error, stdout) => {
315188
+ if (error !== null) {
315189
+ resolve(null);
315190
+ return;
315191
+ }
315192
+ resolve(parsePullRequest(stdout));
315193
+ });
315194
+ } catch {
315195
+ resolve(null);
315196
+ }
315197
+ });
315198
+ }
315199
+ function samePullRequest(a, b) {
315200
+ if (a === null || b === null) return a === b;
315201
+ return a.number === b.number && a.url === b.url;
315202
+ }
315203
+ function parsePullRequest(stdout) {
315204
+ try {
315205
+ const raw = JSON.parse(stdout);
315206
+ if (typeof raw !== "object" || raw === null) return null;
315207
+ const record = raw;
315208
+ const number = record["number"];
315209
+ const url = record["url"];
315210
+ if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
315211
+ if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
315212
+ return {
315213
+ number,
315214
+ url
315215
+ };
315216
+ } catch {
315217
+ return null;
315218
+ }
315219
+ }
315220
+ function isSafeHttpUrl(value) {
315221
+ if (hasControlChars(value)) return false;
315222
+ try {
315223
+ const url = new URL(value);
315224
+ return url.protocol === "https:" || url.protocol === "http:";
315225
+ } catch {
315226
+ return false;
315227
+ }
315228
+ }
315229
+ function hasControlChars(value) {
315230
+ for (const char of value) {
315231
+ const code = char.codePointAt(0) ?? 0;
315232
+ if (code <= 31 || code === 127) return true;
315233
+ }
315234
+ return false;
315235
+ }
315236
+ function formatGitBadgeBase(status) {
315237
+ const parts = [];
315238
+ const diff = formatDiffStats(status);
315239
+ if (diff) parts.push(diff);
315240
+ let sync = "";
315241
+ if (status.ahead > 0) sync += `↑${status.ahead}`;
315242
+ if (status.behind > 0) sync += `↓${status.behind}`;
315243
+ if (sync) parts.push(sync);
315244
+ return parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
315245
+ }
315246
+ function formatPullRequestBadge(pullRequest, options = {}) {
315247
+ const prText = `[PR#${String(pullRequest.number)}]`;
315248
+ return options.linkPullRequest ? toTerminalHyperlink(prText, pullRequest.url) : prText;
315249
+ }
315250
+ function formatDiffStats(status) {
315251
+ const parts = [];
315252
+ if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
315253
+ if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
315254
+ if (parts.length > 0) return parts.join(" ");
315255
+ return status.dirty ? "±" : null;
315256
+ }
315257
+ function toTerminalHyperlink(text, url) {
315258
+ if (!isSafeHttpUrl(url)) return text;
315259
+ return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
315260
+ }
315261
+ //#endregion
315262
+ //#region src/tui/components/chrome/footer.ts
315263
+ const MAX_CWD_SEGMENTS = 3;
315264
+ const GOAL_TIMER_INTERVAL_MS = 1e3;
315265
+ const TIP_ROTATE_INTERVAL_MS$1 = 1e4;
315266
+ const TIP_SEPARATOR = " | ";
315267
+ /**
315268
+ * Expand tips into a rotation sequence using smooth weighted round-robin
315269
+ * (the nginx SWRR algorithm). Higher-`priority` tips appear more often while
315270
+ * staying evenly spread, so a tip generally does not land next to its own
315271
+ * duplicate. Deterministic and computed once at module load. Exported for
315272
+ * unit testing.
315273
+ */
315274
+ function buildWeightedTips(tips) {
315275
+ const items = tips.map((t) => ({
315276
+ tip: t,
315277
+ weight: Math.max(1, Math.trunc(t.priority ?? 1)),
315278
+ current: 0
315279
+ }));
315280
+ const total = items.reduce((sum, it) => sum + it.weight, 0);
315281
+ const seq = [];
315282
+ for (let n = 0; n < total; n++) {
315283
+ let best = items[0];
315284
+ for (const it of items) {
315285
+ it.current += it.weight;
315286
+ if (it.current > best.current) best = it;
315287
+ }
315288
+ best.current -= total;
315289
+ seq.push(best.tip);
315290
+ }
315291
+ return seq;
315292
+ }
315293
+ const ROTATION = buildWeightedTips(ALL_TIPS);
315294
+ function currentTipIndex() {
315295
+ return Math.floor(Date.now() / TIP_ROTATE_INTERVAL_MS$1);
315296
+ }
315297
+ /**
315298
+ * Pick the tip(s) for a rotation index over the weighted ROTATION sequence.
315299
+ * `primary` is always shown when it fits; `pair` (primary + next tip joined
315300
+ * by the separator) is offered for wide terminals. Pairing is skipped when
315301
+ * the current/next tip is `solo` or when the neighbour is a duplicate of the
315302
+ * current tip (which can happen at the wrap boundary), keeping long/important
315303
+ * tips on their own and avoiding "X | X".
315304
+ */
315305
+ function tipsForIndex(index) {
315306
+ const n = ROTATION.length;
315307
+ if (n === 0) return {
315308
+ primary: "",
315309
+ pair: null
315310
+ };
315311
+ const offset = (index % n + n) % n;
315312
+ const current = ROTATION[offset];
315313
+ if (n === 1 || current.solo) return {
315314
+ primary: current.text,
315315
+ pair: null
315316
+ };
315317
+ const next = ROTATION[(offset + 1) % n];
315318
+ if (next.solo || next.text === current.text) return {
315319
+ primary: current.text,
315320
+ pair: null
315321
+ };
315322
+ return {
315323
+ primary: current.text,
315324
+ pair: current.text + TIP_SEPARATOR + next.text
315325
+ };
315326
+ }
315327
+ /**
315328
+ * Footer goal badge, e.g. `[goal ● active · 4m · 7 turns]`. Only shown for a
315329
+ * live (active/paused) goal; terminal/no goal -> no badge. Turn count is a raw
315330
+ * count unless an explicit turn budget is set, in which case it shows used/limit.
315331
+ */
315332
+ function formatGoalBadge(goal, colors, wallClockMs) {
315333
+ if (goal === null || goal === void 0) return null;
315334
+ if (goal.status !== "active" && goal.status !== "paused" && goal.status !== "blocked") return null;
315335
+ const dotColor = goal.status === "active" ? colors.primary : goal.status === "blocked" ? colors.warning : colors.textMuted;
315336
+ const turns = goal.budget.turnBudget !== null ? `${goal.turnsUsed}/${goal.budget.turnBudget} turns` : `${goal.turnsUsed} ${goal.turnsUsed === 1 ? "turn" : "turns"}`;
315337
+ const label = `${goal.status} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
315338
+ return chalk.hex(colors.textMuted)("[goal ") + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
315339
+ }
315340
+ function formatBadgeElapsed(ms) {
315341
+ const totalSeconds = Math.round(ms / 1e3);
315342
+ if (totalSeconds < 60) return `${totalSeconds}s`;
315343
+ const minutes = Math.floor(totalSeconds / 60);
315344
+ if (minutes < 60) return `${minutes}m`;
315345
+ return `${Math.floor(minutes / 60)}h${minutes % 60}m`;
315346
+ }
315347
+ function modelDisplayName(state) {
315348
+ const model = state.availableModels[state.model];
315349
+ return model?.displayName ?? model?.model ?? state.model;
315350
+ }
315351
+ function shortenCwd(path) {
315352
+ if (!path) return path;
315353
+ const home = process.env["HOME"] ?? "";
315354
+ let work = path;
315355
+ if (home && path === home) return "~";
315356
+ if (home && path.startsWith(home + "/")) work = "~" + path.slice(home.length);
315357
+ const segments = work.split("/").filter((s) => s.length > 0);
315358
+ if (segments.length <= MAX_CWD_SEGMENTS) return work;
315359
+ return `…/${segments.slice(-MAX_CWD_SEGMENTS).join("/")}`;
315360
+ }
315361
+ function formatTokenCount(n) {
315362
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
315363
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
315364
+ return String(n);
315365
+ }
315366
+ function safeUsage(usage) {
315367
+ return safeUsageRatio(usage);
315368
+ }
315369
+ function formatContextStatus(usage, tokens, maxTokens) {
315370
+ const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
315371
+ if (maxTokens && maxTokens > 0 && tokens !== void 0) return `context: ${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
315372
+ return `context: ${pct}`;
315373
+ }
315374
+ function formatFooterGitBadge(status, colors) {
315375
+ const base = chalk.hex(colors.textDim)(formatGitBadgeBase(status));
315376
+ if (status.pullRequest === null) return base;
315377
+ return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
315378
+ }
315379
+ var FooterComponent = class {
315380
+ state;
315381
+ onRefresh;
315382
+ gitCache;
315383
+ gitCacheWorkDir;
315384
+ transientHint = null;
315385
+ goalSnapshotKey = null;
315386
+ goalObservedAtMs = Date.now();
315387
+ goalTimer = null;
315388
+ /**
315389
+ * Non-terminal background-task counts split by kind so the footer can
315390
+ * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
315391
+ * spawned via `Shell run_in_background=true`; `agentTasks` covers
315392
+ * `agent-*` BPM tasks (background subagents). Either zero hides its
315393
+ * respective badge.
315394
+ */
315395
+ backgroundBashTaskCount = 0;
315396
+ backgroundAgentCount = 0;
315397
+ constructor(state, onRefresh = () => {}) {
315398
+ this.state = state;
315399
+ this.onRefresh = onRefresh;
315400
+ this.gitCacheWorkDir = state.workDir;
315401
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
315402
+ this.syncGoalClock(state.goal);
315403
+ this.syncGoalTimer(state.goal);
315404
+ }
315405
+ setState(state) {
315406
+ if (state.workDir !== this.gitCacheWorkDir) {
315407
+ this.gitCacheWorkDir = state.workDir;
315408
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
315409
+ }
315410
+ this.syncGoalClock(state.goal);
315411
+ this.syncGoalTimer(state.goal);
315412
+ this.state = state;
315413
+ }
315414
+ /**
315415
+ * Short-lived hint that replaces the rotating toolbar tips on line 1.
315416
+ * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
315417
+ * again to exit" without requiring a toast/overlay subsystem.
315418
+ * Pass `null` to clear.
315419
+ */
315420
+ setTransientHint(hint) {
315421
+ this.transientHint = hint;
315422
+ }
315423
+ getTransientHint() {
315424
+ return this.transientHint;
315425
+ }
315426
+ /**
315427
+ * Sync both background-task badges with live counts. Each non-zero
315428
+ * count produces its own bracketed badge on line 1; zeros hide them
315429
+ * independently.
315430
+ */
315431
+ setBackgroundCounts(counts) {
315432
+ this.backgroundBashTaskCount = Math.max(0, counts.bashTasks);
315433
+ this.backgroundAgentCount = Math.max(0, counts.agentTasks);
315434
+ }
315435
+ invalidate() {}
315436
+ render(width) {
315437
+ const colors = currentTheme.palette;
315438
+ const state = this.state;
315439
+ const left = [];
315440
+ const modes = [];
315441
+ if (state.permissionMode === "auto") modes.push(chalk.hex(colors.warning).bold("auto"));
315442
+ if (state.permissionMode === "yolo") modes.push(chalk.hex(colors.warning).bold("yolo"));
315443
+ if (state.planMode) modes.push(chalk.hex(colors.primary).bold("plan"));
315444
+ if (state.swarmMode) modes.push(chalk.hex(colors.accent).bold("swarm"));
315445
+ if (modes.length > 0) left.push(modes.join(" "));
315446
+ const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
315447
+ if (goalBadge !== null) left.push(goalBadge);
315448
+ const model = modelDisplayName(state);
315449
+ if (model) {
315450
+ const modelLabel = `${model}${state.thinking ? " thinking" : ""}`;
315451
+ let renderedModelLabel = chalk.hex(colors.text)(modelLabel);
315452
+ if (isRainbowDancing()) renderedModelLabel = renderDanceFooterModel(modelLabel);
315453
+ left.push(renderedModelLabel);
315454
+ }
315455
+ if (this.backgroundBashTaskCount > 0) {
315456
+ const noun = this.backgroundBashTaskCount === 1 ? "task" : "tasks";
315457
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)} ${noun} running]`));
315458
+ }
315459
+ if (this.backgroundAgentCount > 0) {
315460
+ const noun = this.backgroundAgentCount === 1 ? "agent" : "agents";
315461
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)} ${noun} running]`));
315462
+ }
315463
+ const cwd = shortenCwd(state.workDir);
315464
+ if (cwd) left.push(chalk.hex(colors.textDim)(cwd));
315465
+ const git = this.gitCache.getStatus();
315466
+ if (git !== null) left.push(formatFooterGitBadge(git, colors));
315467
+ const leftLine = left.join(" ");
315468
+ const leftWidth = visibleWidth(leftLine);
315469
+ const { primary, pair } = tipsForIndex(currentTipIndex());
315470
+ const remaining = Math.max(0, width - leftWidth - 2);
315471
+ let tipText = "";
315472
+ if (pair && visibleWidth(pair) <= remaining) tipText = pair;
315473
+ else if (primary && visibleWidth(primary) <= remaining) tipText = primary;
315474
+ let line1;
315475
+ if (tipText) {
315476
+ const pad = width - leftWidth - visibleWidth(tipText);
315477
+ line1 = leftLine + " ".repeat(Math.max(0, pad)) + chalk.hex(colors.textMuted)(tipText);
315478
+ } else if (leftWidth <= width) line1 = leftLine;
315479
+ else line1 = truncateToWidth(leftLine, width, "…");
315480
+ const contextText = formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens);
315481
+ const contextWidth = visibleWidth(contextText);
315482
+ let line2;
315483
+ if (this.transientHint) {
315484
+ const maxHintWidth = Math.max(0, width - contextWidth - 1);
315485
+ const shownHint = visibleWidth(this.transientHint) <= maxHintWidth ? this.transientHint : truncateToWidth(this.transientHint, maxHintWidth, "…");
315486
+ const hintWidth = visibleWidth(shownHint);
315487
+ const pad = Math.max(0, width - hintWidth - contextWidth);
315488
+ line2 = chalk.hex(colors.warning).bold(shownHint) + " ".repeat(pad) + chalk.hex(colors.text)(contextText);
315489
+ } else {
315490
+ const leftPad = Math.max(0, width - contextWidth);
315491
+ line2 = " ".repeat(leftPad) + chalk.hex(colors.text)(contextText);
315492
+ }
315493
+ return [truncateToWidth(line1, width), truncateToWidth(line2, width)];
315494
+ }
315495
+ syncGoalClock(goal) {
315496
+ const key = goalSnapshotKey(goal);
315497
+ if (key === this.goalSnapshotKey) return;
315498
+ this.goalSnapshotKey = key;
315499
+ this.goalObservedAtMs = Date.now();
315500
+ }
315501
+ syncGoalTimer(goal) {
315502
+ if (goal?.status === "active") {
315503
+ if (this.goalTimer !== null) return;
315504
+ this.goalTimer = setInterval(() => {
315505
+ this.onRefresh();
315506
+ }, GOAL_TIMER_INTERVAL_MS);
315507
+ this.goalTimer.unref?.();
315508
+ return;
315509
+ }
315510
+ if (this.goalTimer !== null) {
315511
+ clearInterval(this.goalTimer);
315512
+ this.goalTimer = null;
315513
+ }
315514
+ }
315515
+ goalWallClockMs(goal) {
315516
+ if (goal === null || goal === void 0) return void 0;
315517
+ if (goal.status !== "active") return goal.wallClockMs;
315518
+ return goal.wallClockMs + Math.max(0, Date.now() - this.goalObservedAtMs);
315519
+ }
315520
+ };
315521
+ function goalSnapshotKey(goal) {
315522
+ if (goal === null || goal === void 0) return null;
315523
+ return [
315524
+ goal.goalId,
315525
+ goal.status,
315526
+ goal.terminalReason ?? "",
315527
+ String(goal.turnsUsed),
315528
+ String(goal.tokensUsed),
315529
+ String(goal.wallClockMs),
315530
+ String(goal.budget.tokenBudget),
315531
+ String(goal.budget.turnBudget),
315532
+ String(goal.budget.wallClockBudgetMs)
315533
+ ].join("\0");
315534
+ }
315535
+ //#endregion
315536
+ //#region src/tui/components/chrome/working-tips.ts
315537
+ const TIP_ROTATE_INTERVAL_MS = 1e4;
315538
+ const WORKING_TIP_ROTATION = buildWeightedTips(WORKING_TIPS);
315539
+ function currentWorkingTip(now = Date.now()) {
315540
+ if (WORKING_TIP_ROTATION.length === 0) return void 0;
315541
+ return WORKING_TIP_ROTATION[Math.floor(now / TIP_ROTATE_INTERVAL_MS) % WORKING_TIP_ROTATION.length];
315542
+ }
315543
+ /**
315544
+ * Pick a random tip from the weighted working-tip rotation.
315545
+ * If `excludeText` is provided and there are other tips available, avoid
315546
+ * returning the same text twice in a row.
315547
+ */
315548
+ function pickRandomWorkingTip(excludeText) {
315549
+ if (WORKING_TIP_ROTATION.length === 0) return void 0;
315550
+ const candidates = excludeText === void 0 || WORKING_TIP_ROTATION.length === 1 ? WORKING_TIP_ROTATION : WORKING_TIP_ROTATION.filter((t) => t.text !== excludeText);
315551
+ const pool = candidates.length > 0 ? candidates : WORKING_TIP_ROTATION;
315552
+ return pool[Math.floor(Math.random() * pool.length)];
315553
+ }
315554
+ //#endregion
314497
315555
  //#region src/tui/components/dialogs/approval-panel.ts
314498
315556
  /**
314499
315557
  * ApprovalPanel — pi-tui version of the approval request UI.
@@ -314957,6 +316015,10 @@ const DEFAULT_KEYBOARD_SHORTCUTS = [
314957
316015
  keys: "Ctrl-O",
314958
316016
  description: "Toggle tool output expansion"
314959
316017
  },
316018
+ {
316019
+ keys: "Ctrl-T",
316020
+ description: "Expand / collapse the todo list (when truncated)"
316021
+ },
314960
316022
  {
314961
316023
  keys: "Ctrl-S",
314962
316024
  description: "Steer — inject a follow-up during streaming"
@@ -316341,20 +317403,20 @@ var NoticeMessageComponent = class extends Container {
316341
317403
  //#endregion
316342
317404
  //#region src/tui/components/panes/activity-pane.ts
316343
317405
  var ActivityPaneComponent = class extends Container {
317406
+ spinnerRef;
316344
317407
  constructor(options) {
316345
317408
  super();
316346
- if (options.mode === "waiting" || options.mode === "tool") {
316347
- if (options.spinner !== void 0) {
316348
- this.addChild(new Spacer(1));
316349
- this.addChild(options.spinner);
316350
- }
316351
- return;
316352
- }
316353
- if (options.mode === "composing" && options.spinner !== void 0) {
317409
+ this.spinnerRef = options.spinner;
317410
+ if ((options.mode === "waiting" || options.mode === "tool" || options.mode === "composing") && options.spinner !== void 0) {
316354
317411
  this.addChild(new Spacer(1));
317412
+ if (options.tip) options.spinner.setTip(` · Tip: ${options.tip}`);
316355
317413
  this.addChild(options.spinner);
316356
317414
  }
316357
317415
  }
317416
+ render(width) {
317417
+ if (this.spinnerRef && "setAvailableWidth" in this.spinnerRef) this.spinnerRef.setAvailableWidth(width);
317418
+ return super.render(width);
317419
+ }
316358
317420
  };
316359
317421
  //#endregion
316360
317422
  //#region src/tui/components/panes/queue-pane.ts
@@ -317191,6 +318253,249 @@ function formatBtwTurnEnd(event) {
317191
318253
  return `BTW turn ended with reason: ${event.reason}`;
317192
318254
  }
317193
318255
  //#endregion
318256
+ //#region src/utils/clipboard/clipboard-common.ts
318257
+ const SUPPORTED_IMAGE_MIME_TYPES = [
318258
+ "image/png",
318259
+ "image/jpeg",
318260
+ "image/webp",
318261
+ "image/gif"
318262
+ ];
318263
+ const DEFAULT_LIST_TIMEOUT_MS = 1e3;
318264
+ const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
318265
+ function baseMimeType(raw) {
318266
+ return raw.split(";")[0]?.trim().toLowerCase() ?? raw.toLowerCase();
318267
+ }
318268
+ function isSupportedImageMimeType(mime) {
318269
+ const base = baseMimeType(mime);
318270
+ return SUPPORTED_IMAGE_MIME_TYPES.includes(base);
318271
+ }
318272
+ function parseTargetList(output) {
318273
+ return output.toString("utf-8").split(/\r?\n/).map((t) => t.trim()).filter((t) => t.length > 0);
318274
+ }
318275
+ function runCommand$1(command, args, options) {
318276
+ const result = spawnSync(command, args, {
318277
+ timeout: options?.timeoutMs ?? 1e3,
318278
+ maxBuffer: DEFAULT_MAX_BUFFER_BYTES,
318279
+ env: options?.env
318280
+ });
318281
+ if (result.error !== void 0 || result.status !== 0) return {
318282
+ ok: false,
318283
+ stdout: Buffer.alloc(0)
318284
+ };
318285
+ return {
318286
+ ok: true,
318287
+ stdout: Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "")
318288
+ };
318289
+ }
318290
+ function isWaylandSession(env) {
318291
+ return Boolean(env["WAYLAND_DISPLAY"]) || env["XDG_SESSION_TYPE"] === "wayland";
318292
+ }
318293
+ function isWSL(env) {
318294
+ if (env["WSL_DISTRO_NAME"] !== void 0 || env["WSLENV"] !== void 0) return true;
318295
+ try {
318296
+ return /microsoft|wsl/i.test(readFileSync("/proc/version", "utf-8"));
318297
+ } catch {
318298
+ return false;
318299
+ }
318300
+ }
318301
+ function isFileLikeNativeFormat(format) {
318302
+ const f = format.toLowerCase();
318303
+ const base = baseMimeType(format);
318304
+ return f.includes("file-url") || f.includes("file url") || f.includes("nsfilenames") || f.includes("com.apple.finder") || base === "text/uri-list" || base === "public.url";
318305
+ }
318306
+ function safeAvailableFormats(clip) {
318307
+ if (clip?.availableFormats === void 0) return [];
318308
+ try {
318309
+ return clip.availableFormats();
318310
+ } catch {
318311
+ return [];
318312
+ }
318313
+ }
318314
+ //#endregion
318315
+ //#region src/utils/clipboard/clipboard-has-image.ts
318316
+ const DEFAULT_POWERSHELL_TIMEOUT_MS$1 = 2e3;
318317
+ function hasImageViaWlPaste(run) {
318318
+ const list = run("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
318319
+ if (!list.ok) return false;
318320
+ return parseTargetList(list.stdout).some((t) => isSupportedImageMimeType(t));
318321
+ }
318322
+ function hasImageViaXclip(run) {
318323
+ const targets = run("xclip", [
318324
+ "-selection",
318325
+ "clipboard",
318326
+ "-t",
318327
+ "TARGETS",
318328
+ "-o"
318329
+ ], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
318330
+ if (!targets.ok) return false;
318331
+ return parseTargetList(targets.stdout).some((t) => isSupportedImageMimeType(t));
318332
+ }
318333
+ function hasImageViaPowerShell(run) {
318334
+ const result = run("powershell.exe", [
318335
+ "-NoProfile",
318336
+ "-Command",
318337
+ "Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); ($img -ne $null)"
318338
+ ], { timeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS$1 });
318339
+ if (!result.ok) return false;
318340
+ return result.stdout.toString("utf-8").trim().toLowerCase() === "true";
318341
+ }
318342
+ async function hasImageViaNative(clip) {
318343
+ if (clip === null) return false;
318344
+ if (safeAvailableFormats(clip).some(isFileLikeNativeFormat)) return false;
318345
+ try {
318346
+ return clip.hasImage();
318347
+ } catch {
318348
+ return false;
318349
+ }
318350
+ }
318351
+ async function clipboardHasImage(options) {
318352
+ const env = options?.env ?? process.env;
318353
+ const platform = options?.platform ?? process.platform;
318354
+ const clip = options?.clipboard ?? clipboard;
318355
+ const run = options?.runCommand ?? runCommand$1;
318356
+ if (env["TERMUX_VERSION"] !== void 0) return false;
318357
+ if (platform === "linux") {
318358
+ const wayland = isWaylandSession(env);
318359
+ const wsl = isWSL(env);
318360
+ let xclipResult;
318361
+ const xclipHasImage = () => {
318362
+ xclipResult ??= hasImageViaXclip(run);
318363
+ return xclipResult;
318364
+ };
318365
+ if (wayland || wsl) {
318366
+ if (hasImageViaWlPaste(run)) return true;
318367
+ if (xclipHasImage()) return true;
318368
+ }
318369
+ if (wsl && hasImageViaPowerShell(run)) return true;
318370
+ if (!wayland) {
318371
+ if (xclipHasImage()) return true;
318372
+ if (await hasImageViaNative(clip)) return true;
318373
+ }
318374
+ return false;
318375
+ }
318376
+ if (platform === "darwin") return hasImageViaNative(clip);
318377
+ if (platform === "win32") return hasImageViaNative(clip);
318378
+ return false;
318379
+ }
318380
+ //#endregion
318381
+ //#region src/tui/constant/clipboard-image-hint.ts
318382
+ const FOCUS_DEBOUNCE_MS = 1e3;
318383
+ const HINT_DISPLAY_MS = 4e3;
318384
+ //#endregion
318385
+ //#region src/tui/utils/terminal-focus.ts
318386
+ function installTerminalFocusTracking(state) {
318387
+ state.terminalState.focused = true;
318388
+ const disposeInputListener = state.ui.addInputListener((data) => handleTerminalFocusInput(state.terminalState, data));
318389
+ state.terminal.write(ENABLE_TERMINAL_FOCUS_REPORTING);
318390
+ return () => {
318391
+ disposeInputListener();
318392
+ state.terminal.write(DISABLE_TERMINAL_FOCUS_REPORTING);
318393
+ state.terminalState.focused = true;
318394
+ };
318395
+ }
318396
+ function handleTerminalFocusInput(state, data) {
318397
+ if (data === TERMINAL_FOCUS_IN) {
318398
+ state.focused = true;
318399
+ return { consume: true };
318400
+ }
318401
+ if (data === TERMINAL_FOCUS_OUT) {
318402
+ state.focused = false;
318403
+ return { consume: true };
318404
+ }
318405
+ }
318406
+ //#endregion
318407
+ //#region src/tui/controllers/clipboard-image-hint.ts
318408
+ function getPasteImageShortcut() {
318409
+ return process.platform === "win32" ? "Alt+V" : "Ctrl+V";
318410
+ }
318411
+ var ClipboardImageHintController = class {
318412
+ host;
318413
+ disposeInputListener;
318414
+ debounceTimer;
318415
+ clearHintTimer;
318416
+ lastHintAtMs = 0;
318417
+ lastHintText;
318418
+ checkGeneration = 0;
318419
+ focused = true;
318420
+ constructor(host) {
318421
+ this.host = host;
318422
+ }
318423
+ start() {
318424
+ this.disposeInputListener = this.host.ui.addInputListener((data) => {
318425
+ this.handleInput(data);
318426
+ });
318427
+ }
318428
+ stop() {
318429
+ this.clearDebounceTimer();
318430
+ this.clearClearHintTimer();
318431
+ this.disposeInputListener?.();
318432
+ this.disposeInputListener = void 0;
318433
+ this.checkGeneration += 1;
318434
+ this.clearOwnedHint();
318435
+ this.lastHintAtMs = 0;
318436
+ }
318437
+ handleInput(data) {
318438
+ if (data === TERMINAL_FOCUS_IN) {
318439
+ this.focused = true;
318440
+ this.scheduleCheck();
318441
+ return;
318442
+ }
318443
+ if (data === TERMINAL_FOCUS_OUT) {
318444
+ this.focused = false;
318445
+ this.clearDebounceTimer();
318446
+ return;
318447
+ }
318448
+ }
318449
+ scheduleCheck() {
318450
+ this.clearDebounceTimer();
318451
+ this.checkGeneration += 1;
318452
+ const generation = this.checkGeneration;
318453
+ this.debounceTimer = setTimeout(() => void this.runCheck(generation), FOCUS_DEBOUNCE_MS);
318454
+ }
318455
+ clearDebounceTimer() {
318456
+ if (this.debounceTimer !== void 0) {
318457
+ clearTimeout(this.debounceTimer);
318458
+ this.debounceTimer = void 0;
318459
+ }
318460
+ }
318461
+ clearClearHintTimer() {
318462
+ if (this.clearHintTimer !== void 0) {
318463
+ clearTimeout(this.clearHintTimer);
318464
+ this.clearHintTimer = void 0;
318465
+ }
318466
+ }
318467
+ clearOwnedHint() {
318468
+ if (this.host.footer.getTransientHint() === this.lastHintText) {
318469
+ this.host.footer.setTransientHint(null);
318470
+ this.host.requestRender();
318471
+ }
318472
+ this.lastHintText = void 0;
318473
+ }
318474
+ async runCheck(generation) {
318475
+ if (!this.focused) return;
318476
+ if (!this.host.getModelSupportsImage()) return;
318477
+ if (Date.now() - this.lastHintAtMs < 3e4) return;
318478
+ let hasImage = false;
318479
+ try {
318480
+ hasImage = await clipboardHasImage();
318481
+ } catch {
318482
+ return;
318483
+ }
318484
+ if (generation !== this.checkGeneration) return;
318485
+ if (!this.focused) return;
318486
+ if (!hasImage) return;
318487
+ const hintText = `Image in clipboard · ${getPasteImageShortcut()} to paste`;
318488
+ this.clearClearHintTimer();
318489
+ this.lastHintText = hintText;
318490
+ this.host.footer.setTransientHint(hintText);
318491
+ this.host.requestRender();
318492
+ this.lastHintAtMs = Date.now();
318493
+ this.clearHintTimer = setTimeout(() => {
318494
+ this.clearOwnedHint();
318495
+ }, HINT_DISPLAY_MS);
318496
+ }
318497
+ };
318498
+ //#endregion
317194
318499
  //#region src/utils/image/image-mime.ts
317195
318500
  function parseImageMeta(bytes) {
317196
318501
  if (isPng(bytes)) return parsePng(bytes);
@@ -317345,12 +318650,6 @@ var ClipboardMediaError = class extends Error {
317345
318650
  this.name = "ClipboardMediaError";
317346
318651
  }
317347
318652
  };
317348
- const SUPPORTED_IMAGE_MIME_TYPES = [
317349
- "image/png",
317350
- "image/jpeg",
317351
- "image/webp",
317352
- "image/gif"
317353
- ];
317354
318653
  const MAX_VIDEO_BYTES = 100 * 1024 * 1024;
317355
318654
  const VIDEO_MIME_BY_SUFFIX = Object.freeze({
317356
318655
  ".mp4": "video/mp4",
@@ -317367,10 +318666,8 @@ const VIDEO_MIME_BY_SUFFIX = Object.freeze({
317367
318666
  ".3gp": "video/3gpp",
317368
318667
  ".3g2": "video/3gpp2"
317369
318668
  });
317370
- const DEFAULT_LIST_TIMEOUT_MS = 1e3;
317371
318669
  const DEFAULT_READ_TIMEOUT_MS = 3e3;
317372
318670
  const DEFAULT_POWERSHELL_TIMEOUT_MS = 5e3;
317373
- const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
317374
318671
  const MACOS_FILE_PATH_SCRIPT = String.raw`
317375
318672
  ObjC.import('AppKit');
317376
318673
  ObjC.import('Foundation');
@@ -317405,24 +318702,6 @@ if (String(pb) !== '[id nil]') {
317405
318702
  }
317406
318703
  out.join('\n');
317407
318704
  `.trim();
317408
- function isWaylandSession(env) {
317409
- return Boolean(env["WAYLAND_DISPLAY"]) || env["XDG_SESSION_TYPE"] === "wayland";
317410
- }
317411
- function isWSL(env) {
317412
- if (env["WSL_DISTRO_NAME"] !== void 0 || env["WSLENV"] !== void 0) return true;
317413
- try {
317414
- return /microsoft|wsl/i.test(readFileSync("/proc/version", "utf-8"));
317415
- } catch {
317416
- return false;
317417
- }
317418
- }
317419
- function baseMimeType(raw) {
317420
- return raw.split(";")[0]?.trim().toLowerCase() ?? raw.toLowerCase();
317421
- }
317422
- function isSupportedImageMimeType(mime) {
317423
- const base = baseMimeType(mime);
317424
- return SUPPORTED_IMAGE_MIME_TYPES.includes(base);
317425
- }
317426
318705
  function selectPreferredImageMimeType(candidates) {
317427
318706
  const normalized = candidates.map((t) => t.trim()).filter((t) => t.length > 0).map((raw) => ({
317428
318707
  raw,
@@ -317519,22 +318798,10 @@ function readMediaFromText(text) {
317519
318798
  return readMediaFromPaths(parseClipboardPaths(text));
317520
318799
  }
317521
318800
  function runCommand(command, args, options) {
317522
- const result = spawnSync(command, args, {
317523
- timeout: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS,
317524
- maxBuffer: DEFAULT_MAX_BUFFER_BYTES,
318801
+ return runCommand$1(command, args, {
318802
+ timeoutMs: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS,
317525
318803
  env: options?.env
317526
318804
  });
317527
- if (result.error !== void 0 || result.status !== 0) return {
317528
- ok: false,
317529
- stdout: Buffer.alloc(0)
317530
- };
317531
- return {
317532
- ok: true,
317533
- stdout: Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "")
317534
- };
317535
- }
317536
- function parseTargetList(output) {
317537
- return output.toString("utf-8").split(/\r?\n/).map((t) => t.trim()).filter((t) => t.length > 0);
317538
318805
  }
317539
318806
  function readClipboardFileMediaViaWlPaste() {
317540
318807
  const list = runCommand("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
@@ -317669,19 +318936,6 @@ function readClipboardFilePathsViaMacOs(run) {
317669
318936
  if (!result.ok || result.stdout.length === 0) return [];
317670
318937
  return parseClipboardPaths(result.stdout.toString("utf-8"));
317671
318938
  }
317672
- function isFileLikeNativeFormat(format) {
317673
- const f = format.toLowerCase();
317674
- const base = baseMimeType(format);
317675
- return f.includes("file-url") || f.includes("file url") || f.includes("nsfilenames") || f.includes("com.apple.finder") || base === "text/uri-list" || base === "public.url";
317676
- }
317677
- function safeAvailableFormats(clip) {
317678
- if (clip?.availableFormats === void 0) return [];
317679
- try {
317680
- return clip.availableFormats();
317681
- } catch {
317682
- return [];
317683
- }
317684
- }
317685
318939
  async function readClipboardFileMediaViaNativeText(clip) {
317686
318940
  if (clip === null) return {
317687
318941
  media: null,
@@ -317910,6 +319164,13 @@ var EditorKeyboardController = class {
317910
319164
  host.track("shortcut_expand");
317911
319165
  host.toggleToolOutputExpansion();
317912
319166
  };
319167
+ editor.onToggleTodoExpand = () => {
319168
+ if (!host.state.todoPanel.hasOverflow()) return false;
319169
+ this.clearPendingExit();
319170
+ host.track("shortcut_todo_expand");
319171
+ host.toggleTodoPanelExpansion();
319172
+ return true;
319173
+ };
317913
319174
  editor.onCtrlS = () => {
317914
319175
  if (host.state.appState.streamingPhase === "idle" || host.state.appState.isCompacting) return;
317915
319176
  const text = editor.getText().trim();
@@ -320773,7 +322034,7 @@ var StreamingUIController = class {
320773
322034
  this._activeCompactionBlock.markDone();
320774
322035
  this._activeCompactionBlock = void 0;
320775
322036
  }
320776
- const block = new CompactionComponent(state.ui, instruction);
322037
+ const block = new CompactionComponent(state.ui, instruction, currentWorkingTip()?.text);
320777
322038
  this._activeCompactionBlock = block;
320778
322039
  state.transcriptContainer.addChild(block);
320779
322040
  state.ui.requestRender();
@@ -322400,625 +323661,6 @@ function adaptQuestionAnswers(event, response) {
322400
323661
  } : null;
322401
323662
  }
322402
323663
  //#endregion
322403
- //#region src/utils/git/git-status.ts
322404
- /**
322405
- * Cached git branch + working-tree status for the footer/statusline.
322406
- *
322407
- * Branch name refreshes every 5s, porcelain status every 15s. Branch
322408
- * and status reads stay synchronous with short timeouts. Pull request
322409
- * lookup uses an async cache so a slow `gh pr view` never blocks
322410
- * footer rendering.
322411
- */
322412
- const BRANCH_TTL_MS = 5e3;
322413
- const STATUS_TTL_MS = 15e3;
322414
- const PULL_REQUEST_TTL_MS = 6e4;
322415
- const SPAWN_TIMEOUT_MS = 500;
322416
- const PR_SPAWN_TIMEOUT_MS = 5e3;
322417
- const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
322418
- function createGitStatusCache(workDir, options = {}) {
322419
- const isRepo = detectGitRepo(workDir);
322420
- let branch = {
322421
- value: null,
322422
- fetchedAt: 0
322423
- };
322424
- let status = {
322425
- dirty: false,
322426
- ahead: 0,
322427
- behind: 0,
322428
- diffAdded: 0,
322429
- diffDeleted: 0,
322430
- fetchedAt: 0
322431
- };
322432
- let pullRequest = {
322433
- value: null,
322434
- branch: null,
322435
- fetchedAt: 0,
322436
- pendingBranch: null,
322437
- requestId: 0
322438
- };
322439
- return { getStatus: () => {
322440
- if (!isRepo) return null;
322441
- const now = Date.now();
322442
- if (now - branch.fetchedAt >= BRANCH_TTL_MS) branch = {
322443
- value: readBranch(workDir),
322444
- fetchedAt: now
322445
- };
322446
- if (branch.value === null) return null;
322447
- if (now - status.fetchedAt >= STATUS_TTL_MS) status = {
322448
- ...readStatus(workDir),
322449
- fetchedAt: now
322450
- };
322451
- refreshPullRequestIfNeeded(branch.value, now);
322452
- return {
322453
- branch: branch.value,
322454
- dirty: status.dirty,
322455
- ahead: status.ahead,
322456
- behind: status.behind,
322457
- diffAdded: status.diffAdded,
322458
- diffDeleted: status.diffDeleted,
322459
- pullRequest: pullRequest.branch === branch.value ? pullRequest.value : null
322460
- };
322461
- } };
322462
- function refreshPullRequestIfNeeded(branchName, now) {
322463
- if (pullRequest.pendingBranch === branchName) return;
322464
- const fetchedAt = pullRequest.branch === branchName ? pullRequest.fetchedAt : 0;
322465
- if (now - fetchedAt < PULL_REQUEST_TTL_MS) return;
322466
- const requestId = pullRequest.requestId + 1;
322467
- pullRequest = {
322468
- value: pullRequest.branch === branchName ? pullRequest.value : null,
322469
- branch: branchName,
322470
- fetchedAt,
322471
- pendingBranch: branchName,
322472
- requestId
322473
- };
322474
- readPullRequest(workDir).then((value) => {
322475
- if (pullRequest.requestId !== requestId) return;
322476
- const changed = !samePullRequest(pullRequest.branch === branchName ? pullRequest.value : null, value);
322477
- pullRequest = {
322478
- value,
322479
- branch: branchName,
322480
- fetchedAt: Date.now(),
322481
- pendingBranch: null,
322482
- requestId
322483
- };
322484
- if (changed) options.onChange?.();
322485
- });
322486
- }
322487
- }
322488
- function detectGitRepo(workDir) {
322489
- try {
322490
- const result = spawnSync("git", [
322491
- "-C",
322492
- workDir,
322493
- "rev-parse",
322494
- "--is-inside-work-tree"
322495
- ], {
322496
- encoding: "utf8",
322497
- timeout: SPAWN_TIMEOUT_MS
322498
- });
322499
- return result.status === 0 && result.stdout.trim() === "true";
322500
- } catch {
322501
- return false;
322502
- }
322503
- }
322504
- function readBranch(workDir) {
322505
- try {
322506
- const result = spawnSync("git", [
322507
- "-C",
322508
- workDir,
322509
- "branch",
322510
- "--show-current"
322511
- ], {
322512
- encoding: "utf8",
322513
- timeout: SPAWN_TIMEOUT_MS
322514
- });
322515
- if (result.status !== 0) return null;
322516
- const name = result.stdout.trim();
322517
- return name.length > 0 ? name : null;
322518
- } catch {
322519
- return null;
322520
- }
322521
- }
322522
- function readStatus(workDir) {
322523
- try {
322524
- const result = spawnSync("git", [
322525
- "-C",
322526
- workDir,
322527
- "status",
322528
- "--porcelain",
322529
- "-b"
322530
- ], {
322531
- encoding: "utf8",
322532
- timeout: SPAWN_TIMEOUT_MS,
322533
- maxBuffer: 4 * 1024 * 1024
322534
- });
322535
- if (result.status !== 0) return {
322536
- dirty: false,
322537
- ahead: 0,
322538
- behind: 0,
322539
- diffAdded: 0,
322540
- diffDeleted: 0
322541
- };
322542
- let dirty = false;
322543
- let ahead = 0;
322544
- let behind = 0;
322545
- for (const line of result.stdout.split("\n")) if (line.startsWith("## ")) {
322546
- const m = AHEAD_BEHIND_RE.exec(line);
322547
- if (m) {
322548
- ahead = Number.parseInt(m[1] ?? "0", 10) || 0;
322549
- behind = Number.parseInt(m[2] ?? "0", 10) || 0;
322550
- }
322551
- } else if (line.trim().length > 0) dirty = true;
322552
- const diff = dirty ? readDiffStats(workDir) : {
322553
- added: 0,
322554
- deleted: 0
322555
- };
322556
- return {
322557
- dirty,
322558
- ahead,
322559
- behind,
322560
- diffAdded: diff.added,
322561
- diffDeleted: diff.deleted
322562
- };
322563
- } catch {
322564
- return {
322565
- dirty: false,
322566
- ahead: 0,
322567
- behind: 0,
322568
- diffAdded: 0,
322569
- diffDeleted: 0
322570
- };
322571
- }
322572
- }
322573
- function readDiffStats(workDir) {
322574
- try {
322575
- const result = spawnSync("git", [
322576
- "-C",
322577
- workDir,
322578
- "diff",
322579
- "--numstat",
322580
- "HEAD",
322581
- "--"
322582
- ], {
322583
- encoding: "utf8",
322584
- timeout: SPAWN_TIMEOUT_MS,
322585
- maxBuffer: 4 * 1024 * 1024
322586
- });
322587
- if (result.status !== 0) return {
322588
- added: 0,
322589
- deleted: 0
322590
- };
322591
- let added = 0;
322592
- let deleted = 0;
322593
- for (const line of result.stdout.split("\n")) {
322594
- if (!line) continue;
322595
- const [addedText, deletedText] = line.split(" ");
322596
- added += parseDiffNumstatCount(addedText);
322597
- deleted += parseDiffNumstatCount(deletedText);
322598
- }
322599
- return {
322600
- added,
322601
- deleted
322602
- };
322603
- } catch {
322604
- return {
322605
- added: 0,
322606
- deleted: 0
322607
- };
322608
- }
322609
- }
322610
- function parseDiffNumstatCount(value) {
322611
- if (value === void 0 || value === "-") return 0;
322612
- const n = Number.parseInt(value, 10);
322613
- return Number.isFinite(n) && n > 0 ? n : 0;
322614
- }
322615
- function readPullRequest(workDir) {
322616
- return new Promise((resolve) => {
322617
- try {
322618
- execFile("gh", [
322619
- "pr",
322620
- "view",
322621
- "--json",
322622
- "number,url"
322623
- ], {
322624
- cwd: workDir,
322625
- encoding: "utf8",
322626
- env: {
322627
- ...process.env,
322628
- GH_NO_UPDATE_NOTIFIER: "1",
322629
- GH_PROMPT_DISABLED: "1"
322630
- },
322631
- timeout: PR_SPAWN_TIMEOUT_MS,
322632
- maxBuffer: 256 * 1024
322633
- }, (error, stdout) => {
322634
- if (error !== null) {
322635
- resolve(null);
322636
- return;
322637
- }
322638
- resolve(parsePullRequest(stdout));
322639
- });
322640
- } catch {
322641
- resolve(null);
322642
- }
322643
- });
322644
- }
322645
- function samePullRequest(a, b) {
322646
- if (a === null || b === null) return a === b;
322647
- return a.number === b.number && a.url === b.url;
322648
- }
322649
- function parsePullRequest(stdout) {
322650
- try {
322651
- const raw = JSON.parse(stdout);
322652
- if (typeof raw !== "object" || raw === null) return null;
322653
- const record = raw;
322654
- const number = record["number"];
322655
- const url = record["url"];
322656
- if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
322657
- if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
322658
- return {
322659
- number,
322660
- url
322661
- };
322662
- } catch {
322663
- return null;
322664
- }
322665
- }
322666
- function isSafeHttpUrl(value) {
322667
- if (hasControlChars(value)) return false;
322668
- try {
322669
- const url = new URL(value);
322670
- return url.protocol === "https:" || url.protocol === "http:";
322671
- } catch {
322672
- return false;
322673
- }
322674
- }
322675
- function hasControlChars(value) {
322676
- for (const char of value) {
322677
- const code = char.codePointAt(0) ?? 0;
322678
- if (code <= 31 || code === 127) return true;
322679
- }
322680
- return false;
322681
- }
322682
- function formatGitBadgeBase(status) {
322683
- const parts = [];
322684
- const diff = formatDiffStats(status);
322685
- if (diff) parts.push(diff);
322686
- let sync = "";
322687
- if (status.ahead > 0) sync += `↑${status.ahead}`;
322688
- if (status.behind > 0) sync += `↓${status.behind}`;
322689
- if (sync) parts.push(sync);
322690
- return parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
322691
- }
322692
- function formatPullRequestBadge(pullRequest, options = {}) {
322693
- const prText = `[PR#${String(pullRequest.number)}]`;
322694
- return options.linkPullRequest ? toTerminalHyperlink(prText, pullRequest.url) : prText;
322695
- }
322696
- function formatDiffStats(status) {
322697
- const parts = [];
322698
- if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
322699
- if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
322700
- if (parts.length > 0) return parts.join(" ");
322701
- return status.dirty ? "±" : null;
322702
- }
322703
- function toTerminalHyperlink(text, url) {
322704
- if (!isSafeHttpUrl(url)) return text;
322705
- return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
322706
- }
322707
- //#endregion
322708
- //#region src/tui/components/chrome/footer.ts
322709
- const MAX_CWD_SEGMENTS = 3;
322710
- const GOAL_TIMER_INTERVAL_MS = 1e3;
322711
- const TIP_ROTATE_INTERVAL_MS = 1e4;
322712
- const TIP_SEPARATOR = " | ";
322713
- const TOOLBAR_TIPS = [
322714
- { text: "shift+tab: plan mode" },
322715
- { text: "/model: switch model" },
322716
- {
322717
- text: "ctrl+s: steer mid-turn",
322718
- priority: 2
322719
- },
322720
- {
322721
- text: "ctrl+b: background task",
322722
- priority: 2
322723
- },
322724
- {
322725
- text: "/compact: compact context",
322726
- priority: 2
322727
- },
322728
- { text: "ctrl+o: expand tool output" },
322729
- { text: "/tasks: background tasks" },
322730
- { text: "shift+enter: newline" },
322731
- {
322732
- text: "/init: generate AGENTS.md",
322733
- priority: 2
322734
- },
322735
- { text: "@: mention files" },
322736
- { text: "ctrl+c: cancel" },
322737
- { text: "/theme: switch theme" },
322738
- { text: "/auto: auto permission mode" },
322739
- { text: "/yolo: toggle yolo" },
322740
- { text: "/help: show commands" },
322741
- { text: "/dance: rainbow mode, because why not" },
322742
- {
322743
- text: "/plugins: manage plugins — try the \"superpowers\" plugin",
322744
- solo: true,
322745
- priority: 3
322746
- },
322747
- {
322748
- text: "ask Kimi to schedule tasks, e.g. \"remind me at 5pm\"",
322749
- solo: true,
322750
- priority: 3
322751
- }
322752
- ];
322753
- /**
322754
- * Expand tips into a rotation sequence using smooth weighted round-robin
322755
- * (the nginx SWRR algorithm). Higher-`priority` tips appear more often while
322756
- * staying evenly spread, so a tip generally does not land next to its own
322757
- * duplicate. Deterministic and computed once at module load. Exported for
322758
- * unit testing.
322759
- */
322760
- function buildWeightedTips(tips) {
322761
- const items = tips.map((t) => ({
322762
- tip: t,
322763
- weight: Math.max(1, Math.trunc(t.priority ?? 1)),
322764
- current: 0
322765
- }));
322766
- const total = items.reduce((sum, it) => sum + it.weight, 0);
322767
- const seq = [];
322768
- for (let n = 0; n < total; n++) {
322769
- let best = items[0];
322770
- for (const it of items) {
322771
- it.current += it.weight;
322772
- if (it.current > best.current) best = it;
322773
- }
322774
- best.current -= total;
322775
- seq.push(best.tip);
322776
- }
322777
- return seq;
322778
- }
322779
- const ROTATION = buildWeightedTips(TOOLBAR_TIPS);
322780
- function currentTipIndex() {
322781
- return Math.floor(Date.now() / TIP_ROTATE_INTERVAL_MS);
322782
- }
322783
- /**
322784
- * Pick the tip(s) for a rotation index over the weighted ROTATION sequence.
322785
- * `primary` is always shown when it fits; `pair` (primary + next tip joined
322786
- * by the separator) is offered for wide terminals. Pairing is skipped when
322787
- * the current/next tip is `solo` or when the neighbour is a duplicate of the
322788
- * current tip (which can happen at the wrap boundary), keeping long/important
322789
- * tips on their own and avoiding "X | X".
322790
- */
322791
- function tipsForIndex(index) {
322792
- const n = ROTATION.length;
322793
- if (n === 0) return {
322794
- primary: "",
322795
- pair: null
322796
- };
322797
- const offset = (index % n + n) % n;
322798
- const current = ROTATION[offset];
322799
- if (n === 1 || current.solo) return {
322800
- primary: current.text,
322801
- pair: null
322802
- };
322803
- const next = ROTATION[(offset + 1) % n];
322804
- if (next.solo || next.text === current.text) return {
322805
- primary: current.text,
322806
- pair: null
322807
- };
322808
- return {
322809
- primary: current.text,
322810
- pair: current.text + TIP_SEPARATOR + next.text
322811
- };
322812
- }
322813
- /**
322814
- * Footer goal badge, e.g. `[goal ● active · 4m · 7 turns]`. Only shown for a
322815
- * live (active/paused) goal; terminal/no goal -> no badge. Turn count is a raw
322816
- * count unless an explicit turn budget is set, in which case it shows used/limit.
322817
- */
322818
- function formatGoalBadge(goal, colors, wallClockMs) {
322819
- if (goal === null || goal === void 0) return null;
322820
- if (goal.status !== "active" && goal.status !== "paused" && goal.status !== "blocked") return null;
322821
- const dotColor = goal.status === "active" ? colors.primary : goal.status === "blocked" ? colors.warning : colors.textMuted;
322822
- const turns = goal.budget.turnBudget !== null ? `${goal.turnsUsed}/${goal.budget.turnBudget} turns` : `${goal.turnsUsed} ${goal.turnsUsed === 1 ? "turn" : "turns"}`;
322823
- const label = `${goal.status} · ${formatBadgeElapsed(wallClockMs ?? goal.wallClockMs)} · ${turns}`;
322824
- return chalk.hex(colors.textMuted)("[goal ") + chalk.hex(dotColor)("●") + chalk.hex(colors.textMuted)(` ${label}]`);
322825
- }
322826
- function formatBadgeElapsed(ms) {
322827
- const totalSeconds = Math.round(ms / 1e3);
322828
- if (totalSeconds < 60) return `${totalSeconds}s`;
322829
- const minutes = Math.floor(totalSeconds / 60);
322830
- if (minutes < 60) return `${minutes}m`;
322831
- return `${Math.floor(minutes / 60)}h${minutes % 60}m`;
322832
- }
322833
- function modelDisplayName(state) {
322834
- const model = state.availableModels[state.model];
322835
- return model?.displayName ?? model?.model ?? state.model;
322836
- }
322837
- function shortenCwd(path) {
322838
- if (!path) return path;
322839
- const home = process.env["HOME"] ?? "";
322840
- let work = path;
322841
- if (home && path === home) return "~";
322842
- if (home && path.startsWith(home + "/")) work = "~" + path.slice(home.length);
322843
- const segments = work.split("/").filter((s) => s.length > 0);
322844
- if (segments.length <= MAX_CWD_SEGMENTS) return work;
322845
- return `…/${segments.slice(-MAX_CWD_SEGMENTS).join("/")}`;
322846
- }
322847
- function formatTokenCount(n) {
322848
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
322849
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
322850
- return String(n);
322851
- }
322852
- function safeUsage(usage) {
322853
- return safeUsageRatio(usage);
322854
- }
322855
- function formatContextStatus(usage, tokens, maxTokens) {
322856
- const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
322857
- if (maxTokens && maxTokens > 0 && tokens !== void 0) return `context: ${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
322858
- return `context: ${pct}`;
322859
- }
322860
- function formatFooterGitBadge(status, colors) {
322861
- const base = chalk.hex(colors.textDim)(formatGitBadgeBase(status));
322862
- if (status.pullRequest === null) return base;
322863
- return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
322864
- }
322865
- var FooterComponent = class {
322866
- state;
322867
- onRefresh;
322868
- gitCache;
322869
- gitCacheWorkDir;
322870
- transientHint = null;
322871
- goalSnapshotKey = null;
322872
- goalObservedAtMs = Date.now();
322873
- goalTimer = null;
322874
- /**
322875
- * Non-terminal background-task counts split by kind so the footer can
322876
- * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
322877
- * spawned via `Shell run_in_background=true`; `agentTasks` covers
322878
- * `agent-*` BPM tasks (background subagents). Either zero hides its
322879
- * respective badge.
322880
- */
322881
- backgroundBashTaskCount = 0;
322882
- backgroundAgentCount = 0;
322883
- constructor(state, onRefresh = () => {}) {
322884
- this.state = state;
322885
- this.onRefresh = onRefresh;
322886
- this.gitCacheWorkDir = state.workDir;
322887
- this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
322888
- this.syncGoalClock(state.goal);
322889
- this.syncGoalTimer(state.goal);
322890
- }
322891
- setState(state) {
322892
- if (state.workDir !== this.gitCacheWorkDir) {
322893
- this.gitCacheWorkDir = state.workDir;
322894
- this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onRefresh });
322895
- }
322896
- this.syncGoalClock(state.goal);
322897
- this.syncGoalTimer(state.goal);
322898
- this.state = state;
322899
- }
322900
- /**
322901
- * Short-lived hint that replaces the rotating toolbar tips on line 1.
322902
- * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
322903
- * again to exit" without requiring a toast/overlay subsystem.
322904
- * Pass `null` to clear.
322905
- */
322906
- setTransientHint(hint) {
322907
- this.transientHint = hint;
322908
- }
322909
- getTransientHint() {
322910
- return this.transientHint;
322911
- }
322912
- /**
322913
- * Sync both background-task badges with live counts. Each non-zero
322914
- * count produces its own bracketed badge on line 1; zeros hide them
322915
- * independently.
322916
- */
322917
- setBackgroundCounts(counts) {
322918
- this.backgroundBashTaskCount = Math.max(0, counts.bashTasks);
322919
- this.backgroundAgentCount = Math.max(0, counts.agentTasks);
322920
- }
322921
- invalidate() {}
322922
- render(width) {
322923
- const colors = currentTheme.palette;
322924
- const state = this.state;
322925
- const left = [];
322926
- const modes = [];
322927
- if (state.permissionMode === "auto") modes.push(chalk.hex(colors.warning).bold("auto"));
322928
- if (state.permissionMode === "yolo") modes.push(chalk.hex(colors.warning).bold("yolo"));
322929
- if (state.planMode) modes.push(chalk.hex(colors.primary).bold("plan"));
322930
- if (state.swarmMode) modes.push(chalk.hex(colors.accent).bold("swarm"));
322931
- if (modes.length > 0) left.push(modes.join(" "));
322932
- const goalBadge = formatGoalBadge(state.goal, colors, this.goalWallClockMs(state.goal));
322933
- if (goalBadge !== null) left.push(goalBadge);
322934
- const model = modelDisplayName(state);
322935
- if (model) {
322936
- const modelLabel = `${model}${state.thinking ? " thinking" : ""}`;
322937
- let renderedModelLabel = chalk.hex(colors.text)(modelLabel);
322938
- if (isRainbowDancing()) renderedModelLabel = renderDanceFooterModel(modelLabel);
322939
- left.push(renderedModelLabel);
322940
- }
322941
- if (this.backgroundBashTaskCount > 0) {
322942
- const noun = this.backgroundBashTaskCount === 1 ? "task" : "tasks";
322943
- left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)} ${noun} running]`));
322944
- }
322945
- if (this.backgroundAgentCount > 0) {
322946
- const noun = this.backgroundAgentCount === 1 ? "agent" : "agents";
322947
- left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)} ${noun} running]`));
322948
- }
322949
- const cwd = shortenCwd(state.workDir);
322950
- if (cwd) left.push(chalk.hex(colors.textDim)(cwd));
322951
- const git = this.gitCache.getStatus();
322952
- if (git !== null) left.push(formatFooterGitBadge(git, colors));
322953
- const leftLine = left.join(" ");
322954
- const leftWidth = visibleWidth(leftLine);
322955
- const { primary, pair } = tipsForIndex(currentTipIndex());
322956
- const remaining = Math.max(0, width - leftWidth - 2);
322957
- let tipText = "";
322958
- if (pair && visibleWidth(pair) <= remaining) tipText = pair;
322959
- else if (primary && visibleWidth(primary) <= remaining) tipText = primary;
322960
- let line1;
322961
- if (tipText) {
322962
- const pad = width - leftWidth - visibleWidth(tipText);
322963
- line1 = leftLine + " ".repeat(Math.max(0, pad)) + chalk.hex(colors.textMuted)(tipText);
322964
- } else if (leftWidth <= width) line1 = leftLine;
322965
- else line1 = truncateToWidth(leftLine, width, "…");
322966
- const contextText = formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens);
322967
- const contextWidth = visibleWidth(contextText);
322968
- let line2;
322969
- if (this.transientHint) {
322970
- const maxHintWidth = Math.max(0, width - contextWidth - 1);
322971
- const shownHint = visibleWidth(this.transientHint) <= maxHintWidth ? this.transientHint : truncateToWidth(this.transientHint, maxHintWidth, "…");
322972
- const hintWidth = visibleWidth(shownHint);
322973
- const pad = Math.max(0, width - hintWidth - contextWidth);
322974
- line2 = chalk.hex(colors.warning).bold(shownHint) + " ".repeat(pad) + chalk.hex(colors.text)(contextText);
322975
- } else {
322976
- const leftPad = Math.max(0, width - contextWidth);
322977
- line2 = " ".repeat(leftPad) + chalk.hex(colors.text)(contextText);
322978
- }
322979
- return [truncateToWidth(line1, width), truncateToWidth(line2, width)];
322980
- }
322981
- syncGoalClock(goal) {
322982
- const key = goalSnapshotKey(goal);
322983
- if (key === this.goalSnapshotKey) return;
322984
- this.goalSnapshotKey = key;
322985
- this.goalObservedAtMs = Date.now();
322986
- }
322987
- syncGoalTimer(goal) {
322988
- if (goal?.status === "active") {
322989
- if (this.goalTimer !== null) return;
322990
- this.goalTimer = setInterval(() => {
322991
- this.onRefresh();
322992
- }, GOAL_TIMER_INTERVAL_MS);
322993
- this.goalTimer.unref?.();
322994
- return;
322995
- }
322996
- if (this.goalTimer !== null) {
322997
- clearInterval(this.goalTimer);
322998
- this.goalTimer = null;
322999
- }
323000
- }
323001
- goalWallClockMs(goal) {
323002
- if (goal === null || goal === void 0) return void 0;
323003
- if (goal.status !== "active") return goal.wallClockMs;
323004
- return goal.wallClockMs + Math.max(0, Date.now() - this.goalObservedAtMs);
323005
- }
323006
- };
323007
- function goalSnapshotKey(goal) {
323008
- if (goal === null || goal === void 0) return null;
323009
- return [
323010
- goal.goalId,
323011
- goal.status,
323012
- goal.terminalReason ?? "",
323013
- String(goal.turnsUsed),
323014
- String(goal.tokensUsed),
323015
- String(goal.wallClockMs),
323016
- String(goal.budget.tokenBudget),
323017
- String(goal.budget.turnBudget),
323018
- String(goal.budget.wallClockBudgetMs)
323019
- ].join("\0");
323020
- }
323021
- //#endregion
323022
323664
  //#region src/tui/components/chrome/todo-panel.ts
323023
323665
  const MAX_VISIBLE = 5;
323024
323666
  /**
@@ -323079,6 +323721,7 @@ function selectVisibleTodos(todos) {
323079
323721
  }
323080
323722
  var TodoPanelComponent = class {
323081
323723
  todos = [];
323724
+ expanded = false;
323082
323725
  setTodos(todos) {
323083
323726
  this.todos = todos.map((t) => ({
323084
323727
  title: t.title,
@@ -323090,18 +323733,34 @@ var TodoPanelComponent = class {
323090
323733
  }
323091
323734
  clear() {
323092
323735
  this.todos = [];
323736
+ this.expanded = false;
323093
323737
  }
323094
323738
  isEmpty() {
323095
323739
  return this.todos.length === 0;
323096
323740
  }
323741
+ /** True when the list exceeds the collapsed cap, i.e. there is something to expand. */
323742
+ hasOverflow() {
323743
+ return this.todos.length > MAX_VISIBLE;
323744
+ }
323745
+ setExpanded(expanded) {
323746
+ this.expanded = expanded;
323747
+ }
323748
+ toggleExpanded() {
323749
+ this.expanded = !this.expanded;
323750
+ }
323097
323751
  invalidate() {}
323098
323752
  render(width) {
323099
323753
  if (this.todos.length === 0) return [];
323100
323754
  const c = currentTheme.palette;
323101
- const { rows, hidden } = selectVisibleTodos(this.todos);
323102
323755
  const lines = [chalk.hex(c.border)("─".repeat(width)), chalk.hex(c.primary).bold(" Todo")];
323103
- for (const todo of rows) lines.push(renderRow(todo, c));
323104
- if (hidden > 0) lines.push(chalk.hex(c.textDim)(` … +${hidden} more`));
323756
+ if (this.expanded) {
323757
+ for (const todo of this.todos) lines.push(renderRow(todo, c));
323758
+ if (this.todos.length > MAX_VISIBLE) lines.push(chalk.hex(c.textDim)(` all ${String(this.todos.length)} items · ctrl+t to collapse`));
323759
+ } else {
323760
+ const { rows, hidden } = selectVisibleTodos(this.todos);
323761
+ for (const todo of rows) lines.push(renderRow(todo, c));
323762
+ if (hidden > 0) lines.push(chalk.hex(c.textDim)(` … +${hidden} more · ctrl+t to expand`));
323763
+ }
323105
323764
  return lines.map((line) => truncateToWidth(line, width));
323106
323765
  }
323107
323766
  };
@@ -323302,6 +323961,8 @@ var CustomEditor = class extends Editor {
323302
323961
  onCtrlS;
323303
323962
  /** Return `true` to consume Ctrl+B; return `false`/`undefined` to fall through to the editor default (cursor-left). */
323304
323963
  onCtrlB;
323964
+ /** Return `true` to consume Ctrl+T (the todo list had overflow to toggle); return `false`/`undefined` to fall through to the editor default. */
323965
+ onToggleTodoExpand;
323305
323966
  onUndo;
323306
323967
  onInsertNewline;
323307
323968
  onTextPaste;
@@ -323454,6 +324115,9 @@ var CustomEditor = class extends Editor {
323454
324115
  if (matchesKey(normalized, Key.ctrl("b"))) {
323455
324116
  if (this.onCtrlB?.() === true) return;
323456
324117
  }
324118
+ if (matchesKey(normalized, Key.ctrl("t"))) {
324119
+ if (this.onToggleTodoExpand?.() === true) return;
324120
+ }
323457
324121
  if (matchesKey(normalized, "shift+tab")) {
323458
324122
  this.onShiftTab?.();
323459
324123
  return;
@@ -323483,21 +324147,29 @@ var CustomEditor = class extends Editor {
323483
324147
  this.onEscape?.();
323484
324148
  return;
323485
324149
  }
324150
+ if (matchesKey(normalized, Key.tab) && !this.isShowingAutocomplete()) return;
323486
324151
  super.handleInput(normalized);
323487
- this.reopenPathCompletionAfterInput();
324152
+ this.reopenAutocompleteAfterInput();
323488
324153
  }
323489
- reopenPathCompletionAfterInput() {
324154
+ reopenAutocompleteAfterInput() {
324155
+ if (this.isShowingAutocomplete()) return;
323490
324156
  const { line, col } = this.getCursor();
323491
324157
  const textBeforeCursor = this.getLines()[line]?.slice(0, col) ?? "";
323492
- if (!textBeforeCursor.endsWith("/")) return;
323493
- if (this.isShowingAutocomplete()) return;
323494
- const isSlashArgument = textBeforeCursor.startsWith("/") && textBeforeCursor.includes(" ");
323495
- const isAtMention = extractAtPrefix(textBeforeCursor) !== null;
323496
- if (!isSlashArgument && !isAtMention) return;
323497
- this.requestAutocomplete?.({
323498
- force: true,
323499
- explicitTab: false
323500
- });
324158
+ const editor = this;
324159
+ if (editor.requestAutocomplete === void 0) return;
324160
+ const trigger = () => {
324161
+ editor.requestAutocomplete?.({
324162
+ force: false,
324163
+ explicitTab: false
324164
+ });
324165
+ };
324166
+ if (textBeforeCursor.endsWith("/")) {
324167
+ const isSlashArgument = textBeforeCursor.startsWith("/") && textBeforeCursor.includes(" ");
324168
+ const isAtMention = extractAtPrefix(textBeforeCursor) !== null;
324169
+ if (isSlashArgument || isAtMention) trigger();
324170
+ return;
324171
+ }
324172
+ if (textBeforeCursor.endsWith(" ") && textBeforeCursor.startsWith("/") && textBeforeCursor.includes(" ")) trigger();
323501
324173
  }
323502
324174
  };
323503
324175
  /**
@@ -323661,6 +324333,7 @@ function createTUIState(options) {
323661
324333
  const theme = currentTheme;
323662
324334
  const terminal = new ProcessTerminal();
323663
324335
  const ui = new TUI(terminal);
324336
+ ui.setClearOnShrink(true);
323664
324337
  return {
323665
324338
  ui,
323666
324339
  terminal,
@@ -323891,28 +324564,6 @@ function isOAuthLoginRequiredError(error) {
323891
324564
  return error.code === OAUTH_LOGIN_REQUIRED_CODE;
323892
324565
  }
323893
324566
  //#endregion
323894
- //#region src/tui/utils/terminal-focus.ts
323895
- function installTerminalFocusTracking(state) {
323896
- state.terminalState.focused = true;
323897
- const disposeInputListener = state.ui.addInputListener((data) => handleTerminalFocusInput(state.terminalState, data));
323898
- state.terminal.write(ENABLE_TERMINAL_FOCUS_REPORTING);
323899
- return () => {
323900
- disposeInputListener();
323901
- state.terminal.write(DISABLE_TERMINAL_FOCUS_REPORTING);
323902
- state.terminalState.focused = true;
323903
- };
323904
- }
323905
- function handleTerminalFocusInput(state, data) {
323906
- if (data === TERMINAL_FOCUS_IN) {
323907
- state.focused = true;
323908
- return { consume: true };
323909
- }
323910
- if (data === TERMINAL_FOCUS_OUT) {
323911
- state.focused = false;
323912
- return { consume: true };
323913
- }
323914
- }
323915
- //#endregion
323916
324567
  //#region src/tui/utils/terminal-theme.ts
323917
324568
  function createTerminalThemeInputState() {
323918
324569
  return { osc11Buffer: "" };
@@ -324035,6 +324686,10 @@ function readTmuxOptionFromProcess(option) {
324035
324686
  }
324036
324687
  //#endregion
324037
324688
  //#region src/tui/kimi-tui.ts
324689
+ function loadingTipKind(mode) {
324690
+ if (mode === "waiting" || mode === "tool") return "moon";
324691
+ if (mode === "composing") return "composing";
324692
+ }
324038
324693
  function sameStringArrays(a, b) {
324039
324694
  return a.length === b.length && a.every((value, index) => value === b[index]);
324040
324695
  }
@@ -324090,6 +324745,7 @@ var KimiTUI = class {
324090
324745
  aborted = false;
324091
324746
  terminalFocusTrackingDispose;
324092
324747
  terminalThemeTrackingDispose;
324748
+ clipboardImageHintController;
324093
324749
  uninstallRainbowDance;
324094
324750
  signalCleanupHandlers = [];
324095
324751
  isShuttingDown = false;
@@ -324097,6 +324753,7 @@ var KimiTUI = class {
324097
324753
  migrateOnly;
324098
324754
  startupNotice;
324099
324755
  lastActivityMode;
324756
+ currentLoadingTip = void 0;
324100
324757
  lastHistoryContent;
324101
324758
  streamingUI;
324102
324759
  authFlow;
@@ -324292,9 +324949,21 @@ var KimiTUI = class {
324292
324949
  }
324293
324950
  startEventLoop() {
324294
324951
  this.state.ui.start();
324952
+ this.startClipboardImageHintController();
324295
324953
  this.terminalFocusTrackingDispose = installTerminalFocusTracking(this.state);
324296
324954
  this.refreshTerminalThemeTracking();
324297
324955
  }
324956
+ startClipboardImageHintController() {
324957
+ this.clipboardImageHintController = new ClipboardImageHintController({
324958
+ ui: this.state.ui,
324959
+ footer: this.state.footer,
324960
+ getModelSupportsImage: () => this.supportsCurrentModelCapability("image_in"),
324961
+ requestRender: () => {
324962
+ this.state.ui.requestRender();
324963
+ }
324964
+ });
324965
+ this.clipboardImageHintController.start();
324966
+ }
324298
324967
  startBackgroundFdAutocomplete() {
324299
324968
  if (this.fdPath !== null || this.fdDownloadStarted) return;
324300
324969
  this.fdDownloadStarted = true;
@@ -324471,6 +325140,8 @@ var KimiTUI = class {
324471
325140
  }
324472
325141
  disposeTerminalTracking() {
324473
325142
  this.stopTerminalThemeTracking();
325143
+ this.clipboardImageHintController?.stop();
325144
+ this.clipboardImageHintController = void 0;
324474
325145
  this.terminalFocusTrackingDispose?.();
324475
325146
  this.terminalFocusTrackingDispose = void 0;
324476
325147
  }
@@ -325116,6 +325787,15 @@ var KimiTUI = class {
325116
325787
  }
325117
325788
  updateActivityPane() {
325118
325789
  const effectiveMode = this.resolveActivityPaneMode();
325790
+ const tipKind = loadingTipKind(effectiveMode);
325791
+ if (effectiveMode === "idle" || effectiveMode === "session" || effectiveMode === "hidden") this.currentLoadingTip = void 0;
325792
+ else if (tipKind !== void 0 && (this.currentLoadingTip === void 0 || this.currentLoadingTip.kind !== tipKind)) {
325793
+ const previousTip = this.currentLoadingTip?.tip;
325794
+ this.currentLoadingTip = {
325795
+ kind: tipKind,
325796
+ tip: pickRandomWorkingTip(previousTip)?.text
325797
+ };
325798
+ }
325119
325799
  this.syncTerminalProgress(this.shouldShowTerminalProgress(effectiveMode));
325120
325800
  const placeSpinnerInAgentSwarm = this.shouldPlaceActivitySpinnerInAgentSwarm(effectiveMode);
325121
325801
  const activityModeKey = `${effectiveMode}:${placeSpinnerInAgentSwarm ? "swarm" : "pane"}`;
@@ -325137,7 +325817,8 @@ var KimiTUI = class {
325137
325817
  if (placeSpinnerInAgentSwarm) break;
325138
325818
  this.state.activityContainer.addChild(new ActivityPaneComponent({
325139
325819
  mode: "waiting",
325140
- spinner
325820
+ spinner,
325821
+ tip: this.currentLoadingTip?.tip
325141
325822
  }));
325142
325823
  break;
325143
325824
  }
@@ -325150,7 +325831,8 @@ var KimiTUI = class {
325150
325831
  this.syncAgentSwarmActivitySpinner(void 0);
325151
325832
  this.state.activityContainer.addChild(new ActivityPaneComponent({
325152
325833
  mode: "composing",
325153
- spinner
325834
+ spinner,
325835
+ tip: this.currentLoadingTip?.tip
325154
325836
  }));
325155
325837
  break;
325156
325838
  }
@@ -325160,7 +325842,8 @@ var KimiTUI = class {
325160
325842
  if (placeSpinnerInAgentSwarm) break;
325161
325843
  this.state.activityContainer.addChild(new ActivityPaneComponent({
325162
325844
  mode: "tool",
325163
- spinner
325845
+ spinner,
325846
+ tip: this.currentLoadingTip?.tip
325164
325847
  }));
325165
325848
  break;
325166
325849
  }
@@ -325199,6 +325882,10 @@ var KimiTUI = class {
325199
325882
  for (const child of this.state.transcriptContainer.children) if (isExpandable(child)) child.setExpanded(this.state.toolOutputExpanded);
325200
325883
  this.state.ui.requestRender();
325201
325884
  }
325885
+ toggleTodoPanelExpansion() {
325886
+ this.state.todoPanel.toggleExpanded();
325887
+ this.state.ui.requestRender();
325888
+ }
325202
325889
  async detachCurrentForegroundTask() {
325203
325890
  const session = this.session;
325204
325891
  if (session === void 0) {