koneck 2.122.0 → 2.124.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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Atomic frames, so a fast animation does not flash.
3
+ *
4
+ * Ink repaints its live region by erasing the lines it last wrote and printing the new ones:
5
+ *
6
+ * stream.write(ansiEscapes.eraseLines(previousLineCount) + output) // ink/build/log-update.js
7
+ *
8
+ * That is one `write`, but a terminal does not treat it as one picture. It erases the region,
9
+ * displays the gap, then draws the replacement — and the gap is a visible flash. The taller the
10
+ * live region, the more obvious it is, which is why it showed up here as flicker rather than as
11
+ * tearing. It was previously worked around by repainting less often: the spinner advanced once a
12
+ * second, the shimmer once every two, and the tick that reveals streamed text ran at 500-1000ms.
13
+ * That hid the flash by making the interface look broken — reported as "even 10x slower than a
14
+ * tortoise" — and it did not remove the cause, so any faster animation brought the flash back.
15
+ *
16
+ * DEC private mode 2026 removes the cause. Between "begin synchronized update" and "end", a
17
+ * terminal that implements it buffers what it receives and presents the result in one frame, so
18
+ * the erase and the redraw are never on screen separately. There is nothing to flash. Every
19
+ * frame Ink writes is wrapped inside a single `write` call, so the begin and the end cannot be
20
+ * separated by a crash, and terminals bound the block with their own timeout regardless.
21
+ *
22
+ * Terminals that do not implement it ignore it: setting and resetting an unrecognised DEC private
23
+ * mode is defined as a no-op, which is why this needs no capability query and prints nothing on an
24
+ * older terminal. It is still limited to a TTY — a pipe or a file should receive the frame bytes
25
+ * and nothing else — and can be turned off with KONECK_NO_SYNC_OUTPUT=1 if some terminal is found
26
+ * that handles it badly.
27
+ */
28
+ /** Begin a synchronized update: hold everything that follows back from the screen. */
29
+ const BEGIN = '\x1b[?2026h';
30
+ /** End it: present everything since BEGIN as one frame. */
31
+ const END = '\x1b[?2026l';
32
+ /** Whether frames written to this stream should be presented atomically. */
33
+ export function wantsSynchronizedFrames(stream = process.stdout, env = process.env) {
34
+ if (!stream.isTTY)
35
+ return false;
36
+ if (env.KONECK_NO_SYNC_OUTPUT === '1')
37
+ return false;
38
+ // A terminal that declares itself dumb is telling us it has no private modes to set.
39
+ if (!env.TERM || env.TERM === 'dumb')
40
+ return false;
41
+ return true;
42
+ }
43
+ /** One frame, bracketed so the terminal shows all of it at once or none of it. */
44
+ export function synchronizedFrame(chunk) {
45
+ // An empty write has no frame to present, and bracketing it would be two escape sequences
46
+ // saying nothing.
47
+ if (chunk === '')
48
+ return chunk;
49
+ return BEGIN + chunk + END;
50
+ }
51
+ /**
52
+ * The same stream, with each write presented as one frame.
53
+ *
54
+ * A Proxy rather than a subclass or a prototype clone because Ink both writes to this object and
55
+ * listens on it, and an EventEmitter keeps its listeners on the instance. Every member other than
56
+ * `write` is read from — and every method bound to — the real stream, so `on('resize')` registers
57
+ * on the stream the terminal actually resizes rather than on a copy that never hears about it.
58
+ */
59
+ export function withSynchronizedFrames(stream) {
60
+ if (!wantsSynchronizedFrames(stream))
61
+ return stream;
62
+ return new Proxy(stream, {
63
+ get(target, prop) {
64
+ if (prop === 'write') {
65
+ // Called on `target`, not lifted off it: a stream's write reaches into `this._writableState`,
66
+ // so a detached reference throws "Cannot read properties of undefined". Found by running
67
+ // the real binary on a pty, which a fake stream in a unit test had not needed to expose.
68
+ return (chunk, ...rest) => {
69
+ const write = target.write;
70
+ return typeof chunk === 'string'
71
+ ? write.call(target, synchronizedFrame(chunk), ...rest)
72
+ : write.call(target, chunk, ...rest);
73
+ };
74
+ }
75
+ const value = Reflect.get(target, prop, target);
76
+ return typeof value === 'function' ? value.bind(target) : value;
77
+ },
78
+ });
79
+ }
80
+ //# sourceMappingURL=frame-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-sync.js","sourceRoot":"","sources":["../src/frame-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sFAAsF;AACtF,MAAM,KAAK,GAAG,aAAa,CAAC;AAC5B,2DAA2D;AAC3D,MAAM,GAAG,GAAG,aAAa,CAAC;AAE1B,4EAA4E;AAC5E,MAAM,UAAU,uBAAuB,CACrC,SAA8B,OAAO,CAAC,MAAM,EAC5C,MAA0C,OAAO,CAAC,GAAG;IAErD,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,GAAG,CAAC,qBAAqB,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACpD,qFAAqF;IACrF,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,0FAA0F;IAC1F,kBAAkB;IAClB,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC;AAC7B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAA+B,MAAS;IAC5E,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACpD,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,IAAI;YACd,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,8FAA8F;gBAC9F,yFAAyF;gBACzF,yFAAyF;gBACzF,OAAO,CAAC,KAAc,EAAE,GAAG,IAAe,EAAW,EAAE;oBACrD,MAAM,KAAK,GAAG,MAAM,CAAC,KAA0D,CAAC;oBAChF,OAAO,OAAO,KAAK,KAAK,QAAQ;wBAC9B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC;wBACvD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;gBACzC,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAChD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAClE,CAAC;KACF,CAAM,CAAC;AACV,CAAC"}
@@ -68,8 +68,46 @@ export declare function pasteForDisplay(text: string, maxRows?: number): string;
68
68
  * reader is watching appear, rather than the whole reply waiting for the turn to end.
69
69
  */
70
70
  export declare function liveReplyLines(termRows: number | undefined): number;
71
- /** Sampling cadence for live stream refs: quick enough to read, slow enough not to flash. */
72
- export declare function liveRefreshDelay(idleForMs: number): number;
71
+ /**
72
+ * How long one animation frame lasts.
73
+ *
74
+ * Every tick both advances the animation and reveals whatever the stream has added since the
75
+ * last one, so this is the interface's frame rate. It was 500ms while a stream was active and
76
+ * 1,000ms once it went quiet, which is where "the loader is moving slowly ... even 10x slower
77
+ * than a tortoise" came from: the spinner took ten seconds to turn once and the shimmer
78
+ * forty-eight to cross a word.
79
+ *
80
+ * The reason it was that slow was flicker, and the flicker was real — but the rate was never its
81
+ * cause. Ink writes a frame by erasing the previous one and drawing the next, and the terminal
82
+ * showed the gap between the two. Repainting less often only made the flash rarer. It is now
83
+ * fixed where it happens, in frame-sync.ts, by presenting each frame atomically, which leaves the
84
+ * frame rate free to be a frame rate.
85
+ *
86
+ * 80ms is what cli-spinners and ora use for braille spinners, and it is comfortably above Ink's
87
+ * own 32ms write throttle (`throttle(this.onRender, 32)` in ink/build/ink.js), so each tick
88
+ * becomes exactly one write rather than being coalesced with its neighbours. Measured cost of a
89
+ * tick on a Raspberry Pi 5: 0.7ms of React render and about 700 bytes written, or roughly 1% of
90
+ * one core at this rate.
91
+ */
92
+ export declare const FRAME_MS = 80;
93
+ /**
94
+ * How long one work word is held before the next is shown.
95
+ *
96
+ * The word is there to be read and enjoyed, not to reassure by moving, so it outlasts the
97
+ * animation by a long way.
98
+ */
99
+ export declare const WORD_HOLD_MS = 60000;
100
+ /**
101
+ * Which spinner cell and shimmer column a turn is showing after this much work.
102
+ *
103
+ * One function so the two cannot drift apart, and pure so the rates can be asserted rather than
104
+ * eyeballed: both advance one step per frame, the spinner wrapping around its glyphs and the
105
+ * shimmer running on for `Shimmer` to wrap over the word plus its trailing gap.
106
+ */
107
+ export declare function animFrames(elapsedMs: number, spinnerLength: number): {
108
+ spin: number;
109
+ shimmer: number;
110
+ };
73
111
  export { WORK_WORDS, shouldName, tierFor, workWord, type WorkTier, type TurnEffort, } from './work-words.js';
74
112
  /** Logical families for the searchable command palette; headers reuse an existing row. */
75
113
  export declare function commandGroup(command: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
package/dist/ink-chat.js CHANGED
@@ -2,8 +2,9 @@ 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 { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice } from './engine.js';
6
- import { estimateCost, formatCost } from './pricing.js';
5
+ import { resolveAgentConcurrency, toolsUnsupportedNotice } from './engine-facts.js';
6
+ import { withSynchronizedFrames } from './frame-sync.js';
7
+ import { estimateCost, formatCost, rateText } from './pricing.js';
7
8
  import { generateSessionId, saveSession, listSessions, loadSession, relativeAge, renameSession, forkSession, archiveSession, findSession, } from './session.js';
8
9
  import { loadMemory } from './memory.js';
9
10
  import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue } from './config-store.js';
@@ -49,16 +50,18 @@ import { logoFor } from './logo.js';
49
50
  import { describeActivity } from './activity.js';
50
51
  import { redactSecrets, containsSecret } from './redact.js';
51
52
  import { renderTrajectory } from './trajectory.js';
52
- import { describeSandbox, resolveSandbox } from './sandbox.js';
53
- import { doctorChecks, doctorReport } from './doctor.js';
54
- import { runQualityGate } from './quality-gate.js';
55
- import { runPreflight, preflightReport, isPreflightReady } from './preflight.js';
53
+ // sandbox.js and doctor.js are loaded by /status and /doctor. sandbox.js imports execaSync to run
54
+ // bubblewrap and doctor.js shells out to check the environment, and both are reached only from
55
+ // handleCommand, which is async. Measured: 122ms and 150ms.
56
+ // quality-gate.js, mcp.js and refactor.js are loaded at the one command that uses each. Each
57
+ // reaches something large — quality-gate takes shellFor from tools.js, refactor takes runAgent
58
+ // from engine.js and ts-morph with it — and an eager import here undid the engine being lazy,
59
+ // because the graph simply arrived by another road. Measured: 339ms, 141ms and 75ms.
60
+ // preflight.js is loaded by the command that runs it: it imports quality-gate.js, and so
61
+ // tools.js, for 505ms that a session which never types /preflight should not pay.
56
62
  import { listAuditEvents } from './audit.js';
57
63
  import { initializePolicy, loadPolicy, POLICY_FILE } from './policy.js';
58
64
  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
65
  const CYAN = '#54D7E7';
63
66
  const GREEN = '#9BCB8F';
64
67
  const CRIMSON = '#E8878D';
@@ -382,9 +385,62 @@ export function liveReplyLines(termRows) {
382
385
  const rows = termRows && termRows > 0 ? termRows : 24;
383
386
  return rows < 12 ? 1 : 2;
384
387
  }
385
- /** Sampling cadence for live stream refs: quick enough to read, slow enough not to flash. */
386
- export function liveRefreshDelay(idleForMs) {
387
- return idleForMs < 2_000 ? 500 : 1_000;
388
+ /**
389
+ * The engine, loaded the first time a turn needs it.
390
+ *
391
+ * Importing it at the top of this module cost 645ms of the 1,163ms it took to load the terminal
392
+ * app on a Raspberry Pi 5 — it reaches the OpenAI SDK, execa and ts-morph through fifty-seven
393
+ * modules — and not one line of it is needed to draw the banner, the prompt or the status bar.
394
+ * Nothing here is on a render path: every caller already runs inside an async handler, and the
395
+ * two things the interface prints synchronously are in engine-facts.js for that reason.
396
+ *
397
+ * The promise is cached, so concurrent callers share one import and later turns pay nothing. It is
398
+ * also warmed in the background as soon as the interface is up, which is why the first turn does
399
+ * not feel the move either.
400
+ */
401
+ let enginePromise;
402
+ function engine() {
403
+ return (enginePromise ??= import('./engine.js'));
404
+ }
405
+ /**
406
+ * How long one animation frame lasts.
407
+ *
408
+ * Every tick both advances the animation and reveals whatever the stream has added since the
409
+ * last one, so this is the interface's frame rate. It was 500ms while a stream was active and
410
+ * 1,000ms once it went quiet, which is where "the loader is moving slowly ... even 10x slower
411
+ * than a tortoise" came from: the spinner took ten seconds to turn once and the shimmer
412
+ * forty-eight to cross a word.
413
+ *
414
+ * The reason it was that slow was flicker, and the flicker was real — but the rate was never its
415
+ * cause. Ink writes a frame by erasing the previous one and drawing the next, and the terminal
416
+ * showed the gap between the two. Repainting less often only made the flash rarer. It is now
417
+ * fixed where it happens, in frame-sync.ts, by presenting each frame atomically, which leaves the
418
+ * frame rate free to be a frame rate.
419
+ *
420
+ * 80ms is what cli-spinners and ora use for braille spinners, and it is comfortably above Ink's
421
+ * own 32ms write throttle (`throttle(this.onRender, 32)` in ink/build/ink.js), so each tick
422
+ * becomes exactly one write rather than being coalesced with its neighbours. Measured cost of a
423
+ * tick on a Raspberry Pi 5: 0.7ms of React render and about 700 bytes written, or roughly 1% of
424
+ * one core at this rate.
425
+ */
426
+ export const FRAME_MS = 80;
427
+ /**
428
+ * How long one work word is held before the next is shown.
429
+ *
430
+ * The word is there to be read and enjoyed, not to reassure by moving, so it outlasts the
431
+ * animation by a long way.
432
+ */
433
+ export const WORD_HOLD_MS = 60_000;
434
+ /**
435
+ * Which spinner cell and shimmer column a turn is showing after this much work.
436
+ *
437
+ * One function so the two cannot drift apart, and pure so the rates can be asserted rather than
438
+ * eyeballed: both advance one step per frame, the spinner wrapping around its glyphs and the
439
+ * shimmer running on for `Shimmer` to wrap over the word plus its trailing gap.
440
+ */
441
+ export function animFrames(elapsedMs, spinnerLength) {
442
+ const frame = Math.floor(Math.max(0, elapsedMs) / FRAME_MS);
443
+ return { spin: frame % spinnerLength, shimmer: frame };
388
444
  }
389
445
  const SPINNER = G.spinner;
390
446
  /**
@@ -1215,7 +1271,7 @@ export function App({ config: initialConfig, clearFrame }) {
1215
1271
  // Tool approval callbacks belong to a session created on an earlier render. Keep the control
1216
1272
  // posture live so turning approval on during a turn governs its next consequential tool call.
1217
1273
  const cfgRef = useRef(initialConfig);
1218
- // Live activity accumulates in a ref and is painted by the 100 ms ticker. Mutating a ref
1274
+ // Live activity accumulates in a ref and is painted by the FRAME_MS ticker. Mutating a ref
1219
1275
  // instead of calling setState per token keeps a Raspberry Pi from re-rendering on every chunk.
1220
1276
  const sayRef = useRef(''); // narration not yet committed
1221
1277
  const liveToolRef = useRef(null); // the one tool still running
@@ -1223,8 +1279,6 @@ export function App({ config: initialConfig, clearFrame }) {
1223
1279
  const replyStartedRef = useRef(false);
1224
1280
  /** Everything streamed this turn. Empty means the provider did not stream at all. */
1225
1281
  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
1282
  /**
1229
1283
  * Frames received before any content — proof the provider has the request and is working.
1230
1284
  *
@@ -1336,7 +1390,6 @@ export function App({ config: initialConfig, clearFrame }) {
1336
1390
  * whatever is left in the buffer.
1337
1391
  */
1338
1392
  function pushSay(text) {
1339
- lastActivityRef.current = Date.now();
1340
1393
  sayRef.current += text;
1341
1394
  streamedRef.current += text;
1342
1395
  charsRef.current += text.length;
@@ -1391,7 +1444,6 @@ export function App({ config: initialConfig, clearFrame }) {
1391
1444
  function pushTool(name, args) {
1392
1445
  commitSay();
1393
1446
  // Photograph every file this tool has announced it will touch, before it touches it.
1394
- lastActivityRef.current = Date.now();
1395
1447
  turnToolsRef.current += 1;
1396
1448
  pendingDiffRef.current = filesTouchedBy(name, args).slice(0, MAX_DIFF_FILES)
1397
1449
  .map(rel => ({ rel, before: readForDiff(rel) }));
@@ -1545,10 +1597,9 @@ export function App({ config: initialConfig, clearFrame }) {
1545
1597
  },
1546
1598
  onToolResult: (name, ok, ms, detail) => finishTool(name, ok, ms, detail),
1547
1599
  onToolOutput: (_name, line) => {
1548
- // Deliberately does not count as activity for the frame rate. A dev server logs a request
1549
- // every second or two, and treating each one as "something is happening" held the UI in its
1550
- // fastest repaint band for as long as the server ran which is the flicker. The line still
1551
- // updates; the whole region just stops being redrawn eight times a second to show it.
1600
+ // Written to the ref rather than to state: a dev server logs a request every second or two,
1601
+ // and a setState per line re-rendered the interface on each one. The ticker paints whatever
1602
+ // the ref holds when the next frame comes round, so a chatty tool costs nothing extra.
1552
1603
  if (liveToolRef.current)
1553
1604
  liveToolRef.current.output = line;
1554
1605
  },
@@ -1597,7 +1648,6 @@ export function App({ config: initialConfig, clearFrame }) {
1597
1648
  // produce tens of thousands of characters per turn, and what anyone watching wants is what
1598
1649
  // it is thinking now.
1599
1650
  thinkingTextRef.current = (thinkingTextRef.current + text).slice(-THINKING_TAIL_CHARS);
1600
- lastActivityRef.current = Date.now();
1601
1651
  },
1602
1652
  onAgentProgress: (list) => { agentsRef.current = list; setAgents(list); },
1603
1653
  onWindowDetected: (limit, source) => setDetectedContext({ limit, source }),
@@ -1923,11 +1973,14 @@ export function App({ config: initialConfig, clearFrame }) {
1923
1973
  */
1924
1974
  const sessionNow = () => {
1925
1975
  if (!activeSession.p) {
1926
- activeSession.p = hold(createAgentSession({
1976
+ // The options are built here, not inside the then, so the hooks and refs are read at the
1977
+ // moment the session was asked for rather than a microtask later.
1978
+ const opts = {
1927
1979
  ...initialConfig,
1928
1980
  pace: paceFrom(initialConfig.pace ?? savedEffort(undefined), 'medium'),
1929
1981
  ...failoverHooks(), silent: true, ...sessionOpts,
1930
- }));
1982
+ };
1983
+ activeSession.p = hold(engine().then(m => m.createAgentSession(opts)));
1931
1984
  }
1932
1985
  return activeSession.p;
1933
1986
  };
@@ -2207,36 +2260,25 @@ export function App({ config: initialConfig, clearFrame }) {
2207
2260
  }
2208
2261
  busyStart.current = Date.now();
2209
2262
  wordTimer.current = 0;
2210
- // The frame rate is the flicker rate. Every tick makes Ink update the live region, so network
2211
- // activity must be sampled for a human reader rather than mirrored frame-for-frame. Half a
2212
- // second keeps streamed prose feeling live; an idle tool only needs a once-a-second
2213
- // heartbeat. This is particularly important in terminals that repaint an ANSI line update as
2214
- // a flash.
2263
+ // One rate, whether or not the stream is saying anything. The spinner earns its place
2264
+ // precisely when nothing is arriving the 2m 44s wait on a provider that had gone quiet is
2265
+ // what this was reported from so backing the animation off when a turn goes idle stopped
2266
+ // it in the one situation it exists for. Flicker is handled where it is caused, by writing
2267
+ // each frame atomically; see frame-sync.ts.
2215
2268
  let timer;
2269
+ const wordEvery = Math.max(1, Math.round(WORD_HOLD_MS / FRAME_MS));
2216
2270
  const tick = () => {
2271
+ // Elapsed is read from the clock rather than counted in ticks, so a frame the event loop
2272
+ // delivers late moves the animation on by what actually passed instead of falling behind.
2217
2273
  const elapsed = Date.now() - busyStart.current;
2218
- const delay = currentDelay();
2219
- // Do not animate several cells for every content refresh. The spinner moves once a second
2220
- // and the shimmer once every two seconds; the intervening refreshes are solely to reveal
2221
- // new streamed text.
2222
- setAnim({
2223
- spin: Math.floor(elapsed / 1_000) % SPINNER.length,
2224
- shimmer: Math.floor(elapsed / 2_000),
2225
- elapsed,
2226
- });
2274
+ const { spin, shimmer } = animFrames(elapsed, SPINNER.length);
2275
+ setAnim({ spin, shimmer, elapsed });
2227
2276
  wordTimer.current += 1;
2228
- // Roughly every 60 seconds, whatever the current rate. Twenty-five felt churned on a long
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) {
2277
+ if (wordTimer.current % wordEvery === 0)
2231
2278
  setSpinWord(w => w + 1);
2232
- }
2233
- timer = setTimeout(tick, delay);
2234
- };
2235
- const currentDelay = () => {
2236
- const idleFor = Date.now() - lastActivityRef.current;
2237
- return liveRefreshDelay(idleFor);
2279
+ timer = setTimeout(tick, FRAME_MS);
2238
2280
  };
2239
- timer = setTimeout(tick, liveRefreshDelay(0));
2281
+ timer = setTimeout(tick, FRAME_MS);
2240
2282
  return () => clearTimeout(timer);
2241
2283
  }, [busy]);
2242
2284
  // A queued prompt runs the moment the agent is free again. Keyed on `busy` rather than done
@@ -2325,7 +2367,8 @@ export function App({ config: initialConfig, clearFrame }) {
2325
2367
  const c = newCfg ?? cfg;
2326
2368
  cfgRef.current = c;
2327
2369
  setDetectedContext(null);
2328
- activeSession.p = hold(createAgentSession({ ...c, pace: paceFrom(effortRef.current, 'medium'), ...failoverHooks(), silent: true, ...sessionOpts }));
2370
+ const opts = { ...c, pace: paceFrom(effortRef.current, 'medium'), ...failoverHooks(), silent: true, ...sessionOpts };
2371
+ activeSession.p = hold(engine().then(m => m.createAgentSession(opts)));
2329
2372
  if (newCfg)
2330
2373
  setCfg(newCfg);
2331
2374
  }
@@ -2419,7 +2462,7 @@ export function App({ config: initialConfig, clearFrame }) {
2419
2462
  : 'The sending session has not done any work yet; this is a fresh instruction.';
2420
2463
  if (transcript.trim() !== '') {
2421
2464
  try {
2422
- const client = buildClient(cfg);
2465
+ const client = (await engine()).buildClient(cfg);
2423
2466
  const res = await client.chat.completions.create({
2424
2467
  model: cfg.model,
2425
2468
  max_tokens: 220,
@@ -2481,7 +2524,8 @@ export function App({ config: initialConfig, clearFrame }) {
2481
2524
  ...(st.note ? { note: st.note } : {}),
2482
2525
  })), updatedAt: Date.now() }
2483
2526
  : undefined;
2484
- activeSession.p = hold(createAgentSession({ ...restoredCfg, silent: true, ...sessionOpts }, messages, { turns: meta.turns, totalTokens: meta.totalTokens }, restoredPlan));
2527
+ const resumeOpts = { ...restoredCfg, silent: true, ...sessionOpts };
2528
+ activeSession.p = hold(engine().then(m => m.createAgentSession(resumeOpts, messages, { turns: meta.turns, totalTokens: meta.totalTokens }, restoredPlan)));
2485
2529
  if (restoredPlan)
2486
2530
  setPlan(restoredPlan);
2487
2531
  setSessionGoal(meta.goal ?? null);
@@ -3104,6 +3148,7 @@ export function App({ config: initialConfig, clearFrame }) {
3104
3148
  // question that actually matters when a provider misbehaves unanswered: where did the
3105
3149
  // request go? Working that out took a proxy and half an hour.
3106
3150
  const endpoint = resolveProvider(cfg.provider, cfg.baseURL).baseURL;
3151
+ const { describeSandbox, resolveSandbox } = await import('./sandbox.js');
3107
3152
  addSystem(`Provider : ${cfg.provider}\n` +
3108
3153
  `Endpoint : ${endpoint}\n` +
3109
3154
  `Model : ${cfg.model}\n` +
@@ -3199,6 +3244,7 @@ export function App({ config: initialConfig, clearFrame }) {
3199
3244
  // rather than a stale global default.
3200
3245
  addSystem(`**KONECK Doctor** — checking ${cfg.provider} and this workspace…`);
3201
3246
  try {
3247
+ const { doctorChecks, doctorReport } = await import('./doctor.js');
3202
3248
  const checks = await doctorChecks(cfg.cwd, cfg.provider, cfg.baseURL, cfg.apiKey);
3203
3249
  setRows(prev => prev.slice(0, -1));
3204
3250
  addSystem(`**KONECK Doctor**\n\n${doctorReport(checks)}`);
@@ -3210,6 +3256,7 @@ export function App({ config: initialConfig, clearFrame }) {
3210
3256
  return;
3211
3257
  }
3212
3258
  case '/mcp': {
3259
+ const { loadMCPConfig } = await import('./mcp.js');
3213
3260
  const configured = await loadMCPConfig(cfg.cwd);
3214
3261
  const session = await sessionNow().catch(() => null);
3215
3262
  addSystem(mcpStatusText(Object.keys(configured?.servers ?? {}), session?.mcpServers ?? []));
@@ -3306,6 +3353,7 @@ export function App({ config: initialConfig, clearFrame }) {
3306
3353
  addSystem(`Running native TypeScript refactor: ${cmd}…`);
3307
3354
  try {
3308
3355
  if (cmd === '/rename') {
3356
+ const { renameSymbol } = await import('./refactor.js');
3309
3357
  const result = await renameSymbol(parts[0], parts[1], cfg.cwd, { announce: false });
3310
3358
  addSystem(`Renamed \`${parts[0]}\` → \`${parts[1]}\` in ${result.filesChanged.length} file(s) (${result.occurrences} occurrence(s)).` +
3311
3359
  (checkpoint
@@ -3313,11 +3361,13 @@ export function App({ config: initialConfig, clearFrame }) {
3313
3361
  : '\n\nNo Git restore point was available. Run `/preflight` before committing.'));
3314
3362
  }
3315
3363
  else if (cmd === '/extract') {
3364
+ const { extractInterface } = await import('./refactor.js');
3316
3365
  await extractInterface(parts[0], parts[1], cfg.cwd, { announce: false });
3317
3366
  addSystem(`Extracted interface for \`${parts[1]}\` from \`${parts[0]}\`.` +
3318
3367
  (checkpoint ? `\n\nRestore point: \`${checkpoint.id}\` — run \`/preflight\`, or \`/revert ${checkpoint.id} --confirm\` to undo.` : '\n\nRun `/preflight` before committing.'));
3319
3368
  }
3320
3369
  else {
3370
+ const { moveSymbol } = await import('./refactor.js');
3321
3371
  await moveSymbol(parts[0], parts[1], parts[2], cfg.cwd, { announce: false });
3322
3372
  addSystem(`Moved \`${parts[0]}\` from \`${parts[1]}\` to \`${parts[2]}\` and updated imports.` +
3323
3373
  (checkpoint ? `\n\nRestore point: \`${checkpoint.id}\` — run \`/preflight\`, or \`/revert ${checkpoint.id} --confirm\` to undo.` : '\n\nRun `/preflight` before committing.'));
@@ -3835,6 +3885,7 @@ export function App({ config: initialConfig, clearFrame }) {
3835
3885
  // the same independent proof on demand, without the legacy TUI writing over Ink's frame.
3836
3886
  addSystem('**Quality gate** — running detected lint and test checks…');
3837
3887
  try {
3888
+ const { runQualityGate } = await import('./quality-gate.js');
3838
3889
  const result = await runQualityGate(cfg.cwd, { report: false });
3839
3890
  setRows(prev => prev.slice(0, -1));
3840
3891
  if (result.checks.length === 0) {
@@ -3861,6 +3912,7 @@ export function App({ config: initialConfig, clearFrame }) {
3861
3912
  case '/preflight': {
3862
3913
  addSystem('**Preflight** — checking patch integrity and detected quality gates…');
3863
3914
  try {
3915
+ const { runPreflight, preflightReport } = await import('./preflight.js');
3864
3916
  const result = await runPreflight(cfg.cwd);
3865
3917
  setRows(prev => prev.slice(0, -1));
3866
3918
  addSystem(preflightReport(result));
@@ -3878,6 +3930,7 @@ export function App({ config: initialConfig, clearFrame }) {
3878
3930
  }
3879
3931
  addSystem('**Ship check** — running deterministic readiness checks…');
3880
3932
  try {
3933
+ const { runPreflight, preflightReport, isPreflightReady } = await import('./preflight.js');
3881
3934
  const result = await runPreflight(cfg.cwd);
3882
3935
  setRows(prev => prev.slice(0, -1));
3883
3936
  addSystem(preflightReport(result));
@@ -5130,7 +5183,6 @@ export function App({ config: initialConfig, clearFrame }) {
5130
5183
  thinkingRef.current = { chars: 0, at: 0 };
5131
5184
  thinkingTextRef.current = '';
5132
5185
  incompleteRef.current = null;
5133
- lastActivityRef.current = Date.now();
5134
5186
  providerFramesRef.current = 0;
5135
5187
  liveToolRef.current = null;
5136
5188
  charsRef.current = 0;
@@ -5789,8 +5841,19 @@ export async function runInkChatMode(config) {
5789
5841
  // appearing at the top with a dead copy of the interface below it — caused by the first version
5790
5842
  // of this repaint, which did exactly that.
5791
5843
  let inkClear;
5792
- const app = render(_jsx(App, { config: config, clearFrame: () => inkClear?.() }), { exitOnCtrlC: false, patchConsole: false });
5844
+ // Frames go out bracketed as synchronized updates, so the erase-then-redraw Ink performs on
5845
+ // every repaint is composited by the terminal instead of being shown as a blank flash. That is
5846
+ // what allows the animation to run at FRAME_MS; see frame-sync.ts.
5847
+ const app = render(_jsx(App, { config: config, clearFrame: () => inkClear?.() }), { stdout: withSynchronizedFrames(process.stdout),
5848
+ exitOnCtrlC: false, patchConsole: false });
5793
5849
  inkClear = app.clear;
5850
+ // The engine is imported on demand so the banner and the prompt are not waiting on the OpenAI
5851
+ // SDK, execa and ts-morph — 645ms of the 1,163ms it took to load this module on a Raspberry Pi
5852
+ // 5. Warmed here, one tick after the interface is up, so the first turn does not pay for it
5853
+ // either: by the time anything has been typed it is already in memory. Failures are ignored
5854
+ // because this is only a head start — the real import happens at the call site and reports
5855
+ // there.
5856
+ setTimeout(() => { void engine().catch(() => { }); }, 0);
5794
5857
  await app.waitUntilExit();
5795
5858
  }
5796
5859
  //# sourceMappingURL=ink-chat.js.map