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.
- 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-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/ink-chat.d.ts +40 -2
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +116 -53
- 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/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +17 -1
- package/dist/web/api.js.map +1 -1
- package/dist/web/sessions.d.ts +5 -1
- package/dist/web/sessions.d.ts.map +1 -1
- package/dist/web/sessions.js +42 -5
- package/dist/web/sessions.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|
package/dist/ink-chat.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
72
|
-
|
|
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;
|
package/dist/ink-chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAyB3D,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAuCtE,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
/**
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
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
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
2211
|
-
//
|
|
2212
|
-
//
|
|
2213
|
-
//
|
|
2214
|
-
//
|
|
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
|
|
2219
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|