koneck 2.128.72 → 2.128.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/config-store.d.ts +2 -0
  2. package/dist/config-store.d.ts.map +1 -1
  3. package/dist/config-store.js +8 -2
  4. package/dist/config-store.js.map +1 -1
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/handoff.d.ts.map +1 -1
  9. package/dist/handoff.js +14 -2
  10. package/dist/handoff.js.map +1 -1
  11. package/dist/index.js +7 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/ink-chat.d.ts +2 -0
  14. package/dist/ink-chat.d.ts.map +1 -1
  15. package/dist/ink-chat.js +123 -25
  16. package/dist/ink-chat.js.map +1 -1
  17. package/dist/plan.d.ts +9 -2
  18. package/dist/plan.d.ts.map +1 -1
  19. package/dist/plan.js +20 -4
  20. package/dist/plan.js.map +1 -1
  21. package/dist/provider-delete.d.ts +37 -0
  22. package/dist/provider-delete.d.ts.map +1 -0
  23. package/dist/provider-delete.js +76 -0
  24. package/dist/provider-delete.js.map +1 -0
  25. package/dist/run-progress.d.ts.map +1 -1
  26. package/dist/run-progress.js +11 -5
  27. package/dist/run-progress.js.map +1 -1
  28. package/dist/session-changes.d.ts +6 -0
  29. package/dist/session-changes.d.ts.map +1 -1
  30. package/dist/session-changes.js +10 -0
  31. package/dist/session-changes.js.map +1 -1
  32. package/dist/tools.d.ts +10 -0
  33. package/dist/tools.d.ts.map +1 -1
  34. package/dist/tools.js +57 -1
  35. package/dist/tools.js.map +1 -1
  36. package/dist/types.d.ts +2 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/web/api.d.ts.map +1 -1
  39. package/dist/web/api.js +14 -9
  40. package/dist/web/api.js.map +1 -1
  41. package/dist/web/ui-client.d.ts.map +1 -1
  42. package/dist/web/ui-client.js +21 -11
  43. package/dist/web/ui-client.js.map +1 -1
  44. package/package.json +1 -1
package/dist/ink-chat.js CHANGED
@@ -10,7 +10,8 @@ import { estimateCost, formatCost, rateText } from './pricing.js';
10
10
  import { generateSessionId, saveSession, listSessions, loadSession, relativeAge, renameSession, forkSession, archiveSession, findSession, } from './session.js';
11
11
  import { loadMemory } from './memory.js';
12
12
  import { loadKoneckConfig as loadConfigAt, saveKoneckConfig as saveConfigAt, setConfigKey as setConfigKeyAt, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue, settingsWindow } from './config-store.js';
13
- import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, removeUserProvider, userProvidersPath, keyEnvFor, BUILT_INS as BUILT_IN_PROVIDERS, servesLocally, servesOwnWeights, endpointForProvider } from './providers.js';
13
+ import { deleteProvider, deletable, describeDeleted } from './provider-delete.js';
14
+ import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath, keyEnvFor, BUILT_INS as BUILT_IN_PROVIDERS, servesLocally, servesOwnWeights, endpointForProvider } from './providers.js';
14
15
  import { normaliseEndpoint, withEndpoint, savedEndpoints, shortSource, resolveModelChoice, withModel } from './endpoints.js';
15
16
  import { loadProjectConfig, loadGlobalConfig, saveGlobalConfig, withoutStoredApiKey } from './config.js';
16
17
  import { rememberKey, forgetAllKeys } from './credentials.js';
@@ -347,6 +348,11 @@ export function wrapPreserving(text, width) {
347
348
  * file, which every earlier grep had failed to match for that reason.
348
349
  */
349
350
  export const ADD_PROVIDER = '\u0000add-provider';
351
+ /** The provider picker's delete row, and the rows of the two pickers it leads to. */
352
+ export const DELETE_PROVIDER = '\u0000delete-provider';
353
+ const DELETE_ONE = '\u0000delete:';
354
+ const DELETE_CONFIRMED = '\u0000delete-confirmed:';
355
+ const DELETE_KEPT = '\u0000delete-kept';
350
356
  /**
351
357
  * Checks one field and says what is wrong with it, or nothing.
352
358
  *
@@ -751,7 +757,7 @@ const COMMANDS = [
751
757
  { cmd: '/key', desc: 'Enter an API key for the current provider' },
752
758
  { cmd: '/logout', desc: 'Forget Koneck-stored API credentials; keep preferences' },
753
759
  { cmd: '/model', desc: 'Show or set the model by name' },
754
- { cmd: '/provider', desc: 'Show or switch provider' },
760
+ { cmd: '/provider', desc: 'Show, switch, add or delete a provider' },
755
761
  { cmd: '/status', desc: 'Provider, model, mode, tokens, cost, workspace' },
756
762
  { cmd: '/diff', desc: 'Open the live diff panel; /diff off closes it' },
757
763
  { cmd: '/permissions', desc: 'Show or set approval posture: on or off' },
@@ -2539,6 +2545,16 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2539
2545
  * been chosen either way (/mouse, or `mouse` in the config), which is then what happens.
2540
2546
  */
2541
2547
  const [sidePanelShown, setSidePanelShown] = useState(false);
2548
+ /*
2549
+ * The bell, for when KONECK needs the person: a long turn finished, or a question is waiting.
2550
+ * A terminal in another tab or window marks it — Windows Terminal flashes the tab and the
2551
+ * taskbar — so a finished hour-long run is noticed without watching for it. `bell: false` stops it.
2552
+ */
2553
+ const ring = useCallback(() => {
2554
+ if (cfg.bell === false || !process.stdout.isTTY)
2555
+ return;
2556
+ process.stdout.write('\u0007');
2557
+ }, [cfg.bell]);
2542
2558
  const mouseChosenRef = useRef(cfg.mouse !== undefined);
2543
2559
  const wheelTracked = mouse || (sidePanelShown && !mouseChosenRef.current);
2544
2560
  /*
@@ -2931,6 +2947,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
2931
2947
  * all — only in the browser, and there as plain text.
2932
2948
  */
2933
2949
  const [userQ, setUserQ] = useState(null);
2950
+ // A question waiting on the person rings once, as it appears: see `ring`.
2951
+ const waitingOnPerson = !!ask || !!planAsk || !!userQ;
2952
+ useEffect(() => { if (waitingOnPerson)
2953
+ ring(); }, [waitingOnPerson, ring]);
2934
2954
  /** The picker's rows as last drawn, for a click to be matched against. */
2935
2955
  const pickerTargetsRef = useRef([]);
2936
2956
  const [updatePane, setUpdatePane] = useState(null);
@@ -4097,6 +4117,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
4097
4117
  return [
4098
4118
  { value: ADD_PROVIDER, label: '+ add a provider',
4099
4119
  desc: 'an OpenAI-compatible endpoint KONECK does not ship with' },
4120
+ // Only when there is something to delete, so the row never leads to an empty list.
4121
+ ...(Object.keys(PROVIDERS).some(n => deletable(n))
4122
+ ? [{ value: DELETE_PROVIDER, label: '- delete one',
4123
+ desc: 'delete a provider you added, or the key you stored for one' }]
4124
+ : []),
4100
4125
  ...Object.values(PROVIDERS).map(p => ({
4101
4126
  value: p.name,
4102
4127
  label: p.name,
@@ -4105,6 +4130,46 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
4105
4130
  })),
4106
4131
  ];
4107
4132
  }
4133
+ /** What can be deleted, as picker rows: yours first, then built-ins with something stored. */
4134
+ function deleteProviderItems() {
4135
+ return Object.values(PROVIDERS).filter(p => deletable(p.name))
4136
+ .sort((a, b) => Number(!!b.userDefined) - Number(!!a.userDefined))
4137
+ .map(p => ({
4138
+ value: DELETE_ONE + p.name, label: p.name,
4139
+ desc: p.userDefined ? `yours - ${p.baseURL}` : 'built in - forgets the key and settings you stored',
4140
+ current: p.name === cfg.provider,
4141
+ }));
4142
+ }
4143
+ /**
4144
+ * Deletes a provider and says what went. The provider in use loses its key in this session too,
4145
+ * and when it is gone altogether the picker opens to choose another — the next message would
4146
+ * otherwise fail against a provider that no longer exists.
4147
+ */
4148
+ async function deleteProviderNamed(name) {
4149
+ const wasCurrent = name === cfg.provider;
4150
+ const done = await deleteProvider(name);
4151
+ if (!done) {
4152
+ addSystem(PROVIDERS[name]
4153
+ ? `Nothing is stored for "${name}" — it ships with KONECK, with no key or settings of yours to delete.`
4154
+ : `No provider called "${name}".`);
4155
+ return;
4156
+ }
4157
+ (await import('./model-catalog.js')).forgetCatalog(name);
4158
+ if (!wasCurrent) {
4159
+ addSystem(describeDeleted(done));
4160
+ return;
4161
+ }
4162
+ if (PROVIDERS[name]) {
4163
+ const cleared = { ...cfg, apiKey: undefined };
4164
+ cfgRef.current = cleared;
4165
+ setCfg(cleared);
4166
+ await resetSession(cleared);
4167
+ addSystem(describeDeleted(done) + ' This session was using it, and has been reset.');
4168
+ return;
4169
+ }
4170
+ addSystem(describeDeleted(done) + ' This session was using it — choose another below.');
4171
+ openPicker('provider', providerItems());
4172
+ }
4108
4173
  /** Opens the declare-a-provider prompt, optionally with fields already known. */
4109
4174
  function startAddProvider(seed = {}) {
4110
4175
  setAddProv({ step: 'name', name: '', url: '', model: '', value: '', ...seed });
@@ -4322,6 +4387,28 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
4322
4387
  startAddProvider();
4323
4388
  return;
4324
4389
  }
4390
+ // Deleting: choose which, then say yes. The key cannot be got back from KONECK once it is gone.
4391
+ if (kind === 'provider' && item.value === DELETE_PROVIDER) {
4392
+ openPicker('provider', deleteProviderItems());
4393
+ return;
4394
+ }
4395
+ if (kind === 'provider' && item.value.startsWith(DELETE_ONE)) {
4396
+ const name = item.value.slice(DELETE_ONE.length);
4397
+ openPicker('provider', [
4398
+ { value: DELETE_CONFIRMED + name, label: `yes, delete ${name}`,
4399
+ desc: PROVIDERS[name]?.userDefined ? 'its declaration, stored key and saved settings' : 'its stored key and saved settings' },
4400
+ { value: DELETE_KEPT, label: 'no, keep it', desc: '' },
4401
+ ]);
4402
+ return;
4403
+ }
4404
+ if (kind === 'provider' && item.value.startsWith(DELETE_CONFIRMED)) {
4405
+ await deleteProviderNamed(item.value.slice(DELETE_CONFIRMED.length));
4406
+ return;
4407
+ }
4408
+ if (kind === 'provider' && item.value === DELETE_KEPT) {
4409
+ addSystem('Kept.');
4410
+ return;
4411
+ }
4325
4412
  if (kind === 'provider') {
4326
4413
  const def = PROVIDERS[item.value];
4327
4414
  // The endpoint saved for the provider being switched to, not its built-in address. Passing
@@ -4792,25 +4879,19 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
4792
4879
  });
4793
4880
  return;
4794
4881
  }
4795
- if (verb?.toLowerCase() === 'remove' || verb?.toLowerCase() === 'forget') {
4882
+ if (['remove', 'delete', 'forget'].includes(verb?.toLowerCase() ?? '')) {
4796
4883
  const target = (restArgs[0] ?? '').toLowerCase();
4797
4884
  if (!target) {
4798
- const mine = Object.values(PROVIDERS).filter(p => p.userDefined).map(p => p.name);
4799
- addSystem(mine.length
4800
- ? `Usage: /provider remove <name>\nYours: ${mine.join(', ')}`
4801
- : 'You have not declared any providers, so there is nothing to remove.');
4802
- return;
4803
- }
4804
- const wasShadowing = BUILT_IN_PROVIDERS[target] !== undefined;
4805
- if (!removeUserProvider(target)) {
4806
- addSystem(PROVIDERS[target]
4807
- ? `"${target}" ships with KONECK — it was not declared by you, so there is nothing to remove.`
4808
- : `No declared provider called "${target}".`);
4885
+ const items = deleteProviderItems();
4886
+ if (!items.length) {
4887
+ addSystem('Nothing to delete: you have added no providers and stored no keys.');
4888
+ return;
4889
+ }
4890
+ openPicker('provider', items);
4809
4891
  return;
4810
4892
  }
4811
- addSystem(wasShadowing
4812
- ? `Removed your "${target}". The built-in one is back, pointing at ${BUILT_IN_PROVIDERS[target].baseURL}.`
4813
- : `Removed "${target}".`);
4893
+ // Typed out in full, the name is the confirmation; the picker asks, because a row is one key away.
4894
+ await deleteProviderNamed(target);
4814
4895
  return;
4815
4896
  }
4816
4897
  if (!arg) {
@@ -4830,7 +4911,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
4830
4911
  + '\nSwitch: /provider <name>, or pick one below\n'
4831
4912
  + 'Add: /provider add — asks for a name, an endpoint and a default model\n'
4832
4913
  + ' /provider add <name> <url> [model] if you have them to hand\n'
4833
- + 'Remove: /provider remove <name>');
4914
+ + 'Delete: /provider delete [name] — one you added, or the key you stored for one');
4834
4915
  openPicker('provider', providerItems());
4835
4916
  return;
4836
4917
  }
@@ -7035,6 +7116,9 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7035
7116
  // whichever of those three happened last, which for anyone who closes the terminal
7036
7117
  // normally was an old snapshot — the reported "always resumes the old one".
7037
7118
  void doSave().catch(() => { });
7119
+ // A turn worth walking away from is worth being called back for.
7120
+ if (elapsed >= 30_000)
7121
+ ring();
7038
7122
  // A run that stopped short says so, rather than letting the footer imply it finished.
7039
7123
  if (incompleteRef.current) {
7040
7124
  addSystem(incompleteRef.current);
@@ -7303,7 +7387,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7303
7387
  // content, so the right edge stepped in and out; and a box inside a box left
7304
7388
  // markdown tables needlessly narrow. A marker plus the content reads better and
7305
7389
  // 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)));
7390
+ _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
7391
  /**
7308
7392
  * A row that has stopped changing, and so never needs rendering twice.
7309
7393
  *
@@ -7808,7 +7892,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7808
7892
  })(), 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
7893
  const w = Math.min(barWidth, 84);
7810
7894
  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) => {
7895
+ 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
7896
  const on = i === themePick.at;
7813
7897
  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
7898
  }), _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 +7904,15 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7820
7904
  '',
7821
7905
  'Press a number, or up/down and enter, or click one — or type your own answer. Esc hides this.',
7822
7906
  ];
7823
- return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: AMBER, title: "The model is asking", children: lines }) }));
7907
+ /*
7908
+ * Never shrunk: the transcript above gives way instead.
7909
+ *
7910
+ * Reported with a screenshot: on the alternate screen, after a long reply, only the last
7911
+ * line of this panel showed — "minor bug fixes" in an amber box — and the question and the
7912
+ * other options were clipped away, so it looked like a stray message rather than a
7913
+ * question. The transcript is the box meant to give ground; this one must not.
7914
+ */
7915
+ 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
7916
  })(), planAsk && (() => {
7825
7917
  /*
7826
7918
  * The question, straight after the plan rather than instead of it.
@@ -7837,7 +7929,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7837
7929
  'Up/down and enter, or press the number. Esc leaves it in plan mode.',
7838
7930
  ...(planAsk.file ? [`Saved to \`${planAsk.file}\``] : ['This plan could not be written to disk.']),
7839
7931
  ];
7840
- return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: CYAN, title: "Plan ready", children: lines }) }));
7932
+ return (_jsx(Box, { marginTop: 1, flexShrink: 0, children: _jsx(Panel, { width: Math.min(barWidth, 84), color: CYAN, title: "Plan ready", children: lines }) }));
7841
7933
  })(), ask && (() => {
7842
7934
  let detail = '';
7843
7935
  let isCommand = false;
@@ -7955,9 +8047,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7955
8047
  const notices = [];
7956
8048
  if (agentState !== 'ready')
7957
8049
  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
8050
  if (effort !== 'medium')
7962
8051
  notices.push({ text: `effort ${effort}`, color: AMBER });
7963
8052
  if (autoUpdated)
@@ -7970,6 +8059,15 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
7970
8059
  notices.push({ text: `${pasteCount} paste${pasteCount === 1 ? '' : 's'} held, sent in full`,
7971
8060
  color: AMBER });
7972
8061
  }
8062
+ // The plan last, given only the room the rest leaves: its step is the one notice of no
8063
+ // fixed length, and the screen edge used to cut it mid-word ("…critical journeys; r").
8064
+ if (plan) {
8065
+ const used = displayWidth(ml.title) + 2 + displayWidth(ml.hint)
8066
+ + notices.reduce((w, n) => w + 5 + displayWidth(n.text), 0) + 5 + displayWidth('plan ');
8067
+ const planNote = planSummary(plan, Math.max(0, uiWidth - 9 - used));
8068
+ if (planNote)
8069
+ notices.push({ text: `plan ${planNote}`, color: CYAN });
8070
+ }
7973
8071
  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
8072
  })(), _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
8073
  const sel = selectionRange(selAnchor, caret, draft.length);