koneck 2.25.55 → 2.25.57
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.d.ts +27 -0
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +84 -17
- package/dist/ink-chat.js.map +1 -1
- package/dist/logo.d.ts +5 -1
- package/dist/logo.d.ts.map +1 -1
- package/dist/logo.js +5 -5
- package/dist/logo.js.map +1 -1
- package/dist/redact.d.ts +21 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +61 -0
- package/dist/redact.js.map +1 -0
- package/dist/session.d.ts +14 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +8 -4
- package/dist/session.js.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.d.ts
CHANGED
|
@@ -129,6 +129,33 @@ export declare function askOptions(tool: string, prefix: string): AskOption[];
|
|
|
129
129
|
* characters of one enormous line, which is a fragment of a sentence and tells nobody anything.
|
|
130
130
|
*/
|
|
131
131
|
export declare function thinkingTail(text: string, lines: number, width: number): string[];
|
|
132
|
+
/**
|
|
133
|
+
* Where a paste belongs, given what is on screen.
|
|
134
|
+
*
|
|
135
|
+
* Pulled out as a decision of its own because it was wrong and invisible: the paste capture ran
|
|
136
|
+
* before every other branch and always routed to the composer, so pasting an API key into the
|
|
137
|
+
* prompt asking for one put the key in the message box — on screen, and one keystroke from being
|
|
138
|
+
* sent to a model.
|
|
139
|
+
*/
|
|
140
|
+
export type PasteTarget = 'key-prompt' | 'composer' | 'drop';
|
|
141
|
+
export declare function pasteTarget(open: {
|
|
142
|
+
keyPrompt?: boolean;
|
|
143
|
+
picker?: boolean;
|
|
144
|
+
ask?: boolean;
|
|
145
|
+
}): PasteTarget;
|
|
146
|
+
/** Bytes as a person reads them, for a session listing. */
|
|
147
|
+
export declare function humanBytes(bytes: number): string;
|
|
148
|
+
/**
|
|
149
|
+
* What to call a session in a list.
|
|
150
|
+
*
|
|
151
|
+
* A name if one was given, otherwise the first thing asked of it — which is what people remember
|
|
152
|
+
* a piece of work by. The id is the last resort, because a timestamp identifies nothing.
|
|
153
|
+
*/
|
|
154
|
+
export declare function sessionTitle(meta: {
|
|
155
|
+
name?: string;
|
|
156
|
+
task?: string;
|
|
157
|
+
id: string;
|
|
158
|
+
}): string;
|
|
132
159
|
export declare function runInkChatMode(config: AgentConfig): Promise<void>;
|
|
133
160
|
export {};
|
|
134
161
|
//# sourceMappingURL=ink-chat.d.ts.map
|
package/dist/ink-chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
|
package/dist/ink-chat.js
CHANGED
|
@@ -36,6 +36,7 @@ import { AskQueue } from './ask-queue.js';
|
|
|
36
36
|
import { planSummary } from './plan.js';
|
|
37
37
|
import { logoFor } from './logo.js';
|
|
38
38
|
import { describeActivity } from './activity.js';
|
|
39
|
+
import { redactSecrets, containsSecret } from './redact.js';
|
|
39
40
|
import { renderTrajectory } from './trajectory.js';
|
|
40
41
|
import { describeSandbox, resolveSandbox } from './sandbox.js';
|
|
41
42
|
const CYAN = '#54D7E7';
|
|
@@ -448,6 +449,7 @@ const COMMANDS = [
|
|
|
448
449
|
{ cmd: '/thinking', desc: 'Show or hide the model\'s reasoning as it arrives' },
|
|
449
450
|
{ cmd: '/activity', desc: 'What the model has done this session, and what it is on now' },
|
|
450
451
|
{ cmd: '/trajectory', desc: 'Where the time went: per-request timings, tool durations, waits' },
|
|
452
|
+
{ cmd: '/update', desc: 'Check for a new version and update' },
|
|
451
453
|
{ cmd: '/name', desc: 'Name this session, so /resume can find it by name' },
|
|
452
454
|
{ cmd: '/fork', desc: 'Copy this session and continue on the copy' },
|
|
453
455
|
{ cmd: '/archive', desc: 'Keep a session but hide it from /resume' },
|
|
@@ -561,13 +563,44 @@ export function thinkingTail(text, lines, width) {
|
|
|
561
563
|
.filter(line => line.trim() !== '');
|
|
562
564
|
return wrapped.slice(-lines);
|
|
563
565
|
}
|
|
566
|
+
export function pasteTarget(open) {
|
|
567
|
+
if (open.keyPrompt)
|
|
568
|
+
return 'key-prompt';
|
|
569
|
+
// A picker and a permission question both take single keys. A pasted blob is not an answer to
|
|
570
|
+
// either, and answering something nobody chose is worse than ignoring it.
|
|
571
|
+
if (open.picker || open.ask)
|
|
572
|
+
return 'drop';
|
|
573
|
+
return 'composer';
|
|
574
|
+
}
|
|
575
|
+
/** Bytes as a person reads them, for a session listing. */
|
|
576
|
+
export function humanBytes(bytes) {
|
|
577
|
+
if (bytes < 1024)
|
|
578
|
+
return `${bytes}B`;
|
|
579
|
+
if (bytes < 1024 * 1024)
|
|
580
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
581
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* What to call a session in a list.
|
|
585
|
+
*
|
|
586
|
+
* A name if one was given, otherwise the first thing asked of it — which is what people remember
|
|
587
|
+
* a piece of work by. The id is the last resort, because a timestamp identifies nothing.
|
|
588
|
+
*/
|
|
589
|
+
export function sessionTitle(meta) {
|
|
590
|
+
if (meta.name)
|
|
591
|
+
return meta.name;
|
|
592
|
+
const task = redactSecrets(meta.task ?? '').replace(/\s+/g, ' ').trim();
|
|
593
|
+
if (!task || task === '(chat session)')
|
|
594
|
+
return meta.id;
|
|
595
|
+
return task.length > 64 ? task.slice(0, 63) + '…' : task;
|
|
596
|
+
}
|
|
564
597
|
const MODES = MODE_SPECS.map(m => m.name);
|
|
565
598
|
const HELP_TEXT = `
|
|
566
599
|
Type / on an empty line to browse every command; keep typing to filter.
|
|
567
600
|
|
|
568
601
|
Slash commands:
|
|
569
602
|
/help Show this help
|
|
570
|
-
|
|
603
|
+
/update Updates and a key guide, in a side panel
|
|
571
604
|
/models [filter] Browse models from the connected provider and switch
|
|
572
605
|
/connect [provider] Connect a provider (picker when no name given)
|
|
573
606
|
/connect <name> <url> Declare a provider KONECK does not ship with, e.g.
|
|
@@ -1232,8 +1265,12 @@ function App({ config: initialConfig }) {
|
|
|
1232
1265
|
async function doSave() {
|
|
1233
1266
|
const s = await activeSession.p.catch(() => null);
|
|
1234
1267
|
const firstUser = s?.messages.find((m) => m.role === 'user');
|
|
1268
|
+
// Masked before it is written. A key pasted into the composer became the first user message,
|
|
1269
|
+
// which is where this title comes from — so it was persisted to disk and then printed in the
|
|
1270
|
+
// resume picker in full.
|
|
1235
1271
|
const task = firstUser && typeof firstUser.content === 'string'
|
|
1236
|
-
? firstUser.content.slice(0, 120)
|
|
1272
|
+
? redactSecrets(firstUser.content).slice(0, 120)
|
|
1273
|
+
: '(chat session)';
|
|
1237
1274
|
// Both of these come from the live session rather than from React state: a save fired at the
|
|
1238
1275
|
// end of a turn runs inside a closure that captured state as it was when the turn began, so
|
|
1239
1276
|
// reading `stats` here recorded turn counts one behind and, after a resume, the wrong id.
|
|
@@ -1243,8 +1280,14 @@ function App({ config: initialConfig }) {
|
|
|
1243
1280
|
// runs every turn would otherwise wipe a name the moment the agent replied.
|
|
1244
1281
|
const existing = await loadSession(cfg.cwd, id).then(r => r.meta).catch(() => null);
|
|
1245
1282
|
const livePlan = s?.plan?.steps.length ? { steps: s.plan.steps } : undefined;
|
|
1283
|
+
// Captured when saving, because a branch cannot be recovered from the file later — by then the
|
|
1284
|
+
// checkout has moved on.
|
|
1285
|
+
const branch = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: cfg.cwd, reject: false })
|
|
1286
|
+
.then(r => r.stdout.trim())
|
|
1287
|
+
.catch(() => '');
|
|
1246
1288
|
return saveSession(cfg.cwd, id, {
|
|
1247
1289
|
...(livePlan ? { plan: livePlan } : {}),
|
|
1290
|
+
...(branch && branch !== 'HEAD' ? { branch } : {}),
|
|
1248
1291
|
...(existing?.name ? { name: existing.name } : {}),
|
|
1249
1292
|
...(existing?.archived ? { archived: existing.archived } : {}),
|
|
1250
1293
|
...(existing?.forkedFrom ? { forkedFrom: existing.forkedFrom } : {}),
|
|
@@ -1782,6 +1825,10 @@ function App({ config: initialConfig }) {
|
|
|
1782
1825
|
: 'Hiding the reasoning. The token count stays on the working line.');
|
|
1783
1826
|
return;
|
|
1784
1827
|
}
|
|
1828
|
+
case '/update': {
|
|
1829
|
+
void openUpdatePane();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1785
1832
|
case '/trajectory': {
|
|
1786
1833
|
// The timings the transcript cannot answer for: where the time went, which tool was slow,
|
|
1787
1834
|
// how much of a long turn was spent waiting for a first token rather than generating.
|
|
@@ -1936,13 +1983,21 @@ function App({ config: initialConfig }) {
|
|
|
1936
1983
|
return;
|
|
1937
1984
|
}
|
|
1938
1985
|
if (pick === '') {
|
|
1986
|
+
// The title comes first because that is what tells two sessions apart; the id is a
|
|
1987
|
+
// timestamp and says only when. Everything under it is context: how long ago, which
|
|
1988
|
+
// branch, how much work. Masked on display as well as on write, since the files already
|
|
1989
|
+
// on disk were written before that existed and one of them holds a key.
|
|
1939
1990
|
openPicker('resume', saved.slice(0, 20).map((x, i) => ({
|
|
1940
1991
|
value: x.id,
|
|
1941
|
-
label: x
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1992
|
+
label: sessionTitle(x),
|
|
1993
|
+
desc: [
|
|
1994
|
+
i === 0 ? 'latest' : relativeAge(x.updatedAt),
|
|
1995
|
+
...(i === 0 ? [relativeAge(x.updatedAt)] : []),
|
|
1996
|
+
...(x.branch ? [x.branch] : []),
|
|
1997
|
+
`${x.turns} turn${x.turns === 1 ? '' : 's'}`,
|
|
1998
|
+
...(x.totalTokens > 0 ? [`${humanTokens(x.totalTokens)} tok`] : []),
|
|
1999
|
+
...(x.sizeBytes ? [humanBytes(x.sizeBytes)] : []),
|
|
2000
|
+
].join(' · '),
|
|
1946
2001
|
})));
|
|
1947
2002
|
return;
|
|
1948
2003
|
}
|
|
@@ -2253,6 +2308,15 @@ function App({ config: initialConfig }) {
|
|
|
2253
2308
|
function filePaste(text) {
|
|
2254
2309
|
const clean = text.replace(/\r\n?/g, '\n');
|
|
2255
2310
|
const lines = clean.split('\n').length;
|
|
2311
|
+
// Whatever owns the keyboard gets the paste.
|
|
2312
|
+
const target = pasteTarget({ keyPrompt: Boolean(keyPrompt), picker: Boolean(picker), ask: Boolean(ask) });
|
|
2313
|
+
if (target === 'drop')
|
|
2314
|
+
return;
|
|
2315
|
+
if (target === 'key-prompt') {
|
|
2316
|
+
// A key pasted from a manager often carries a trailing newline; it is not part of the key.
|
|
2317
|
+
setKeyPrompt(p => (p ? { ...p, value: p.value + clean.replace(/\s+$/, '') } : p));
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2256
2320
|
if (!clean.includes('\n') && clean.length <= 200) {
|
|
2257
2321
|
setDraft(d => d.slice(0, caret) + clean + d.slice(caret));
|
|
2258
2322
|
setCaret(c => c + clean.length);
|
|
@@ -2576,15 +2640,11 @@ function App({ config: initialConfig }) {
|
|
|
2576
2640
|
}
|
|
2577
2641
|
}
|
|
2578
2642
|
// Shift+U → the update and guide panel.
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
else
|
|
2585
|
-
void openUpdatePane();
|
|
2586
|
-
return;
|
|
2587
|
-
}
|
|
2643
|
+
// There is deliberately no shortcut to open this any more. It was shift+u, which is exactly
|
|
2644
|
+
// how a capital U is typed: the pane opened instead of the letter appearing, and there was no
|
|
2645
|
+
// way to write one at all. /update opens it, which is discoverable from /help and the palette
|
|
2646
|
+
// rather than from a habit, and cannot collide with anything anyone types.
|
|
2647
|
+
//
|
|
2588
2648
|
// Inside the panel: u applies the update, esc closes it.
|
|
2589
2649
|
if (updatePane) {
|
|
2590
2650
|
if (key.escape) {
|
|
@@ -2764,6 +2824,13 @@ function App({ config: initialConfig }) {
|
|
|
2764
2824
|
setPasteCount(0);
|
|
2765
2825
|
// Paths that really exist are shortened to their name for the transcript only. The model
|
|
2766
2826
|
// still receives the full path, which it needs in order to open the file.
|
|
2827
|
+
// Said out loud rather than quietly sent. A key in a prompt reaches the provider, the
|
|
2828
|
+
// session file and the scrollback, and the only useful moment to mention it is now.
|
|
2829
|
+
if (containsSecret(task)) {
|
|
2830
|
+
addSystem('That looks like an API key or token. It has been sent as typed — treat it as exposed ' +
|
|
2831
|
+
'and rotate it. Keys belong in /key or an environment variable, where they are not ' +
|
|
2832
|
+
'written into the session log.');
|
|
2833
|
+
}
|
|
2767
2834
|
const mentions = findMentions(task, pathKind);
|
|
2768
2835
|
const forDisplay = pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task);
|
|
2769
2836
|
addRow({ role: 'user', text: forDisplay });
|
|
@@ -3231,7 +3298,7 @@ function App({ config: initialConfig }) {
|
|
|
3231
3298
|
}
|
|
3232
3299
|
lines.push('');
|
|
3233
3300
|
lines.push('---');
|
|
3234
|
-
lines.push('**Keys**
|
|
3301
|
+
lines.push('**Keys** tab analytics · shift+tab mode · ctrl+c exit · /update for this panel');
|
|
3235
3302
|
lines.push('**Editing** arrows move · ctrl+a/e ends · ctrl+w word · ctrl+u/k cut');
|
|
3236
3303
|
lines.push('**Sessions** /sessions · /send <repo> <task> · /ask <repo> <q> · /resume');
|
|
3237
3304
|
lines.push('**Changelog** github.com/Gubevu/konech/commits/main');
|