koneck 2.128.71 → 2.128.73

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/ink-chat.js CHANGED
@@ -1669,10 +1669,10 @@ function renderCodeLine(text, kind, width, lang) {
1669
1669
  * through the list, Alt+PgUp/PgDn and the wheel scroll the file, Alt+T opens the fold.
1670
1670
  */
1671
1671
  export function DiffPanel({ files, width, height, activity, activityRows = 0, scroll = 0, selected = 0, showGenerated = false }) {
1672
- // A rule down the left instead of a box round it: two rows more for the diff, and the panel
1673
- // reads as a pane beside the conversation, as in the panel it was modelled on.
1672
+ // Both side borders and the left padding occupy three columns. Reserve a row for the
1673
+ // bottom border so the last visible diff line and the scroll count agree.
1674
1674
  const inner = Math.max(20, width - 3);
1675
- const room = Math.max(4, height - activityRows);
1675
+ const room = Math.max(4, height - activityRows - 1);
1676
1676
  const listLen = listLength(files, showGenerated);
1677
1677
  const listRows = Math.max(1, Math.min(listLen, 8, Math.max(2, Math.floor(room * 0.3))));
1678
1678
  const view = fileListView(files, { selected, rows: listRows, showGenerated });
@@ -1694,7 +1694,7 @@ export function DiffPanel({ files, width, height, activity, activityRows = 0, sc
1694
1694
  * element per row is not. So the rows are described in full — the scroll needs the count — and
1695
1695
  * drawn only for the window.
1696
1696
  */
1697
- const fixedRows = 2 + list.length + hints.length + (d ? 4 : 0) + (activity ? activityRows : 0);
1697
+ const fixedRows = 3 + list.length + hints.length + (d ? 4 : 0) + (activity ? activityRows : 0);
1698
1698
  const bodyHeight = Math.max(1, height - fixedRows);
1699
1699
  const numbers = d ? d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0)) : [];
1700
1700
  const gutter = Math.max(3, String(Math.max(0, ...numbers)).length);
@@ -1719,7 +1719,7 @@ export function DiffPanel({ files, width, height, activity, activityRows = 0, sc
1719
1719
  const scrolled = maxScroll > 0
1720
1720
  ? `${from > 0 ? `↑${from}` : ''}${from < maxScroll ? ` ↓${maxScroll - from}` : ''}`.trim()
1721
1721
  : '';
1722
- return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: DIM, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: files.length === 0 ? 'no changes yet' : `${files.length} file${files.length === 1 ? '' : 's'} changed` }), view.added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", view.added] }), view.removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", view.removed] })] }), _jsx(Text, { color: DIM, children: "/diff off" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Text, { children: " " }), list, hints, d && _jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsx(Text, { color: INK, bold: true, wrap: "truncate-start", children: d.path }), _jsxs(Text, { color: DIM, children: [d.created && d.removed === 0 ? ' new' : d.deleted ? ' deleted' : '', scrolled ? ` ${scrolled}` : ''] })] }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body })] }));
1722
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: DIM, borderTop: false, paddingLeft: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: files.length === 0 ? 'no changes yet' : `${files.length} file${files.length === 1 ? '' : 's'} changed` }), view.added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", view.added] }), view.removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", view.removed] })] }), _jsx(Text, { color: DIM, children: "/diff off" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Text, { children: " " }), list, hints, d && _jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsx(Text, { color: INK, bold: true, wrap: "truncate-start", children: d.path }), _jsxs(Text, { color: DIM, children: [d.created && d.removed === 0 ? ' new' : d.deleted ? ' deleted' : '', scrolled ? ` ${scrolled}` : ''] })] }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body })] }));
1723
1723
  }
1724
1724
  /**
1725
1725
  * Exported for one reason: so a test can mount it.
@@ -2539,6 +2539,16 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2539
2539
  * been chosen either way (/mouse, or `mouse` in the config), which is then what happens.
2540
2540
  */
2541
2541
  const [sidePanelShown, setSidePanelShown] = useState(false);
2542
+ /*
2543
+ * The bell, for when KONECK needs the person: a long turn finished, or a question is waiting.
2544
+ * A terminal in another tab or window marks it — Windows Terminal flashes the tab and the
2545
+ * taskbar — so a finished hour-long run is noticed without watching for it. `bell: false` stops it.
2546
+ */
2547
+ const ring = useCallback(() => {
2548
+ if (cfg.bell === false || !process.stdout.isTTY)
2549
+ return;
2550
+ process.stdout.write('\u0007');
2551
+ }, [cfg.bell]);
2542
2552
  const mouseChosenRef = useRef(cfg.mouse !== undefined);
2543
2553
  const wheelTracked = mouse || (sidePanelShown && !mouseChosenRef.current);
2544
2554
  /*
@@ -2931,6 +2941,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2931
2941
  * all — only in the browser, and there as plain text.
2932
2942
  */
2933
2943
  const [userQ, setUserQ] = useState(null);
2944
+ // A question waiting on the person rings once, as it appears: see `ring`.
2945
+ const waitingOnPerson = !!ask || !!planAsk || !!userQ;
2946
+ useEffect(() => { if (waitingOnPerson)
2947
+ ring(); }, [waitingOnPerson, ring]);
2934
2948
  /** The picker's rows as last drawn, for a click to be matched against. */
2935
2949
  const pickerTargetsRef = useRef([]);
2936
2950
  const [updatePane, setUpdatePane] = useState(null);
@@ -7035,6 +7049,9 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7035
7049
  // whichever of those three happened last, which for anyone who closes the terminal
7036
7050
  // normally was an old snapshot — the reported "always resumes the old one".
7037
7051
  void doSave().catch(() => { });
7052
+ // A turn worth walking away from is worth being called back for.
7053
+ if (elapsed >= 30_000)
7054
+ ring();
7038
7055
  // A run that stopped short says so, rather than letting the footer imply it finished.
7039
7056
  if (incompleteRef.current) {
7040
7057
  addSystem(incompleteRef.current);
@@ -7303,7 +7320,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7303
7320
  // content, so the right edge stepped in and out; and a box inside a box left
7304
7321
  // markdown tables needlessly narrow. A marker plus the content reads better and
7305
7322
  // cannot misalign, which is how Claude Code presents its own answers.
7306
- _jsxs(Box, { flexDirection: "column", marginTop: row.continued ? 0 : 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: row.continued ? ' ' : G.brand }), _jsx(Box, { flexDirection: "column", children: _jsx(Markdown, { text: row.text ?? '', width: barWidth - 3 }) })] }), row.tokens != null && (_jsxs(Text, { color: MUTED, children: [_jsx(Text, { color: GREEN, children: "* " }), row.done ? `${row.done} in ${fmtElapsed(row.elapsed ?? 0)}` : fmtElapsed(row.elapsed ?? 0), ' | ', row.tokens, " tokens", ' | ', cfg.provider, " ", modelShort] }))] }, index)));
7323
+ _jsxs(Box, { flexDirection: "column", marginTop: row.continued ? 0 : 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: row.continued ? ' ' : G.brand }), _jsx(Box, { flexDirection: "column", children: _jsx(Markdown, { text: row.text ?? '', width: barWidth - 3 }) })] }), row.tokens != null && (_jsxs(Text, { color: MUTED, children: [_jsxs(Text, { color: GREEN, bold: true, children: [G.ok, " Done"] }), ` in ${fmtElapsed(row.elapsed ?? 0)}`, ' | ', row.tokens, " tokens", ' | ', cfg.provider, " ", modelShort] }))] }, index)));
7307
7324
  /**
7308
7325
  * A row that has stopped changing, and so never needs rendering twice.
7309
7326
  *
@@ -7808,7 +7825,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7808
7825
  })(), keyPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AMBER, paddingX: 1, marginTop: 1, width: barWidth, children: [_jsxs(Text, { color: AMBER, bold: true, children: ["API key for ", keyPrompt.provider] }), _jsx(Text, { color: MUTED, children: "Paste it and press enter, or ctrl+v to read the clipboard. Held in memory for this session only; esc to cancel." }), keyPrompt.note && _jsx(Text, { color: CYAN, children: keyPrompt.note }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: MUTED, children: [keyPrompt.env, ": "] }), _jsx(Text, { color: INK, children: '*'.repeat(Math.min(keyPrompt.value.length, 48)) }), _jsx(Cursor, {}), keyPrompt.value.length > 0 && (_jsxs(Text, { color: DIM, children: [" ", keyPrompt.value.length, " characters"] }))] })] })), themePick && (() => {
7809
7826
  const w = Math.min(barWidth, 84);
7810
7827
  const labelWidth = Math.max(...THEMES.map(t => t.label.length));
7811
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: CYAN, paddingX: 1, width: w, children: [_jsx(Text, { color: INK, bold: true, children: "Theme" }), _jsx(Text, { color: MUTED, children: "Sets KONECK's colours, not your terminal's background." }), _jsx(Text, { children: " " }), THEMES.map((t, i) => {
7828
+ return (_jsxs(Box, { marginTop: 1, flexShrink: 0, flexDirection: "column", borderStyle: "round", borderColor: CYAN, paddingX: 1, width: w, children: [_jsx(Text, { color: INK, bold: true, children: "Theme" }), _jsx(Text, { color: MUTED, children: "Sets KONECK's colours, not your terminal's background." }), _jsx(Text, { children: " " }), THEMES.map((t, i) => {
7812
7829
  const on = i === themePick.at;
7813
7830
  return (_jsxs(Text, { wrap: "truncate", ...(on ? { backgroundColor: CYAN, color: ON_ACCENT } : { color: INK }), children: [on ? ' › ' : ' ', _jsx(Text, { bold: true, children: i + 1 }), ' ', t.label.padEnd(labelWidth), _jsx(Text, { ...(on ? {} : { color: DIM }), children: ' ' + t.note }), t.name === themePick.was ? ' (current)' : '', ' '] }, t.name));
7814
7831
  }), _jsx(Text, { children: " " }), _jsx(ThemePreview, { theme: THEMES[themePick.at].name, width: w - 4 }), _jsxs(Text, { color: DIM, children: ["\u2191\u2193 or 1\u2013", THEMES.length, " to try \u00B7 enter to keep \u00B7 esc to go back"] })] }));
@@ -7820,7 +7837,15 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7820
7837
  '',
7821
7838
  'Press a number, or up/down and enter, or click one — or type your own answer. Esc hides this.',
7822
7839
  ];
7823
- return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: AMBER, title: "The model is asking", children: lines }) }));
7840
+ /*
7841
+ * Never shrunk: the transcript above gives way instead.
7842
+ *
7843
+ * Reported with a screenshot: on the alternate screen, after a long reply, only the last
7844
+ * line of this panel showed — "minor bug fixes" in an amber box — and the question and the
7845
+ * other options were clipped away, so it looked like a stray message rather than a
7846
+ * question. The transcript is the box meant to give ground; this one must not.
7847
+ */
7848
+ return (_jsx(Box, { marginTop: 1, flexShrink: 0, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: AMBER, title: "The model is asking", children: lines }) }));
7824
7849
  })(), planAsk && (() => {
7825
7850
  /*
7826
7851
  * The question, straight after the plan rather than instead of it.
@@ -7837,7 +7862,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7837
7862
  'Up/down and enter, or press the number. Esc leaves it in plan mode.',
7838
7863
  ...(planAsk.file ? [`Saved to \`${planAsk.file}\``] : ['This plan could not be written to disk.']),
7839
7864
  ];
7840
- return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: CYAN, title: "Plan ready", children: lines }) }));
7865
+ return (_jsx(Box, { marginTop: 1, flexShrink: 0, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: CYAN, title: "Plan ready", children: lines }) }));
7841
7866
  })(), ask && (() => {
7842
7867
  let detail = '';
7843
7868
  let isCommand = false;
@@ -7955,9 +7980,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7955
7980
  const notices = [];
7956
7981
  if (agentState !== 'ready')
7957
7982
  notices.push({ text: statusLabel.toLowerCase(), color: statusColor });
7958
- const planNote = plan ? planSummary(plan) : '';
7959
- if (planNote)
7960
- notices.push({ text: `plan ${planNote}`, color: CYAN });
7961
7983
  if (effort !== 'medium')
7962
7984
  notices.push({ text: `effort ${effort}`, color: AMBER });
7963
7985
  if (autoUpdated)
@@ -7970,6 +7992,15 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7970
7992
  notices.push({ text: `${pasteCount} paste${pasteCount === 1 ? '' : 's'} held, sent in full`,
7971
7993
  color: AMBER });
7972
7994
  }
7995
+ // The plan last, given only the room the rest leaves: its step is the one notice of no
7996
+ // fixed length, and the screen edge used to cut it mid-word ("…critical journeys; r").
7997
+ if (plan) {
7998
+ const used = displayWidth(ml.title) + 2 + displayWidth(ml.hint)
7999
+ + notices.reduce((w, n) => w + 5 + displayWidth(n.text), 0) + 5 + displayWidth('plan ');
8000
+ const planNote = planSummary(plan, Math.max(0, uiWidth - 9 - used));
8001
+ if (planNote)
8002
+ notices.push({ text: `plan ${planNote}`, color: CYAN });
8003
+ }
7973
8004
  return (_jsx(Box, { marginTop: 1, paddingX: 2, width: uiWidth - 5, flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: TONE[ml.tone], bold: true, children: ml.title }), _jsxs(Text, { color: MUTED, children: [' ', ml.hint] }), notices.map((n, i) => (_jsxs(Text, { children: [_jsx(Text, { color: DIM, children: ' · ' }), _jsx(Text, { color: n.color, children: n.text })] }, i)))] }, `m${notices.length}`) }));
7974
8005
  })(), _jsx(Box, { borderStyle: "single", borderLeft: false, borderRight: false, borderColor: DIM, paddingX: 2, width: uiWidth - 5, flexShrink: 0, children: _jsxs(Text, { wrap: "wrap", children: [_jsxs(Text, { color: busy ? DIM : AMBER, bold: true, children: [G.caret, " "] }), (() => {
7975
8006
  const sel = selectionRange(selAnchor, caret, draft.length);