koneck 2.25.61 → 2.25.63
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/endpoints.d.ts +7 -0
- package/dist/endpoints.d.ts.map +1 -1
- package/dist/endpoints.js +27 -0
- package/dist/endpoints.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +91 -1
- package/dist/engine.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +70 -7
- package/dist/ink-chat.js.map +1 -1
- package/dist/logo.d.ts +11 -11
- package/dist/logo.d.ts.map +1 -1
- package/dist/logo.js +23 -29
- package/dist/logo.js.map +1 -1
- package/dist/repetition.d.ts +64 -0
- package/dist/repetition.d.ts.map +1 -0
- package/dist/repetition.js +118 -0
- package/dist/repetition.js.map +1 -0
- package/dist/tool-call-text.d.ts +50 -0
- package/dist/tool-call-text.d.ts.map +1 -1
- package/dist/tool-call-text.js +136 -0
- package/dist/tool-call-text.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -2,13 +2,13 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Static, Text, render, useApp, useInput, useStdin } from 'ink';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice } from './engine.js';
|
|
5
|
+
import { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice, isLocalRuntime } from './engine.js';
|
|
6
6
|
import { estimateCost, formatCost } from './pricing.js';
|
|
7
7
|
import { generateSessionId, saveSession, listSessions, loadSession, relativeAge, renameSession, forkSession, archiveSession, findSession, } from './session.js';
|
|
8
8
|
import { loadMemory } from './memory.js';
|
|
9
9
|
import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue } from './config-store.js';
|
|
10
10
|
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath } from './providers.js';
|
|
11
|
-
import { resolveEndpoint, normaliseEndpoint, withEndpoint, savedEndpoints } from './endpoints.js';
|
|
11
|
+
import { resolveEndpoint, normaliseEndpoint, withEndpoint, savedEndpoints, shortSource } from './endpoints.js';
|
|
12
12
|
import { loadProjectConfig, loadGlobalConfig } from './config.js';
|
|
13
13
|
import { listCheckpoints, revertCheckpoint, snapshotTree, filesTouchedBy } from './checkpoint.js';
|
|
14
14
|
import { fileDiff, diffSummary } from './diff-view.js';
|
|
@@ -501,6 +501,7 @@ const COMMANDS = [
|
|
|
501
501
|
{ cmd: '/models', desc: 'Browse and switch models for the connected provider' },
|
|
502
502
|
{ cmd: '/connect', desc: 'Connect a provider, or declare a new one with a URL' },
|
|
503
503
|
{ cmd: '/endpoint', desc: "Show or change this provider's URL, e.g. a remote Ollama" },
|
|
504
|
+
{ cmd: '/redraw', desc: 'Repaint the terminal — same as ctrl+L' },
|
|
504
505
|
{ cmd: '/key', desc: 'Enter an API key for the current provider' },
|
|
505
506
|
{ cmd: '/model', desc: 'Show or set the model by name' },
|
|
506
507
|
{ cmd: '/provider', desc: 'Show or switch provider' },
|
|
@@ -733,6 +734,7 @@ Slash commands:
|
|
|
733
734
|
/connect [provider] Connect a provider (picker when no name given)
|
|
734
735
|
/connect <name> <url> Declare a provider KONECK does not ship with, e.g.
|
|
735
736
|
/connect openrouter https://openrouter.ai/api/v1
|
|
737
|
+
/redraw Repaint the terminal (ctrl+L) if a stale frame is left on screen
|
|
736
738
|
/endpoint Show the endpoint in force and where it came from
|
|
737
739
|
/endpoint <url> Point this provider somewhere else, e.g.
|
|
738
740
|
/endpoint http://192.168.1.50:11434/v1
|
|
@@ -1012,6 +1014,18 @@ function App({ config: initialConfig }) {
|
|
|
1012
1014
|
toolsWarnedRef.current = model;
|
|
1013
1015
|
addSystem(toolsUnsupportedNotice(model));
|
|
1014
1016
|
},
|
|
1017
|
+
onLooping: (copies) => {
|
|
1018
|
+
// The loop itself is the symptom. On a local model the cause is almost always the window:
|
|
1019
|
+
// Ollama serves 4096 tokens unless told otherwise, whatever the model was trained for, and
|
|
1020
|
+
// a conversation that no longer fits is exactly what makes a model start repeating.
|
|
1021
|
+
const local = isLocalRuntime(cfg.provider);
|
|
1022
|
+
addSystem(`${cfg.model} repeated the same text ${copies} times, so the reply was cut short.` +
|
|
1023
|
+
(local
|
|
1024
|
+
? `\n\nThis is usually the context window rather than the model. Ollama serves 4096 ` +
|
|
1025
|
+
`tokens by default however large the model is — set ` +
|
|
1026
|
+
`\`OLLAMA_CONTEXT_LENGTH=32768\` and restart it, then \`/context\` to confirm.`
|
|
1027
|
+
: ''));
|
|
1028
|
+
},
|
|
1015
1029
|
onThinking: (text, totalChars) => {
|
|
1016
1030
|
thinkingRef.current = { chars: totalChars, at: Date.now() };
|
|
1017
1031
|
// The text was discarded before this, so the model's reasoning was never visible — only a
|
|
@@ -1075,6 +1089,8 @@ function App({ config: initialConfig }) {
|
|
|
1075
1089
|
* reading something further up is not yanked away by the next tool call.
|
|
1076
1090
|
*/
|
|
1077
1091
|
const [scrollBack, setScrollBack] = useState(0);
|
|
1092
|
+
/** Bumped to make Ink write a frame whose content has not changed. See redrawScreen. */
|
|
1093
|
+
const [repaint, setRepaint] = useState(0);
|
|
1078
1094
|
const [ask, setAsk] = useState(null);
|
|
1079
1095
|
useEffect(() => {
|
|
1080
1096
|
if (!process.stdout.isTTY)
|
|
@@ -1415,6 +1431,30 @@ function App({ config: initialConfig }) {
|
|
|
1415
1431
|
if (newCfg)
|
|
1416
1432
|
setCfg(newCfg);
|
|
1417
1433
|
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Repaint the terminal from scratch.
|
|
1436
|
+
*
|
|
1437
|
+
* Reported as KONECK looking "split into two parts, two message input box": a previous frame
|
|
1438
|
+
* left on screen above the live one, with the transcript written in between. Ink erases the
|
|
1439
|
+
* lines it believes it wrote, and any line the terminal wrapped occupies two rows while
|
|
1440
|
+
* counting as one — so the erase falls short and the old frame is stranded. It cannot be
|
|
1441
|
+
* unstranded from inside the frame, because Ink is not aware it is there.
|
|
1442
|
+
*
|
|
1443
|
+
* Ctrl+L is what everybody presses when a terminal looks wrong, and KONECK had no binding for
|
|
1444
|
+
* it at all. It wipes the screen and its scrollback; the conversation is untouched, since it
|
|
1445
|
+
* lives in the session rather than in the terminal's memory of it.
|
|
1446
|
+
*/
|
|
1447
|
+
function redrawScreen() {
|
|
1448
|
+
if (!process.stdout.isTTY)
|
|
1449
|
+
return;
|
|
1450
|
+
// Clear the screen, clear the scrollback with it — a stranded frame that has scrolled out of
|
|
1451
|
+
// view is still in the buffer, and leaving it there means it reappears on the next scroll —
|
|
1452
|
+
// then home the cursor so Ink's next frame starts from a known position.
|
|
1453
|
+
process.stdout.write('\u001b[2J\u001b[3J\u001b[H');
|
|
1454
|
+
// Ink writes a frame only when its content differs from the last one, and the content has
|
|
1455
|
+
// not changed — the screen it was drawn on has. Bumping this forces the write.
|
|
1456
|
+
setRepaint(n => n + 1);
|
|
1457
|
+
}
|
|
1418
1458
|
async function doSave() {
|
|
1419
1459
|
const s = await activeSession.p.catch(() => null);
|
|
1420
1460
|
const firstUser = s?.messages.find((m) => m.role === 'user');
|
|
@@ -1977,6 +2017,10 @@ function App({ config: initialConfig }) {
|
|
|
1977
2017
|
`Session reset. /endpoint <url> changes the address.`);
|
|
1978
2018
|
return;
|
|
1979
2019
|
}
|
|
2020
|
+
case '/redraw': {
|
|
2021
|
+
redrawScreen();
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
1980
2024
|
case '/url':
|
|
1981
2025
|
case '/baseurl':
|
|
1982
2026
|
case '/endpoint': {
|
|
@@ -2673,6 +2717,12 @@ function App({ config: initialConfig }) {
|
|
|
2673
2717
|
void doSave().catch(() => { }).then(() => exit());
|
|
2674
2718
|
return;
|
|
2675
2719
|
}
|
|
2720
|
+
// Ctrl+L repaints, as it does everywhere else. The conversation is not touched — only the
|
|
2721
|
+
// terminal's picture of it, which is the thing that was wrong.
|
|
2722
|
+
if (key.ctrl && input === 'l') {
|
|
2723
|
+
redrawScreen();
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2676
2726
|
// ── Credential entry owns the keyboard, and echoes nothing ───────────────
|
|
2677
2727
|
// Scrolling the transcript, which on the alternate screen is the application's job: the
|
|
2678
2728
|
// terminal's own buffer is not there to fall back on.
|
|
@@ -2879,6 +2929,10 @@ function App({ config: initialConfig }) {
|
|
|
2879
2929
|
}
|
|
2880
2930
|
return;
|
|
2881
2931
|
}
|
|
2932
|
+
if (key.ctrl && input === 'u') {
|
|
2933
|
+
setEditing(e => (e ? { ...e, value: '' } : e));
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2882
2936
|
if (key.backspace || key.delete) {
|
|
2883
2937
|
setEditing(e => (e ? { ...e, value: e.value.slice(0, -1) } : e));
|
|
2884
2938
|
return;
|
|
@@ -3295,7 +3349,16 @@ function App({ config: initialConfig }) {
|
|
|
3295
3349
|
// window empty. A cap is still available through `maxWidth` in config for anyone who
|
|
3296
3350
|
// prefers a narrow measure on a very wide screen, but nothing is capped by default.
|
|
3297
3351
|
const uiWidth = computeUiWidth(termCols, configMaxWidth);
|
|
3298
|
-
|
|
3352
|
+
// uiWidth minus the container's paddingX={2} on both sides, and then one column of slack.
|
|
3353
|
+
//
|
|
3354
|
+
// Without the slack a full-width box draws a line exactly as wide as the terminal. Ink counts
|
|
3355
|
+
// the lines it wrote in order to erase them next frame, and a line that the terminal wrapped
|
|
3356
|
+
// occupies two rows while counting as one — so the erase falls short and the previous frame is
|
|
3357
|
+
// stranded on screen. That is how a screen ends up showing two composers and two status bars.
|
|
3358
|
+
// One character of a different width than Ink assumed is enough to cause it, and East Asian
|
|
3359
|
+
// ambiguous glyphs (·, ↑↓, ⌕, –, all of which the UI uses) are rendered two cells wide by some
|
|
3360
|
+
// terminals and one by others. A spare column costs nothing and removes the whole class.
|
|
3361
|
+
const barWidth = uiWidth - 5;
|
|
3299
3362
|
// Space a rendered reply actually has: the bar, minus the response box border (2), its
|
|
3300
3363
|
// paddingX={1} (2), and the "ꓘK " prefix plus its gap (3). Getting this even one column
|
|
3301
3364
|
// too wide makes Ink reflow table rows and shatter their borders.
|
|
@@ -3449,7 +3512,7 @@ function App({ config: initialConfig }) {
|
|
|
3449
3512
|
: displayValue(value);
|
|
3450
3513
|
const shown = beingTyped
|
|
3451
3514
|
? `${editing.value}_`
|
|
3452
|
-
: isEndpoint ? `${raw} (${endpoint.source})`
|
|
3515
|
+
: isEndpoint ? `${raw} (${shortSource(endpoint.source)})`
|
|
3453
3516
|
: raw + (here || value === undefined ? '' : ' (inherited)');
|
|
3454
3517
|
// The marker is what makes the selection legible without colour, which a panel line
|
|
3455
3518
|
// drawn as one string cannot carry per-segment.
|
|
@@ -3458,12 +3521,12 @@ function App({ config: initialConfig }) {
|
|
|
3458
3521
|
: f.type.kind === 'boolean' ? ' enter toggles'
|
|
3459
3522
|
: f.type.kind === 'choice' ? ` enter cycles: ${f.type.choices.join(' / ')}`
|
|
3460
3523
|
: f.type.kind === 'number' ? ` type a number, enter to save${f.clearable ? ', x clears' : ''}`
|
|
3461
|
-
: ` enter to
|
|
3524
|
+
: ` enter to edit${f.clearable ? ', x clears' : ''}`;
|
|
3462
3525
|
lines.push(`${mark} ${f.label.padEnd(labelW)} ${selected ? '**' + shown + '**' : shown}${hint}`);
|
|
3463
3526
|
}
|
|
3464
3527
|
lines.push('');
|
|
3465
3528
|
lines.push(editing
|
|
3466
|
-
? 'Typing. **enter** saves, **esc** abandons.'
|
|
3529
|
+
? 'Typing. **enter** saves, **ctrl+u** empties the field, **esc** abandons.'
|
|
3467
3530
|
: 'Up/down choose · enter change · x clear · left/right tabs');
|
|
3468
3531
|
lines.push(`Saved to ${CONFIG_FILE}`);
|
|
3469
3532
|
}
|
|
@@ -3673,7 +3736,7 @@ function App({ config: initialConfig }) {
|
|
|
3673
3736
|
? _jsx(Text, { color: INK, children: draft.slice(caret) })
|
|
3674
3737
|
: caret < draft.length
|
|
3675
3738
|
? _jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: CYAN, color: "#10222A", children: draft[caret] }), _jsx(Text, { color: INK, children: draft.slice(caret + 1) })] })
|
|
3676
|
-
: _jsx(Text, { color: CYAN, children: "\u2588" })] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { wrap: "truncate", backgroundColor: "#24343B", children: [_jsx(Text, { color: MUTED, children: "TAB " }), _jsx(Text, { color: INK, children: "Analytics" }), _jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: CYAN, children: "/HELP" }), _jsx(Text, { color: INK, children: " Commands" }), _jsx(Text, { color: MUTED, children: " / SHIFT+TAB " }), _jsx(Text, { color: INK, children: "Mode" }), _jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: statusColor, children: statusLabel.toLowerCase() }), plan && planSummary(plan) && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / PLAN: " }), _jsx(Text, { color: CYAN, children: planSummary(plan) })] })), _jsx(Text, { color: MUTED, children: " / MODE: " }), _jsx(Text, { color: mode === 'auto' ? INK : AMBER, children: modeSpec(mode).label }), _jsx(Text, { color: MUTED, children: " / EFFORT: " }), _jsx(Text, { color: effort === 'medium' ? INK : AMBER, children: effort }), _jsx(Text, { color: MUTED, children: " / MODEL: " }), _jsx(Text, { color: INK, children: modelShort }), _jsx(Text, { color: MUTED, children: " / v" }), _jsx(Text, { color: updatePane?.behind ? AMBER : INK, children: VERSION }), updatePane?.behind && _jsx(Text, { color: AMBER, children: " \u2191" }), pasteCount > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / " }), _jsxs(Text, { color: AMBER, children: [pasteCount, " paste", pasteCount === 1 ? '' : 's', " held, sent in full"] })] }))] }) })] }));
|
|
3739
|
+
: _jsx(Text, { color: CYAN, children: "\u2588" })] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { wrap: "truncate", backgroundColor: "#24343B", children: [repaint % 2 === 1 ? '\u200b' : '', _jsx(Text, { color: MUTED, children: "TAB " }), _jsx(Text, { color: INK, children: "Analytics" }), _jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: CYAN, children: "/HELP" }), _jsx(Text, { color: INK, children: " Commands" }), _jsx(Text, { color: MUTED, children: " / SHIFT+TAB " }), _jsx(Text, { color: INK, children: "Mode" }), _jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: statusColor, children: statusLabel.toLowerCase() }), plan && planSummary(plan) && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / PLAN: " }), _jsx(Text, { color: CYAN, children: planSummary(plan) })] })), _jsx(Text, { color: MUTED, children: " / MODE: " }), _jsx(Text, { color: mode === 'auto' ? INK : AMBER, children: modeSpec(mode).label }), _jsx(Text, { color: MUTED, children: " / EFFORT: " }), _jsx(Text, { color: effort === 'medium' ? INK : AMBER, children: effort }), _jsx(Text, { color: MUTED, children: " / MODEL: " }), _jsx(Text, { color: INK, children: modelShort }), _jsx(Text, { color: MUTED, children: " / v" }), _jsx(Text, { color: updatePane?.behind ? AMBER : INK, children: VERSION }), updatePane?.behind && _jsx(Text, { color: AMBER, children: " \u2191" }), pasteCount > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / " }), _jsxs(Text, { color: AMBER, children: [pasteCount, " paste", pasteCount === 1 ? '' : 's', " held, sent in full"] })] }))] }) })] }));
|
|
3677
3740
|
}
|
|
3678
3741
|
export async function runInkChatMode(config) {
|
|
3679
3742
|
// Nothing is written before the first frame. Filling the screen to push the prompt to the foot
|