@vincemakes/kiso-code 0.22.0 → 0.23.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/config.d.ts +7 -0
- package/dist/config.js +12 -0
- package/dist/index.js +81 -6
- package/dist/update-check.d.ts +65 -0
- package/dist/update-check.js +191 -0
- package/package.json +14 -14
package/dist/config.d.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface KisoConfig {
|
|
|
52
52
|
readonly contextWindow?: number;
|
|
53
53
|
readonly autoCompact?: AutoCompactConfig;
|
|
54
54
|
readonly projectTrust?: "ask" | "never";
|
|
55
|
+
/** DC-3 §3 rung 1, persisted. The terminal's light/dark, for terminals
|
|
56
|
+
* that answer neither `CSI ? 996 n` nor OSC 11. USER-LEVEL ONLY: a
|
|
57
|
+
* terminal is a property of the human sitting at one, not of the
|
|
58
|
+
* repository they happen to have open, so the same setting in a
|
|
59
|
+
* project file is a LOUD error rather than a silent win. `KISO_THEME`
|
|
60
|
+
* still outranks it — the environment is the more local answer. */
|
|
61
|
+
readonly theme?: "dark" | "light";
|
|
55
62
|
}
|
|
56
63
|
/** The resolved, merged config — project wins over user, both validated. */
|
|
57
64
|
export interface ResolvedConfig {
|
package/dist/config.js
CHANGED
|
@@ -51,6 +51,18 @@ export function parseConfig(text, source) {
|
|
|
51
51
|
const fail = (key, why) => {
|
|
52
52
|
throw new ConfigError(`config ${source}: ${key} — ${why}`);
|
|
53
53
|
};
|
|
54
|
+
if (obj.theme !== undefined) {
|
|
55
|
+
// LOUD on both counts: an invalid value, and a valid value in the
|
|
56
|
+
// wrong file. A theme silently ignored is the worst outcome here —
|
|
57
|
+
// the human set it precisely because their terminal answers
|
|
58
|
+
// nothing, so a quiet failure looks exactly like the defect it was
|
|
59
|
+
// meant to fix.
|
|
60
|
+
if (obj.theme !== "dark" && obj.theme !== "light")
|
|
61
|
+
fail("theme", 'expected "dark" or "light"');
|
|
62
|
+
if (source.startsWith("<cwd>"))
|
|
63
|
+
fail("theme", "belongs in the USER config — a terminal is a property of the person at it, not of the project");
|
|
64
|
+
out.theme = obj.theme;
|
|
65
|
+
}
|
|
54
66
|
if (obj.model !== undefined) {
|
|
55
67
|
if (typeof obj.model !== "string" || obj.model === "")
|
|
56
68
|
fail("model", "expected a profile name or provider/model string");
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
|
40
40
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
41
41
|
import { autoCompactFromEnv, chat, contextWindowTokens, estimateCtxRatio } from "./chat.js";
|
|
42
42
|
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
|
|
43
|
+
import { checkForUpdate } from "./update-check.js";
|
|
43
44
|
import { resume } from "./resume.js";
|
|
44
45
|
import { resumeTail } from "./resume-tail.js";
|
|
45
46
|
import { armByteTrace } from "./byte-trace.js";
|
|
@@ -229,7 +230,23 @@ function editorInput(editor) {
|
|
|
229
230
|
/** One input source per process: the raw-mode Editor on a TTY (entered
|
|
230
231
|
* here, bound to the dock once — the trust question, chat, and resume
|
|
231
232
|
* all read through it), readline elsewhere. */
|
|
233
|
+
/** The user config's `theme`, read the way the mode is (index.ts's
|
|
234
|
+
* `loadUserConfig()?.mode`): the ground is settled inside
|
|
235
|
+
* `makeLineInput`, which runs long before `mergedConfig` exists, so the
|
|
236
|
+
* merged view is not available and the USER file is the only one that
|
|
237
|
+
* may carry it anyway (config.ts rejects a project-level theme LOUDLY).
|
|
238
|
+
* A broken config is not this function's business to report — the
|
|
239
|
+
* loader is LOUD about that on its own path. */
|
|
240
|
+
function userThemeSetting() {
|
|
241
|
+
try {
|
|
242
|
+
return loadUserConfig()?.theme;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
232
248
|
function makeLineInput() {
|
|
249
|
+
const userTheme = userThemeSetting();
|
|
233
250
|
if (process.stdin.isTTY) {
|
|
234
251
|
const editor = new Editor(() => (dock.active ? dock.redraw() : editor.selfRender()));
|
|
235
252
|
editor.enter();
|
|
@@ -254,22 +271,45 @@ function makeLineInput() {
|
|
|
254
271
|
// exists FOR the terminals without OSC 11, and could only ever be
|
|
255
272
|
// consulted when an OSC reply had arrived and been malformed. With
|
|
256
273
|
// §3.2 recording that rung 2 is unmeasured, that was the mainline.
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
274
|
+
// The two answers accumulate here; whichever arrives first is used
|
|
275
|
+
// at once, and the ladder decides between them when both have.
|
|
276
|
+
let osc;
|
|
277
|
+
let colorScheme;
|
|
278
|
+
const theme = () => process.env.KISO_THEME ?? userTheme;
|
|
279
|
+
const rewalk = () => {
|
|
280
|
+
const next = resolveGround({ theme: theme(), colorScheme, osc, colorfgbg: process.env.COLORFGBG });
|
|
281
|
+
// DC-3/DC-14's model: the first frame never waits for a reply.
|
|
282
|
+
// A reply that changes nothing repaints nothing — which is also
|
|
283
|
+
// why two answers that AGREE cost one repaint and not two.
|
|
262
284
|
if (next === currentGround())
|
|
263
285
|
return;
|
|
264
286
|
setGround(next);
|
|
265
287
|
body.onGroundChange();
|
|
288
|
+
};
|
|
289
|
+
setGround(resolveGround({ theme: theme(), colorfgbg: process.env.COLORFGBG }));
|
|
290
|
+
// the reply, when there is one, re-walks the ladder WITH it — and
|
|
291
|
+
// `theme` goes in first again, so an explicit answer still wins.
|
|
292
|
+
editor.onOsc((reply) => {
|
|
293
|
+
osc = reply;
|
|
294
|
+
rewalk();
|
|
295
|
+
});
|
|
296
|
+
// …and the terminal's OWN account of its scheme, which outranks the
|
|
297
|
+
// luminance kiso would infer from a background colour (§3 rung 2).
|
|
298
|
+
editor.onColorScheme((scheme) => {
|
|
299
|
+
colorScheme = scheme;
|
|
300
|
+
rewalk();
|
|
266
301
|
});
|
|
267
302
|
// The query is COLOUR machinery, so it obeys the colour gate: a piped
|
|
268
303
|
// stdout and NO_COLOR both carry zero ANSI, and an OSC written into a
|
|
269
304
|
// pipe would be the first byte to break that. `palette().bold` is the
|
|
270
305
|
// same test the dock activates on — one gate, not a second opinion.
|
|
306
|
+
// Both questions ride ONE write and ONE gate: `CSI ? 996 n` asks the
|
|
307
|
+
// terminal to report its colour scheme, OSC 11 asks for its
|
|
308
|
+
// background colour. Neither is waited on (§3.2) — a terminal that
|
|
309
|
+
// answers neither leaves the ground `unknown`, which is a supported
|
|
310
|
+
// palette rather than a failure.
|
|
271
311
|
if (palette().bold !== "" && process.stdout.isTTY)
|
|
272
|
-
process.stdout.write("\x1b]11;?\x07");
|
|
312
|
+
process.stdout.write("\x1b[?996n\x1b]11;?\x07");
|
|
273
313
|
// W6: the box's prompt goes light — "› " (the box already says
|
|
274
314
|
// "input lives here"; the line-mode path keeps the brick ▌, so
|
|
275
315
|
// pipe bytes do not change)
|
|
@@ -318,6 +358,35 @@ function bannerExtensionText() {
|
|
|
318
358
|
* W5 resume list as a LIVE banner cell (W1: the tier re-derives on
|
|
319
359
|
* resize; the resume list re-gates with the tier); off-TTY: the
|
|
320
360
|
* historical `[N extensions: ...]` standalone line (zero change). */
|
|
361
|
+
/**
|
|
362
|
+
* The update line, when there is one, appended after the opening.
|
|
363
|
+
*
|
|
364
|
+
* It never blocks: the caller does not await it, and the banner is
|
|
365
|
+
* already drawn when this starts. If the check fails, times out, is
|
|
366
|
+
* switched off, or finds nothing newer, this does nothing at all and
|
|
367
|
+
* says nothing about having tried.
|
|
368
|
+
*/
|
|
369
|
+
async function announceUpdate() {
|
|
370
|
+
try {
|
|
371
|
+
const line = await checkForUpdate({ kisoHome: kisoHome(), version: VERSION, isTTY: process.stdout.isTTY === true, faux: currentFaux });
|
|
372
|
+
if (line === null)
|
|
373
|
+
return;
|
|
374
|
+
// THE RAW CHANNEL, not `notice`. A notice renders through ErrorLine,
|
|
375
|
+
// which calls `escapeTerminal` — so every SGR in it is stripped and
|
|
376
|
+
// the line could only ever be full-strength prose. The raw channel
|
|
377
|
+
// is the one the banner itself uses: the styling is applied HERE, at
|
|
378
|
+
// composition, and the fold keeps the span intact (#16b).
|
|
379
|
+
//
|
|
380
|
+
// A turn may have started while the request was in flight. A raw
|
|
381
|
+
// cell is a cell like any other, so it lands at the transcript's
|
|
382
|
+
// end — never spliced into a frame the reader is mid-way through.
|
|
383
|
+
bodyLog(`${palette().dim}${line}${palette().reset}`, "words");
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// belt and braces: checkForUpdate does not throw, and if it ever
|
|
387
|
+
// did, a version check is not a reason to disturb a session.
|
|
388
|
+
}
|
|
389
|
+
}
|
|
321
390
|
function extensionsBanner(resume = []) {
|
|
322
391
|
const text = bannerExtensionText();
|
|
323
392
|
if (!process.stdout.isTTY) {
|
|
@@ -654,6 +723,12 @@ async function chatLoop(agent, firstId, input, autoCompact) {
|
|
|
654
723
|
// throws on profile drift — so one drifted session anywhere in the
|
|
655
724
|
// history stopped kiso from starting at all.
|
|
656
725
|
extensionsBanner();
|
|
726
|
+
// The one line the opening may gain (§7.10's ruling): "there is
|
|
727
|
+
// a newer kiso". FIRED AND FORGOTTEN — the banner above is
|
|
728
|
+
// already on screen and this never delays it. When the answer
|
|
729
|
+
// arrives it rides `body.notice`, the commit path notices
|
|
730
|
+
// already use, rather than a second channel into the frame.
|
|
731
|
+
void announceUpdate();
|
|
657
732
|
}
|
|
658
733
|
else {
|
|
659
734
|
bodyLog(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "There is a newer kiso."
|
|
3
|
+
*
|
|
4
|
+
* The owner's ruling on the opening (2026-09-02): no logo, the name is
|
|
5
|
+
* the mark (§7.10 stands), and the banner may gain AT MOST one line
|
|
6
|
+
* saying a new version exists. This is that one line's machinery, and
|
|
7
|
+
* almost all of it is about the things it must not do.
|
|
8
|
+
*
|
|
9
|
+
* IT IS kiso's ONLY OUTBOUND REQUEST. Everything else kiso sends goes to
|
|
10
|
+
* the model endpoint the human configured. This one goes to the public
|
|
11
|
+
* npm registry, carries no identity, no usage and no session data — a
|
|
12
|
+
* GET of one dist-tags document — and can be switched off entirely with
|
|
13
|
+
* `KISO_NO_UPDATE_CHECK=1`. That is a real change in what the product
|
|
14
|
+
* is, which is why it is stated here rather than buried.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT MUST NEVER DO:
|
|
17
|
+
*
|
|
18
|
+
* - block the first frame. The check is fired and forgotten; the
|
|
19
|
+
* opening is drawn from local facts and never waits.
|
|
20
|
+
* - say anything when it fails. No network, a proxy, a 500, a
|
|
21
|
+
* malformed body, a timeout — all of them are silence. A version
|
|
22
|
+
* check that interrupts you to report that it could not check is
|
|
23
|
+
* worse than no version check.
|
|
24
|
+
* - run where it cannot be wanted: a faux session (the test and demo
|
|
25
|
+
* path), a non-TTY (a pipe has no banner to append to), or an
|
|
26
|
+
* explicit opt-out.
|
|
27
|
+
* - ask twice in a day, or nag about a version it has already
|
|
28
|
+
* mentioned. Both are remembered in the cache file.
|
|
29
|
+
*/
|
|
30
|
+
export interface UpdateCache {
|
|
31
|
+
/** epoch ms of the last COMPLETED check, successful or not */
|
|
32
|
+
readonly checkedAt: number;
|
|
33
|
+
/** the latest version the registry reported, if the check succeeded */
|
|
34
|
+
readonly latest?: string;
|
|
35
|
+
/** the version already mentioned to this human — never mentioned twice */
|
|
36
|
+
readonly told?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Semver-ish "is b newer than a", for the shapes this line ever sees:
|
|
40
|
+
* three numeric parts, optionally a prerelease we treat as older. A
|
|
41
|
+
* version we cannot parse is never "newer" — the failure direction is
|
|
42
|
+
* silence, here as everywhere else in this file.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isNewer(current: string, latest: string): boolean;
|
|
45
|
+
/** The one line, when there is one. Words only — a human can read it,
|
|
46
|
+
* paste it, and nothing about it needs colour to be understood (§1.2). */
|
|
47
|
+
export declare const updateLine: (latest: string) => string;
|
|
48
|
+
export interface UpdateCheckDeps {
|
|
49
|
+
readonly kisoHome: string;
|
|
50
|
+
readonly version: string;
|
|
51
|
+
readonly isTTY: boolean;
|
|
52
|
+
readonly faux: boolean;
|
|
53
|
+
readonly now?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Should this launch even ask? Pure, so the reasons are testable without
|
|
57
|
+
* a network, a clock or a terminal.
|
|
58
|
+
*/
|
|
59
|
+
export declare function shouldCheck(deps: UpdateCheckDeps): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The check itself: fire, forget, and resolve to the line to show or
|
|
62
|
+
* null. NEVER throws, never rejects, never blocks a caller who does not
|
|
63
|
+
* await it.
|
|
64
|
+
*/
|
|
65
|
+
export declare function checkForUpdate(deps: UpdateCheckDeps): Promise<string | null>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "There is a newer kiso."
|
|
3
|
+
*
|
|
4
|
+
* The owner's ruling on the opening (2026-09-02): no logo, the name is
|
|
5
|
+
* the mark (§7.10 stands), and the banner may gain AT MOST one line
|
|
6
|
+
* saying a new version exists. This is that one line's machinery, and
|
|
7
|
+
* almost all of it is about the things it must not do.
|
|
8
|
+
*
|
|
9
|
+
* IT IS kiso's ONLY OUTBOUND REQUEST. Everything else kiso sends goes to
|
|
10
|
+
* the model endpoint the human configured. This one goes to the public
|
|
11
|
+
* npm registry, carries no identity, no usage and no session data — a
|
|
12
|
+
* GET of one dist-tags document — and can be switched off entirely with
|
|
13
|
+
* `KISO_NO_UPDATE_CHECK=1`. That is a real change in what the product
|
|
14
|
+
* is, which is why it is stated here rather than buried.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT MUST NEVER DO:
|
|
17
|
+
*
|
|
18
|
+
* - block the first frame. The check is fired and forgotten; the
|
|
19
|
+
* opening is drawn from local facts and never waits.
|
|
20
|
+
* - say anything when it fails. No network, a proxy, a 500, a
|
|
21
|
+
* malformed body, a timeout — all of them are silence. A version
|
|
22
|
+
* check that interrupts you to report that it could not check is
|
|
23
|
+
* worse than no version check.
|
|
24
|
+
* - run where it cannot be wanted: a faux session (the test and demo
|
|
25
|
+
* path), a non-TTY (a pipe has no banner to append to), or an
|
|
26
|
+
* explicit opt-out.
|
|
27
|
+
* - ask twice in a day, or nag about a version it has already
|
|
28
|
+
* mentioned. Both are remembered in the cache file.
|
|
29
|
+
*/
|
|
30
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
/** The dist-tags document — the smallest thing the registry will tell us
|
|
33
|
+
* that answers the question. The full packument is megabytes. */
|
|
34
|
+
const ENDPOINT = "https://registry.npmjs.org/-/package/@vincemakes/kiso-code/dist-tags";
|
|
35
|
+
const TIMEOUT_MS = 1_500;
|
|
36
|
+
const EVERY_MS = 24 * 60 * 60 * 1_000;
|
|
37
|
+
/** The registry, redirectable for tests ONLY. A stub server's URL goes
|
|
38
|
+
* here so the suite can drive every branch — a slow answer, no answer,
|
|
39
|
+
* a malformed body — without reaching the network. Never documented for
|
|
40
|
+
* users: it is not a setting, it is a seam. */
|
|
41
|
+
const endpoint = () => process.env.KISO_UPDATE_ENDPOINT ?? ENDPOINT;
|
|
42
|
+
const cachePath = (kisoHome) => join(kisoHome, "update-check.json");
|
|
43
|
+
function readCache(kisoHome) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = JSON.parse(readFileSync(cachePath(kisoHome), "utf8"));
|
|
46
|
+
if (raw === null || typeof raw !== "object")
|
|
47
|
+
return null;
|
|
48
|
+
const c = raw;
|
|
49
|
+
if (typeof c.checkedAt !== "number")
|
|
50
|
+
return null;
|
|
51
|
+
return {
|
|
52
|
+
checkedAt: c.checkedAt,
|
|
53
|
+
...(typeof c.latest === "string" ? { latest: c.latest } : {}),
|
|
54
|
+
...(typeof c.told === "string" ? { told: c.told } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// a missing, unreadable or corrupt cache is simply no cache
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writeCache(kisoHome, c) {
|
|
63
|
+
try {
|
|
64
|
+
mkdirSync(dirname(cachePath(kisoHome)), { recursive: true });
|
|
65
|
+
writeFileSync(cachePath(kisoHome), `${JSON.stringify(c)}\n`, "utf8");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// an unwritable home means we check again next time — the only
|
|
69
|
+
// cost is a request, and there is nothing here worth a message.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Semver-ish "is b newer than a", for the shapes this line ever sees:
|
|
74
|
+
* three numeric parts, optionally a prerelease we treat as older. A
|
|
75
|
+
* version we cannot parse is never "newer" — the failure direction is
|
|
76
|
+
* silence, here as everywhere else in this file.
|
|
77
|
+
*/
|
|
78
|
+
export function isNewer(current, latest) {
|
|
79
|
+
const parse = (v) => {
|
|
80
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(v.trim());
|
|
81
|
+
return m === null ? null : [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
82
|
+
};
|
|
83
|
+
const a = parse(current);
|
|
84
|
+
const b = parse(latest);
|
|
85
|
+
if (a === null || b === null)
|
|
86
|
+
return false;
|
|
87
|
+
// a prerelease of the same numbers is not newer
|
|
88
|
+
if (a[0] === b[0] && a[1] === b[1] && a[2] === b[2])
|
|
89
|
+
return false;
|
|
90
|
+
for (let i = 0; i < 3; i += 1) {
|
|
91
|
+
if (b[i] > a[i])
|
|
92
|
+
return true;
|
|
93
|
+
if (b[i] < a[i])
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/** The one line, when there is one. Words only — a human can read it,
|
|
99
|
+
* paste it, and nothing about it needs colour to be understood (§1.2). */
|
|
100
|
+
export const updateLine = (latest) => `${latest} is out · npm i -g @vincemakes/kiso-code@latest`;
|
|
101
|
+
/**
|
|
102
|
+
* Should this launch even ask? Pure, so the reasons are testable without
|
|
103
|
+
* a network, a clock or a terminal.
|
|
104
|
+
*/
|
|
105
|
+
export function shouldCheck(deps) {
|
|
106
|
+
return reasonNotToCheck(deps) === null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* WHY a launch does not ask — and the distinction matters, because only
|
|
110
|
+
* ONE of these reasons permits the line to be shown from cache.
|
|
111
|
+
*
|
|
112
|
+
* "recent" means the request is redundant; the human is still someone
|
|
113
|
+
* who wants to be told. The other three mean the LINE is unwanted: an
|
|
114
|
+
* opt-out is an answer about the feature, a faux session is a test or a
|
|
115
|
+
* demo, and a pipe has no banner — worse, an inactive Body writes a
|
|
116
|
+
* notice straight to stdout, so showing it there would put a row of
|
|
117
|
+
* kiso's own prose into piped bytes and break pipe identity.
|
|
118
|
+
*/
|
|
119
|
+
function reasonNotToCheck({ kisoHome, isTTY, faux, now = Date.now() }) {
|
|
120
|
+
if (process.env.KISO_NO_UPDATE_CHECK === "1")
|
|
121
|
+
return "opt-out";
|
|
122
|
+
// A faux session is the test and demo path, and the rule it exists for
|
|
123
|
+
// is "never reaches the network". The redirect below is a TEST SEAM
|
|
124
|
+
// pointing at a local stub — where it is set there is no network to
|
|
125
|
+
// reach, and refusing anyway would make the one behaviour worth
|
|
126
|
+
// showing on a screen unobservable. In production nothing sets it, so
|
|
127
|
+
// a faux session still never asks.
|
|
128
|
+
if (faux && process.env.KISO_UPDATE_ENDPOINT === undefined)
|
|
129
|
+
return "faux";
|
|
130
|
+
if (!isTTY)
|
|
131
|
+
return "not-a-tty"; // and never writes a line into a pipe
|
|
132
|
+
const cached = readCache(kisoHome);
|
|
133
|
+
if (cached !== null && now - cached.checkedAt < EVERY_MS)
|
|
134
|
+
return "recent";
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The check itself: fire, forget, and resolve to the line to show or
|
|
139
|
+
* null. NEVER throws, never rejects, never blocks a caller who does not
|
|
140
|
+
* await it.
|
|
141
|
+
*/
|
|
142
|
+
export async function checkForUpdate(deps) {
|
|
143
|
+
const { kisoHome, version, now = Date.now() } = deps;
|
|
144
|
+
const why = reasonNotToCheck(deps);
|
|
145
|
+
if (why !== null) {
|
|
146
|
+
// ONLY a recent check may still speak. The other three reasons are
|
|
147
|
+
// about the LINE, not about the request, so the cache is no way
|
|
148
|
+
// around them — an opt-out that still got announced, or a piped
|
|
149
|
+
// run that gained a row of kiso's prose, would be the feature
|
|
150
|
+
// ignoring the answer it was given.
|
|
151
|
+
if (why !== "recent")
|
|
152
|
+
return null;
|
|
153
|
+
const cached = readCache(kisoHome);
|
|
154
|
+
if (cached?.latest !== undefined && cached.told !== cached.latest && isNewer(version, cached.latest)) {
|
|
155
|
+
writeCache(kisoHome, { ...cached, told: cached.latest });
|
|
156
|
+
return updateLine(cached.latest);
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
let latest;
|
|
161
|
+
try {
|
|
162
|
+
const ctl = new AbortController();
|
|
163
|
+
const timer = setTimeout(() => ctl.abort(), TIMEOUT_MS);
|
|
164
|
+
try {
|
|
165
|
+
const res = await fetch(endpoint(), { signal: ctl.signal, headers: { accept: "application/json" } });
|
|
166
|
+
if (res.ok) {
|
|
167
|
+
const body = await res.json();
|
|
168
|
+
const tag = body?.latest;
|
|
169
|
+
if (typeof tag === "string" && tag !== "")
|
|
170
|
+
latest = tag;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// every failure is silence — see the header
|
|
179
|
+
}
|
|
180
|
+
const prev = readCache(kisoHome);
|
|
181
|
+
const show = latest !== undefined && isNewer(version, latest) && prev?.told !== latest;
|
|
182
|
+
// `told` survives a check that learned nothing: forgetting it would
|
|
183
|
+
// re-announce a version this human has already been told about.
|
|
184
|
+
const told = show ? latest : prev?.told;
|
|
185
|
+
writeCache(kisoHome, {
|
|
186
|
+
checkedAt: now,
|
|
187
|
+
...(latest === undefined ? {} : { latest }),
|
|
188
|
+
...(told === undefined ? {} : { told }),
|
|
189
|
+
});
|
|
190
|
+
return show && latest !== undefined ? updateLine(latest) : null;
|
|
191
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "kiso CLI — the durable coding agent that survives kill -9: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-ask-ext": "0.
|
|
22
|
-
"@vincemakes/kiso-core": "0.
|
|
23
|
-
"@vincemakes/kiso-evals": "0.
|
|
24
|
-
"@vincemakes/kiso-mcp-ext": "0.
|
|
25
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
26
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
27
|
-
"@vincemakes/kiso-runtime": "0.
|
|
28
|
-
"@vincemakes/kiso-skills-ext": "0.
|
|
29
|
-
"@vincemakes/kiso-subagent-ext": "0.
|
|
30
|
-
"@vincemakes/kiso-task-ext": "0.
|
|
31
|
-
"@vincemakes/kiso-tools-node": "0.
|
|
32
|
-
"@vincemakes/kiso-tui": "0.
|
|
33
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.23.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.23.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.23.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.23.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.23.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.23.0",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.23.0",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.23.0",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.23.0",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.23.0",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.23.0",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.23.0",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.23.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|