@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 +409 -80
- package/package.json +1 -1
- package/src/agent/session-store.ts +8 -1
- package/src/agent/tool-output-guard.ts +261 -0
- package/src/services/mcp/types.ts +2 -0
- package/src/tools/mcp.ts +14 -1
- package/src/tui/app.tsx +21 -14
- package/src/tui/components/command-panel.tsx +38 -21
- package/src/tui/components/connect-modal.tsx +65 -5
- package/src/tui/components/input-box.tsx +49 -5
- package/src/tui/components/model-modal.tsx +93 -9
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:
|
|
415643
|
-
|
|
415644
|
-
|
|
415645
|
-
|
|
415646
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
486730
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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:
|
|
487178
|
-
|
|
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
|
-
|
|
487183
|
-
|
|
487184
|
-
|
|
487185
|
-
|
|
487186
|
-
|
|
487187
|
-
|
|
487188
|
-
|
|
487189
|
-
|
|
487190
|
-
|
|
487191
|
-
|
|
487192
|
-
|
|
487193
|
-
|
|
487194
|
-
|
|
487195
|
-
|
|
487196
|
-
|
|
487197
|
-
|
|
487198
|
-
|
|
487199
|
-
|
|
487200
|
-
|
|
487201
|
-
|
|
487202
|
-
|
|
487203
|
-
|
|
487204
|
-
|
|
487205
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: () =>
|
|
489911
|
-
|
|
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
|
-
|
|
489972
|
-
|
|
489973
|
-
|
|
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
|
-
|
|
490303
|
+
if (!isStreaming) {
|
|
490304
|
+
setShowForkModal(true);
|
|
490305
|
+
}
|
|
489979
490306
|
return;
|
|
489980
490307
|
}
|
|
489981
490308
|
if (key.name === "tab" && key.shift) {
|
|
489982
|
-
|
|
490309
|
+
if (!isStreaming) {
|
|
490310
|
+
handleModeSwitch();
|
|
490311
|
+
}
|
|
489983
490312
|
return;
|
|
489984
490313
|
}
|
|
489985
490314
|
if (key.ctrl && key.name === "o") {
|