koneck 2.128.0 → 2.128.1
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/browser.d.ts +2 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -2
- package/dist/browser.js.map +1 -1
- package/dist/clipboard.d.ts +17 -0
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +133 -0
- package/dist/clipboard.js.map +1 -1
- package/dist/config-store.d.ts +1 -0
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +3 -2
- package/dist/config-store.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +30 -12
- package/dist/engine.js.map +1 -1
- package/dist/handoff.d.ts.map +1 -1
- package/dist/handoff.js +1 -2
- package/dist/handoff.js.map +1 -1
- package/dist/image-draft.d.ts +9 -0
- package/dist/image-draft.d.ts.map +1 -0
- package/dist/image-draft.js +39 -0
- package/dist/image-draft.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +112 -29
- package/dist/ink-chat.js.map +1 -1
- package/dist/ledger.d.ts +3 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +21 -6
- package/dist/ledger.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/ink-chat.js
CHANGED
|
@@ -13,7 +13,8 @@ import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, removeUserProvi
|
|
|
13
13
|
import { normaliseEndpoint, withEndpoint, savedEndpoints, shortSource, resolveModelChoice, withModel } from './endpoints.js';
|
|
14
14
|
import { loadProjectConfig, loadGlobalConfig, saveGlobalConfig, withoutStoredApiKey } from './config.js';
|
|
15
15
|
import { rememberKey, forgetAllKeys } from './credentials.js';
|
|
16
|
-
import {
|
|
16
|
+
import { ImageDraft, pastedImagePath } from './image-draft.js';
|
|
17
|
+
import { readClipboard, readClipboardImage, startSelectionCopy, cleanPastedSecret } from './clipboard.js';
|
|
17
18
|
import { paceFrom } from './pace.js';
|
|
18
19
|
import { policyFrom, FAILOVER_OFF } from './failover.js';
|
|
19
20
|
import { listCheckpoints, revertCheckpoint, previewCheckpoint, snapshotTree, filesTouchedBy, createWorkspaceCheckpoint, checkpointScope } from './checkpoint.js';
|
|
@@ -500,7 +501,7 @@ const COMMANDS = [
|
|
|
500
501
|
{ cmd: '/queue', desc: 'Inspect or clear prompts waiting behind the current task' },
|
|
501
502
|
{ cmd: '/new', desc: 'New session (alias of /clear)' },
|
|
502
503
|
{ cmd: '/reset', desc: 'Reset session (alias of /clear)' },
|
|
503
|
-
{ cmd: '/mode', desc: 'Set mode: auto,
|
|
504
|
+
{ cmd: '/mode', desc: 'Set mode: auto, edits, careful or plan' },
|
|
504
505
|
{ cmd: '/effort', desc: 'Set reasoning effort: low, medium, high, max (picker)' },
|
|
505
506
|
{ cmd: '/config', desc: 'View or set persistent config' },
|
|
506
507
|
{ cmd: '/usage', desc: 'Usage, context, model status and limits (tabbed pane)' },
|
|
@@ -1019,6 +1020,11 @@ export function inkApprovalDecision(requireApproval, mode, tool, argsJson, rules
|
|
|
1019
1020
|
// silently erase. It remains stronger than this session's convenience posture.
|
|
1020
1021
|
if (policyRequiresApproval)
|
|
1021
1022
|
return 'ask';
|
|
1023
|
+
// Auto is the explicit promise to carry a task through. A globally saved approval preference
|
|
1024
|
+
// must not quietly turn it into careful mode; changing into Auto is the newer, more specific
|
|
1025
|
+
// choice. Workspace policies above still win, and plan mode was already denied by `decide`.
|
|
1026
|
+
if (modeSpec(mode).name === 'auto')
|
|
1027
|
+
return 'allow';
|
|
1022
1028
|
if (!requireApproval)
|
|
1023
1029
|
return 'allow';
|
|
1024
1030
|
if (toolRisk(tool) === 'read')
|
|
@@ -1095,7 +1101,7 @@ Slash commands:
|
|
|
1095
1101
|
/queue [clear|drop n] Inspect, clear, or drop one queued prompt
|
|
1096
1102
|
/model [name] Show or switch model
|
|
1097
1103
|
/provider [name] Show or switch provider
|
|
1098
|
-
/mode [auto|plan
|
|
1104
|
+
/mode [auto|edits|careful|plan] Show or set mode
|
|
1099
1105
|
/effort [low|med|high|max] Set reasoning effort
|
|
1100
1106
|
/config [key] [value] View or set persistent config
|
|
1101
1107
|
/aside <question> Ask a side question without interrupting the running task
|
|
@@ -1486,6 +1492,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
1486
1492
|
diffBaseRef.current.set(rel, before);
|
|
1487
1493
|
}
|
|
1488
1494
|
liveToolRef.current = { kind: 'tool', name, detail: toolDetail(name, args), startedAt: Date.now() };
|
|
1495
|
+
setRepaint(v => v + 1);
|
|
1489
1496
|
}
|
|
1490
1497
|
function finishTool(name, ok, ms, detail) {
|
|
1491
1498
|
if (!ok)
|
|
@@ -1715,6 +1722,19 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
1715
1722
|
const [showDiffPanel, setShowDiffPanel] = useState(false);
|
|
1716
1723
|
const diffPanelTouchedRef = useRef(false);
|
|
1717
1724
|
const [draft, setDraft] = useState('');
|
|
1725
|
+
const imageDraftRef = useRef(new ImageDraft());
|
|
1726
|
+
const [copyNotice, setCopyNotice] = useState('');
|
|
1727
|
+
useEffect(() => {
|
|
1728
|
+
if (!process.stdin.isTTY || cfg.autoCopy === false)
|
|
1729
|
+
return;
|
|
1730
|
+
return startSelectionCopy(count => setCopyNotice(`Copied ${count} chars to clipboard`));
|
|
1731
|
+
}, [cfg.autoCopy]);
|
|
1732
|
+
useEffect(() => {
|
|
1733
|
+
if (!copyNotice)
|
|
1734
|
+
return;
|
|
1735
|
+
const timer = setTimeout(() => setCopyNotice(''), 3000);
|
|
1736
|
+
return () => clearTimeout(timer);
|
|
1737
|
+
}, [copyNotice]);
|
|
1718
1738
|
// Recall is deliberately session-local. It is for quick retries and refinements, not another
|
|
1719
1739
|
// persistence channel for prompts (which may contain sensitive project context).
|
|
1720
1740
|
const draftHistoryRef = useRef([]);
|
|
@@ -2315,9 +2335,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2315
2335
|
setAnim({ spin: 0, shimmer: 0, elapsed: 0 });
|
|
2316
2336
|
return;
|
|
2317
2337
|
}
|
|
2318
|
-
// Turned off: no frames at all, so nothing repaints unless something actually changes.
|
|
2319
|
-
if (!animate)
|
|
2320
|
-
return;
|
|
2321
2338
|
busyStart.current = Date.now();
|
|
2322
2339
|
wordTimer.current = 0;
|
|
2323
2340
|
// One rate, whether or not the stream is saying anything. The spinner earns its place
|
|
@@ -2336,11 +2353,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2336
2353
|
* how a Pi ends up lagging while the same build feels instant on a laptop. See frame-pace.ts.
|
|
2337
2354
|
*/
|
|
2338
2355
|
const floor = floorFor(syncedFramesRef.current);
|
|
2339
|
-
let interval = startingInterval(syncedFramesRef.current);
|
|
2356
|
+
let interval = animate ? startingInterval(syncedFramesRef.current) : 2_000;
|
|
2340
2357
|
let asked = Date.now();
|
|
2341
2358
|
const tick = () => {
|
|
2342
2359
|
const now = Date.now();
|
|
2343
|
-
interval = pacedInterval(interval, now - asked, floor);
|
|
2360
|
+
interval = animate ? pacedInterval(interval, now - asked, floor) : 2_000;
|
|
2344
2361
|
/*
|
|
2345
2362
|
* Nothing is repainted while somebody is typing.
|
|
2346
2363
|
*
|
|
@@ -2364,10 +2381,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2364
2381
|
// Elapsed is read from the clock rather than counted in ticks, so a frame the event loop
|
|
2365
2382
|
// delivers late moves the animation on by what actually passed instead of falling behind.
|
|
2366
2383
|
const elapsed = now - busyStart.current;
|
|
2367
|
-
const { spin, shimmer } = animFrames(elapsed, SPINNER.length);
|
|
2384
|
+
const { spin, shimmer } = animate ? animFrames(elapsed, SPINNER.length) : { spin: 0, shimmer: 0 };
|
|
2368
2385
|
setAnim({ spin, shimmer, elapsed });
|
|
2369
2386
|
wordTimer.current += 1;
|
|
2370
|
-
if (wordTimer.current % wordEvery === 0)
|
|
2387
|
+
if (animate && wordTimer.current % wordEvery === 0)
|
|
2371
2388
|
setSpinWord(w => w + 1);
|
|
2372
2389
|
asked = Date.now();
|
|
2373
2390
|
timer = setTimeout(tick, interval);
|
|
@@ -2759,6 +2776,12 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2759
2776
|
case 'mouse':
|
|
2760
2777
|
setMouse(value === true);
|
|
2761
2778
|
break;
|
|
2779
|
+
case 'animate':
|
|
2780
|
+
setAnimate(value !== false);
|
|
2781
|
+
break;
|
|
2782
|
+
case 'autoCopy':
|
|
2783
|
+
setCfg(c => ({ ...c, autoCopy: value !== false }));
|
|
2784
|
+
break;
|
|
2762
2785
|
case 'provider': {
|
|
2763
2786
|
const name = typeof value === 'string' && value.trim() !== '' ? value.trim() : cfg.provider;
|
|
2764
2787
|
const def = PROVIDERS[name.toLowerCase()];
|
|
@@ -3286,7 +3309,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3286
3309
|
const choice = arg.trim().toLowerCase();
|
|
3287
3310
|
if (!choice) {
|
|
3288
3311
|
addSystem(`**Approval posture**\n\n` +
|
|
3289
|
-
`Session approval: ${
|
|
3312
|
+
`Session approval: ${mode === 'auto'
|
|
3313
|
+
? 'Auto mode runs ordinary work without asking; workspace policy can still require approval'
|
|
3314
|
+
: cfg.requireApproval
|
|
3315
|
+
? 'on — KONECK asks before writes and non-read-only commands'
|
|
3316
|
+
: 'off — KONECK follows the selected mode and workspace policy'}\n` +
|
|
3290
3317
|
`Mode: ${modeSpec(mode).label} — ${modeSpec(mode).blurb}\n\n` +
|
|
3291
3318
|
'`/permissions on` keeps you in control of every consequential action.\n' +
|
|
3292
3319
|
'`/permissions off` removes the extra session gate; workspace policy and plan mode still win.');
|
|
@@ -3305,9 +3332,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3305
3332
|
// and the rebuilt session makes the new posture durable for every later turn.
|
|
3306
3333
|
if (!busy)
|
|
3307
3334
|
await resetSession(merged);
|
|
3308
|
-
addSystem(
|
|
3309
|
-
?
|
|
3310
|
-
:
|
|
3335
|
+
addSystem(mode === 'auto'
|
|
3336
|
+
? `Approval ${next ? 'on' : 'off'} saved. Auto mode still runs ordinary work without asking; workspace policy remains enforceable.`
|
|
3337
|
+
: next
|
|
3338
|
+
? 'Approval on. KONECK will ask in this interface before writes and consequential commands. Read-only inspection stays uninterrupted.'
|
|
3339
|
+
: 'Approval off. KONECK follows the selected mode and workspace policy; plan mode and policy blocks still cannot be bypassed.');
|
|
3311
3340
|
return;
|
|
3312
3341
|
}
|
|
3313
3342
|
case '/worktree': {
|
|
@@ -3677,7 +3706,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3677
3706
|
? 'It repaints a few times a second while a turn runs. On a terminal that does not '
|
|
3678
3707
|
+ 'composite a redraw that is visible as flicker. `/animation off` stops it — the '
|
|
3679
3708
|
+ 'elapsed time and the status bar still say what is happening.'
|
|
3680
|
-
: '
|
|
3709
|
+
: 'The spinner is off. Activity and elapsed time refresh every two seconds while you are not typing. `/animation on` brings the spinner back.'));
|
|
3681
3710
|
return;
|
|
3682
3711
|
}
|
|
3683
3712
|
const next = want === 'on';
|
|
@@ -3687,8 +3716,8 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3687
3716
|
setStored(saved);
|
|
3688
3717
|
addSystem(next
|
|
3689
3718
|
? 'Working animation **on**.'
|
|
3690
|
-
: 'Working animation **off
|
|
3691
|
-
+ 'progress
|
|
3719
|
+
: 'Working animation **off**. The status bar still shows '
|
|
3720
|
+
+ 'progress and elapsed time every two seconds, pausing while you type.');
|
|
3692
3721
|
return;
|
|
3693
3722
|
}
|
|
3694
3723
|
case '/redraw': {
|
|
@@ -3744,8 +3773,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3744
3773
|
case '/auto':
|
|
3745
3774
|
case '/plan':
|
|
3746
3775
|
case '/edits':
|
|
3776
|
+
case '/edit':
|
|
3777
|
+
case '/code':
|
|
3747
3778
|
case '/careful': {
|
|
3748
|
-
const
|
|
3779
|
+
const requested = cmd === '/mode' ? arg.trim() : cmd.slice(1);
|
|
3780
|
+
const target = requested === 'edit' || requested === 'code' ? 'edits' : requested;
|
|
3749
3781
|
const names = MODE_SPECS.map(m => m.name).join(', ');
|
|
3750
3782
|
if (!target) {
|
|
3751
3783
|
addSystem(`Current mode: ${modeSpec(mode).label} — ${modeSpec(mode).blurb}\n\n` +
|
|
@@ -3916,6 +3948,21 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3916
3948
|
}
|
|
3917
3949
|
return;
|
|
3918
3950
|
}
|
|
3951
|
+
const { readAll } = await import('./ledger.js');
|
|
3952
|
+
const notes = await readAll(cfg.cwd);
|
|
3953
|
+
// Older releases shortened an entire shell pipeline to its first two words. That turned
|
|
3954
|
+
// `cd project && slow-test` into a bogus claim that changing directory was slow. Keep the
|
|
3955
|
+
// old line on disk for auditability, but do not present it as knowledge or send it back to
|
|
3956
|
+
// a model.
|
|
3957
|
+
const usableNotes = notes.filter(n => !(n.kind === 'timing'
|
|
3958
|
+
&& n.commands?.some(command => /^cd\s+[^;&|]+$/.test(command))));
|
|
3959
|
+
if (usableNotes.length) {
|
|
3960
|
+
addSystem('Saved project notes (.koneck/ledger.jsonl):\n'
|
|
3961
|
+
+ usableNotes.slice(-20).map(n => '• ' + n.text).join('\n'));
|
|
3962
|
+
}
|
|
3963
|
+
if (usableNotes.length !== notes.length) {
|
|
3964
|
+
addSystem(`${notes.length - usableNotes.length} old timing note${notes.length - usableNotes.length === 1 ? '' : 's'} ignored: it measured only a directory change, not the command that followed it.`);
|
|
3965
|
+
}
|
|
3919
3966
|
const mem = await loadMemory(cfg.cwd).catch(() => null);
|
|
3920
3967
|
addSystem(mem?.trim()
|
|
3921
3968
|
? mem + '\n\n(/memory clear removes these notes)'
|
|
@@ -4337,6 +4384,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
4337
4384
|
const n = Number(val);
|
|
4338
4385
|
setConfigMaxWidth(Number.isFinite(n) && n >= 40 ? n : undefined);
|
|
4339
4386
|
}
|
|
4387
|
+
if (key === 'autoCopy')
|
|
4388
|
+
setCfg(c => ({ ...c, autoCopy: ['true', '1', 'yes'].includes(val.toLowerCase()) }));
|
|
4389
|
+
if (key === 'animate')
|
|
4390
|
+
setAnimate(['true', '1', 'yes'].includes(val.toLowerCase()));
|
|
4340
4391
|
if (key === 'mouse') {
|
|
4341
4392
|
setMouse(['true', '1', 'yes'].includes(val.toLowerCase()));
|
|
4342
4393
|
}
|
|
@@ -4667,6 +4718,13 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
4667
4718
|
setKeyPrompt(p => (p ? { ...p, value: p.value + clean.replace(/\s+$/, '') } : p));
|
|
4668
4719
|
return;
|
|
4669
4720
|
}
|
|
4721
|
+
const imagePath = pastedImagePath(clean, cfg.cwd);
|
|
4722
|
+
if (imagePath) {
|
|
4723
|
+
const token = imageDraftRef.current.add(imagePath);
|
|
4724
|
+
setDraft(d => d.slice(0, caret) + token + d.slice(caret));
|
|
4725
|
+
setCaret(c => c + token.length);
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4670
4728
|
if (!clean.includes('\n') && clean.length <= 200) {
|
|
4671
4729
|
setDraft(d => d.slice(0, caret) + clean + d.slice(caret));
|
|
4672
4730
|
setCaret(c => c + clean.length);
|
|
@@ -5109,6 +5167,21 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5109
5167
|
});
|
|
5110
5168
|
return;
|
|
5111
5169
|
}
|
|
5170
|
+
if (key.ctrl && input.toLowerCase() === 'v') {
|
|
5171
|
+
void (async () => {
|
|
5172
|
+
const image = await readClipboardImage();
|
|
5173
|
+
if (image.path) {
|
|
5174
|
+
filePaste(image.path);
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5177
|
+
const text = await readClipboard();
|
|
5178
|
+
if (text.ok && text.text)
|
|
5179
|
+
filePaste(text.text);
|
|
5180
|
+
else
|
|
5181
|
+
addSystem(image.reason || text.reason || 'The clipboard is empty.');
|
|
5182
|
+
})();
|
|
5183
|
+
return;
|
|
5184
|
+
}
|
|
5112
5185
|
// Only submission waits for the agent. Editing stays live throughout, so a thought had
|
|
5113
5186
|
// while it works can be typed as it arrives rather than held until the turn ends — and
|
|
5114
5187
|
// cancelling with esc then hands the keyboard back to a composer that still has it.
|
|
@@ -5137,7 +5210,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5137
5210
|
}
|
|
5138
5211
|
addSystem(`${cmd} changes the session, so it waits until this turn finishes.`);
|
|
5139
5212
|
}
|
|
5140
|
-
setQueued(q => [...q, expandPastes(text, pastesRef.current)]);
|
|
5213
|
+
setQueued(q => [...q, imageDraftRef.current.expand(expandPastes(text, pastesRef.current))]);
|
|
5141
5214
|
setDraft('');
|
|
5142
5215
|
setCaret(0);
|
|
5143
5216
|
pastesRef.current = [];
|
|
@@ -5233,6 +5306,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5233
5306
|
const text = input.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, '');
|
|
5234
5307
|
if (text === '')
|
|
5235
5308
|
return;
|
|
5309
|
+
if (text.length > 1 && pastedImagePath(text, cfg.cwd)) {
|
|
5310
|
+
filePaste(text);
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5236
5313
|
const next = draft.slice(0, caret) + text + draft.slice(caret);
|
|
5237
5314
|
const nextCaret = caret + text.length;
|
|
5238
5315
|
// Once the recalled line is edited it is a new draft, not a history cursor position.
|
|
@@ -5277,7 +5354,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5277
5354
|
const shown = raw.trim();
|
|
5278
5355
|
if (!shown)
|
|
5279
5356
|
return;
|
|
5280
|
-
const task = expandPastes(shown, pastesRef.current).trim();
|
|
5357
|
+
const task = imageDraftRef.current.expand(expandPastes(shown, pastesRef.current)).trim();
|
|
5281
5358
|
if (!task)
|
|
5282
5359
|
return;
|
|
5283
5360
|
const generated = generatedTaskRef.current?.task === task ? generatedTaskRef.current : null;
|
|
@@ -5307,7 +5384,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5307
5384
|
'written into the session log.');
|
|
5308
5385
|
}
|
|
5309
5386
|
const mentions = findMentions(task, pathKind);
|
|
5310
|
-
const forDisplay = taskTranscriptLabel(pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task), generated?.label);
|
|
5387
|
+
const forDisplay = taskTranscriptLabel(shown.includes('[Image #') ? shown : pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task), generated?.label);
|
|
5311
5388
|
addRow({ role: 'user', text: forDisplay });
|
|
5312
5389
|
if (isSlashCommand(task)) {
|
|
5313
5390
|
const spaceIdx = task.indexOf(' ');
|
|
@@ -5498,11 +5575,14 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5498
5575
|
* Only on the alternate screen: in inline mode the transcript is committed to the terminal's own
|
|
5499
5576
|
* scrollback full-width, and there is no frame to put a second column in.
|
|
5500
5577
|
*/
|
|
5578
|
+
const activityPanelOpen = busy && uiWidth >= 100 && termRows >= 24
|
|
5579
|
+
&& (diffPanelTouchedRef.current ? showDiffPanel : cfg.diffPanel !== false);
|
|
5501
5580
|
const diffPanelOpen = showDiffPanel && altScreen && liveDiffs.length > 0;
|
|
5581
|
+
const sidePanelOpen = activityPanelOpen || diffPanelOpen;
|
|
5502
5582
|
// Read by renderStep, which is defined above this line, so it goes through a ref rather than
|
|
5503
5583
|
// depending on declaration order.
|
|
5504
5584
|
diffPanelOpenRef.current = diffPanelOpen;
|
|
5505
|
-
const diffPanelWidth =
|
|
5585
|
+
const diffPanelWidth = sidePanelOpen
|
|
5506
5586
|
? Math.max(34, Math.min(Math.floor(uiWidth * 0.46), 96))
|
|
5507
5587
|
: 0;
|
|
5508
5588
|
const contentWidth = uiWidth - diffPanelWidth;
|
|
@@ -5625,7 +5705,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5625
5705
|
// Both halves matter. Clipping the bottom would hide what just happened; anchoring
|
|
5626
5706
|
// always to the bottom is what put the banner there.
|
|
5627
5707
|
_jsx(Box, { flexGrow: 0, flexShrink: 1, flexDirection: "column", justifyContent: "flex-end", overflowY: "hidden", children: visibleRows.map((row, i) => (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: renderRow(row, i) }, i))) })), altScreen && scrollBack > 0 && (_jsx(Text, { color: AMBER, children: `${G.caret} scrolled back ${scrollBack} rows — PageDown, or just type, to follow again` })), showAnalytics && (_jsxs(Box, { borderStyle: "round", borderColor: AMBER, paddingX: 1, marginBottom: 1, flexDirection: "column", width: barWidth, children: [_jsx(Text, { color: AMBER, bold: true, children: "\u25C6 Session Analytics" }), _jsxs(Text, { color: MUTED, children: ["Turns : ", _jsx(Text, { color: INK, children: stats.turns })] }), _jsxs(Text, { color: MUTED, children: ["Prompt tok : ", _jsx(Text, { color: INK, children: stats.promptTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Output tok : ", _jsx(Text, { color: INK, children: stats.completionTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Total tok : ", _jsx(Text, { color: INK, children: stats.totalTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Model : ", _jsx(Text, { color: INK, children: modelShort }), " Provider: ", _jsx(Text, { color: INK, children: cfg.provider })] }), _jsx(Text, { color: DIM, children: "Tab to close" })] })), _jsx(Static, { items: altScreen ? [] : rows, children: (row, index) => renderRow(row, index) }), _jsx(Box, { flexDirection: "column", flexShrink: 0, children: busy && (agentState === 'processing' || agentState === 'syncing') && (_jsxs(Box, { flexDirection: "column", children: [!ask && sayRef.current.trim() !== '' && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: replyStartedRef.current ? ' ' : G.brand }), _jsx(Box, { flexDirection: "column", children: tailLines(sayRef.current, liveReplyLines(termRows), replyWidth - 3)
|
|
5628
|
-
.split('\n').map((line, i) => _jsx(Text, { color: INK, children: line }, i)) })] })), liveToolRef.current && renderStep(liveToolRef.current, 1), queued.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: AMBER, children: "\u25AA" }), _jsxs(Text, { color: MUTED, children: [queued.length, " queued, will run when this finishes \u00B7 /aside to ask without waiting"] })] }), queued.map((q, i) => (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: fitCells(q.split('\n')[0] ?? '', Math.max(20, barWidth - 6), true) }) }, i)))] })), agents.length > 0 && (() => {
|
|
5708
|
+
.split('\n').map((line, i) => _jsx(Text, { color: INK, children: line }, i)) })] })), liveToolRef.current && renderStep(liveToolRef.current, 1), queued.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: AMBER, children: "\u25AA" }), _jsxs(Text, { color: MUTED, children: [queued.length, " queued, will run when this finishes \u00B7 /aside to ask without waiting"] })] }), queued.map((q, i) => (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: fitCells(q.split('\n')[0] ?? '', Math.max(20, barWidth - 6), true) }) }, i)))] })), !activityPanelOpen && agents.length > 0 && (() => {
|
|
5629
5709
|
const done = agents.filter(a => a.status !== 'running').length;
|
|
5630
5710
|
const failed = agents.filter(a => a.status === 'failed').length;
|
|
5631
5711
|
const tokens = agents.reduce((n, a) => n + a.tokens, 0);
|
|
@@ -5642,7 +5722,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5642
5722
|
const task = a.task.length > taskWidth ? a.task.slice(0, taskWidth - 3) + '...' : a.task;
|
|
5643
5723
|
return (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: DIM, children: String(a.index + 1).padStart(2) }), _jsx(Text, { color: a.status === 'running' ? INK : MUTED, children: fitCells(task, taskWidth) }), _jsx(Text, { color: DIM, children: humanTokens(a.tokens).padStart(6) }), _jsx(Text, { color: DIM, children: fmtElapsed(took).padStart(6) })] }, a.index));
|
|
5644
5724
|
})] }));
|
|
5645
|
-
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [SPINNER[spinFrame], " "] }), _jsx(Shimmer, { text: workWord({
|
|
5725
|
+
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [animate ? SPINNER[spinFrame] : '●', " "] }), _jsx(Shimmer, { text: workWord({
|
|
5646
5726
|
elapsedMs, tools: turnToolsRef.current,
|
|
5647
5727
|
tokens: liveTokens, recovered: turnRecoveredRef.current,
|
|
5648
5728
|
}, spinWord)[0], frame: shimmerFrame, base: MUTED }), _jsx(Text, { color: MUTED, children: "\u2026 " }), _jsxs(Text, { color: DIM, children: ["(", fmtElapsed(elapsedMs), " | ", liveTokens > 0
|
|
@@ -5650,9 +5730,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5650
5730
|
// A gateway routing to several backends sends content-free frames while it
|
|
5651
5731
|
// finds one. Saying "waiting for the first token" through that reads as a
|
|
5652
5732
|
// hang, when in fact the request was accepted and is being worked on.
|
|
5653
|
-
:
|
|
5654
|
-
:
|
|
5655
|
-
:
|
|
5733
|
+
: liveToolRef.current ? 'running ' + liveToolRef.current.name
|
|
5734
|
+
: providerFramesRef.current > 0 ? `${cfg.provider} is holding the line, no output yet`
|
|
5735
|
+
: elapsedMs > 8_000 ? `no response from ${cfg.provider} yet`
|
|
5736
|
+
: 'starting', thinkingRef.current.chars > 0 && Date.now() - thinkingRef.current.at < 2_000
|
|
5656
5737
|
? ` | thinking, ${fmtTokens(Math.round(thinkingRef.current.chars / 4))} reasoning tokens`
|
|
5657
5738
|
: '', ")"] })] }) }), showThinking && !ask && thinkingTextRef.current.trim() !== '' && (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: thinkingTail(thinkingTextRef.current, THINKING_LINES, barWidth - 6).map((line, i) => (_jsx(Text, { color: DIM, italic: true, children: line }, i))) })), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: "esc to cancel and keep your input \u00B7 ctrl+c to exit" }) })] })) }), usagePane && (() => {
|
|
5658
5739
|
const width = Math.min(barWidth, usagePane === 'settings' ? 96 : 66);
|
|
@@ -5972,11 +6053,13 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5972
6053
|
: '*type the number, or arrows and enter · esc refuses*',
|
|
5973
6054
|
];
|
|
5974
6055
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 46), color: AMBER, title: `Permission — ${modeSpec(mode).label} mode`, children: lines }) }));
|
|
5975
|
-
})()] }),
|
|
6056
|
+
})()] }), sidePanelOpen && (_jsxs(Box, { flexDirection: "column", width: diffPanelWidth, flexShrink: 0, children: [activityPanelOpen && (_jsxs(Box, { borderStyle: "round", borderColor: CYAN, flexDirection: "column", paddingX: 1, height: Math.min(18, Math.max(8, termRows - 7)), overflowY: "hidden", children: [_jsxs(Text, { color: CYAN, bold: true, children: ["Activity \u00B7 ", fmtElapsed(elapsedMs)] }), _jsx(Text, { color: AMBER, wrap: "truncate", children: ask ? 'Waiting for your approval' : liveToolRef.current
|
|
6057
|
+
? 'Running ' + liveToolRef.current.name : thinkingRef.current.chars > 0
|
|
6058
|
+
? 'Waiting for model · reasoning received' : 'Waiting for model' }), liveToolRef.current && _jsx(Text, { color: INK, wrap: "truncate", children: liveToolRef.current.detail }), liveToolRef.current && _jsxs(Text, { color: DIM, children: ["Running for ", fmtElapsed(Date.now() - liveToolRef.current.startedAt)] }), _jsx(Text, { color: MUTED, wrap: "truncate", children: liveToolRef.current?.output || 'Status updates every 2s; esc to interrupt' }), plan && _jsx(Text, { color: MUTED, wrap: "truncate", children: plan.steps.find(s => s.state !== 'done')?.text || 'Plan complete' }), agents.length > 0 && _jsxs(Text, { color: CYAN, bold: true, children: ["Agents \u00B7 ", agents.filter(a => a.status !== 'running').length, "/", agents.length, " finished"] }), agents.map(a => _jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsxs(Text, { color: a.status === 'failed' ? CRIMSON : a.status === 'done' ? GREEN : AMBER, wrap: "truncate", children: [a.index + 1, ". ", a.status === 'running' ? (a.activity === 'Queued' ? 'queued' : 'working') : a.status, " \u00B7 ", a.activity, " \u00B7 ", a.task] }) }, a.index))] })), diffPanelOpen && _jsx(DiffPanel, { files: liveDiffs, width: diffPanelWidth, height: Math.max(4, frameRows - (activityPanelOpen ? 24 : 6)) })] }))] }), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: busy ? DIM : CYAN, paddingX: 1, width: uiWidth - 5, flexShrink: 0, children: [_jsxs(Text, { color: CYAN, bold: true, children: [G.caret, " "] }), _jsx(Text, { color: INK, children: draft.slice(0, caret) }), busy
|
|
5976
6059
|
? _jsx(Text, { color: INK, children: draft.slice(caret) })
|
|
5977
6060
|
: caret < draft.length
|
|
5978
6061
|
? _jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: CYAN, color: "#10222A", children: draft[caret] }), _jsx(Text, { color: INK, children: draft.slice(caret + 1) })] })
|
|
5979
|
-
: _jsx(Text, { color: CYAN, children: "\u2588" })] }), _jsx(Box, { marginTop: 1, paddingX: 1, flexShrink: 0, 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 }), mouse && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: AMBER, children: "WHEEL: on \u2014 shift to select \u00B7 /mouse off" })] })), _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"] })] }))] }) })] }));
|
|
6062
|
+
: _jsx(Text, { color: CYAN, children: "\u2588" })] }), copyNotice && _jsx(Text, { color: CYAN, children: copyNotice }), _jsx(Box, { marginTop: 1, paddingX: 1, flexShrink: 0, 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 }), mouse && (_jsxs(_Fragment, { children: [_jsx(Text, { color: MUTED, children: " / " }), _jsx(Text, { color: AMBER, children: "WHEEL: on \u2014 shift to select \u00B7 /mouse off" })] })), _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"] })] }))] }) })] }));
|
|
5980
6063
|
}
|
|
5981
6064
|
export async function runInkChatMode(config) {
|
|
5982
6065
|
// Nothing is written before the first frame. Filling the screen to push the prompt to the foot
|