@meowlynxsea/koi 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -415526,7 +415526,7 @@ var InputBox = import_react20.forwardRef(function InputBox2({
415526
415526
  }
415527
415527
  }
415528
415528
  }
415529
- if (event.name === "tab" && event.shift && onModeSwitch) {
415529
+ if (event.name === "tab" && event.shift && onModeSwitch && !isBusy) {
415530
415530
  event.preventDefault();
415531
415531
  event.stopPropagation();
415532
415532
  onModeSwitch();
@@ -415556,6 +415556,7 @@ var InputBox = import_react20.forwardRef(function InputBox2({
415556
415556
  { name: "return", shift: true, action: "newline" }
415557
415557
  ], []);
415558
415558
  const [wavePhase, setWavePhase] = import_react20.useState(0);
415559
+ const [shimmerIndex, setShimmerIndex] = import_react20.useState(-1);
415559
415560
  import_react20.useEffect(() => {
415560
415561
  if (!isBusy)
415561
415562
  return;
@@ -415564,6 +415565,24 @@ var InputBox = import_react20.forwardRef(function InputBox2({
415564
415565
  }, 150);
415565
415566
  return () => clearInterval(interval);
415566
415567
  }, [isBusy]);
415568
+ import_react20.useEffect(() => {
415569
+ if (!isBusy) {
415570
+ setShimmerIndex(-1);
415571
+ return;
415572
+ }
415573
+ const modeText = MODE_PREFIX[mode];
415574
+ const textLength = modeText.length;
415575
+ let currentIdx = -1;
415576
+ const interval = setInterval(() => {
415577
+ currentIdx = (currentIdx + 1) % (textLength + 4);
415578
+ if (currentIdx >= textLength) {
415579
+ setShimmerIndex(-1);
415580
+ } else {
415581
+ setShimmerIndex(currentIdx);
415582
+ }
415583
+ }, 80);
415584
+ return () => clearInterval(interval);
415585
+ }, [isBusy, mode]);
415567
415586
  const getInkWaveChars = import_react20.useCallback(() => {
415568
415587
  const totalWidth = width ?? 80;
415569
415588
  const phaseData = INK_WAVE_PHASES[wavePhase];
@@ -415637,13 +415656,37 @@ var InputBox = import_react20.forwardRef(function InputBox2({
415637
415656
  height: 3,
415638
415657
  children: [
415639
415658
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
415659
+ flexDirection: "row",
415640
415660
  marginRight: 1,
415641
415661
  flexShrink: 0,
415642
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
415643
- fg: MODE_COLOR[mode],
415644
- attributes: createTextAttributes({ bold: true }),
415645
- children: MODE_PREFIX[mode]
415646
- }, undefined, false, undefined, this)
415662
+ children: (() => {
415663
+ const modeText = MODE_PREFIX[mode];
415664
+ const highlightColor = mode === "build" ? "#86efac" : mode === "ask" ? "#fde68a" : "#93c5fd";
415665
+ const nearColor = mode === "build" ? "#bbf7d0" : mode === "ask" ? "#fef08a" : "#bfdbfe";
415666
+ return modeText.split("").map((char, i) => {
415667
+ const isHighlighted = isBusy && shimmerIndex === i;
415668
+ const isNearHighlight = isBusy && shimmerIndex !== -1 && (shimmerIndex === i - 1 || shimmerIndex === i + 1);
415669
+ if (isHighlighted) {
415670
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
415671
+ fg: highlightColor,
415672
+ attributes: createTextAttributes({ bold: true }),
415673
+ children: char
415674
+ }, i, false, undefined, this);
415675
+ }
415676
+ if (isNearHighlight) {
415677
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
415678
+ fg: nearColor,
415679
+ attributes: createTextAttributes({ bold: true }),
415680
+ children: char
415681
+ }, i, false, undefined, this);
415682
+ }
415683
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
415684
+ fg: MODE_COLOR[mode],
415685
+ attributes: createTextAttributes({ bold: true }),
415686
+ children: char
415687
+ }, i, false, undefined, this);
415688
+ });
415689
+ })()
415647
415690
  }, undefined, false, undefined, this),
415648
415691
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
415649
415692
  flexGrow: 1,
@@ -484172,6 +484215,124 @@ var forkManager = new ForkManager;
484172
484215
  // src/agent/session-store.ts
484173
484216
  init_mcp();
484174
484217
  init_mode();
484218
+
484219
+ // src/agent/tool-output-guard.ts
484220
+ var TOOL_LIMITS = {
484221
+ bash: { maxLines: 3000, maxBytes: 100 * 1024, truncateFrom: "tail" },
484222
+ read: { maxLines: 2000, maxBytes: 100 * 1024, truncateFrom: "head" },
484223
+ grep: { maxLines: 500, maxBytes: 50 * 1024, truncateFrom: "tail" },
484224
+ glob: { maxLines: 500, maxBytes: 20 * 1024, truncateFrom: "tail" },
484225
+ ls: { maxLines: 500, maxBytes: 20 * 1024, truncateFrom: "tail" },
484226
+ write: { maxLines: 0, maxBytes: 0, truncateFrom: "tail" },
484227
+ mcp: { maxLines: 1000, maxBytes: 100 * 1024, truncateFrom: "tail" }
484228
+ };
484229
+ var DEFAULT_LIMITS = {
484230
+ maxLines: DEFAULT_MAX_LINES,
484231
+ maxBytes: DEFAULT_MAX_BYTES,
484232
+ truncateFrom: "tail"
484233
+ };
484234
+ function extractToolName(toolName) {
484235
+ if (toolName.startsWith("mcp__")) {
484236
+ const parts = toolName.split("__");
484237
+ if (parts.length >= 3) {
484238
+ return "mcp";
484239
+ }
484240
+ }
484241
+ return toolName.split(":").pop() ?? toolName;
484242
+ }
484243
+ function truncateTextBlock(text, limits) {
484244
+ if (limits.maxLines === 0 && limits.maxBytes === 0) {
484245
+ return { text, wasTruncated: false, truncatedBy: null };
484246
+ }
484247
+ const options4 = {
484248
+ maxLines: limits.maxLines || DEFAULT_MAX_LINES,
484249
+ maxBytes: limits.maxBytes || DEFAULT_MAX_BYTES
484250
+ };
484251
+ const result = limits.truncateFrom === "head" ? truncateHead(text, options4) : truncateTail(text, options4);
484252
+ return {
484253
+ text: result.content,
484254
+ wasTruncated: result.truncated,
484255
+ truncatedBy: result.truncatedBy
484256
+ };
484257
+ }
484258
+ function truncateToolContent(content, limits) {
484259
+ const textBlocks = [];
484260
+ let hasTruncation = false;
484261
+ let lastTruncatedBy = null;
484262
+ for (const item of content) {
484263
+ if (item.type === "text") {
484264
+ const result = truncateTextBlock(item.text ?? "", limits);
484265
+ textBlocks.push(result);
484266
+ if (result.wasTruncated) {
484267
+ hasTruncation = true;
484268
+ lastTruncatedBy = result.truncatedBy;
484269
+ }
484270
+ } else if (item.type === "image") {
484271
+ textBlocks.push({ text: `[Image: ${item.data?.length ?? 0} bytes]`, wasTruncated: false, truncatedBy: null });
484272
+ }
484273
+ }
484274
+ if (!hasTruncation) {
484275
+ return { content, wasTruncated: false };
484276
+ }
484277
+ const truncatedContent = [];
484278
+ for (const block2 of textBlocks) {
484279
+ if (block2.text) {
484280
+ truncatedContent.push({ type: "text", text: block2.text });
484281
+ }
484282
+ }
484283
+ return {
484284
+ content: truncatedContent,
484285
+ wasTruncated: true,
484286
+ truncationInfo: {
484287
+ truncatedBy: lastTruncatedBy ?? "unknown",
484288
+ totalLines: 0,
484289
+ outputLines: 0,
484290
+ totalBytes: 0,
484291
+ outputBytes: 0
484292
+ }
484293
+ };
484294
+ }
484295
+ function createToolOutputGuard(customLimits) {
484296
+ const limits = { ...TOOL_LIMITS, ...customLimits };
484297
+ return async (context) => {
484298
+ const { result, toolCall } = context;
484299
+ const toolName = extractToolName(toolCall.name);
484300
+ const toolLimits = limits[toolName] ?? DEFAULT_LIMITS;
484301
+ if (!toolLimits || toolLimits.maxLines === 0 && toolLimits.maxBytes === 0) {
484302
+ return;
484303
+ }
484304
+ const { content, wasTruncated } = truncateToolContent(result.content, toolLimits);
484305
+ if (!wasTruncated) {
484306
+ return;
484307
+ }
484308
+ const truncateNote = buildTruncationNote(toolName);
484309
+ const finalContent = [...content];
484310
+ const lastTextBlock = [...finalContent].reverse().find((c2) => c2.type === "text");
484311
+ if (lastTextBlock) {
484312
+ const idx = finalContent.findIndex((c2) => c2.type === "text" && c2.text === lastTextBlock.text);
484313
+ if (idx >= 0) {
484314
+ finalContent[idx] = {
484315
+ type: "text",
484316
+ text: `${lastTextBlock.text.trimEnd()}
484317
+
484318
+ ${truncateNote}`
484319
+ };
484320
+ }
484321
+ } else {
484322
+ finalContent.push({ type: "text", text: truncateNote });
484323
+ }
484324
+ return {
484325
+ content: finalContent
484326
+ };
484327
+ };
484328
+ }
484329
+ function buildTruncationNote(toolName) {
484330
+ const toolLabel = toolName === "mcp" ? "MCP tool" : `"${toolName}"`;
484331
+ return `[Output truncated to prevent context overflow. Set a higher limit or use filters to see more of the ${toolLabel} output.]`;
484332
+ }
484333
+ var MCP_TOOL_LIMITS = TOOL_LIMITS["mcp"];
484334
+
484335
+ // src/agent/session-store.ts
484175
484336
  var CONFIG_DIR3 = path25.join(os16.homedir(), ".config", "koi");
484176
484337
  var KOI_SESSIONS_DIR2 = path25.join(CONFIG_DIR3, "sessions");
484177
484338
  var PI_AGENT_DIR2 = path25.join(CONFIG_DIR3, "pi");
@@ -484420,7 +484581,7 @@ async function createAgentSessionWithConfig(sessionManager, config4) {
484420
484581
  try {
484421
484582
  fs18.appendFileSync(logPath, logLine);
484422
484583
  } catch {}
484423
- return createAgentSession({
484584
+ const result = await createAgentSession({
484424
484585
  cwd: process.cwd(),
484425
484586
  agentDir: PI_AGENT_DIR2,
484426
484587
  authStorage: config4.authStorage,
@@ -484432,6 +484593,9 @@ async function createAgentSessionWithConfig(sessionManager, config4) {
484432
484593
  sessionManager,
484433
484594
  resourceLoader
484434
484595
  });
484596
+ const afterToolCall = createToolOutputGuard();
484597
+ result.session.agent.afterToolCall = afterToolCall;
484598
+ return result;
484435
484599
  }
484436
484600
  async function listSessions() {
484437
484601
  try {
@@ -486678,7 +486842,7 @@ function CommandPanel({ isActive, onClose, commands }) {
486678
486842
  }
486679
486843
  }, [isActive]);
486680
486844
  const query2 = filterText;
486681
- const { flatItems, cmdCount } = import_react29.useMemo(() => {
486845
+ const { flatItems } = import_react29.useMemo(() => {
486682
486846
  let filtered = commands;
486683
486847
  if (query2) {
486684
486848
  const q3 = query2.toLowerCase();
@@ -486705,7 +486869,7 @@ function CommandPanel({ isActive, onClose, commands }) {
486705
486869
  cmdIdx++;
486706
486870
  }
486707
486871
  }
486708
- return { flatItems: items3, cmdCount: cmdIdx };
486872
+ return { flatItems: items3 };
486709
486873
  }, [commands, query2]);
486710
486874
  const effectiveScrollOffset = scrollOffsetRef.current;
486711
486875
  useKeyboard((key) => {
@@ -486726,8 +486890,25 @@ function CommandPanel({ isActive, onClose, commands }) {
486726
486890
  if (key.name === "up" || key.name === "down") {
486727
486891
  key.preventDefault();
486728
486892
  key.stopPropagation();
486729
- const newIndex = key.name === "up" ? Math.max(0, selectedCmdIndex - 1) : Math.min(cmdCount - 1, selectedCmdIndex + 1);
486730
- const newFlatIndex = flatItems.findIndex((i) => i.type === "command" && i.cmdIndex === newIndex);
486893
+ const enabledIndices = flatItems.filter((i) => i.type === "command" && !i.cmd?.disabled && i.cmdIndex !== undefined).map((i) => i.cmdIndex);
486894
+ if (enabledIndices.length === 0)
486895
+ return;
486896
+ const currentIdx = enabledIndices.indexOf(selectedCmdIndex);
486897
+ let targetIndex;
486898
+ if (key.name === "up") {
486899
+ if (currentIdx <= 0) {
486900
+ targetIndex = enabledIndices[enabledIndices.length - 1];
486901
+ } else {
486902
+ targetIndex = enabledIndices[currentIdx - 1];
486903
+ }
486904
+ } else {
486905
+ if (currentIdx < 0 || currentIdx >= enabledIndices.length - 1) {
486906
+ targetIndex = enabledIndices[0];
486907
+ } else {
486908
+ targetIndex = enabledIndices[currentIdx + 1];
486909
+ }
486910
+ }
486911
+ const newFlatIndex = flatItems.findIndex((i) => i.type === "command" && i.cmdIndex === targetIndex);
486731
486912
  const currentScroll = scrollOffsetRef.current;
486732
486913
  let newScrollOffset = currentScroll;
486733
486914
  if (newFlatIndex !== -1) {
@@ -486737,16 +486918,8 @@ function CommandPanel({ isActive, onClose, commands }) {
486737
486918
  newScrollOffset = newFlatIndex - listHeight + 1;
486738
486919
  }
486739
486920
  }
486740
- console.log("[CommandPanel] Key press:", {
486741
- key: key.name,
486742
- newIndex,
486743
- newFlatIndex,
486744
- currentScroll,
486745
- listHeight,
486746
- newScrollOffset
486747
- });
486748
486921
  scrollOffsetRef.current = newScrollOffset;
486749
- setSelectedCmdIndex(newIndex);
486922
+ setSelectedCmdIndex(targetIndex);
486750
486923
  return;
486751
486924
  }
486752
486925
  });
@@ -486758,7 +486931,7 @@ function CommandPanel({ isActive, onClose, commands }) {
486758
486931
  };
486759
486932
  const handleSubmit = () => {
486760
486933
  const selectedItem = flatItems.find((i) => i.type === "command" && i.cmdIndex === selectedCmdIndex);
486761
- if (selectedItem?.cmd) {
486934
+ if (selectedItem?.cmd && !selectedItem.cmd.disabled) {
486762
486935
  onClose();
486763
486936
  selectedItem.cmd.action();
486764
486937
  }
@@ -486824,14 +486997,15 @@ function CommandPanel({ isActive, onClose, commands }) {
486824
486997
  }, undefined, false, undefined, this)
486825
486998
  }, `h-${item.section}-${flatIndex}`, false, undefined, this);
486826
486999
  }
486827
- const isSelected = item.cmdIndex === selectedCmdIndex;
487000
+ const isDisabled = item.cmd?.disabled;
487001
+ const isSelected = !isDisabled && item.cmdIndex === selectedCmdIndex;
486828
487002
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
486829
487003
  height: 1,
486830
487004
  backgroundColor: isSelected ? "#44475a" : undefined,
486831
487005
  paddingLeft: 2,
486832
487006
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
486833
- fg: isSelected ? "#ff79c6" : "#f8f8f2",
486834
- children: `${item.cmd.id} ${item.cmd.label}`
487007
+ fg: isDisabled ? "#666666" : isSelected ? "#ff79c6" : "#f8f8f2",
487008
+ children: `${item.cmd.id} ${isDisabled ? item.cmd.label + " (busy)" : item.cmd.label}`
486835
487009
  }, undefined, false, undefined, this)
486836
487010
  }, `c-${item.cmd.id}-${flatIndex}`, false, undefined, this);
486837
487011
  }),
@@ -486976,8 +487150,23 @@ function ConnectModal({ isActive, onClose }) {
486976
487150
  const [verifyResult, setVerifyResult] = import_react33.useState(null);
486977
487151
  const [spinnerFrame, setSpinnerFrame] = import_react33.useState(0);
486978
487152
  const [scrollOffset, setScrollOffset] = import_react33.useState(0);
487153
+ const [filterText, setFilterText] = import_react33.useState("");
486979
487154
  const inputRef = import_react33.useRef(null);
487155
+ const searchRef = import_react33.useRef(null);
486980
487156
  const listHeight = Math.min(10, Math.floor(height * 0.35));
487157
+ const query2 = filterText;
487158
+ const filteredProviders = import_react33.useMemo(() => {
487159
+ if (!query2)
487160
+ return providers;
487161
+ const q3 = query2.toLowerCase();
487162
+ return providers.filter((p) => p.toLowerCase().includes(q3));
487163
+ }, [providers, query2]);
487164
+ const handleSearchChange = () => {
487165
+ const text = searchRef.current?.editBuffer.getText() ?? "";
487166
+ setFilterText(text);
487167
+ setSelectedProviderIndex(0);
487168
+ setScrollOffset(0);
487169
+ };
486981
487170
  import_react33.useEffect(() => {
486982
487171
  if (isActive) {
486983
487172
  setStep("provider");
@@ -486987,6 +487176,12 @@ function ConnectModal({ isActive, onClose }) {
486987
487176
  setVerifyResult(null);
486988
487177
  setSpinnerFrame(0);
486989
487178
  setScrollOffset(0);
487179
+ setFilterText("");
487180
+ const ta = searchRef.current;
487181
+ if (ta) {
487182
+ ta.editBuffer.replaceText("");
487183
+ ta.focus();
487184
+ }
486990
487185
  }
486991
487186
  }, [isActive]);
486992
487187
  import_react33.useEffect(() => {
@@ -487053,6 +487248,11 @@ function ConnectModal({ isActive, onClose }) {
487053
487248
  setScrollOffset(selectedProviderIndex - listHeight + 1);
487054
487249
  }
487055
487250
  }, [selectedProviderIndex, listHeight, scrollOffset]);
487251
+ import_react33.useEffect(() => {
487252
+ if (selectedProviderIndex >= filteredProviders.length && filteredProviders.length > 0) {
487253
+ setSelectedProviderIndex(filteredProviders.length - 1);
487254
+ }
487255
+ }, [filteredProviders.length, selectedProviderIndex]);
487056
487256
  const handleSelectProvider = (provider) => {
487057
487257
  setSelectedProvider(provider);
487058
487258
  if (isProviderConfigured(provider)) {
@@ -487094,11 +487294,11 @@ function ConnectModal({ isActive, onClose }) {
487094
487294
  return;
487095
487295
  }
487096
487296
  if (key.name === "down") {
487097
- setSelectedProviderIndex((prev) => Math.max(0, Math.min(providers.length - 1, prev + 1)));
487297
+ setSelectedProviderIndex((prev) => Math.max(0, Math.min(filteredProviders.length - 1, prev + 1)));
487098
487298
  return;
487099
487299
  }
487100
487300
  if (key.name === "return") {
487101
- const provider = providers[selectedProviderIndex];
487301
+ const provider = filteredProviders[selectedProviderIndex];
487102
487302
  if (provider) {
487103
487303
  handleSelectProvider(provider);
487104
487304
  }
@@ -487143,7 +487343,7 @@ function ConnectModal({ isActive, onClose }) {
487143
487343
  const existingConfig = selectedProvider ? getProviderConfig(selectedProvider) : undefined;
487144
487344
  if (!isActive)
487145
487345
  return null;
487146
- const visibleProviders = providers.slice(scrollOffset, scrollOffset + listHeight);
487346
+ const visibleProviders = filteredProviders.slice(scrollOffset, scrollOffset + listHeight);
487147
487347
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487148
487348
  position: "absolute",
487149
487349
  top: 0,
@@ -487169,46 +487369,84 @@ function ConnectModal({ isActive, onClose }) {
487169
487369
  fg: "#ff79c6",
487170
487370
  children: "Select Provider"
487171
487371
  }, undefined, false, undefined, this),
487372
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487373
+ height: 1,
487374
+ marginTop: 1,
487375
+ backgroundColor: "#16213e",
487376
+ paddingX: 1,
487377
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("textarea", {
487378
+ ref: searchRef,
487379
+ initialValue: "",
487380
+ focused: isActive,
487381
+ showCursor: true,
487382
+ height: 1,
487383
+ wrapMode: "none",
487384
+ textColor: "#f8f8f2",
487385
+ backgroundColor: "#16213e",
487386
+ onContentChange: handleSearchChange
487387
+ }, undefined, false, undefined, this)
487388
+ }, undefined, false, undefined, this),
487389
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487390
+ height: 1,
487391
+ marginTop: 1,
487392
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487393
+ fg: "#4a4a5a",
487394
+ children: "\u2500".repeat(56)
487395
+ }, undefined, false, undefined, this)
487396
+ }, undefined, false, undefined, this),
487172
487397
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487173
487398
  height: listHeight,
487174
487399
  flexDirection: "column",
487175
487400
  overflow: "hidden",
487176
487401
  marginTop: 1,
487177
- children: visibleProviders.map((p, i) => {
487178
- const actualIndex = scrollOffset + i;
487179
- const configured = isProviderConfigured(p);
487180
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487402
+ children: [
487403
+ filteredProviders.length === 0 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487181
487404
  height: 1,
487182
- backgroundColor: actualIndex === selectedProviderIndex ? "#44475a" : undefined,
487183
- paddingLeft: 1,
487184
- flexDirection: "row",
487185
- onMouseUp: (e) => {
487186
- e.stopPropagation();
487187
- handleSelectProvider(p);
487188
- },
487189
- children: [
487190
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487191
- fg: actualIndex === selectedProviderIndex ? "#ff79c6" : "#f8f8f2",
487192
- children: [
487193
- configured ? "\u25CF " : " ",
487194
- p
487195
- ]
487196
- }, undefined, true, undefined, this),
487197
- configured && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487198
- fg: "#00ff99",
487199
- marginLeft: 1,
487200
- children: "configured"
487201
- }, undefined, false, undefined, this)
487202
- ]
487203
- }, p, true, undefined, this);
487204
- })
487205
- }, undefined, false, undefined, this),
487405
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487406
+ fg: "#6c6c7c",
487407
+ children: [
487408
+ 'No providers match "',
487409
+ filterText,
487410
+ '"'
487411
+ ]
487412
+ }, undefined, true, undefined, this)
487413
+ }, undefined, false, undefined, this),
487414
+ visibleProviders.map((p, i) => {
487415
+ const actualIndex = scrollOffset + i;
487416
+ const configured = isProviderConfigured(p);
487417
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487418
+ height: 1,
487419
+ backgroundColor: actualIndex === selectedProviderIndex ? "#44475a" : undefined,
487420
+ paddingLeft: 1,
487421
+ flexDirection: "row",
487422
+ onMouseUp: (e) => {
487423
+ e.stopPropagation();
487424
+ handleSelectProvider(p);
487425
+ },
487426
+ children: [
487427
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487428
+ fg: actualIndex === selectedProviderIndex ? "#ff79c6" : "#f8f8f2",
487429
+ children: [
487430
+ configured ? "\u25CF " : " ",
487431
+ p
487432
+ ]
487433
+ }, undefined, true, undefined, this),
487434
+ configured && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487435
+ fg: "#00ff99",
487436
+ marginLeft: 1,
487437
+ children: "configured"
487438
+ }, undefined, false, undefined, this)
487439
+ ]
487440
+ }, p, true, undefined, this);
487441
+ })
487442
+ ]
487443
+ }, undefined, true, undefined, this),
487206
487444
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487207
487445
  marginTop: 1,
487208
487446
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487209
487447
  fg: "#6c6c7c",
487210
487448
  attributes: createTextAttributes({ dim: true }),
487211
- children: "\u2191\u2193 Navigate Enter Select Esc Cancel"
487449
+ children: "\u2191\u2193 Navigate Enter Select Esc Cancel Type to search"
487212
487450
  }, undefined, false, undefined, this)
487213
487451
  }, undefined, false, undefined, this)
487214
487452
  ]
@@ -487411,7 +487649,9 @@ function ModelModal({
487411
487649
  const configuredProviders = getConfiguredProviders();
487412
487650
  const [selectedModelIndex, setSelectedModelIndex] = import_react35.useState(0);
487413
487651
  const [activeTab, setActiveTab] = import_react35.useState("primary");
487652
+ const [filterText, setFilterText] = import_react35.useState("");
487414
487653
  const scrollOffsetRef = import_react35.useRef(0);
487654
+ const inputRef = import_react35.useRef(null);
487415
487655
  const listHeight = Math.min(12, Math.floor(height * 0.4));
487416
487656
  const primaryModel = getCurrentModel();
487417
487657
  const auxiliaryModel2 = getAuxiliaryModel();
@@ -487419,33 +487659,73 @@ function ModelModal({
487419
487659
  if (isActive) {
487420
487660
  setSelectedModelIndex(0);
487421
487661
  setActiveTab("primary");
487662
+ setFilterText("");
487422
487663
  scrollOffsetRef.current = 0;
487664
+ const ta = inputRef.current;
487665
+ if (ta) {
487666
+ ta.editBuffer.replaceText("");
487667
+ ta.focus();
487668
+ }
487423
487669
  }
487424
487670
  }, [isActive]);
487671
+ const query2 = filterText;
487425
487672
  const { flatItems, modelCount } = import_react35.useMemo(() => {
487426
- const items3 = [];
487427
- let mIdx = 0;
487673
+ const allModels = [];
487428
487674
  for (const provider of configuredProviders) {
487429
- items3.push({ type: "header", provider });
487430
487675
  const models2 = getProviderModels(provider);
487431
487676
  for (const model of models2) {
487432
- items3.push({
487433
- type: "model",
487677
+ allModels.push({
487434
487678
  provider,
487435
487679
  modelId: model.id,
487436
- modelName: model.name || model.id,
487680
+ modelName: model.name || model.id
487681
+ });
487682
+ }
487683
+ }
487684
+ let filteredModels = allModels;
487685
+ if (query2) {
487686
+ const q3 = query2.toLowerCase();
487687
+ filteredModels = allModels.map((m2) => {
487688
+ const providerMatch = m2.provider.toLowerCase().includes(q3);
487689
+ const modelIdMatch = m2.modelId.toLowerCase().includes(q3);
487690
+ const modelNameMatch = m2.modelName.toLowerCase().includes(q3);
487691
+ const priority = !providerMatch && !modelIdMatch && !modelNameMatch ? 3 : providerMatch ? 0 : modelIdMatch ? 1 : 2;
487692
+ return { model: m2, priority };
487693
+ }).filter((item) => item.priority < 3).sort((a, b2) => a.priority - b2.priority).map((item) => item.model);
487694
+ }
487695
+ const grouped = new Map;
487696
+ for (const m2 of filteredModels) {
487697
+ if (!grouped.has(m2.provider))
487698
+ grouped.set(m2.provider, []);
487699
+ grouped.get(m2.provider).push(m2);
487700
+ }
487701
+ const items3 = [];
487702
+ let mIdx = 0;
487703
+ for (const [provider, models2] of grouped) {
487704
+ items3.push({ type: "header", provider });
487705
+ for (const m2 of models2) {
487706
+ items3.push({
487707
+ type: "model",
487708
+ provider: m2.provider,
487709
+ modelId: m2.modelId,
487710
+ modelName: m2.modelName,
487437
487711
  modelIndex: mIdx
487438
487712
  });
487439
487713
  mIdx++;
487440
487714
  }
487441
487715
  }
487442
487716
  return { flatItems: items3, modelCount: mIdx };
487443
- }, [configuredProviders]);
487717
+ }, [configuredProviders, query2]);
487444
487718
  import_react35.useEffect(() => {
487445
487719
  if (selectedModelIndex >= modelCount && modelCount > 0) {
487446
487720
  setSelectedModelIndex(modelCount - 1);
487447
487721
  }
487448
487722
  }, [modelCount, selectedModelIndex]);
487723
+ const handleContentChange = () => {
487724
+ const text = inputRef.current?.editBuffer.getText() ?? "";
487725
+ setFilterText(text);
487726
+ setSelectedModelIndex(0);
487727
+ scrollOffsetRef.current = 0;
487728
+ };
487449
487729
  const effectiveScrollOffset = scrollOffsetRef.current;
487450
487730
  useKeyboard((key) => {
487451
487731
  if (!isActive)
@@ -487574,6 +487854,31 @@ function ModelModal({
487574
487854
  }, undefined, true, undefined, this)
487575
487855
  ]
487576
487856
  }, undefined, true, undefined, this),
487857
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487858
+ height: 1,
487859
+ marginTop: 1,
487860
+ backgroundColor: "#16213e",
487861
+ paddingX: 1,
487862
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("textarea", {
487863
+ ref: inputRef,
487864
+ initialValue: "",
487865
+ focused: isActive,
487866
+ showCursor: true,
487867
+ height: 1,
487868
+ wrapMode: "none",
487869
+ textColor: "#f8f8f2",
487870
+ backgroundColor: "#16213e",
487871
+ onContentChange: handleContentChange
487872
+ }, undefined, false, undefined, this)
487873
+ }, undefined, false, undefined, this),
487874
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487875
+ height: 1,
487876
+ marginTop: 1,
487877
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487878
+ fg: "#4a4a5a",
487879
+ children: "\u2500".repeat(56)
487880
+ }, undefined, false, undefined, this)
487881
+ }, undefined, false, undefined, this),
487577
487882
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487578
487883
  height: listHeight,
487579
487884
  flexDirection: "column",
@@ -487587,6 +487892,17 @@ function ModelModal({
487587
487892
  children: "No providers configured. Use /connect to add one."
487588
487893
  }, undefined, false, undefined, this)
487589
487894
  }, undefined, false, undefined, this),
487895
+ configuredProviders.length > 0 && modelCount === 0 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
487896
+ height: 1,
487897
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487898
+ fg: "#6c6c7c",
487899
+ children: [
487900
+ 'No models match "',
487901
+ filterText,
487902
+ '"'
487903
+ ]
487904
+ }, undefined, true, undefined, this)
487905
+ }, undefined, false, undefined, this),
487590
487906
  visibleItems.map((item, idx) => {
487591
487907
  const flatIndex = effectiveScrollOffset + idx;
487592
487908
  if (item.type === "header") {
@@ -487632,7 +487948,7 @@ function ModelModal({
487632
487948
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
487633
487949
  fg: "#6c6c7c",
487634
487950
  attributes: createTextAttributes({ dim: true }),
487635
- children: "Tab Switch"
487951
+ children: "Type to search Tab Switch"
487636
487952
  }, undefined, false, undefined, this)
487637
487953
  ]
487638
487954
  }, undefined, true, undefined, this)
@@ -489785,7 +490101,8 @@ function App({ onExit }) {
489785
490101
  return;
489786
490102
  }
489787
490103
  if (text.trim() === "/exit" || text.trim() === "/quit") {
489788
- setShowExitModal(true);
490104
+ saveCurrentState();
490105
+ onExit();
489789
490106
  return;
489790
490107
  }
489791
490108
  const skillInvocation = detectSkillInvocation(text);
@@ -489897,27 +490214,33 @@ function App({ onExit }) {
489897
490214
  }));
489898
490215
  }, [skills, session, prompt]);
489899
490216
  const commands = import_react48.useMemo(() => [
489900
- { id: "/new", label: "Start a new session", section: "Session", action: () => void handleNewSession() },
489901
- { id: "/fork", label: "Fork current session", section: "Session", action: () => setShowForkModal(true) },
490217
+ { id: "/new", label: "Start a new session", section: "Session", action: () => void handleNewSession(), disabled: isStreaming },
490218
+ { id: "/fork", label: "Fork current session", section: "Session", action: () => setShowForkModal(true), disabled: isStreaming },
489902
490219
  { id: "/sessions", label: "Browse sessions", section: "Session", action: async () => {
489903
490220
  await refreshSessionList();
489904
490221
  setShowSessionModal(true);
489905
- } },
490222
+ }, disabled: isStreaming },
489906
490223
  { id: "/compact", label: "Compact current session", section: "Session", action: () => {
489907
490224
  session?.compact().catch(() => {});
489908
- } },
490225
+ }, disabled: isStreaming },
489909
490226
  { id: "/rename", label: "Rename session", section: "Session", action: () => setShowRenameModal(true) },
489910
- { id: "/exit", label: "Exit Koi", section: "Session", action: () => setShowExitModal(true) },
489911
- { id: "/quit", label: "Exit Koi (alias)", section: "Session", action: () => setShowExitModal(true) },
490227
+ { id: "/exit", label: "Exit Koi", section: "Session", action: () => {
490228
+ saveCurrentState();
490229
+ onExit();
490230
+ } },
490231
+ { id: "/quit", label: "Exit Koi (alias)", section: "Session", action: () => {
490232
+ saveCurrentState();
490233
+ onExit();
490234
+ } },
489912
490235
  { id: "/yolo", label: "Toggle YOLO mode (auto-approve all permissions)", section: "Mode", action: () => setYoloMode2((prev) => !prev) },
489913
490236
  { id: "/mode", label: `Cycle agent mode (${agentMode})`, section: "Mode", action: () => handleModeSwitch() },
489914
- { id: "/plan", label: "Switch to plan mode (read-only, no file modifications)", section: "Mode", action: () => applyAgentMode("plan") },
490237
+ { id: "/plan", label: "Switch to plan mode (read-only, no file modifications)", section: "Mode", action: () => applyAgentMode("plan"), disabled: isStreaming },
489915
490238
  { id: "/connect", label: "Connect to a provider", section: "Model", action: () => setShowConnectModal(true) },
489916
- { id: "/model", label: "Select a model", section: "Model", action: () => setShowModelModal(true) },
490239
+ { id: "/model", label: "Select a model", section: "Model", action: () => setShowModelModal(true), disabled: isStreaming },
489917
490240
  { id: "/mcp", label: "Open MCP settings", section: "Extensions", action: () => setShowMCPSettings(true) },
489918
490241
  { id: "/skills", label: "List and manage skills", section: "Extensions", action: () => setShowSkillsModal(true) },
489919
490242
  ...skillCommands
489920
- ], [session, handleNewSession, refreshSessionList, agentMode, handleModeSwitch, applyAgentMode, skillCommands]);
490243
+ ], [isStreaming, session, handleNewSession, refreshSessionList, agentMode, handleModeSwitch, applyAgentMode, skillCommands]);
489921
490244
  useKeyboard((key) => {
489922
490245
  if (anyModalOpen && !permissionModalOpen)
489923
490246
  return;
@@ -489968,18 +490291,24 @@ function App({ onExit }) {
489968
490291
  return;
489969
490292
  }
489970
490293
  if (key.ctrl && key.name === "s") {
489971
- (async () => {
489972
- await refreshSessionList();
489973
- setShowSessionModal(true);
489974
- })();
490294
+ if (!isStreaming) {
490295
+ (async () => {
490296
+ await refreshSessionList();
490297
+ setShowSessionModal(true);
490298
+ })();
490299
+ }
489975
490300
  return;
489976
490301
  }
489977
490302
  if (key.ctrl && key.name === "f") {
489978
- setShowForkModal(true);
490303
+ if (!isStreaming) {
490304
+ setShowForkModal(true);
490305
+ }
489979
490306
  return;
489980
490307
  }
489981
490308
  if (key.name === "tab" && key.shift) {
489982
- handleModeSwitch();
490309
+ if (!isStreaming) {
490310
+ handleModeSwitch();
490311
+ }
489983
490312
  return;
489984
490313
  }
489985
490314
  if (key.ctrl && key.name === "o") {