koneck 2.123.0 → 2.125.0
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/engine-facts.d.ts +36 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +44 -0
- package/dist/engine-facts.js.map +1 -0
- package/dist/engine.d.ts +1 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +5 -29
- package/dist/engine.js.map +1 -1
- package/dist/frame-pace.d.ts +41 -0
- package/dist/frame-pace.d.ts.map +1 -0
- package/dist/frame-pace.js +65 -0
- package/dist/frame-pace.js.map +1 -0
- package/dist/frame-sync.d.ts +43 -0
- package/dist/frame-sync.d.ts.map +1 -0
- package/dist/frame-sync.js +80 -0
- package/dist/frame-sync.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/ink-chat.d.ts +40 -2
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +133 -56
- package/dist/ink-chat.js.map +1 -1
- package/dist/model-catalog.d.ts +1 -2
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +4 -8
- package/dist/model-catalog.js.map +1 -1
- package/dist/pricing.d.ts +10 -0
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +16 -0
- package/dist/pricing.js.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -2,8 +2,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Static, Text, render, useApp, useInput, useStdin } from 'ink';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { resolveAgentConcurrency, toolsUnsupportedNotice } from './engine-facts.js';
|
|
6
|
+
import { withSynchronizedFrames } from './frame-sync.js';
|
|
7
|
+
import { pacedInterval } from './frame-pace.js';
|
|
8
|
+
import { estimateCost, formatCost, rateText } from './pricing.js';
|
|
7
9
|
import { generateSessionId, saveSession, listSessions, loadSession, relativeAge, renameSession, forkSession, archiveSession, findSession, } from './session.js';
|
|
8
10
|
import { loadMemory } from './memory.js';
|
|
9
11
|
import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue } from './config-store.js';
|
|
@@ -49,16 +51,18 @@ import { logoFor } from './logo.js';
|
|
|
49
51
|
import { describeActivity } from './activity.js';
|
|
50
52
|
import { redactSecrets, containsSecret } from './redact.js';
|
|
51
53
|
import { renderTrajectory } from './trajectory.js';
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// sandbox.js and doctor.js are loaded by /status and /doctor. sandbox.js imports execaSync to run
|
|
55
|
+
// bubblewrap and doctor.js shells out to check the environment, and both are reached only from
|
|
56
|
+
// handleCommand, which is async. Measured: 122ms and 150ms.
|
|
57
|
+
// quality-gate.js, mcp.js and refactor.js are loaded at the one command that uses each. Each
|
|
58
|
+
// reaches something large — quality-gate takes shellFor from tools.js, refactor takes runAgent
|
|
59
|
+
// from engine.js and ts-morph with it — and an eager import here undid the engine being lazy,
|
|
60
|
+
// because the graph simply arrived by another road. Measured: 339ms, 141ms and 75ms.
|
|
61
|
+
// preflight.js is loaded by the command that runs it: it imports quality-gate.js, and so
|
|
62
|
+
// tools.js, for 505ms that a session which never types /preflight should not pay.
|
|
56
63
|
import { listAuditEvents } from './audit.js';
|
|
57
64
|
import { initializePolicy, loadPolicy, POLICY_FILE } from './policy.js';
|
|
58
65
|
import { isWorkspaceTrusted, trustWorkspace, untrustWorkspace } from './workspace-trust.js';
|
|
59
|
-
import { loadMCPConfig } from './mcp.js';
|
|
60
|
-
import { renameSymbol, extractInterface, moveSymbol } from './refactor.js';
|
|
61
|
-
import { rateText } from './model-catalog.js';
|
|
62
66
|
const CYAN = '#54D7E7';
|
|
63
67
|
const GREEN = '#9BCB8F';
|
|
64
68
|
const CRIMSON = '#E8878D';
|
|
@@ -382,9 +386,62 @@ export function liveReplyLines(termRows) {
|
|
|
382
386
|
const rows = termRows && termRows > 0 ? termRows : 24;
|
|
383
387
|
return rows < 12 ? 1 : 2;
|
|
384
388
|
}
|
|
385
|
-
/**
|
|
386
|
-
|
|
387
|
-
|
|
389
|
+
/**
|
|
390
|
+
* The engine, loaded the first time a turn needs it.
|
|
391
|
+
*
|
|
392
|
+
* Importing it at the top of this module cost 645ms of the 1,163ms it took to load the terminal
|
|
393
|
+
* app on a Raspberry Pi 5 — it reaches the OpenAI SDK, execa and ts-morph through fifty-seven
|
|
394
|
+
* modules — and not one line of it is needed to draw the banner, the prompt or the status bar.
|
|
395
|
+
* Nothing here is on a render path: every caller already runs inside an async handler, and the
|
|
396
|
+
* two things the interface prints synchronously are in engine-facts.js for that reason.
|
|
397
|
+
*
|
|
398
|
+
* The promise is cached, so concurrent callers share one import and later turns pay nothing. It is
|
|
399
|
+
* also warmed in the background as soon as the interface is up, which is why the first turn does
|
|
400
|
+
* not feel the move either.
|
|
401
|
+
*/
|
|
402
|
+
let enginePromise;
|
|
403
|
+
function engine() {
|
|
404
|
+
return (enginePromise ??= import('./engine.js'));
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* How long one animation frame lasts.
|
|
408
|
+
*
|
|
409
|
+
* Every tick both advances the animation and reveals whatever the stream has added since the
|
|
410
|
+
* last one, so this is the interface's frame rate. It was 500ms while a stream was active and
|
|
411
|
+
* 1,000ms once it went quiet, which is where "the loader is moving slowly ... even 10x slower
|
|
412
|
+
* than a tortoise" came from: the spinner took ten seconds to turn once and the shimmer
|
|
413
|
+
* forty-eight to cross a word.
|
|
414
|
+
*
|
|
415
|
+
* The reason it was that slow was flicker, and the flicker was real — but the rate was never its
|
|
416
|
+
* cause. Ink writes a frame by erasing the previous one and drawing the next, and the terminal
|
|
417
|
+
* showed the gap between the two. Repainting less often only made the flash rarer. It is now
|
|
418
|
+
* fixed where it happens, in frame-sync.ts, by presenting each frame atomically, which leaves the
|
|
419
|
+
* frame rate free to be a frame rate.
|
|
420
|
+
*
|
|
421
|
+
* 80ms is what cli-spinners and ora use for braille spinners, and it is comfortably above Ink's
|
|
422
|
+
* own 32ms write throttle (`throttle(this.onRender, 32)` in ink/build/ink.js), so each tick
|
|
423
|
+
* becomes exactly one write rather than being coalesced with its neighbours. Measured cost of a
|
|
424
|
+
* tick on a Raspberry Pi 5: 0.7ms of React render and about 700 bytes written, or roughly 1% of
|
|
425
|
+
* one core at this rate.
|
|
426
|
+
*/
|
|
427
|
+
export const FRAME_MS = 80;
|
|
428
|
+
/**
|
|
429
|
+
* How long one work word is held before the next is shown.
|
|
430
|
+
*
|
|
431
|
+
* The word is there to be read and enjoyed, not to reassure by moving, so it outlasts the
|
|
432
|
+
* animation by a long way.
|
|
433
|
+
*/
|
|
434
|
+
export const WORD_HOLD_MS = 60_000;
|
|
435
|
+
/**
|
|
436
|
+
* Which spinner cell and shimmer column a turn is showing after this much work.
|
|
437
|
+
*
|
|
438
|
+
* One function so the two cannot drift apart, and pure so the rates can be asserted rather than
|
|
439
|
+
* eyeballed: both advance one step per frame, the spinner wrapping around its glyphs and the
|
|
440
|
+
* shimmer running on for `Shimmer` to wrap over the word plus its trailing gap.
|
|
441
|
+
*/
|
|
442
|
+
export function animFrames(elapsedMs, spinnerLength) {
|
|
443
|
+
const frame = Math.floor(Math.max(0, elapsedMs) / FRAME_MS);
|
|
444
|
+
return { spin: frame % spinnerLength, shimmer: frame };
|
|
388
445
|
}
|
|
389
446
|
const SPINNER = G.spinner;
|
|
390
447
|
/**
|
|
@@ -1215,7 +1272,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1215
1272
|
// Tool approval callbacks belong to a session created on an earlier render. Keep the control
|
|
1216
1273
|
// posture live so turning approval on during a turn governs its next consequential tool call.
|
|
1217
1274
|
const cfgRef = useRef(initialConfig);
|
|
1218
|
-
// Live activity accumulates in a ref and is painted by the
|
|
1275
|
+
// Live activity accumulates in a ref and is painted by the FRAME_MS ticker. Mutating a ref
|
|
1219
1276
|
// instead of calling setState per token keeps a Raspberry Pi from re-rendering on every chunk.
|
|
1220
1277
|
const sayRef = useRef(''); // narration not yet committed
|
|
1221
1278
|
const liveToolRef = useRef(null); // the one tool still running
|
|
@@ -1223,8 +1280,6 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1223
1280
|
const replyStartedRef = useRef(false);
|
|
1224
1281
|
/** Everything streamed this turn. Empty means the provider did not stream at all. */
|
|
1225
1282
|
const streamedRef = useRef('');
|
|
1226
|
-
/** When the live region last had something new to show, used to pick the repaint rate. */
|
|
1227
|
-
const lastActivityRef = useRef(Date.now());
|
|
1228
1283
|
/**
|
|
1229
1284
|
* Frames received before any content — proof the provider has the request and is working.
|
|
1230
1285
|
*
|
|
@@ -1336,7 +1391,6 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1336
1391
|
* whatever is left in the buffer.
|
|
1337
1392
|
*/
|
|
1338
1393
|
function pushSay(text) {
|
|
1339
|
-
lastActivityRef.current = Date.now();
|
|
1340
1394
|
sayRef.current += text;
|
|
1341
1395
|
streamedRef.current += text;
|
|
1342
1396
|
charsRef.current += text.length;
|
|
@@ -1391,7 +1445,6 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1391
1445
|
function pushTool(name, args) {
|
|
1392
1446
|
commitSay();
|
|
1393
1447
|
// Photograph every file this tool has announced it will touch, before it touches it.
|
|
1394
|
-
lastActivityRef.current = Date.now();
|
|
1395
1448
|
turnToolsRef.current += 1;
|
|
1396
1449
|
pendingDiffRef.current = filesTouchedBy(name, args).slice(0, MAX_DIFF_FILES)
|
|
1397
1450
|
.map(rel => ({ rel, before: readForDiff(rel) }));
|
|
@@ -1545,10 +1598,9 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1545
1598
|
},
|
|
1546
1599
|
onToolResult: (name, ok, ms, detail) => finishTool(name, ok, ms, detail),
|
|
1547
1600
|
onToolOutput: (_name, line) => {
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
//
|
|
1551
|
-
// updates; the whole region just stops being redrawn eight times a second to show it.
|
|
1601
|
+
// Written to the ref rather than to state: a dev server logs a request every second or two,
|
|
1602
|
+
// and a setState per line re-rendered the interface on each one. The ticker paints whatever
|
|
1603
|
+
// the ref holds when the next frame comes round, so a chatty tool costs nothing extra.
|
|
1552
1604
|
if (liveToolRef.current)
|
|
1553
1605
|
liveToolRef.current.output = line;
|
|
1554
1606
|
},
|
|
@@ -1597,7 +1649,6 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1597
1649
|
// produce tens of thousands of characters per turn, and what anyone watching wants is what
|
|
1598
1650
|
// it is thinking now.
|
|
1599
1651
|
thinkingTextRef.current = (thinkingTextRef.current + text).slice(-THINKING_TAIL_CHARS);
|
|
1600
|
-
lastActivityRef.current = Date.now();
|
|
1601
1652
|
},
|
|
1602
1653
|
onAgentProgress: (list) => { agentsRef.current = list; setAgents(list); },
|
|
1603
1654
|
onWindowDetected: (limit, source) => setDetectedContext({ limit, source }),
|
|
@@ -1923,11 +1974,14 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
1923
1974
|
*/
|
|
1924
1975
|
const sessionNow = () => {
|
|
1925
1976
|
if (!activeSession.p) {
|
|
1926
|
-
|
|
1977
|
+
// The options are built here, not inside the then, so the hooks and refs are read at the
|
|
1978
|
+
// moment the session was asked for rather than a microtask later.
|
|
1979
|
+
const opts = {
|
|
1927
1980
|
...initialConfig,
|
|
1928
1981
|
pace: paceFrom(initialConfig.pace ?? savedEffort(undefined), 'medium'),
|
|
1929
1982
|
...failoverHooks(), silent: true, ...sessionOpts,
|
|
1930
|
-
}
|
|
1983
|
+
};
|
|
1984
|
+
activeSession.p = hold(engine().then(m => m.createAgentSession(opts)));
|
|
1931
1985
|
}
|
|
1932
1986
|
return activeSession.p;
|
|
1933
1987
|
};
|
|
@@ -2207,36 +2261,38 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
2207
2261
|
}
|
|
2208
2262
|
busyStart.current = Date.now();
|
|
2209
2263
|
wordTimer.current = 0;
|
|
2210
|
-
//
|
|
2211
|
-
//
|
|
2212
|
-
//
|
|
2213
|
-
//
|
|
2214
|
-
//
|
|
2264
|
+
// One rate, whether or not the stream is saying anything. The spinner earns its place
|
|
2265
|
+
// precisely when nothing is arriving — the 2m 44s wait on a provider that had gone quiet is
|
|
2266
|
+
// what this was reported from — so backing the animation off when a turn goes idle stopped
|
|
2267
|
+
// it in the one situation it exists for. Flicker is handled where it is caused, by writing
|
|
2268
|
+
// each frame atomically; see frame-sync.ts.
|
|
2215
2269
|
let timer;
|
|
2270
|
+
const wordEvery = Math.max(1, Math.round(WORD_HOLD_MS / FRAME_MS));
|
|
2271
|
+
/*
|
|
2272
|
+
* The rate this machine can actually sustain, not the one we would like.
|
|
2273
|
+
*
|
|
2274
|
+
* Each frame records how long the timer really took to arrive. On time means the loop is
|
|
2275
|
+
* keeping up and the rate stands; late means the event loop had more work than the frame's own
|
|
2276
|
+
* budget, and asking again just as soon only queues a frame that cannot be painted — which is
|
|
2277
|
+
* how a Pi ends up lagging while the same build feels instant on a laptop. See frame-pace.ts.
|
|
2278
|
+
*/
|
|
2279
|
+
let interval = FRAME_MS;
|
|
2280
|
+
let asked = Date.now();
|
|
2216
2281
|
const tick = () => {
|
|
2217
|
-
const
|
|
2218
|
-
|
|
2219
|
-
//
|
|
2220
|
-
//
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
shimmer: Math.floor(elapsed / 2_000),
|
|
2225
|
-
elapsed,
|
|
2226
|
-
});
|
|
2282
|
+
const now = Date.now();
|
|
2283
|
+
interval = pacedInterval(interval, now - asked);
|
|
2284
|
+
// Elapsed is read from the clock rather than counted in ticks, so a frame the event loop
|
|
2285
|
+
// delivers late moves the animation on by what actually passed instead of falling behind.
|
|
2286
|
+
const elapsed = now - busyStart.current;
|
|
2287
|
+
const { spin, shimmer } = animFrames(elapsed, SPINNER.length);
|
|
2288
|
+
setAnim({ spin, shimmer, elapsed });
|
|
2227
2289
|
wordTimer.current += 1;
|
|
2228
|
-
|
|
2229
|
-
// run — the word is there to be read and enjoyed, not to reassure by moving.
|
|
2230
|
-
if (wordTimer.current % Math.max(20, Math.round(60_000 / delay)) === 0) {
|
|
2290
|
+
if (wordTimer.current % wordEvery === 0)
|
|
2231
2291
|
setSpinWord(w => w + 1);
|
|
2232
|
-
|
|
2233
|
-
timer = setTimeout(tick,
|
|
2234
|
-
};
|
|
2235
|
-
const currentDelay = () => {
|
|
2236
|
-
const idleFor = Date.now() - lastActivityRef.current;
|
|
2237
|
-
return liveRefreshDelay(idleFor);
|
|
2292
|
+
asked = Date.now();
|
|
2293
|
+
timer = setTimeout(tick, interval);
|
|
2238
2294
|
};
|
|
2239
|
-
timer = setTimeout(tick,
|
|
2295
|
+
timer = setTimeout(tick, interval);
|
|
2240
2296
|
return () => clearTimeout(timer);
|
|
2241
2297
|
}, [busy]);
|
|
2242
2298
|
// A queued prompt runs the moment the agent is free again. Keyed on `busy` rather than done
|
|
@@ -2325,7 +2381,8 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
2325
2381
|
const c = newCfg ?? cfg;
|
|
2326
2382
|
cfgRef.current = c;
|
|
2327
2383
|
setDetectedContext(null);
|
|
2328
|
-
|
|
2384
|
+
const opts = { ...c, pace: paceFrom(effortRef.current, 'medium'), ...failoverHooks(), silent: true, ...sessionOpts };
|
|
2385
|
+
activeSession.p = hold(engine().then(m => m.createAgentSession(opts)));
|
|
2329
2386
|
if (newCfg)
|
|
2330
2387
|
setCfg(newCfg);
|
|
2331
2388
|
}
|
|
@@ -2419,7 +2476,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
2419
2476
|
: 'The sending session has not done any work yet; this is a fresh instruction.';
|
|
2420
2477
|
if (transcript.trim() !== '') {
|
|
2421
2478
|
try {
|
|
2422
|
-
const client = buildClient(cfg);
|
|
2479
|
+
const client = (await engine()).buildClient(cfg);
|
|
2423
2480
|
const res = await client.chat.completions.create({
|
|
2424
2481
|
model: cfg.model,
|
|
2425
2482
|
max_tokens: 220,
|
|
@@ -2481,7 +2538,8 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
2481
2538
|
...(st.note ? { note: st.note } : {}),
|
|
2482
2539
|
})), updatedAt: Date.now() }
|
|
2483
2540
|
: undefined;
|
|
2484
|
-
|
|
2541
|
+
const resumeOpts = { ...restoredCfg, silent: true, ...sessionOpts };
|
|
2542
|
+
activeSession.p = hold(engine().then(m => m.createAgentSession(resumeOpts, messages, { turns: meta.turns, totalTokens: meta.totalTokens }, restoredPlan)));
|
|
2485
2543
|
if (restoredPlan)
|
|
2486
2544
|
setPlan(restoredPlan);
|
|
2487
2545
|
setSessionGoal(meta.goal ?? null);
|
|
@@ -3104,6 +3162,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3104
3162
|
// question that actually matters when a provider misbehaves unanswered: where did the
|
|
3105
3163
|
// request go? Working that out took a proxy and half an hour.
|
|
3106
3164
|
const endpoint = resolveProvider(cfg.provider, cfg.baseURL).baseURL;
|
|
3165
|
+
const { describeSandbox, resolveSandbox } = await import('./sandbox.js');
|
|
3107
3166
|
addSystem(`Provider : ${cfg.provider}\n` +
|
|
3108
3167
|
`Endpoint : ${endpoint}\n` +
|
|
3109
3168
|
`Model : ${cfg.model}\n` +
|
|
@@ -3199,6 +3258,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3199
3258
|
// rather than a stale global default.
|
|
3200
3259
|
addSystem(`**KONECK Doctor** — checking ${cfg.provider} and this workspace…`);
|
|
3201
3260
|
try {
|
|
3261
|
+
const { doctorChecks, doctorReport } = await import('./doctor.js');
|
|
3202
3262
|
const checks = await doctorChecks(cfg.cwd, cfg.provider, cfg.baseURL, cfg.apiKey);
|
|
3203
3263
|
setRows(prev => prev.slice(0, -1));
|
|
3204
3264
|
addSystem(`**KONECK Doctor**\n\n${doctorReport(checks)}`);
|
|
@@ -3210,6 +3270,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3210
3270
|
return;
|
|
3211
3271
|
}
|
|
3212
3272
|
case '/mcp': {
|
|
3273
|
+
const { loadMCPConfig } = await import('./mcp.js');
|
|
3213
3274
|
const configured = await loadMCPConfig(cfg.cwd);
|
|
3214
3275
|
const session = await sessionNow().catch(() => null);
|
|
3215
3276
|
addSystem(mcpStatusText(Object.keys(configured?.servers ?? {}), session?.mcpServers ?? []));
|
|
@@ -3306,6 +3367,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3306
3367
|
addSystem(`Running native TypeScript refactor: ${cmd}…`);
|
|
3307
3368
|
try {
|
|
3308
3369
|
if (cmd === '/rename') {
|
|
3370
|
+
const { renameSymbol } = await import('./refactor.js');
|
|
3309
3371
|
const result = await renameSymbol(parts[0], parts[1], cfg.cwd, { announce: false });
|
|
3310
3372
|
addSystem(`Renamed \`${parts[0]}\` → \`${parts[1]}\` in ${result.filesChanged.length} file(s) (${result.occurrences} occurrence(s)).` +
|
|
3311
3373
|
(checkpoint
|
|
@@ -3313,11 +3375,13 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3313
3375
|
: '\n\nNo Git restore point was available. Run `/preflight` before committing.'));
|
|
3314
3376
|
}
|
|
3315
3377
|
else if (cmd === '/extract') {
|
|
3378
|
+
const { extractInterface } = await import('./refactor.js');
|
|
3316
3379
|
await extractInterface(parts[0], parts[1], cfg.cwd, { announce: false });
|
|
3317
3380
|
addSystem(`Extracted interface for \`${parts[1]}\` from \`${parts[0]}\`.` +
|
|
3318
3381
|
(checkpoint ? `\n\nRestore point: \`${checkpoint.id}\` — run \`/preflight\`, or \`/revert ${checkpoint.id} --confirm\` to undo.` : '\n\nRun `/preflight` before committing.'));
|
|
3319
3382
|
}
|
|
3320
3383
|
else {
|
|
3384
|
+
const { moveSymbol } = await import('./refactor.js');
|
|
3321
3385
|
await moveSymbol(parts[0], parts[1], parts[2], cfg.cwd, { announce: false });
|
|
3322
3386
|
addSystem(`Moved \`${parts[0]}\` from \`${parts[1]}\` to \`${parts[2]}\` and updated imports.` +
|
|
3323
3387
|
(checkpoint ? `\n\nRestore point: \`${checkpoint.id}\` — run \`/preflight\`, or \`/revert ${checkpoint.id} --confirm\` to undo.` : '\n\nRun `/preflight` before committing.'));
|
|
@@ -3835,6 +3899,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3835
3899
|
// the same independent proof on demand, without the legacy TUI writing over Ink's frame.
|
|
3836
3900
|
addSystem('**Quality gate** — running detected lint and test checks…');
|
|
3837
3901
|
try {
|
|
3902
|
+
const { runQualityGate } = await import('./quality-gate.js');
|
|
3838
3903
|
const result = await runQualityGate(cfg.cwd, { report: false });
|
|
3839
3904
|
setRows(prev => prev.slice(0, -1));
|
|
3840
3905
|
if (result.checks.length === 0) {
|
|
@@ -3861,6 +3926,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3861
3926
|
case '/preflight': {
|
|
3862
3927
|
addSystem('**Preflight** — checking patch integrity and detected quality gates…');
|
|
3863
3928
|
try {
|
|
3929
|
+
const { runPreflight, preflightReport } = await import('./preflight.js');
|
|
3864
3930
|
const result = await runPreflight(cfg.cwd);
|
|
3865
3931
|
setRows(prev => prev.slice(0, -1));
|
|
3866
3932
|
addSystem(preflightReport(result));
|
|
@@ -3878,6 +3944,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
3878
3944
|
}
|
|
3879
3945
|
addSystem('**Ship check** — running deterministic readiness checks…');
|
|
3880
3946
|
try {
|
|
3947
|
+
const { runPreflight, preflightReport, isPreflightReady } = await import('./preflight.js');
|
|
3881
3948
|
const result = await runPreflight(cfg.cwd);
|
|
3882
3949
|
setRows(prev => prev.slice(0, -1));
|
|
3883
3950
|
addSystem(preflightReport(result));
|
|
@@ -5130,7 +5197,6 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
5130
5197
|
thinkingRef.current = { chars: 0, at: 0 };
|
|
5131
5198
|
thinkingTextRef.current = '';
|
|
5132
5199
|
incompleteRef.current = null;
|
|
5133
|
-
lastActivityRef.current = Date.now();
|
|
5134
5200
|
providerFramesRef.current = 0;
|
|
5135
5201
|
liveToolRef.current = null;
|
|
5136
5202
|
charsRef.current = 0;
|
|
@@ -5422,7 +5488,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
5422
5488
|
//
|
|
5423
5489
|
// Both halves matter. Clipping the bottom would hide what just happened; anchoring
|
|
5424
5490
|
// always to the bottom is what put the banner there.
|
|
5425
|
-
_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: [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)
|
|
5491
|
+
_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)
|
|
5426
5492
|
.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 && (() => {
|
|
5427
5493
|
const done = agents.filter(a => a.status !== 'running').length;
|
|
5428
5494
|
const failed = agents.filter(a => a.status === 'failed').length;
|
|
@@ -5452,7 +5518,7 @@ export function App({ config: initialConfig, clearFrame }) {
|
|
|
5452
5518
|
: elapsedMs > 8_000 ? `no response from ${cfg.provider} yet`
|
|
5453
5519
|
: 'starting', thinkingRef.current.chars > 0 && Date.now() - thinkingRef.current.at < 2_000
|
|
5454
5520
|
? ` | thinking, ${fmtTokens(Math.round(thinkingRef.current.chars / 4))} reasoning tokens`
|
|
5455
|
-
: '', ")"] })] }) }), showThinking && 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 && (() => {
|
|
5521
|
+
: '', ")"] })] }) }), 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 && (() => {
|
|
5456
5522
|
const width = Math.min(barWidth, usagePane === 'settings' ? 96 : 66);
|
|
5457
5523
|
const inner = width - 4;
|
|
5458
5524
|
const BAR_W = Math.max(12, inner - 14);
|
|
@@ -5789,8 +5855,19 @@ export async function runInkChatMode(config) {
|
|
|
5789
5855
|
// appearing at the top with a dead copy of the interface below it — caused by the first version
|
|
5790
5856
|
// of this repaint, which did exactly that.
|
|
5791
5857
|
let inkClear;
|
|
5792
|
-
|
|
5858
|
+
// Frames go out bracketed as synchronized updates, so the erase-then-redraw Ink performs on
|
|
5859
|
+
// every repaint is composited by the terminal instead of being shown as a blank flash. That is
|
|
5860
|
+
// what allows the animation to run at FRAME_MS; see frame-sync.ts.
|
|
5861
|
+
const app = render(_jsx(App, { config: config, clearFrame: () => inkClear?.() }), { stdout: withSynchronizedFrames(process.stdout),
|
|
5862
|
+
exitOnCtrlC: false, patchConsole: false });
|
|
5793
5863
|
inkClear = app.clear;
|
|
5864
|
+
// The engine is imported on demand so the banner and the prompt are not waiting on the OpenAI
|
|
5865
|
+
// SDK, execa and ts-morph — 645ms of the 1,163ms it took to load this module on a Raspberry Pi
|
|
5866
|
+
// 5. Warmed here, one tick after the interface is up, so the first turn does not pay for it
|
|
5867
|
+
// either: by the time anything has been typed it is already in memory. Failures are ignored
|
|
5868
|
+
// because this is only a head start — the real import happens at the call site and reports
|
|
5869
|
+
// there.
|
|
5870
|
+
setTimeout(() => { void engine().catch(() => { }); }, 0);
|
|
5794
5871
|
await app.waitUntilExit();
|
|
5795
5872
|
}
|
|
5796
5873
|
//# sourceMappingURL=ink-chat.js.map
|