koneck 2.128.0 → 2.128.2
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 +19 -0
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +144 -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 +5 -1
- package/dist/index.js.map +1 -1
- package/dist/ink-chat.d.ts +9 -0
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +154 -77
- 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)' },
|
|
@@ -515,7 +516,7 @@ const COMMANDS = [
|
|
|
515
516
|
{ cmd: '/name', desc: 'Name this session, so /resume can find it by name' },
|
|
516
517
|
{ cmd: '/fork', desc: 'Copy this session and continue on the copy' },
|
|
517
518
|
{ cmd: '/archive', desc: 'Keep a session but hide it from /resume' },
|
|
518
|
-
{ cmd: '/diff', desc: 'Show
|
|
519
|
+
{ cmd: '/diff [on|off]', desc: 'Show changes, or control the live diff panel' },
|
|
519
520
|
{ cmd: '/changes', desc: 'Summarize staged, unstaged, and untracked changes' },
|
|
520
521
|
{ cmd: '/quality', desc: 'Run the detected lint and test checks' },
|
|
521
522
|
{ cmd: '/preflight', desc: 'Run the deterministic ready-to-review gate' },
|
|
@@ -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
|
|
@@ -1125,8 +1131,8 @@ Slash commands:
|
|
|
1125
1131
|
|
|
1126
1132
|
Keyboard:
|
|
1127
1133
|
Tab Toggle analytics panel
|
|
1128
|
-
Shift+Tab Cycle mode (auto →
|
|
1129
|
-
|
|
1134
|
+
Shift+Tab Cycle mode (auto → edits → careful → plan)
|
|
1135
|
+
Ctrl+P / Ctrl+N Recall prior submitted prompts in the composer
|
|
1130
1136
|
Ctrl+C Cancel task / exit if idle
|
|
1131
1137
|
`.trim();
|
|
1132
1138
|
/**
|
|
@@ -1159,6 +1165,19 @@ export function looksLikeMouseReport(input) {
|
|
|
1159
1165
|
return true;
|
|
1160
1166
|
return false;
|
|
1161
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Some terminals turn a wheel movement into an ordinary arrow key while using native scrollback.
|
|
1170
|
+
* Arrow keys must therefore never replace the current draft: scrolling should not recall a prompt.
|
|
1171
|
+
*/
|
|
1172
|
+
export function draftHistoryDirection(input, key) {
|
|
1173
|
+
if (!key.ctrl)
|
|
1174
|
+
return undefined;
|
|
1175
|
+
if (input.toLowerCase() === 'p')
|
|
1176
|
+
return -1;
|
|
1177
|
+
if (input.toLowerCase() === 'n')
|
|
1178
|
+
return 1;
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1162
1181
|
export function defaultAltScreen(stdout = process.stdout, env = process.env,
|
|
1163
1182
|
/**
|
|
1164
1183
|
* Whether this terminal composites a redraw, from the probe in frame-sync.ts.
|
|
@@ -1486,6 +1505,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
1486
1505
|
diffBaseRef.current.set(rel, before);
|
|
1487
1506
|
}
|
|
1488
1507
|
liveToolRef.current = { kind: 'tool', name, detail: toolDetail(name, args), startedAt: Date.now() };
|
|
1508
|
+
setRepaint(v => v + 1);
|
|
1489
1509
|
}
|
|
1490
1510
|
function finishTool(name, ok, ms, detail) {
|
|
1491
1511
|
if (!ok)
|
|
@@ -1715,6 +1735,19 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
1715
1735
|
const [showDiffPanel, setShowDiffPanel] = useState(false);
|
|
1716
1736
|
const diffPanelTouchedRef = useRef(false);
|
|
1717
1737
|
const [draft, setDraft] = useState('');
|
|
1738
|
+
const imageDraftRef = useRef(new ImageDraft());
|
|
1739
|
+
const [copyNotice, setCopyNotice] = useState('');
|
|
1740
|
+
useEffect(() => {
|
|
1741
|
+
if (!process.stdin.isTTY || cfg.autoCopy === false)
|
|
1742
|
+
return;
|
|
1743
|
+
return startSelectionCopy(count => setCopyNotice(`Copied ${count} chars to clipboard`));
|
|
1744
|
+
}, [cfg.autoCopy]);
|
|
1745
|
+
useEffect(() => {
|
|
1746
|
+
if (!copyNotice)
|
|
1747
|
+
return;
|
|
1748
|
+
const timer = setTimeout(() => setCopyNotice(''), 3000);
|
|
1749
|
+
return () => clearTimeout(timer);
|
|
1750
|
+
}, [copyNotice]);
|
|
1718
1751
|
// Recall is deliberately session-local. It is for quick retries and refinements, not another
|
|
1719
1752
|
// persistence channel for prompts (which may contain sensitive project context).
|
|
1720
1753
|
const draftHistoryRef = useRef([]);
|
|
@@ -2315,9 +2348,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2315
2348
|
setAnim({ spin: 0, shimmer: 0, elapsed: 0 });
|
|
2316
2349
|
return;
|
|
2317
2350
|
}
|
|
2318
|
-
// Turned off: no frames at all, so nothing repaints unless something actually changes.
|
|
2319
|
-
if (!animate)
|
|
2320
|
-
return;
|
|
2321
2351
|
busyStart.current = Date.now();
|
|
2322
2352
|
wordTimer.current = 0;
|
|
2323
2353
|
// One rate, whether or not the stream is saying anything. The spinner earns its place
|
|
@@ -2336,11 +2366,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2336
2366
|
* how a Pi ends up lagging while the same build feels instant on a laptop. See frame-pace.ts.
|
|
2337
2367
|
*/
|
|
2338
2368
|
const floor = floorFor(syncedFramesRef.current);
|
|
2339
|
-
let interval = startingInterval(syncedFramesRef.current);
|
|
2369
|
+
let interval = animate ? startingInterval(syncedFramesRef.current) : 2_000;
|
|
2340
2370
|
let asked = Date.now();
|
|
2341
2371
|
const tick = () => {
|
|
2342
2372
|
const now = Date.now();
|
|
2343
|
-
interval = pacedInterval(interval, now - asked, floor);
|
|
2373
|
+
interval = animate ? pacedInterval(interval, now - asked, floor) : 2_000;
|
|
2344
2374
|
/*
|
|
2345
2375
|
* Nothing is repainted while somebody is typing.
|
|
2346
2376
|
*
|
|
@@ -2364,10 +2394,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2364
2394
|
// Elapsed is read from the clock rather than counted in ticks, so a frame the event loop
|
|
2365
2395
|
// delivers late moves the animation on by what actually passed instead of falling behind.
|
|
2366
2396
|
const elapsed = now - busyStart.current;
|
|
2367
|
-
const { spin, shimmer } = animFrames(elapsed, SPINNER.length);
|
|
2397
|
+
const { spin, shimmer } = animate ? animFrames(elapsed, SPINNER.length) : { spin: 0, shimmer: 0 };
|
|
2368
2398
|
setAnim({ spin, shimmer, elapsed });
|
|
2369
2399
|
wordTimer.current += 1;
|
|
2370
|
-
if (wordTimer.current % wordEvery === 0)
|
|
2400
|
+
if (animate && wordTimer.current % wordEvery === 0)
|
|
2371
2401
|
setSpinWord(w => w + 1);
|
|
2372
2402
|
asked = Date.now();
|
|
2373
2403
|
timer = setTimeout(tick, interval);
|
|
@@ -2759,6 +2789,12 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2759
2789
|
case 'mouse':
|
|
2760
2790
|
setMouse(value === true);
|
|
2761
2791
|
break;
|
|
2792
|
+
case 'animate':
|
|
2793
|
+
setAnimate(value !== false);
|
|
2794
|
+
break;
|
|
2795
|
+
case 'autoCopy':
|
|
2796
|
+
setCfg(c => ({ ...c, autoCopy: value !== false }));
|
|
2797
|
+
break;
|
|
2762
2798
|
case 'provider': {
|
|
2763
2799
|
const name = typeof value === 'string' && value.trim() !== '' ? value.trim() : cfg.provider;
|
|
2764
2800
|
const def = PROVIDERS[name.toLowerCase()];
|
|
@@ -3286,7 +3322,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3286
3322
|
const choice = arg.trim().toLowerCase();
|
|
3287
3323
|
if (!choice) {
|
|
3288
3324
|
addSystem(`**Approval posture**\n\n` +
|
|
3289
|
-
`Session approval: ${
|
|
3325
|
+
`Session approval: ${mode === 'auto'
|
|
3326
|
+
? 'Auto mode runs ordinary work without asking; workspace policy can still require approval'
|
|
3327
|
+
: cfg.requireApproval
|
|
3328
|
+
? 'on — KONECK asks before writes and non-read-only commands'
|
|
3329
|
+
: 'off — KONECK follows the selected mode and workspace policy'}\n` +
|
|
3290
3330
|
`Mode: ${modeSpec(mode).label} — ${modeSpec(mode).blurb}\n\n` +
|
|
3291
3331
|
'`/permissions on` keeps you in control of every consequential action.\n' +
|
|
3292
3332
|
'`/permissions off` removes the extra session gate; workspace policy and plan mode still win.');
|
|
@@ -3305,9 +3345,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3305
3345
|
// and the rebuilt session makes the new posture durable for every later turn.
|
|
3306
3346
|
if (!busy)
|
|
3307
3347
|
await resetSession(merged);
|
|
3308
|
-
addSystem(
|
|
3309
|
-
?
|
|
3310
|
-
:
|
|
3348
|
+
addSystem(mode === 'auto'
|
|
3349
|
+
? `Approval ${next ? 'on' : 'off'} saved. Auto mode still runs ordinary work without asking; workspace policy remains enforceable.`
|
|
3350
|
+
: next
|
|
3351
|
+
? 'Approval on. KONECK will ask in this interface before writes and consequential commands. Read-only inspection stays uninterrupted.'
|
|
3352
|
+
: 'Approval off. KONECK follows the selected mode and workspace policy; plan mode and policy blocks still cannot be bypassed.');
|
|
3311
3353
|
return;
|
|
3312
3354
|
}
|
|
3313
3355
|
case '/worktree': {
|
|
@@ -3677,7 +3719,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3677
3719
|
? 'It repaints a few times a second while a turn runs. On a terminal that does not '
|
|
3678
3720
|
+ 'composite a redraw that is visible as flicker. `/animation off` stops it — the '
|
|
3679
3721
|
+ 'elapsed time and the status bar still say what is happening.'
|
|
3680
|
-
: '
|
|
3722
|
+
: 'The spinner is off. Activity and elapsed time refresh every two seconds while you are not typing. `/animation on` brings the spinner back.'));
|
|
3681
3723
|
return;
|
|
3682
3724
|
}
|
|
3683
3725
|
const next = want === 'on';
|
|
@@ -3687,8 +3729,8 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3687
3729
|
setStored(saved);
|
|
3688
3730
|
addSystem(next
|
|
3689
3731
|
? 'Working animation **on**.'
|
|
3690
|
-
: 'Working animation **off
|
|
3691
|
-
+ 'progress
|
|
3732
|
+
: 'Working animation **off**. The status bar still shows '
|
|
3733
|
+
+ 'progress and elapsed time every two seconds, pausing while you type.');
|
|
3692
3734
|
return;
|
|
3693
3735
|
}
|
|
3694
3736
|
case '/redraw': {
|
|
@@ -3744,8 +3786,11 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3744
3786
|
case '/auto':
|
|
3745
3787
|
case '/plan':
|
|
3746
3788
|
case '/edits':
|
|
3789
|
+
case '/edit':
|
|
3790
|
+
case '/code':
|
|
3747
3791
|
case '/careful': {
|
|
3748
|
-
const
|
|
3792
|
+
const requested = cmd === '/mode' ? arg.trim() : cmd.slice(1);
|
|
3793
|
+
const target = requested === 'edit' || requested === 'code' ? 'edits' : requested;
|
|
3749
3794
|
const names = MODE_SPECS.map(m => m.name).join(', ');
|
|
3750
3795
|
if (!target) {
|
|
3751
3796
|
addSystem(`Current mode: ${modeSpec(mode).label} — ${modeSpec(mode).blurb}\n\n` +
|
|
@@ -3823,45 +3868,6 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3823
3868
|
'Pick a model with a known rate via /models to see cost, or read it from your provider dashboard.');
|
|
3824
3869
|
return;
|
|
3825
3870
|
}
|
|
3826
|
-
/*
|
|
3827
|
-
* The live diff panel, on or off.
|
|
3828
|
-
*
|
|
3829
|
-
* Takes on/off as well as toggling, so it can be put in a shell alias or a hook, and says why
|
|
3830
|
-
* nothing appeared when it cannot open — an inline session has no frame to put a column in,
|
|
3831
|
-
* and a session that has changed nothing has nothing to draw.
|
|
3832
|
-
*/
|
|
3833
|
-
case '/diff': {
|
|
3834
|
-
const want = arg.trim() === 'off' ? false : arg.trim() === 'on' ? true : !showDiffPanel;
|
|
3835
|
-
setShowDiffPanel(want);
|
|
3836
|
-
diffPanelTouchedRef.current = true;
|
|
3837
|
-
/*
|
|
3838
|
-
* Opening the panel switches to the alternate screen, because it needs a frame.
|
|
3839
|
-
*
|
|
3840
|
-
* The default is now the terminal's own scrollback wherever a repaint would be visible, so
|
|
3841
|
-
* asking for the panel used to be answered with "/altscreen on, then /diff" — two commands
|
|
3842
|
-
* to reach one feature, for a reason that is KONECK's business rather than the person's.
|
|
3843
|
-
* The panel is a fixed side-by-side layout and there is nowhere to put a second column in
|
|
3844
|
-
* scrollback, so asking for it is asking for the frame. Closing it hands the scrollback
|
|
3845
|
-
* back, and with it selectable text.
|
|
3846
|
-
*/
|
|
3847
|
-
if (want && !altScreen)
|
|
3848
|
-
setAltScreen(true);
|
|
3849
|
-
if (!want && !(cfg.altScreen ?? false)
|
|
3850
|
-
&& !defaultAltScreen(process.stdout, process.env, syncedFramesRef.current)) {
|
|
3851
|
-
setAltScreen(false);
|
|
3852
|
-
}
|
|
3853
|
-
const saved = { ...(await loadKoneckConfig()), diffPanel: want };
|
|
3854
|
-
await saveKoneckConfig(saved);
|
|
3855
|
-
setStored(saved);
|
|
3856
|
-
addSystem(want && liveDiffs.length === 0
|
|
3857
|
-
? 'Diff panel on — it will appear beside the conversation with the first edit. '
|
|
3858
|
-
+ 'Saved as your default.'
|
|
3859
|
-
: want
|
|
3860
|
-
? 'Diff panel open beside the conversation, following every edit as it lands. '
|
|
3861
|
-
+ '/diff to hide it.'
|
|
3862
|
-
: 'Diff panel hidden. /diff to bring it back.');
|
|
3863
|
-
return;
|
|
3864
|
-
}
|
|
3865
3871
|
case '/altscreen':
|
|
3866
3872
|
case '/fullscreen': {
|
|
3867
3873
|
const next = arg.trim() === 'off' ? false : arg.trim() === 'on' ? true : !altScreen;
|
|
@@ -3916,6 +3922,21 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3916
3922
|
}
|
|
3917
3923
|
return;
|
|
3918
3924
|
}
|
|
3925
|
+
const { readAll } = await import('./ledger.js');
|
|
3926
|
+
const notes = await readAll(cfg.cwd);
|
|
3927
|
+
// Older releases shortened an entire shell pipeline to its first two words. That turned
|
|
3928
|
+
// `cd project && slow-test` into a bogus claim that changing directory was slow. Keep the
|
|
3929
|
+
// old line on disk for auditability, but do not present it as knowledge or send it back to
|
|
3930
|
+
// a model.
|
|
3931
|
+
const usableNotes = notes.filter(n => !(n.kind === 'timing'
|
|
3932
|
+
&& n.commands?.some(command => /^cd\s+[^;&|]+$/.test(command))));
|
|
3933
|
+
if (usableNotes.length) {
|
|
3934
|
+
addSystem('Saved project notes (.koneck/ledger.jsonl):\n'
|
|
3935
|
+
+ usableNotes.slice(-20).map(n => '• ' + n.text).join('\n'));
|
|
3936
|
+
}
|
|
3937
|
+
if (usableNotes.length !== notes.length) {
|
|
3938
|
+
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.`);
|
|
3939
|
+
}
|
|
3919
3940
|
const mem = await loadMemory(cfg.cwd).catch(() => null);
|
|
3920
3941
|
addSystem(mem?.trim()
|
|
3921
3942
|
? mem + '\n\n(/memory clear removes these notes)'
|
|
@@ -3923,6 +3944,25 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
3923
3944
|
return;
|
|
3924
3945
|
}
|
|
3925
3946
|
case '/diff': {
|
|
3947
|
+
const panel = arg.trim().toLowerCase();
|
|
3948
|
+
if (panel === 'on' || panel === 'off') {
|
|
3949
|
+
const want = panel === 'on';
|
|
3950
|
+
setShowDiffPanel(want);
|
|
3951
|
+
diffPanelTouchedRef.current = true;
|
|
3952
|
+
if (want && !altScreen)
|
|
3953
|
+
setAltScreen(true);
|
|
3954
|
+
if (!want && !(cfg.altScreen ?? false)
|
|
3955
|
+
&& !defaultAltScreen(process.stdout, process.env, syncedFramesRef.current)) {
|
|
3956
|
+
setAltScreen(false);
|
|
3957
|
+
}
|
|
3958
|
+
const saved = { ...(await loadKoneckConfig()), diffPanel: want };
|
|
3959
|
+
await saveKoneckConfig(saved);
|
|
3960
|
+
setStored(saved);
|
|
3961
|
+
addSystem(want
|
|
3962
|
+
? 'Live diff panel on. It will appear beside the conversation with the first edit.'
|
|
3963
|
+
: 'Live diff panel hidden.');
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3926
3966
|
try {
|
|
3927
3967
|
const [unstaged, staged, untracked] = await Promise.all([
|
|
3928
3968
|
execa('git', ['diff', '--no-ext-diff'], { cwd: cfg.cwd, all: true, reject: false }),
|
|
@@ -4337,6 +4377,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
4337
4377
|
const n = Number(val);
|
|
4338
4378
|
setConfigMaxWidth(Number.isFinite(n) && n >= 40 ? n : undefined);
|
|
4339
4379
|
}
|
|
4380
|
+
if (key === 'autoCopy')
|
|
4381
|
+
setCfg(c => ({ ...c, autoCopy: ['true', '1', 'yes'].includes(val.toLowerCase()) }));
|
|
4382
|
+
if (key === 'animate')
|
|
4383
|
+
setAnimate(['true', '1', 'yes'].includes(val.toLowerCase()));
|
|
4340
4384
|
if (key === 'mouse') {
|
|
4341
4385
|
setMouse(['true', '1', 'yes'].includes(val.toLowerCase()));
|
|
4342
4386
|
}
|
|
@@ -4667,6 +4711,13 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
4667
4711
|
setKeyPrompt(p => (p ? { ...p, value: p.value + clean.replace(/\s+$/, '') } : p));
|
|
4668
4712
|
return;
|
|
4669
4713
|
}
|
|
4714
|
+
const imagePath = pastedImagePath(clean, cfg.cwd);
|
|
4715
|
+
if (imagePath) {
|
|
4716
|
+
const token = imageDraftRef.current.add(imagePath);
|
|
4717
|
+
setDraft(d => d.slice(0, caret) + token + d.slice(caret));
|
|
4718
|
+
setCaret(c => c + token.length);
|
|
4719
|
+
return;
|
|
4720
|
+
}
|
|
4670
4721
|
if (!clean.includes('\n') && clean.length <= 200) {
|
|
4671
4722
|
setDraft(d => d.slice(0, caret) + clean + d.slice(caret));
|
|
4672
4723
|
setCaret(c => c + clean.length);
|
|
@@ -5109,6 +5160,21 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5109
5160
|
});
|
|
5110
5161
|
return;
|
|
5111
5162
|
}
|
|
5163
|
+
if (key.ctrl && input.toLowerCase() === 'v') {
|
|
5164
|
+
void (async () => {
|
|
5165
|
+
const image = await readClipboardImage();
|
|
5166
|
+
if (image.path) {
|
|
5167
|
+
filePaste(image.path);
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5170
|
+
const text = await readClipboard();
|
|
5171
|
+
if (text.ok && text.text)
|
|
5172
|
+
filePaste(text.text);
|
|
5173
|
+
else
|
|
5174
|
+
addSystem(image.reason || text.reason || 'The clipboard is empty.');
|
|
5175
|
+
})();
|
|
5176
|
+
return;
|
|
5177
|
+
}
|
|
5112
5178
|
// Only submission waits for the agent. Editing stays live throughout, so a thought had
|
|
5113
5179
|
// while it works can be typed as it arrives rather than held until the turn ends — and
|
|
5114
5180
|
// cancelling with esc then hands the keyboard back to a composer that still has it.
|
|
@@ -5137,7 +5203,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5137
5203
|
}
|
|
5138
5204
|
addSystem(`${cmd} changes the session, so it waits until this turn finishes.`);
|
|
5139
5205
|
}
|
|
5140
|
-
setQueued(q => [...q, expandPastes(text, pastesRef.current)]);
|
|
5206
|
+
setQueued(q => [...q, imageDraftRef.current.expand(expandPastes(text, pastesRef.current))]);
|
|
5141
5207
|
setDraft('');
|
|
5142
5208
|
setCaret(0);
|
|
5143
5209
|
pastesRef.current = [];
|
|
@@ -5151,12 +5217,9 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5151
5217
|
// The composer used to be append-and-backspace only, so fixing a typo near the start of a
|
|
5152
5218
|
// long prompt meant deleting everything after it. Everything below is the readline set a
|
|
5153
5219
|
// shell gives you, so an edit costs one keystroke rather than a retype.
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
}
|
|
5158
|
-
if (key.downArrow) {
|
|
5159
|
-
recallDraft(1);
|
|
5220
|
+
const historyDirection = draftHistoryDirection(input, key);
|
|
5221
|
+
if (historyDirection) {
|
|
5222
|
+
recallDraft(historyDirection);
|
|
5160
5223
|
return;
|
|
5161
5224
|
}
|
|
5162
5225
|
if (key.leftArrow) {
|
|
@@ -5233,6 +5296,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5233
5296
|
const text = input.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, '');
|
|
5234
5297
|
if (text === '')
|
|
5235
5298
|
return;
|
|
5299
|
+
if (text.length > 1 && pastedImagePath(text, cfg.cwd)) {
|
|
5300
|
+
filePaste(text);
|
|
5301
|
+
return;
|
|
5302
|
+
}
|
|
5236
5303
|
const next = draft.slice(0, caret) + text + draft.slice(caret);
|
|
5237
5304
|
const nextCaret = caret + text.length;
|
|
5238
5305
|
// Once the recalled line is edited it is a new draft, not a history cursor position.
|
|
@@ -5277,7 +5344,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5277
5344
|
const shown = raw.trim();
|
|
5278
5345
|
if (!shown)
|
|
5279
5346
|
return;
|
|
5280
|
-
const task = expandPastes(shown, pastesRef.current).trim();
|
|
5347
|
+
const task = imageDraftRef.current.expand(expandPastes(shown, pastesRef.current)).trim();
|
|
5281
5348
|
if (!task)
|
|
5282
5349
|
return;
|
|
5283
5350
|
const generated = generatedTaskRef.current?.task === task ? generatedTaskRef.current : null;
|
|
@@ -5307,7 +5374,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5307
5374
|
'written into the session log.');
|
|
5308
5375
|
}
|
|
5309
5376
|
const mentions = findMentions(task, pathKind);
|
|
5310
|
-
const forDisplay = taskTranscriptLabel(pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task), generated?.label);
|
|
5377
|
+
const forDisplay = taskTranscriptLabel(shown.includes('[Image #') ? shown : pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task), generated?.label);
|
|
5311
5378
|
addRow({ role: 'user', text: forDisplay });
|
|
5312
5379
|
if (isSlashCommand(task)) {
|
|
5313
5380
|
const spaceIdx = task.indexOf(' ');
|
|
@@ -5498,11 +5565,18 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5498
5565
|
* Only on the alternate screen: in inline mode the transcript is committed to the terminal's own
|
|
5499
5566
|
* scrollback full-width, and there is no frame to put a second column in.
|
|
5500
5567
|
*/
|
|
5568
|
+
// An inline terminal commits output to scrollback. Repainting a side column there makes every
|
|
5569
|
+
// status update expensive and produces the apparent "starts fast, then lags" failure on long
|
|
5570
|
+
// sessions. The compact status line remains live in both modes; the full panel belongs to the
|
|
5571
|
+
// fixed alternate-screen frame where Ink can update it in place.
|
|
5572
|
+
const activityPanelOpen = busy && altScreen && uiWidth >= 100 && termRows >= 24
|
|
5573
|
+
&& (diffPanelTouchedRef.current ? showDiffPanel : cfg.diffPanel !== false);
|
|
5501
5574
|
const diffPanelOpen = showDiffPanel && altScreen && liveDiffs.length > 0;
|
|
5575
|
+
const sidePanelOpen = activityPanelOpen || diffPanelOpen;
|
|
5502
5576
|
// Read by renderStep, which is defined above this line, so it goes through a ref rather than
|
|
5503
5577
|
// depending on declaration order.
|
|
5504
5578
|
diffPanelOpenRef.current = diffPanelOpen;
|
|
5505
|
-
const diffPanelWidth =
|
|
5579
|
+
const diffPanelWidth = sidePanelOpen
|
|
5506
5580
|
? Math.max(34, Math.min(Math.floor(uiWidth * 0.46), 96))
|
|
5507
5581
|
: 0;
|
|
5508
5582
|
const contentWidth = uiWidth - diffPanelWidth;
|
|
@@ -5625,7 +5699,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5625
5699
|
// Both halves matter. Clipping the bottom would hide what just happened; anchoring
|
|
5626
5700
|
// always to the bottom is what put the banner there.
|
|
5627
5701
|
_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 && (() => {
|
|
5702
|
+
.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
5703
|
const done = agents.filter(a => a.status !== 'running').length;
|
|
5630
5704
|
const failed = agents.filter(a => a.status === 'failed').length;
|
|
5631
5705
|
const tokens = agents.reduce((n, a) => n + a.tokens, 0);
|
|
@@ -5642,7 +5716,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5642
5716
|
const task = a.task.length > taskWidth ? a.task.slice(0, taskWidth - 3) + '...' : a.task;
|
|
5643
5717
|
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
5718
|
})] }));
|
|
5645
|
-
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [SPINNER[spinFrame], " "] }), _jsx(Shimmer, { text: workWord({
|
|
5719
|
+
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [animate ? SPINNER[spinFrame] : '●', " "] }), _jsx(Shimmer, { text: workWord({
|
|
5646
5720
|
elapsedMs, tools: turnToolsRef.current,
|
|
5647
5721
|
tokens: liveTokens, recovered: turnRecoveredRef.current,
|
|
5648
5722
|
}, spinWord)[0], frame: shimmerFrame, base: MUTED }), _jsx(Text, { color: MUTED, children: "\u2026 " }), _jsxs(Text, { color: DIM, children: ["(", fmtElapsed(elapsedMs), " | ", liveTokens > 0
|
|
@@ -5650,9 +5724,10 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5650
5724
|
// A gateway routing to several backends sends content-free frames while it
|
|
5651
5725
|
// finds one. Saying "waiting for the first token" through that reads as a
|
|
5652
5726
|
// hang, when in fact the request was accepted and is being worked on.
|
|
5653
|
-
:
|
|
5654
|
-
:
|
|
5655
|
-
:
|
|
5727
|
+
: liveToolRef.current ? 'running ' + liveToolRef.current.name
|
|
5728
|
+
: providerFramesRef.current > 0 ? `${cfg.provider} is holding the line, no output yet`
|
|
5729
|
+
: elapsedMs > 8_000 ? `no response from ${cfg.provider} yet`
|
|
5730
|
+
: 'starting', thinkingRef.current.chars > 0 && Date.now() - thinkingRef.current.at < 2_000
|
|
5656
5731
|
? ` | thinking, ${fmtTokens(Math.round(thinkingRef.current.chars / 4))} reasoning tokens`
|
|
5657
5732
|
: '', ")"] })] }) }), 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
5733
|
const width = Math.min(barWidth, usagePane === 'settings' ? 96 : 66);
|
|
@@ -5972,11 +6047,13 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5972
6047
|
: '*type the number, or arrows and enter · esc refuses*',
|
|
5973
6048
|
];
|
|
5974
6049
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 46), color: AMBER, title: `Permission — ${modeSpec(mode).label} mode`, children: lines }) }));
|
|
5975
|
-
})()] }),
|
|
6050
|
+
})()] }), 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
|
|
6051
|
+
? 'Running ' + liveToolRef.current.name : thinkingRef.current.chars > 0
|
|
6052
|
+
? '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
6053
|
? _jsx(Text, { color: INK, children: draft.slice(caret) })
|
|
5977
6054
|
: caret < draft.length
|
|
5978
6055
|
? _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"] })] }))] }) })] }));
|
|
6056
|
+
: _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
6057
|
}
|
|
5981
6058
|
export async function runInkChatMode(config) {
|
|
5982
6059
|
// Nothing is written before the first frame. Filling the screen to push the prompt to the foot
|