koneck 2.25.2 → 2.25.3
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/diff-view.d.ts +52 -0
- package/dist/diff-view.d.ts.map +1 -0
- package/dist/diff-view.js +208 -0
- package/dist/diff-view.js.map +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +31 -6
- package/dist/engine.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +115 -5
- package/dist/ink-chat.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -8,11 +8,12 @@ import { generateSessionId, saveSession, listSessions, loadSession } from './ses
|
|
|
8
8
|
import { loadMemory } from './memory.js';
|
|
9
9
|
import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE } from './config-store.js';
|
|
10
10
|
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath } from './providers.js';
|
|
11
|
-
import { listCheckpoints, revertCheckpoint, snapshotTree } from './checkpoint.js';
|
|
11
|
+
import { listCheckpoints, revertCheckpoint, snapshotTree, filesTouchedBy } from './checkpoint.js';
|
|
12
|
+
import { fileDiff, diffSummary } from './diff-view.js';
|
|
12
13
|
import { bar, humanTokens, humanDuration, diffStatSince, modelInfoFrom, contextWindowFor, recordStatus, classifyResponse, stateLabel, readModelStatus, } from './usage.js';
|
|
13
14
|
import { Markdown, Panel } from './markdown-ink.js';
|
|
14
15
|
import { fetchUpdateStatus, updateCommand } from './update-check.js';
|
|
15
|
-
import { readFileSync } from 'fs';
|
|
16
|
+
import { readFileSync, statSync } from 'fs';
|
|
16
17
|
import { fileURLToPath } from 'url';
|
|
17
18
|
import path from 'path';
|
|
18
19
|
/** Read once at load so the running version can always be shown without a lookup per render. */
|
|
@@ -33,6 +34,13 @@ const AMBER = '#F6C453';
|
|
|
33
34
|
const MUTED = '#9AAAB2';
|
|
34
35
|
const INK = '#F4F1EA';
|
|
35
36
|
const DIM = '#5C717A';
|
|
37
|
+
// Diff colours. Deliberately not terminal green/red: those clash with the warm palette and read
|
|
38
|
+
// as errors. These are the existing accents lifted a little, over bands dark enough to sit on a
|
|
39
|
+
// black terminal without glowing.
|
|
40
|
+
const DIFF_ADD = '#7FE3B0';
|
|
41
|
+
const DIFF_ADD_BG = '#10291F';
|
|
42
|
+
const DIFF_DEL = '#F2909B';
|
|
43
|
+
const DIFF_DEL_BG = '#2B1419';
|
|
36
44
|
// The user's message bar: light-on-dark, high contrast against the terminal ground so an
|
|
37
45
|
// input is easy to find when scrolling back. Deliberately not the cyan used for KONECK.
|
|
38
46
|
const USER_BG = '#3A434A';
|
|
@@ -257,6 +265,10 @@ const EFFORT_BLURB = {
|
|
|
257
265
|
high: 'Thorough — covers edge cases, costs more tokens',
|
|
258
266
|
max: 'Most rigorous analysis available; slowest and priciest',
|
|
259
267
|
};
|
|
268
|
+
/** Past this, a file is not read for diffing — the render would be useless anyway. */
|
|
269
|
+
const MAX_DIFF_BYTES = 512 * 1024;
|
|
270
|
+
/** One tool rarely touches more; the cap stops a bulk move from flooding scrollback. */
|
|
271
|
+
const MAX_DIFF_FILES = 4;
|
|
260
272
|
/** The tabs of the usage pane, in the order they are shown. */
|
|
261
273
|
const USAGE_TABS = ['session', 'context', 'models', 'limits'];
|
|
262
274
|
const MIN_REPLY_WIDTH = 24; // below this a table cannot render legibly anyway
|
|
@@ -331,6 +343,8 @@ function App({ config: initialConfig }) {
|
|
|
331
343
|
// instead of calling setState per token keeps a Raspberry Pi from re-rendering on every chunk.
|
|
332
344
|
const sayRef = useRef(''); // narration not yet committed
|
|
333
345
|
const liveToolRef = useRef(null); // the one tool still running
|
|
346
|
+
/** Pre-edit contents of the files the running tool said it would touch. */
|
|
347
|
+
const pendingDiffRef = useRef([]);
|
|
334
348
|
const charsRef = useRef(0); // streamed chars, for the live token estimate
|
|
335
349
|
const realTokRef = useRef(0); // real output tokens this turn, once the provider reports usage
|
|
336
350
|
const tokBaseRef = useRef(0); // session output-token count at the start of this turn
|
|
@@ -349,25 +363,73 @@ function App({ config: initialConfig }) {
|
|
|
349
363
|
if (text !== '')
|
|
350
364
|
setRows(prev => [...prev, { role: 'steps', steps: [{ kind: 'say', text }] }]);
|
|
351
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Reads a file for diffing, or null if it is absent or too big to be worth showing. Reading is
|
|
368
|
+
* synchronous on purpose: the tool runs the instant this callback returns, so an await here
|
|
369
|
+
* would race the very write it is trying to photograph.
|
|
370
|
+
*/
|
|
371
|
+
function readForDiff(rel) {
|
|
372
|
+
try {
|
|
373
|
+
const abs = path.resolve(cfg.cwd, rel);
|
|
374
|
+
if (statSync(abs).size > MAX_DIFF_BYTES)
|
|
375
|
+
return null;
|
|
376
|
+
return readFileSync(abs, 'utf-8');
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return null; // absent: a creation, which fileDiff renders as such
|
|
380
|
+
}
|
|
381
|
+
}
|
|
352
382
|
function pushTool(name, args) {
|
|
353
383
|
commitSay();
|
|
384
|
+
// Photograph every file this tool has announced it will touch, before it touches it.
|
|
385
|
+
pendingDiffRef.current = filesTouchedBy(name, args).slice(0, MAX_DIFF_FILES)
|
|
386
|
+
.map(rel => ({ rel, before: readForDiff(rel) }));
|
|
354
387
|
liveToolRef.current = { kind: 'tool', name, detail: toolDetail(name, args), startedAt: Date.now() };
|
|
355
388
|
}
|
|
356
389
|
function finishTool(name, ok, ms) {
|
|
357
390
|
if (!ok)
|
|
358
391
|
turnRecoveredRef.current = true;
|
|
359
392
|
const live = liveToolRef.current;
|
|
393
|
+
const pending = pendingDiffRef.current;
|
|
360
394
|
liveToolRef.current = null;
|
|
395
|
+
pendingDiffRef.current = [];
|
|
361
396
|
if (!live)
|
|
362
397
|
return;
|
|
363
|
-
|
|
398
|
+
// A failed tool leaves the file as it was, so there is nothing to show.
|
|
399
|
+
const diffs = ok
|
|
400
|
+
? pending
|
|
401
|
+
.map(({ rel, before }) => fileDiff(rel, before, readForDiff(rel)))
|
|
402
|
+
.filter(d => !d.unchanged)
|
|
403
|
+
: [];
|
|
404
|
+
const done = { ...live, endedAt: live.startedAt + ms, ok, ...(diffs.length ? { diffs } : {}) };
|
|
364
405
|
setRows(prev => [...prev, { role: 'steps', steps: [done] }]);
|
|
365
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Moves the agent panel into scrollback once the turn is over. The live panel is transient by
|
|
409
|
+
* design — it repaints — so without this the record of what the agents did would scroll away
|
|
410
|
+
* with the last repaint and leave nothing behind.
|
|
411
|
+
*/
|
|
412
|
+
function flushAgents() {
|
|
413
|
+
const list = agentsRef.current;
|
|
414
|
+
if (list.length === 0)
|
|
415
|
+
return;
|
|
416
|
+
agentsRef.current = [];
|
|
417
|
+
setAgents([]);
|
|
418
|
+
const done = list.filter(a => a.status === 'done').length;
|
|
419
|
+
const failed = list.filter(a => a.status === 'failed').length;
|
|
420
|
+
const tokens = list.reduce((n, a) => n + a.tokens, 0);
|
|
421
|
+
// The elapsed span is the longest agent, not the sum: they ran at the same time.
|
|
422
|
+
const span = Math.max(...list.map(a => (a.endedAt ?? Date.now()) - a.startedAt));
|
|
423
|
+
addRow({ role: 'steps', steps: [{ kind: 'say', text: `${list.length} parallel agents finished — ${done} succeeded` +
|
|
424
|
+
(failed ? `, ${failed} failed` : '') +
|
|
425
|
+
` · ${humanTokens(tokens)} tokens · ${fmtElapsed(span)}` }] });
|
|
426
|
+
}
|
|
366
427
|
// Session Promise is created eagerly at mount so it resolves before the user's first message.
|
|
367
428
|
const sessionOpts = {
|
|
368
429
|
onChunk: (text) => pushSay(text),
|
|
369
430
|
onToolCall: (name, args) => pushTool(name, args),
|
|
370
431
|
onToolResult: (name, ok, ms) => finishTool(name, ok, ms),
|
|
432
|
+
onAgentProgress: (list) => { agentsRef.current = list; setAgents(list); },
|
|
371
433
|
onTokens: (s) => {
|
|
372
434
|
realTokRef.current = Math.max(0, s.completionTokens - tokBaseRef.current);
|
|
373
435
|
},
|
|
@@ -404,6 +466,11 @@ function App({ config: initialConfig }) {
|
|
|
404
466
|
// compare against. Collapsing the two would flash "not a git repo" at every open.
|
|
405
467
|
const [usageDiff, setUsageDiff] = useState(undefined);
|
|
406
468
|
const [modelCatalog, setModelCatalog] = useState([]);
|
|
469
|
+
/** Live state of parallel sub-agents. Empty except while spawn_agents is running. */
|
|
470
|
+
const [agents, setAgents] = useState([]);
|
|
471
|
+
// Mirrored in a ref because the turn's exit paths run inside a closure that would otherwise
|
|
472
|
+
// see the state as it was when the turn began.
|
|
473
|
+
const agentsRef = useRef([]);
|
|
407
474
|
// Masked credential entry. The value is held in component state for the session and passed
|
|
408
475
|
// to the client as a runtime override — KONECK never writes it to disk, and it is never put
|
|
409
476
|
// into a transcript row, so it cannot end up in a saved session file.
|
|
@@ -1576,6 +1643,7 @@ function App({ config: initialConfig }) {
|
|
|
1576
1643
|
if (controller.signal.aborted) {
|
|
1577
1644
|
sayRef.current = '';
|
|
1578
1645
|
liveToolRef.current = null;
|
|
1646
|
+
flushAgents();
|
|
1579
1647
|
addRow({ role: 'system', text: `Cancelled after ${fmtElapsed(Date.now() - started)}. Anything already done is above; ` +
|
|
1580
1648
|
'your composer is untouched.' });
|
|
1581
1649
|
setAgentState('ready');
|
|
@@ -1588,6 +1656,7 @@ function App({ config: initialConfig }) {
|
|
|
1588
1656
|
const content = last && typeof last.content === 'string'
|
|
1589
1657
|
? last.content : 'Task finished with no written response.';
|
|
1590
1658
|
setLastAgentText(content);
|
|
1659
|
+
flushAgents();
|
|
1591
1660
|
const elapsed = Date.now() - started;
|
|
1592
1661
|
apiMsRef.current += elapsed;
|
|
1593
1662
|
// A turn that completed is the only proof a model actually works that costs nothing
|
|
@@ -1627,6 +1696,7 @@ function App({ config: initialConfig }) {
|
|
|
1627
1696
|
const trailing = sayRef.current.trim();
|
|
1628
1697
|
sayRef.current = '';
|
|
1629
1698
|
liveToolRef.current = null;
|
|
1699
|
+
flushAgents();
|
|
1630
1700
|
setRows(prev => [
|
|
1631
1701
|
...prev,
|
|
1632
1702
|
...(trailing ? [{ role: 'steps', steps: [{ kind: 'say', text: trailing }] }] : []),
|
|
@@ -1678,6 +1748,29 @@ function App({ config: initialConfig }) {
|
|
|
1678
1748
|
// Live output token count: real usage when the provider reports it, else a char estimate
|
|
1679
1749
|
const liveTokens = Math.max(realTokRef.current, Math.round(charsRef.current / 4));
|
|
1680
1750
|
/** Renders one activity step. Shared by the live view and committed scrollback so they match. */
|
|
1751
|
+
/**
|
|
1752
|
+
* A file change, drawn the way a diff is read: a gutter of line numbers, a sign, and the line
|
|
1753
|
+
* itself on a tinted band. Every rendered line is padded to the same width so the tint forms a
|
|
1754
|
+
* solid block rather than a ragged edge, which is what makes an added run legible at a glance.
|
|
1755
|
+
*/
|
|
1756
|
+
const renderDiff = (d, key) => {
|
|
1757
|
+
const numbers = d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0));
|
|
1758
|
+
const gutter = Math.max(2, String(Math.max(0, ...numbers)).length);
|
|
1759
|
+
const width = Math.max(24, barWidth - gutter - 8);
|
|
1760
|
+
const header = d.created ? 'new file' : d.deleted ? 'deleted' : diffSummary(d);
|
|
1761
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }), d.binary
|
|
1762
|
+
? _jsx(Text, { color: DIM, children: ' binary file - not shown' })
|
|
1763
|
+
: d.hunks.map((hunk, hi) => (_jsxs(Box, { flexDirection: "column", children: [hi > 0 && _jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ..."] }), hunk.lines.map((l, li) => {
|
|
1764
|
+
const shown = l.kind === 'del' ? l.before : l.after;
|
|
1765
|
+
const sign = l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ';
|
|
1766
|
+
const fg = l.kind === 'add' ? DIFF_ADD : l.kind === 'del' ? DIFF_DEL : MUTED;
|
|
1767
|
+
const bg = l.kind === 'add' ? DIFF_ADD_BG : l.kind === 'del' ? DIFF_DEL_BG : undefined;
|
|
1768
|
+
// Tabs would make the tinted block a ragged width, so they are expanded.
|
|
1769
|
+
const body = `${sign} ${l.text}`.replace(/\t/g, ' ');
|
|
1770
|
+
const cell = body.length > width ? body.slice(0, width - 3) + '...' : body.padEnd(width);
|
|
1771
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [String(shown ?? '').padStart(gutter), " "] }), _jsx(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: cell })] }, li));
|
|
1772
|
+
})] }, hi))), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
|
|
1773
|
+
};
|
|
1681
1774
|
const renderStep = (step, key) => {
|
|
1682
1775
|
if (step.kind === 'say') {
|
|
1683
1776
|
return (_jsxs(Box, { marginTop: 1, gap: 1, children: [_jsx(Text, { color: CYAN, children: "\u25CF" }), _jsx(Text, { color: INK, children: step.text.trim() })] }, key));
|
|
@@ -1686,7 +1779,7 @@ function App({ config: initialConfig }) {
|
|
|
1686
1779
|
const ms = (step.endedAt ?? Date.now()) - step.startedAt;
|
|
1687
1780
|
const glyph = running ? SPINNER[spinFrame] : step.ok === false ? '✖' : '✓';
|
|
1688
1781
|
const color = running ? AMBER : step.ok === false ? CRIMSON : GREEN;
|
|
1689
|
-
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: AMBER, bold: true, children: step.name }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(ms) })] }), step.detail !== '' && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: MUTED, children: step.detail })] }))] }, key));
|
|
1782
|
+
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: AMBER, bold: true, children: step.name }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(ms) })] }), step.detail !== '' && (step.diffs ?? []).length === 0 && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: MUTED, children: step.detail })] })), (step.diffs ?? []).map((d, i) => renderDiff(d, i))] }, key));
|
|
1690
1783
|
};
|
|
1691
1784
|
return (_jsxs(Box, { flexDirection: "column", width: uiWidth, paddingX: 2, children: [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: rows, children: (row, index) => row.role === 'header' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: "KONECK / SOFTWARE DELIVERY SYSTEM" }), _jsx(Text, { color: MUTED, children: "Deliberate engineering, with control at every change." })] }), _jsx(Panel, { width: barWidth, color: CYAN, title: "KONECK", children: [`MODEL: \`${modelShort}\` PROVIDER: \`${cfg.provider}\` WORKSPACE: \`${workspace}\``] })] }, index)) : row.role === 'user' ? (
|
|
1692
1785
|
// Painted as a filled bar so the user's own words are the most findable thing
|
|
@@ -1696,7 +1789,24 @@ function App({ config: initialConfig }) {
|
|
|
1696
1789
|
// content, so the right edge stepped in and out; and a box inside a box left
|
|
1697
1790
|
// markdown tables needlessly narrow. A marker plus the content reads better and
|
|
1698
1791
|
// cannot misalign, which is how Claude Code presents its own answers.
|
|
1699
|
-
_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: "\uA7B0K" }), _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 ?? 'Done', " in ", fmtElapsed(row.elapsed ?? 0), ' | ', row.tokens, " tokens", ' | ', cfg.provider, " ", modelShort] }))] }, index)) }), _jsx(Box, { flexDirection: "column", children: busy && (agentState === 'processing' || agentState === 'syncing') && (_jsxs(Box, { flexDirection: "column", children: [sayRef.current.trim() !== '' && renderStep({ kind: 'say', text: tailLines(sayRef.current, 2, replyWidth) }, 0), liveToolRef.current && renderStep(liveToolRef.current, 1),
|
|
1792
|
+
_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: "\uA7B0K" }), _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 ?? 'Done', " in ", fmtElapsed(row.elapsed ?? 0), ' | ', row.tokens, " tokens", ' | ', cfg.provider, " ", modelShort] }))] }, index)) }), _jsx(Box, { flexDirection: "column", children: busy && (agentState === 'processing' || agentState === 'syncing') && (_jsxs(Box, { flexDirection: "column", children: [sayRef.current.trim() !== '' && renderStep({ kind: 'say', text: tailLines(sayRef.current, 2, replyWidth) }, 0), liveToolRef.current && renderStep(liveToolRef.current, 1), agents.length > 0 && (() => {
|
|
1793
|
+
const done = agents.filter(a => a.status !== 'running').length;
|
|
1794
|
+
const failed = agents.filter(a => a.status === 'failed').length;
|
|
1795
|
+
const tokens = agents.reduce((n, a) => n + a.tokens, 0);
|
|
1796
|
+
const started = Math.min(...agents.map(a => a.startedAt));
|
|
1797
|
+
// The task text is clipped to whatever is left after the fixed columns, so a long
|
|
1798
|
+
// task never wraps and breaks the alignment of the rows under it.
|
|
1799
|
+
const taskWidth = Math.max(16, barWidth - 34);
|
|
1800
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, children: "\u25C6" }), _jsxs(Text, { color: INK, bold: true, children: [agents.length, " agents"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsxs(Text, { color: done === agents.length ? GREEN : AMBER, children: [done, "/", agents.length, " complete"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsxs(Text, { color: MUTED, children: [humanTokens(tokens), " tok"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(Date.now() - started) }), failed > 0 && _jsxs(Text, { color: CRIMSON, children: ["\u00B7 ", failed, " failed"] })] }), agents.map(a => {
|
|
1801
|
+
const glyph = a.status === 'running' ? SPINNER[spinFrame]
|
|
1802
|
+
: a.status === 'failed' ? '✖' : '✓';
|
|
1803
|
+
const color = a.status === 'running' ? AMBER
|
|
1804
|
+
: a.status === 'failed' ? CRIMSON : GREEN;
|
|
1805
|
+
const took = (a.endedAt ?? Date.now()) - a.startedAt;
|
|
1806
|
+
const task = a.task.length > taskWidth ? a.task.slice(0, taskWidth - 3) + '...' : a.task;
|
|
1807
|
+
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: task.padEnd(taskWidth) }), _jsx(Text, { color: DIM, children: humanTokens(a.tokens).padStart(6) }), _jsx(Text, { color: DIM, children: fmtElapsed(took).padStart(6) })] }, a.index));
|
|
1808
|
+
})] }));
|
|
1809
|
+
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [SPINNER[spinFrame], " "] }), _jsx(Shimmer, { text: workWord(elapsedMs, spinWord, turnRecoveredRef.current)[0], frame: shimmerFrame, base: MUTED }), _jsx(Text, { color: MUTED, children: "\u2026 " }), _jsxs(Text, { color: DIM, children: ["(", fmtElapsed(elapsedMs), " | ", liveTokens > 0
|
|
1700
1810
|
? `${fmtTokens(liveTokens)} tokens`
|
|
1701
1811
|
: elapsedMs > 20_000 ? 'waiting for the first token' : 'starting', ")"] })] }) }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: "esc to cancel and keep your input \u00B7 ctrl+c to exit" }) })] })) }), usagePane && (() => {
|
|
1702
1812
|
const width = Math.min(barWidth, 66);
|