@vincemakes/kiso-code 0.40.1 → 0.40.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat.d.ts +73 -1
- package/dist/chat.js +151 -22
- package/dist/config.d.ts +12 -1
- package/dist/config.js +5 -1
- package/dist/dispatch.js +22 -9
- package/dist/empty-sessions.d.ts +22 -0
- package/dist/empty-sessions.js +102 -0
- package/dist/index.js +64 -3
- package/dist/learned-windows.d.ts +23 -0
- package/dist/learned-windows.js +100 -0
- package/dist/provider-label.d.ts +8 -3
- package/dist/provider-label.js +12 -4
- package/dist/state.d.ts +4 -0
- package/dist/state.js +11 -0
- package/package.json +15 -15
package/dist/chat.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { type RunUsage } from "@vincemakes/kiso-tui";
|
|
|
9
9
|
import { type SaferFailure, type SaferOption } from "@vincemakes/kiso-tui";
|
|
10
10
|
import type { AgentSession, Run } from "@vincemakes/kiso-runtime";
|
|
11
11
|
import { type LineInput } from "./state.js";
|
|
12
|
+
import { type ContextWindowSource } from "@vincemakes/kiso-runtime/internal";
|
|
12
13
|
/**
|
|
13
14
|
* The ergonomics batch C8 — the /compact auto-trigger, OPT-IN (default off: only an
|
|
14
15
|
* explicit KISO_AUTO_COMPACT=<ratio> enables it — the CLI never defaults
|
|
@@ -42,7 +43,9 @@ export declare function autoCompactFromEnv(): AutoCompact | undefined;
|
|
|
42
43
|
* THE WINDOW SOMEBODY STATED, or null when nobody has.
|
|
43
44
|
*
|
|
44
45
|
* Three sources, in order: the config/profile window, KISO_CONTEXT_WINDOW,
|
|
45
|
-
* and the metadata registry
|
|
46
|
+
* and the metadata registry (CW-1: this endpoint's row, the upstream's,
|
|
47
|
+
* then the model's own — see lookupContextWindow). Each is a claim
|
|
48
|
+
* someone made and dated. When
|
|
46
49
|
* none of them speaks, this returns NULL rather than a number — because
|
|
47
50
|
* the question "how much of the window is left" has no answer without a
|
|
48
51
|
* window, and every display that shows a percentage needs this one, not
|
|
@@ -57,6 +60,37 @@ export declare function knownContextWindow(of?: {
|
|
|
57
60
|
readonly model: string;
|
|
58
61
|
readonly baseUrl?: string;
|
|
59
62
|
}): number | null;
|
|
63
|
+
/** CW-1: a stated window and WHO stated it — `set` is the user (profile,
|
|
64
|
+
* config or KISO_CONTEXT_WINDOW), `faux` is ours, the rest are the
|
|
65
|
+
* registry's steps (lookupContextWindow). */
|
|
66
|
+
export interface StatedWindow {
|
|
67
|
+
readonly tokens: number;
|
|
68
|
+
readonly source: "set" | "faux" | "learned" | ContextWindowSource;
|
|
69
|
+
/** the registry row's model id, for the registry's steps */
|
|
70
|
+
readonly from?: string;
|
|
71
|
+
/** CW-1 batch 2: the day an endpoint's refusal stated it, for `learned` */
|
|
72
|
+
readonly observedAt?: string;
|
|
73
|
+
}
|
|
74
|
+
/** CW-1: `knownContextWindow` with its source — the chain the displays name.
|
|
75
|
+
* The user's figure is the live binding's unless `own` carries a
|
|
76
|
+
* profile's: the /model listing passes each row's, and a row with none
|
|
77
|
+
* must not borrow the live one (`own: { configured: undefined }` — an
|
|
78
|
+
* optional parameter defaulted to the live value would take it back,
|
|
79
|
+
* since an explicit undefined selects the default). */
|
|
80
|
+
export declare function statedContextWindow(of?: {
|
|
81
|
+
readonly model: string;
|
|
82
|
+
readonly baseUrl?: string;
|
|
83
|
+
readonly upstream?: string;
|
|
84
|
+
}, own?: {
|
|
85
|
+
readonly configured: number | undefined;
|
|
86
|
+
}): StatedWindow | null;
|
|
87
|
+
/** CW-1: a window as the displays write it — `1M`, `1.05M`, `272K`,
|
|
88
|
+
* `1,048,576` (a figure that is not round is shown whole). */
|
|
89
|
+
export declare function windowLabel(tokens: number): string;
|
|
90
|
+
/** CW-1: the source, said. The short form rides a /model row; the long
|
|
91
|
+
* form is /status's. Only the model step is marked in the short form —
|
|
92
|
+
* it is the one figure no row states for this endpoint. */
|
|
93
|
+
export declare function windowSourceNote(w: StatedWindow | null, form: "short" | "long"): string;
|
|
60
94
|
export declare function contextWindowTokens(of?: {
|
|
61
95
|
readonly model: string;
|
|
62
96
|
readonly baseUrl?: string;
|
|
@@ -100,6 +134,22 @@ export declare function statusModelLabel(session: {
|
|
|
100
134
|
readonly effort: string;
|
|
101
135
|
};
|
|
102
136
|
}): string;
|
|
137
|
+
/** ADR-0055 Amendment 2 (decision 5): with no stated window the status row
|
|
138
|
+
* shows `ctx ?`, yet the compaction tiers still assume the fallback (128K
|
|
139
|
+
* since CW-1 batch 2)
|
|
140
|
+
* — the assumption is said out loud once, at agent build. */
|
|
141
|
+
export declare function unknownWindowNotice(model: string): string;
|
|
142
|
+
/** CW-1 batch 2: said once, when an endpoint's refusal states its cap — the
|
|
143
|
+
* tiers aim below it from the next request, and later sessions start on it. */
|
|
144
|
+
export declare function windowLearnedNotice(model: string, host: string, tokens: number): string;
|
|
145
|
+
/** ADR-0055 Amendment 2: the notice for a checkpoint the shrink invariant
|
|
146
|
+
* discarded — chars/4 sizes only; the checkpoint's text is the owner's
|
|
147
|
+
* work and never reaches the screen. */
|
|
148
|
+
export declare function compactionDiscardedNotice(d: {
|
|
149
|
+
readonly pre: number;
|
|
150
|
+
readonly post: number;
|
|
151
|
+
readonly summary: number;
|
|
152
|
+
}): string;
|
|
103
153
|
export declare function displayCtxRatio(session: AgentSession): number;
|
|
104
154
|
/** 0.40.0 item 9: how long a prompt cache is assumed to live. PROVISIONAL:
|
|
105
155
|
* DeepSeek does not publish its TTL (the owner's cache was gone after 27
|
|
@@ -219,6 +269,28 @@ export declare function turnUsageLedger(): {
|
|
|
219
269
|
report: (u: RunUsage) => void;
|
|
220
270
|
total: () => RunUsage | null;
|
|
221
271
|
};
|
|
272
|
+
/**
|
|
273
|
+
* The per-CALL view of usage reports for the two figures that must not
|
|
274
|
+
* double: the cache-miss estimate and the session's running cost.
|
|
275
|
+
*
|
|
276
|
+
* A call may report its usage more than once — the op gateway sent two
|
|
277
|
+
* usage chunks for one request (the owner's session
|
|
278
|
+
* 2026-09-23T03-00-01-230c: `fresh 3k · miss 3k` on a first turn, both
|
|
279
|
+
* reports 3,041 in / 69 out). The ledger above already makes a call's
|
|
280
|
+
* LATEST report replace the earlier; the miss and the cost were read per
|
|
281
|
+
* EVENT, so the second report compared itself with the first and called
|
|
282
|
+
* the whole prompt a miss, and a priced route would have been charged
|
|
283
|
+
* twice. Here the carrier is the PREVIOUS call's total, and a call's cost
|
|
284
|
+
* is added as the difference from what that call already added.
|
|
285
|
+
*/
|
|
286
|
+
export declare function callUsageMeter(): {
|
|
287
|
+
/** what a report's miss is measured against: the previous call's total */
|
|
288
|
+
carrier: () => number | null;
|
|
289
|
+
/** one report of the current call; returns the cost to ADD (or null) */
|
|
290
|
+
report: (total: number | null, costUsd: number | null) => number | null;
|
|
291
|
+
/** a stop ends the call */
|
|
292
|
+
endCall: () => void;
|
|
293
|
+
};
|
|
222
294
|
/** v2b: the spinner merged into the STATUS BAR (the v2a standalone glyph
|
|
223
295
|
* is gone) — docked only, 200ms rotation between the request and the
|
|
224
296
|
* first event. */
|
package/dist/chat.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync, statSync } from "node:fs";
|
|
8
|
-
import { escapeTerminal, cacheHitPct, decodeRate, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, verifyOfferView, STATUS_GLYPHS, } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { escapeTerminal, cacheHitPct, decodeRate, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, verifyOfferView, STATUS_GLYPHS, kUnit, } from "@vincemakes/kiso-tui";
|
|
9
9
|
import { askView, coldResumeLine, coldResumeView, deletionRiskHint, editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
10
|
import { canonicalTargetPath, isProtectedPath, protectedIdentity, shellProgressPath } from "@vincemakes/kiso-tools-node";
|
|
11
11
|
import { queuedSwitchLines } from "./state.js";
|
|
@@ -14,14 +14,19 @@ import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
|
|
|
14
14
|
import { canonicalizeUsageForModel, requestBudget } from "@vincemakes/kiso-runtime/internal";
|
|
15
15
|
import { dispatch, abortBangCommand } from "./dispatch.js";
|
|
16
16
|
import { paintWindowTitle } from "./window-title.js";
|
|
17
|
-
import { agentBaseUrl, agentModel, body, bodyLog, configuredWindow, dock, retryOnRow, retryShown, setRetryShown, floorOn, protectedFiles } from "./state.js";
|
|
17
|
+
import { agentBaseUrl, agentModel, body, bodyLog, configuredWindow, dock, retryOnRow, retryShown, setRetryShown, floorOn, protectedFiles, upstreamOf } from "./state.js";
|
|
18
18
|
import { attachImages } from "./attachments.js";
|
|
19
|
-
import {
|
|
19
|
+
import { learnedWindowFor } from "./learned-windows.js";
|
|
20
|
+
import { lookupContextWindow, lookupModelMetadata } from "@vincemakes/kiso-runtime/internal";
|
|
20
21
|
import { addDontAskAgainRule, askPanel, fixHintFor, pendingAsk, resolveUncertains } from "./trust-ui.js";
|
|
21
22
|
import { FauxExhaustionError, failOnFauxExhaustion } from "./faux-glue.js";
|
|
22
23
|
import { OFFERED_MODES, getMode, setMode } from "./mode.js";
|
|
23
|
-
/** B area: default context window for the ~ctx estimate (config overridable).
|
|
24
|
-
|
|
24
|
+
/** B area: default context window for the ~ctx estimate (config overridable).
|
|
25
|
+
* CW-1 batch 2: 128,000, down from 200,000 — the figure a model nobody
|
|
26
|
+
* states a window for is assumed to hold (the reference implementation's
|
|
27
|
+
* default too). Too small costs an early compaction; too large costs a
|
|
28
|
+
* refused request on a 128K model. Registered models never reach it. */
|
|
29
|
+
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
25
30
|
/** The in-process fake provider's id, and the window we declare for it.
|
|
26
31
|
* Ours to state: faux is not a vendor's model, so "nobody published a
|
|
27
32
|
* window" — the reason a real model's capacity is unknown — cannot apply.
|
|
@@ -61,7 +66,9 @@ export function autoCompactFromEnv() {
|
|
|
61
66
|
* THE WINDOW SOMEBODY STATED, or null when nobody has.
|
|
62
67
|
*
|
|
63
68
|
* Three sources, in order: the config/profile window, KISO_CONTEXT_WINDOW,
|
|
64
|
-
* and the metadata registry
|
|
69
|
+
* and the metadata registry (CW-1: this endpoint's row, the upstream's,
|
|
70
|
+
* then the model's own — see lookupContextWindow). Each is a claim
|
|
71
|
+
* someone made and dated. When
|
|
65
72
|
* none of them speaks, this returns NULL rather than a number — because
|
|
66
73
|
* the question "how much of the window is left" has no answer without a
|
|
67
74
|
* window, and every display that shows a percentage needs this one, not
|
|
@@ -73,11 +80,21 @@ export function autoCompactFromEnv() {
|
|
|
73
80
|
* confident `ctx left ~82%` against a figure nobody had measured.
|
|
74
81
|
*/
|
|
75
82
|
export function knownContextWindow(of) {
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
return statedContextWindow(of)?.tokens ?? null;
|
|
84
|
+
}
|
|
85
|
+
/** CW-1: `knownContextWindow` with its source — the chain the displays name.
|
|
86
|
+
* The user's figure is the live binding's unless `own` carries a
|
|
87
|
+
* profile's: the /model listing passes each row's, and a row with none
|
|
88
|
+
* must not borrow the live one (`own: { configured: undefined }` — an
|
|
89
|
+
* optional parameter defaulted to the live value would take it back,
|
|
90
|
+
* since an explicit undefined selects the default). */
|
|
91
|
+
export function statedContextWindow(of, own) {
|
|
92
|
+
const configured = own !== undefined ? own.configured : configuredWindow;
|
|
93
|
+
if (configured !== undefined)
|
|
94
|
+
return { tokens: configured, source: "set" };
|
|
78
95
|
const env = Number.parseInt(process.env.KISO_CONTEXT_WINDOW ?? "", 10);
|
|
79
96
|
if (Number.isFinite(env) && env > 0)
|
|
80
|
-
return env;
|
|
97
|
+
return { tokens: env, source: "set" };
|
|
81
98
|
const model = of?.model ?? agentModel;
|
|
82
99
|
// `faux` is OURS. The registry carries no row for it because it is not a
|
|
83
100
|
// vendor's model, but the reason the window is unknown elsewhere — nobody
|
|
@@ -86,9 +103,47 @@ export function knownContextWindow(of) {
|
|
|
86
103
|
// keeps faux mode showing a real percentage instead of the `ctx ?` that
|
|
87
104
|
// belongs to models whose capacity genuinely nobody states.
|
|
88
105
|
if (model === FAUX_MODEL)
|
|
89
|
-
return FAUX_CONTEXT_WINDOW;
|
|
90
|
-
const
|
|
91
|
-
|
|
106
|
+
return { tokens: FAUX_CONTEXT_WINDOW, source: "faux" };
|
|
107
|
+
const baseUrl = of !== undefined ? of.baseUrl : agentBaseUrl;
|
|
108
|
+
// CW-1 batch 2: what this endpoint's own refusal stated, before any
|
|
109
|
+
// statement about the model — a measurement of the route.
|
|
110
|
+
const learned = learnedWindowFor(model, baseUrl);
|
|
111
|
+
if (learned !== undefined)
|
|
112
|
+
return { tokens: learned.tokens, source: "learned", observedAt: learned.observedAt };
|
|
113
|
+
return lookupContextWindow(model, baseUrl, of?.upstream ?? upstreamOf(baseUrl));
|
|
114
|
+
}
|
|
115
|
+
/** CW-1: a window as the displays write it — `1M`, `1.05M`, `272K`,
|
|
116
|
+
* `1,048,576` (a figure that is not round is shown whole). */
|
|
117
|
+
export function windowLabel(tokens) {
|
|
118
|
+
if (tokens >= 1_000_000 && tokens % 10_000 === 0)
|
|
119
|
+
return `${tokens / 1_000_000}M`;
|
|
120
|
+
if (tokens % 1000 === 0)
|
|
121
|
+
return `${tokens / 1000}K`;
|
|
122
|
+
return tokens.toLocaleString("en-US");
|
|
123
|
+
}
|
|
124
|
+
/** CW-1: the source, said. The short form rides a /model row; the long
|
|
125
|
+
* form is /status's. Only the model step is marked in the short form —
|
|
126
|
+
* it is the one figure no row states for this endpoint. */
|
|
127
|
+
export function windowSourceNote(w, form) {
|
|
128
|
+
if (w === null)
|
|
129
|
+
return form === "short" ? "ctx ?" : `window unknown — compaction assumes ${windowLabel(DEFAULT_CONTEXT_WINDOW)}; set contextWindow on the profile to state it`;
|
|
130
|
+
const size = windowLabel(w.tokens);
|
|
131
|
+
if (form === "short")
|
|
132
|
+
return w.source === "model" ? `ctx ${size} inferred` : `ctx ${size}`;
|
|
133
|
+
switch (w.source) {
|
|
134
|
+
case "set":
|
|
135
|
+
return `window ${size}, as you set it`;
|
|
136
|
+
case "faux":
|
|
137
|
+
return `window ${size} (faux)`;
|
|
138
|
+
case "learned":
|
|
139
|
+
return `window ${size}, learned from this endpoint's refusal (${w.observedAt ?? "date unknown"})`;
|
|
140
|
+
case "route":
|
|
141
|
+
return `window ${size}, the registry's for this endpoint`;
|
|
142
|
+
case "upstream":
|
|
143
|
+
return `window ${size}, the registry's for the upstream`;
|
|
144
|
+
case "model":
|
|
145
|
+
return `window ${size}, inferred from the model (${w.from}) — not stated for this endpoint`;
|
|
146
|
+
}
|
|
92
147
|
}
|
|
93
148
|
export function contextWindowTokens(of) {
|
|
94
149
|
const windowOverride = configuredWindow;
|
|
@@ -112,9 +167,21 @@ export function contextWindowTokens(of) {
|
|
|
112
167
|
// asked again. The claim is true now because the switch path calls
|
|
113
168
|
// `microcompactThresholdFor` below, not because deriving a number
|
|
114
169
|
// from this function makes anything follow it.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
170
|
+
//
|
|
171
|
+
// CW-1: the registry's steps are lookupContextWindow's — the route's row,
|
|
172
|
+
// the upstream's, then the model's own identity — the same chain
|
|
173
|
+
// `statedContextWindow` names for the displays.
|
|
174
|
+
const baseUrl = of !== undefined ? of.baseUrl : agentBaseUrl;
|
|
175
|
+
// CW-1 batch 2: faux is declared here too — it read the old 200K
|
|
176
|
+
// fallback by accident, and the fallback moving must not move it.
|
|
177
|
+
if ((of?.model ?? agentModel) === FAUX_MODEL)
|
|
178
|
+
return FAUX_CONTEXT_WINDOW;
|
|
179
|
+
const learned = learnedWindowFor(of?.model ?? agentModel, baseUrl);
|
|
180
|
+
if (learned !== undefined)
|
|
181
|
+
return learned.tokens;
|
|
182
|
+
const known = lookupContextWindow(of?.model ?? agentModel, baseUrl, upstreamOf(baseUrl));
|
|
183
|
+
if (known !== null)
|
|
184
|
+
return known.tokens;
|
|
118
185
|
return DEFAULT_CONTEXT_WINDOW;
|
|
119
186
|
}
|
|
120
187
|
/**
|
|
@@ -160,10 +227,32 @@ export function statusModelLabel(session) {
|
|
|
160
227
|
// identity is asked for (`/status`), not in a row that has ~40 columns and
|
|
161
228
|
// cut the host to `deepseek/d…ndcode.ai`. Unnecessary at best, misleading at
|
|
162
229
|
// worst: it read as a path, not as an account.
|
|
163
|
-
|
|
230
|
+
// The owner, 2026-09-22: the row shows the MODEL's name only — a vendor
|
|
231
|
+
// prefix (`deepseek/deepseek-v4.1-flash`) cost the row its tail. The full
|
|
232
|
+
// id stays in `/status` and `/model`, where identity is asked for.
|
|
233
|
+
const name = agentModel.slice(agentModel.lastIndexOf("/") + 1) || agentModel;
|
|
234
|
+
return effort !== undefined && effort !== "default" ? `${name} · ${effort}` : name;
|
|
235
|
+
}
|
|
236
|
+
/** ADR-0055 Amendment 2 (decision 5): with no stated window the status row
|
|
237
|
+
* shows `ctx ?`, yet the compaction tiers still assume the fallback (128K
|
|
238
|
+
* since CW-1 batch 2)
|
|
239
|
+
* — the assumption is said out loud once, at agent build. */
|
|
240
|
+
export function unknownWindowNotice(model) {
|
|
241
|
+
return `[kiso] context window unknown for ${model} at this endpoint — compaction assumes ${DEFAULT_CONTEXT_WINDOW / 1000}K; set contextWindow on the profile to state it`;
|
|
242
|
+
}
|
|
243
|
+
/** CW-1 batch 2: said once, when an endpoint's refusal states its cap — the
|
|
244
|
+
* tiers aim below it from the next request, and later sessions start on it. */
|
|
245
|
+
export function windowLearnedNotice(model, host, tokens) {
|
|
246
|
+
return `✦ window learned — ${host === "" ? "the endpoint" : host} refused ${model} past ${windowLabel(tokens)} tokens; compaction now aims below it`;
|
|
247
|
+
}
|
|
248
|
+
/** ADR-0055 Amendment 2: the notice for a checkpoint the shrink invariant
|
|
249
|
+
* discarded — chars/4 sizes only; the checkpoint's text is the owner's
|
|
250
|
+
* work and never reaches the screen. */
|
|
251
|
+
export function compactionDiscardedNotice(d) {
|
|
252
|
+
return `✦ compaction discarded — the checkpoint did not shrink the context (~${kUnit(d.pre)} → ~${kUnit(d.post)}; it wrote ~${kUnit(d.summary)})`;
|
|
164
253
|
}
|
|
165
254
|
export function displayCtxRatio(session) {
|
|
166
|
-
// CAPACITY is not POLICY. `contextWindowTokens` falls back to
|
|
255
|
+
// CAPACITY is not POLICY. `contextWindowTokens` falls back to 128,000 so
|
|
167
256
|
// that the compaction threshold always HAS a value — a policy needs a
|
|
168
257
|
// number. A percentage on screen is a different kind of thing: it is a
|
|
169
258
|
// claim about the model, and an unstated window makes it unanswerable.
|
|
@@ -369,6 +458,43 @@ export function turnUsageLedger() {
|
|
|
369
458
|
},
|
|
370
459
|
};
|
|
371
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* The per-CALL view of usage reports for the two figures that must not
|
|
463
|
+
* double: the cache-miss estimate and the session's running cost.
|
|
464
|
+
*
|
|
465
|
+
* A call may report its usage more than once — the op gateway sent two
|
|
466
|
+
* usage chunks for one request (the owner's session
|
|
467
|
+
* 2026-09-23T03-00-01-230c: `fresh 3k · miss 3k` on a first turn, both
|
|
468
|
+
* reports 3,041 in / 69 out). The ledger above already makes a call's
|
|
469
|
+
* LATEST report replace the earlier; the miss and the cost were read per
|
|
470
|
+
* EVENT, so the second report compared itself with the first and called
|
|
471
|
+
* the whole prompt a miss, and a priced route would have been charged
|
|
472
|
+
* twice. Here the carrier is the PREVIOUS call's total, and a call's cost
|
|
473
|
+
* is added as the difference from what that call already added.
|
|
474
|
+
*/
|
|
475
|
+
export function callUsageMeter() {
|
|
476
|
+
let prevCallTotal = null;
|
|
477
|
+
let callTotal = null;
|
|
478
|
+
let callCost = null;
|
|
479
|
+
return {
|
|
480
|
+
carrier: () => prevCallTotal,
|
|
481
|
+
report(total, costUsd) {
|
|
482
|
+
if (total !== null)
|
|
483
|
+
callTotal = total;
|
|
484
|
+
if (costUsd === null)
|
|
485
|
+
return null;
|
|
486
|
+
const add = costUsd - (callCost ?? 0);
|
|
487
|
+
callCost = costUsd;
|
|
488
|
+
return add;
|
|
489
|
+
},
|
|
490
|
+
endCall() {
|
|
491
|
+
if (callTotal !== null)
|
|
492
|
+
prevCallTotal = callTotal;
|
|
493
|
+
callTotal = null;
|
|
494
|
+
callCost = null;
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
}
|
|
372
498
|
/** v2b: the spinner merged into the STATUS BAR (the v2a standalone glyph
|
|
373
499
|
* is gone) — docked only, 200ms rotation between the request and the
|
|
374
500
|
* first event. */
|
|
@@ -791,7 +917,7 @@ submitTurn) {
|
|
|
791
917
|
// and read at that call's usage event. Per CALL, not per turn — a turn
|
|
792
918
|
// with three model calls reports the third, and each one times itself.
|
|
793
919
|
let callFirstEventAt = null;
|
|
794
|
-
|
|
920
|
+
const meter = callUsageMeter();
|
|
795
921
|
let missed = null;
|
|
796
922
|
// v3 §02: the recap line derives ENTIRELY from the local event stream
|
|
797
923
|
// (zero tokens). R3g: what it derives is the turn's COST — wall
|
|
@@ -828,6 +954,8 @@ submitTurn) {
|
|
|
828
954
|
// the stream watchdog and the repeated-failure breaker remain active.
|
|
829
955
|
// W22-R1: the ledger sees every event; a stop is what ends a call.
|
|
830
956
|
ledger.observe(ev);
|
|
957
|
+
if (ev.type === "stop")
|
|
958
|
+
meter.endCall();
|
|
831
959
|
// ADR-0055 Amendment 1 (A1b): a compaction inside the run says so, once.
|
|
832
960
|
if (ev.type === "summarized" || ev.type === "microcompacted") {
|
|
833
961
|
const r = displayCtxRatio(session);
|
|
@@ -944,11 +1072,12 @@ submitTurn) {
|
|
|
944
1072
|
body.notice("stream interrupted — the draft above is abandoned");
|
|
945
1073
|
break;
|
|
946
1074
|
case "usage": {
|
|
947
|
-
const delta = usageFromEvent(session.provider, ev,
|
|
1075
|
+
const delta = usageFromEvent(session.provider, ev, meter.carrier(), agentModel, session.baseUrl);
|
|
948
1076
|
// W22-R1: this call's LATEST report replaces its earlier one;
|
|
949
|
-
// the turn's figure is the settled calls plus this one.
|
|
1077
|
+
// the turn's figure is the settled calls plus this one. The
|
|
1078
|
+
// miss and the cost follow the same rule (callUsageMeter).
|
|
950
1079
|
ledger.report(delta.usage);
|
|
951
|
-
|
|
1080
|
+
const costToAdd = meter.report(delta.total, delta.costUsd);
|
|
952
1081
|
missed = delta.missed;
|
|
953
1082
|
// TUI2-R1 (E): the request's canonical cost rides the same
|
|
954
1083
|
// callback the usage does — one settled request, one addition.
|
|
@@ -957,7 +1086,7 @@ submitTurn) {
|
|
|
957
1086
|
// second call is timed from ITS own first event.
|
|
958
1087
|
const tokPerSec = callFirstEventAt === null ? null : decodeRate(ev.outputTokens, Date.now() - callFirstEventAt);
|
|
959
1088
|
callFirstEventAt = null;
|
|
960
|
-
statusCb?.(turnUsage() ?? UNKNOWN_USAGE, displayCtxRatio(session),
|
|
1089
|
+
statusCb?.(turnUsage() ?? UNKNOWN_USAGE, displayCtxRatio(session), costToAdd, tokPerSec);
|
|
961
1090
|
break;
|
|
962
1091
|
}
|
|
963
1092
|
case "uncertain_pending":
|
package/dist/config.d.ts
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* direct write (provider: "openai-compat" |
|
|
12
12
|
* "anthropic" | "openai-responses")
|
|
13
13
|
* models?: { [name]: { kind: "openai-compat"|"anthropic"|"openai-responses",
|
|
14
|
-
* baseUrl?: string, model: string, apiKeyEnv: string
|
|
14
|
+
* baseUrl?: string, model: string, apiKeyEnv: string,
|
|
15
|
+
* upstream?: string } } — upstream: never where requests go
|
|
15
16
|
* mode?: "manual"|"default"|"accept-edits"|"plan"|"bypass"|"dontAsk"
|
|
16
17
|
* contextWindow?: number — tokens
|
|
17
18
|
* autoCompact?: { thresholdRatio: number } — 0<r<1; default off
|
|
@@ -64,6 +65,16 @@ export interface ModelProfile {
|
|
|
64
65
|
* registry, where every figure has to carry a source.
|
|
65
66
|
*/
|
|
66
67
|
readonly contextWindow?: number;
|
|
68
|
+
/**
|
|
69
|
+
* What this endpoint FORWARDS to, when `baseUrl` is a local proxy — a URL
|
|
70
|
+
* (`https://gateway.example/v1`) or a name (`my gateway`). It never
|
|
71
|
+
* moves a request — they still go to `baseUrl`. The owner, 2026-09-23: a
|
|
72
|
+
* profile behind a local forwarder showed `@127.0.0.1:47821`, which says
|
|
73
|
+
* where the bytes go and not who is billed; the row now reads
|
|
74
|
+
* `@gateway.example via 127.0.0.1:47821`. CW-1: a URL here is also where
|
|
75
|
+
* the registry looks for this model's window when `baseUrl` has no row.
|
|
76
|
+
*/
|
|
77
|
+
readonly upstream?: string;
|
|
67
78
|
}
|
|
68
79
|
export interface AutoCompactConfig {
|
|
69
80
|
readonly thresholdRatio: number;
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* direct write (provider: "openai-compat" |
|
|
12
12
|
* "anthropic" | "openai-responses")
|
|
13
13
|
* models?: { [name]: { kind: "openai-compat"|"anthropic"|"openai-responses",
|
|
14
|
-
* baseUrl?: string, model: string, apiKeyEnv: string
|
|
14
|
+
* baseUrl?: string, model: string, apiKeyEnv: string,
|
|
15
|
+
* upstream?: string } } — upstream: never where requests go
|
|
15
16
|
* mode?: "manual"|"default"|"accept-edits"|"plan"|"bypass"|"dontAsk"
|
|
16
17
|
* contextWindow?: number — tokens
|
|
17
18
|
* autoCompact?: { thresholdRatio: number } — 0<r<1; default off
|
|
@@ -146,6 +147,8 @@ export function parseConfig(text, source) {
|
|
|
146
147
|
// your profile") had never done anything.
|
|
147
148
|
if (p.contextWindow !== undefined && (typeof p.contextWindow !== "number" || !Number.isFinite(p.contextWindow) || p.contextWindow <= 0))
|
|
148
149
|
fail(`models.${name}.contextWindow`, "expected a positive token count");
|
|
150
|
+
if (p.upstream !== undefined && (typeof p.upstream !== "string" || p.upstream.trim() === ""))
|
|
151
|
+
fail(`models.${name}.upstream`, "expected the URL or name of what this endpoint forwards to");
|
|
149
152
|
models[name] = {
|
|
150
153
|
kind: p.kind,
|
|
151
154
|
model: p.model,
|
|
@@ -154,6 +157,7 @@ export function parseConfig(text, source) {
|
|
|
154
157
|
...(typeof p.promptCaching === "boolean" ? { promptCaching: p.promptCaching } : {}),
|
|
155
158
|
...(typeof p.streamIdleMs === "number" ? { streamIdleMs: p.streamIdleMs } : {}),
|
|
156
159
|
...(typeof p.contextWindow === "number" ? { contextWindow: p.contextWindow } : {}),
|
|
160
|
+
...(typeof p.upstream === "string" ? { upstream: p.upstream.trim() } : {}),
|
|
157
161
|
};
|
|
158
162
|
}
|
|
159
163
|
out.models = models;
|
package/dist/dispatch.js
CHANGED
|
@@ -9,11 +9,11 @@ import { buildAdapter, lookupModelMetadata, resolveContinuationScope, resolveRea
|
|
|
9
9
|
import { MODES, MODE_NOTE, OFFERED_MODES, getMode, setMode } from "./mode.js";
|
|
10
10
|
import { clipboardWrite, lastAnswer } from "./clipboard.js";
|
|
11
11
|
import { protectedBangReason, protectedShellVerdict } from "./protected-shell.js";
|
|
12
|
-
import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, setLastBinding } from "./state.js";
|
|
12
|
+
import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, setLastBinding } from "./state.js";
|
|
13
13
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
14
14
|
import { profileProviderLabel, providerLabel } from "./provider-label.js";
|
|
15
15
|
import { queuedSwitchLines } from "./state.js";
|
|
16
|
-
import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner } from "./chat.js";
|
|
16
|
+
import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner, statedContextWindow, windowSourceNote } from "./chat.js";
|
|
17
17
|
import { authForProfile, directWriteProfile, profileAvailable, resolveContextWindow, unavailableReason } from "./config.js";
|
|
18
18
|
import { shellTool } from "@vincemakes/kiso-tools-node";
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
@@ -109,6 +109,14 @@ function signInNote(p) {
|
|
|
109
109
|
}
|
|
110
110
|
return p.apiKeyEnv ?? "no key";
|
|
111
111
|
}
|
|
112
|
+
/** CW-1 (owner, 2026-09-23): a profile's window on its /model row — the
|
|
113
|
+
* profile's own stated figure first, then the registry's chain for its
|
|
114
|
+
* model, endpoint and upstream; `inferred` marks the one no row states
|
|
115
|
+
* for that endpoint. */
|
|
116
|
+
function windowNoteOf(p) {
|
|
117
|
+
const w = statedContextWindow({ model: p.model, ...(p.baseUrl !== undefined ? { baseUrl: p.baseUrl } : {}), ...(p.upstream !== undefined ? { upstream: p.upstream } : {}) }, { configured: resolveContextWindow(mergedConfig, p) });
|
|
118
|
+
return windowSourceNote(w, "short");
|
|
119
|
+
}
|
|
112
120
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
113
121
|
* replay routes through it too — a queued "/last" must never become a
|
|
114
122
|
* user turn (v2c: the rl lives in main, so lines arrive earlier and the
|
|
@@ -487,7 +495,10 @@ export function dispatch(line, ctx) {
|
|
|
487
495
|
const ctxPct = Number.isFinite(ctxRatio) ? `~${Math.round(ctxRatio * 100)}%` : "~?";
|
|
488
496
|
bodyLog(`session ${ctx.session.id}`);
|
|
489
497
|
bodyLog(`${ctx.session.log.all.length} events`);
|
|
490
|
-
|
|
498
|
+
// CW-1: the percentage names its denominator and who stated it — a
|
|
499
|
+
// window inferred from the model reads differently from one the
|
|
500
|
+
// registry states for this endpoint.
|
|
501
|
+
bodyLog(`ctx ${ctxPct} · ${windowSourceNote(statedContextWindow(), "long")}`);
|
|
491
502
|
// The owner, 2026-09-21: /status is where "what am I actually
|
|
492
503
|
// running on" is answered, so the identity is spelled out HERE —
|
|
493
504
|
// the model, the host the request goes to, the profile it came
|
|
@@ -502,7 +513,7 @@ export function dispatch(line, ctx) {
|
|
|
502
513
|
// restored, not switched. (The real fix is to stop overloading that
|
|
503
514
|
// variable; until then, a label that cannot lie.)
|
|
504
515
|
const profileOf = currentProfileName === null ? "" : ` · profile ${currentProfileName}`;
|
|
505
|
-
bodyLog(`model ${agentModel}${providerLabel(agentBaseUrl)}${profileOf}`);
|
|
516
|
+
bodyLog(`model ${agentModel}${providerLabel(agentBaseUrl, upstreamOf(agentBaseUrl))}${profileOf}`);
|
|
506
517
|
ctx.input.prompt();
|
|
507
518
|
});
|
|
508
519
|
return;
|
|
@@ -607,8 +618,10 @@ export function dispatch(line, ctx) {
|
|
|
607
618
|
// profile key is what `/model <name>` takes, not what a chooser
|
|
608
619
|
// reading a list needs. What remains is what the chooser CANNOT
|
|
609
620
|
// infer: availability, and which one is live.
|
|
610
|
-
|
|
611
|
-
|
|
621
|
+
// CW-1: the window rides LAST — availability and the live mark
|
|
622
|
+
// are what a narrow row must keep.
|
|
623
|
+
const marks = [...(profileAvailable(profile) ? [] : ["unavailable"]), ...(name === currentProfileName ? ["current"] : []), windowNoteOf(profile)];
|
|
624
|
+
const host = profileProviderLabel(profile.kind, profile.baseUrl, profile.upstream);
|
|
612
625
|
return { label: `${profile.kind}/${profile.model}${host === "" ? "" : ` ${host}`}`, note: marks.join(" · "), ...effortAxis(profile) };
|
|
613
626
|
}),
|
|
614
627
|
// PH-1a (finding PH-F4): the example must be a syntax
|
|
@@ -658,8 +671,8 @@ export function dispatch(line, ctx) {
|
|
|
658
671
|
else {
|
|
659
672
|
for (const name of names) {
|
|
660
673
|
const p = configModels[name];
|
|
661
|
-
const hostOf = profileProviderLabel(p.kind, p.baseUrl);
|
|
662
|
-
bodyLog(` ${name} → ${p.kind}/${p.model}${hostOf === "" ? "" : ` ${hostOf}`} · ${signInNote(p)} ${profileAvailable(p) ? "(available)" : "(unavailable)"} · ${effortNote(p)}`);
|
|
674
|
+
const hostOf = profileProviderLabel(p.kind, p.baseUrl, p.upstream);
|
|
675
|
+
bodyLog(` ${name} → ${p.kind}/${p.model}${hostOf === "" ? "" : ` ${hostOf}`} · ${signInNote(p)} ${profileAvailable(p) ? "(available)" : "(unavailable)"} · ${effortNote(p)} · ${windowNoteOf(p)}`);
|
|
663
676
|
}
|
|
664
677
|
}
|
|
665
678
|
bodyLog("switch with /model <profile-name|provider/model>");
|
|
@@ -775,7 +788,7 @@ export function dispatch(line, ctx) {
|
|
|
775
788
|
// (and the owner's) read `gpt-6-astra · CH 92%`, the new
|
|
776
789
|
// model beside the previous model's figure.
|
|
777
790
|
ctx.modelSwitched();
|
|
778
|
-
body.notice(`model → ${profName} (${profile.model}${providerLabel(profile.baseUrl) === "" ? "" : ` ${providerLabel(profile.baseUrl)}`}${effortTok !== undefined ? ` · ${effortTok}` : ""}) — takes effect on the next turn`);
|
|
791
|
+
body.notice(`model → ${profName} (${profile.model}${providerLabel(profile.baseUrl, profile.upstream) === "" ? "" : ` ${providerLabel(profile.baseUrl, profile.upstream)}`}${effortTok !== undefined ? ` · ${effortTok}` : ""}) — takes effect on the next turn`);
|
|
779
792
|
}
|
|
780
793
|
}
|
|
781
794
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface EmptySession {
|
|
2
|
+
/** The session folder (a project folder, or the legacy sessions dir). */
|
|
3
|
+
readonly dir: string;
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** The files that name it, relative to `dir`. */
|
|
6
|
+
readonly files: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface EmptyScan {
|
|
9
|
+
readonly empty: readonly EmptySession[];
|
|
10
|
+
/** Empty by the files, but a live process holds the lock. */
|
|
11
|
+
readonly inUse: number;
|
|
12
|
+
}
|
|
13
|
+
/** A lock whose pid answers signal 0 belongs to a running process. */
|
|
14
|
+
export declare function pidAlive(pid: number): boolean;
|
|
15
|
+
/** Every session in `dirs` with a sidecar and no log. Reads only. */
|
|
16
|
+
export declare function findEmptySessions(dirs: readonly string[], alive?: (pid: number) => boolean): EmptyScan;
|
|
17
|
+
/** The platform's Trash: ~/.Trash on macOS, the freedesktop one elsewhere. */
|
|
18
|
+
export declare function defaultTrashRoot(): string;
|
|
19
|
+
/** Move the sessions into one new folder under `trashRoot`, keeping each
|
|
20
|
+
* session folder's name; returns that folder. Never deletes: a file that
|
|
21
|
+
* cannot be renamed across devices is copied, then unlinked. */
|
|
22
|
+
export declare function moveToTrash(sessions: readonly EmptySession[], trashRoot: string, now?: Date): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DC-60 — the empty sessions an older kiso left behind.
|
|
3
|
+
*
|
|
4
|
+
* Until DC-60 every process that opened a session wrote its sidecar (and,
|
|
5
|
+
* through the start-up recovery, a trace) whether or not anything ever
|
|
6
|
+
* happened in it: a started-and-closed kiso, a /resume that went nowhere, a
|
|
7
|
+
* delegated child that failed before its first turn. Each left a sidecar
|
|
8
|
+
* with no log, and each showed up in /resume as a card with no summary.
|
|
9
|
+
* The owner's disk held 28 on 2026-09-23.
|
|
10
|
+
*
|
|
11
|
+
* `kiso sessions --prune-empty` lists them. With `--yes` it MOVES them to
|
|
12
|
+
* the Trash — never deletes — keeping each project folder's name, so a
|
|
13
|
+
* mistake is a drag back. A session whose lock names a live process is
|
|
14
|
+
* skipped: something is using it right now.
|
|
15
|
+
*/
|
|
16
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { basename, join } from "node:path";
|
|
19
|
+
const SIDECAR = ".meta.json";
|
|
20
|
+
/** A lock whose pid answers signal 0 belongs to a running process. */
|
|
21
|
+
export function pidAlive(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return err.code === "EPERM";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function lockPid(path) {
|
|
31
|
+
try {
|
|
32
|
+
const pid = JSON.parse(readFileSync(path, "utf8")).pid;
|
|
33
|
+
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Every session in `dirs` with a sidecar and no log. Reads only. */
|
|
40
|
+
export function findEmptySessions(dirs, alive = pidAlive) {
|
|
41
|
+
const empty = [];
|
|
42
|
+
let inUse = 0;
|
|
43
|
+
for (const dir of dirs) {
|
|
44
|
+
let names;
|
|
45
|
+
try {
|
|
46
|
+
names = readdirSync(dir);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
if (!name.endsWith(SIDECAR))
|
|
53
|
+
continue;
|
|
54
|
+
const id = name.slice(0, -SIDECAR.length);
|
|
55
|
+
if (names.includes(`${id}.jsonl`))
|
|
56
|
+
continue;
|
|
57
|
+
const lock = `${id}.lock`;
|
|
58
|
+
if (names.includes(lock)) {
|
|
59
|
+
const pid = lockPid(join(dir, lock));
|
|
60
|
+
if (pid !== null && alive(pid)) {
|
|
61
|
+
inUse += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const files = [name];
|
|
66
|
+
if (names.includes(lock))
|
|
67
|
+
files.push(lock);
|
|
68
|
+
if (existsSync(join(dir, "traces", `${id}.jsonl`)))
|
|
69
|
+
files.push(join("traces", `${id}.jsonl`));
|
|
70
|
+
empty.push({ dir, id, files });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { empty, inUse };
|
|
74
|
+
}
|
|
75
|
+
/** The platform's Trash: ~/.Trash on macOS, the freedesktop one elsewhere. */
|
|
76
|
+
export function defaultTrashRoot() {
|
|
77
|
+
if (process.platform === "darwin")
|
|
78
|
+
return join(homedir(), ".Trash");
|
|
79
|
+
return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "Trash", "files");
|
|
80
|
+
}
|
|
81
|
+
/** Move the sessions into one new folder under `trashRoot`, keeping each
|
|
82
|
+
* session folder's name; returns that folder. Never deletes: a file that
|
|
83
|
+
* cannot be renamed across devices is copied, then unlinked. */
|
|
84
|
+
export function moveToTrash(sessions, trashRoot, now = new Date()) {
|
|
85
|
+
const target = join(trashRoot, `kiso-empty-sessions-${now.toISOString().replace(/[:.]/g, "-")}`);
|
|
86
|
+
for (const s of sessions) {
|
|
87
|
+
for (const file of s.files) {
|
|
88
|
+
const to = join(target, basename(s.dir), file);
|
|
89
|
+
mkdirSync(join(to, ".."), { recursive: true });
|
|
90
|
+
try {
|
|
91
|
+
renameSync(join(s.dir, file), to);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err.code !== "EXDEV")
|
|
95
|
+
throw err;
|
|
96
|
+
copyFileSync(join(s.dir, file), to);
|
|
97
|
+
unlinkSync(join(s.dir, file));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return target;
|
|
102
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,14 @@ import { newSessionId } from "./session-id.js";
|
|
|
29
29
|
import { createInterface } from "node:readline";
|
|
30
30
|
import { Readable } from "node:stream";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
|
-
import { join } from "node:path";
|
|
32
|
+
import { basename, join } from "node:path";
|
|
33
33
|
import { Body, Editor, PROMPT, bannerLines, currentGround, resolveGround, setGround, escapeTerminal, extensionsBannerText, idColumn, idleStatus, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListHeader, sessionListRow, sessionListUnknownLine, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
34
34
|
import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
|
|
35
35
|
import { listSessionSidecars, migrateSummaries, readProfile, runsACheck, summaryMigrationPending } from "@vincemakes/kiso-runtime/internal";
|
|
36
36
|
import { skillMenuItems } from "./skill-invoke.js";
|
|
37
37
|
import { canonicalPath, claimProjectDir, hasSession, locateSession, projectLayoutActive, sessionFolders } from "./projects.js";
|
|
38
38
|
import { migrationNotice, pendingLegacyIds, planMigration, reverseMigration, runMigration } from "./session-migration.js";
|
|
39
|
+
import { defaultTrashRoot, findEmptySessions, moveToTrash } from "./empty-sessions.js";
|
|
39
40
|
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
40
41
|
import { createCodingTools, isProtectedPath, protectedIdentity, PROTECTED_REFUSAL } from "@vincemakes/kiso-tools-node";
|
|
41
42
|
import { MODES, OFFERED_MODES, getMode, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
|
|
@@ -50,7 +51,9 @@ import { maxRetriesFromEnv } from "./retries.js";
|
|
|
50
51
|
import { askUi, resolveProjectTrust } from "./trust-ui.js";
|
|
51
52
|
import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
52
53
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
53
|
-
import { chat, contextWindowTokens, displayCtxRatio, microcompactThresholdFor, statusModelLabel } from "./chat.js";
|
|
54
|
+
import { chat, compactionDiscardedNotice, contextWindowTokens, displayCtxRatio, knownContextWindow, microcompactThresholdFor, statusModelLabel, unknownWindowNotice, windowLearnedNotice } from "./chat.js";
|
|
55
|
+
import { recordLearnedWindow, useLearnedWindows } from "./learned-windows.js";
|
|
56
|
+
import { providerHost } from "./provider-label.js";
|
|
54
57
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
55
58
|
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
|
|
56
59
|
import { checkForUpdate, knownUpdate, updateCardLines } from "./update-check.js";
|
|
@@ -775,6 +778,9 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
775
778
|
// shell tool would then write into this project's folder.
|
|
776
779
|
process.env.KISO_DELEGATION_CONFIG_JSON = JSON.stringify({ checks: merged.checks ?? {}, profiles: Object.keys(merged.models ?? {}), sessionsDir: sessionsDir() });
|
|
777
780
|
setConfigModels(merged.models ?? {});
|
|
781
|
+
// CW-1 batch 2: the windows endpoints stated by refusing — read before the
|
|
782
|
+
// first window is asked for (the unknown-window notice below).
|
|
783
|
+
useLearnedWindows();
|
|
778
784
|
const resolved = resolveModel(modelFlag, merged);
|
|
779
785
|
// AFTER the model resolves: the window a PROFILE states is about that
|
|
780
786
|
// profile's model, so it cannot be read before we know which profile is
|
|
@@ -794,6 +800,13 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
794
800
|
setCurrentProfileName(resolved.name);
|
|
795
801
|
}
|
|
796
802
|
setAgentModel(model, resolved?.profile.baseUrl); // v2b: the status bar shows it; OR-1: the endpoint rides along
|
|
803
|
+
// ADR-0055 Amendment 2: the row says `ctx ?` for an unstated window, but
|
|
804
|
+
// the tiers still need a number and assume the fallback — say so, once,
|
|
805
|
+
// at build.
|
|
806
|
+
if (!unknownWindowNoticed && knownContextWindow() === null) {
|
|
807
|
+
unknownWindowNoticed = true;
|
|
808
|
+
console.error(unknownWindowNotice(model));
|
|
809
|
+
}
|
|
797
810
|
// W21: the extensions array is built ONCE per agent and shared with
|
|
798
811
|
// the runtime by reference — the don't-ask-again writer pushes the
|
|
799
812
|
// generated extension into it so a first-time rule joins the chain
|
|
@@ -860,7 +873,24 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
860
873
|
// 1 reads the user's configured checks first, then the runner table.
|
|
861
874
|
contextPolicy: {
|
|
862
875
|
...(contextPolicy ?? {}),
|
|
863
|
-
tiers: {
|
|
876
|
+
tiers: {
|
|
877
|
+
windowTokens: contextWindowTokens(),
|
|
878
|
+
isCheck: (command) => runsACheck(command, Object.values(merged.checks ?? {})),
|
|
879
|
+
// ADR-0055 Amendment 2: a discarded checkpoint says so, in sizes only.
|
|
880
|
+
onDiscard: (d) => body.notice(compactionDiscardedNotice(d)),
|
|
881
|
+
// ADR-0055 Amendment 2 (decision 3): only a window someone stated
|
|
882
|
+
// arms the overflow belt — read from the LIVE binding, so it moves
|
|
883
|
+
// with /model and /resume; the fallback reads as null.
|
|
884
|
+
statedWindow: () => knownContextWindow(),
|
|
885
|
+
// CW-1 batch 2: an endpoint's refusal stated its cap. The tiers
|
|
886
|
+
// took it already; kept, it is where the next session starts, and
|
|
887
|
+
// the status row's denominator moves with it. Said once per new
|
|
888
|
+
// figure — a refusal at a cap already kept says nothing.
|
|
889
|
+
onWindowLearned: (w) => {
|
|
890
|
+
if (recordLearnedWindow(w.model, w.baseUrl, w.tokens))
|
|
891
|
+
body.notice(windowLearnedNotice(w.model, providerHost(w.baseUrl) ?? "", w.tokens));
|
|
892
|
+
},
|
|
893
|
+
},
|
|
864
894
|
},
|
|
865
895
|
// R3e (owner ruling, 2026-08-28): NO turn limit on an interactive
|
|
866
896
|
// session. This was `maxTurns: 20`, hardcoded on 2026-08-03 with no
|
|
@@ -1109,6 +1139,7 @@ async function pickSession(agent, input) {
|
|
|
1109
1139
|
* once; 0.41.0 removes it.
|
|
1110
1140
|
*/
|
|
1111
1141
|
let autoCompactNoticed = false;
|
|
1142
|
+
let unknownWindowNoticed = false;
|
|
1112
1143
|
function retiredAutoCompact(merged) {
|
|
1113
1144
|
if (!autoCompactNoticed && (process.env.KISO_AUTO_COMPACT !== undefined || resolveAutoCompact(merged) !== undefined)) {
|
|
1114
1145
|
autoCompactNoticed = true;
|
|
@@ -1646,7 +1677,18 @@ async function main() {
|
|
|
1646
1677
|
// = each form's default (TTY: current; pipe: all, today's bytes).
|
|
1647
1678
|
let listScope;
|
|
1648
1679
|
let reverseManifest;
|
|
1680
|
+
// DC-60: `kiso sessions --prune-empty [--yes]` — list the sessions that
|
|
1681
|
+
// never began; with --yes, move them to the Trash
|
|
1682
|
+
let pruneEmpty;
|
|
1649
1683
|
if (args[0] === "sessions") {
|
|
1684
|
+
const pe = args.indexOf("--prune-empty");
|
|
1685
|
+
if (pe !== -1) {
|
|
1686
|
+
args.splice(pe, 1);
|
|
1687
|
+
const yes = args.indexOf("--yes");
|
|
1688
|
+
if (yes !== -1)
|
|
1689
|
+
args.splice(yes, 1);
|
|
1690
|
+
pruneEmpty = yes !== -1 ? "move" : "list";
|
|
1691
|
+
}
|
|
1650
1692
|
// 0.40.0: the undo of the per-project move, named by its manifest
|
|
1651
1693
|
const r = args.indexOf("--reverse-migration");
|
|
1652
1694
|
if (r !== -1) {
|
|
@@ -1874,6 +1916,25 @@ async function main() {
|
|
|
1874
1916
|
// "reading 'question'" on the dock-less branch). The input
|
|
1875
1917
|
// exists so the gate's ask can be answered; the listing
|
|
1876
1918
|
// itself never touches it.
|
|
1919
|
+
if (pruneEmpty !== undefined) {
|
|
1920
|
+
// DC-60: before makeAgent — the scan only reads, and nothing
|
|
1921
|
+
// needs an agent to move a file to the Trash
|
|
1922
|
+
const { empty, inUse } = findEmptySessions(listingFolders().map((f) => f.dir));
|
|
1923
|
+
const n = (k, w) => `${k} ${w}${k === 1 ? "" : "s"}`;
|
|
1924
|
+
if (empty.length === 0)
|
|
1925
|
+
console.log("no empty sessions");
|
|
1926
|
+
else if (pruneEmpty === "list") {
|
|
1927
|
+
console.log(`${n(empty.length, "empty session")} — a sidecar and no log; none of them ever ran:`);
|
|
1928
|
+
for (const s of empty)
|
|
1929
|
+
console.log(` ${basename(s.dir)}/${s.id}`);
|
|
1930
|
+
console.log("kiso sessions --prune-empty --yes moves them to the Trash");
|
|
1931
|
+
}
|
|
1932
|
+
else
|
|
1933
|
+
console.log(`moved ${n(empty.length, "empty session")} to ${moveToTrash(empty, defaultTrashRoot())}`);
|
|
1934
|
+
if (inUse > 0)
|
|
1935
|
+
console.log(`skipped ${n(inUse, "empty session")} in use — a live process holds the lock`);
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1877
1938
|
if (reverseManifest !== undefined) {
|
|
1878
1939
|
// before makeAgent: its folder preparation would otherwise run
|
|
1879
1940
|
// the very move this undoes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface LearnedWindow {
|
|
2
|
+
readonly tokens: number;
|
|
3
|
+
/** the day the refusal was read, YYYY-MM-DD */
|
|
4
|
+
readonly observedAt: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function learnedWindowsPath(home?: string): string;
|
|
7
|
+
/** The key for one model at one endpoint; no baseUrl is the provider's own. */
|
|
8
|
+
export declare function learnedKey(model: string, baseUrl: string | undefined): string;
|
|
9
|
+
/** Startup: read the file and keep figures learned from here on in it.
|
|
10
|
+
* Until this is called, nothing is read and nothing is kept. */
|
|
11
|
+
export declare function useLearnedWindows(path?: string): void;
|
|
12
|
+
/** The learned window for a model at an endpoint, if a refusal stated one. */
|
|
13
|
+
export declare function learnedWindowFor(model: string, baseUrl: string | undefined): LearnedWindow | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Keep a refusal's cap. Returns true when it changed what is kept (a new
|
|
16
|
+
* key, or a smaller figure), false when a figure at or below it is kept
|
|
17
|
+
* already — or when no file was named (`useLearnedWindows`). The file is
|
|
18
|
+
* re-read before the write, so a figure another process learned meanwhile
|
|
19
|
+
* is not lost.
|
|
20
|
+
*/
|
|
21
|
+
export declare function recordLearnedWindow(model: string, baseUrl: string | undefined, tokens: number, today?: Date): boolean;
|
|
22
|
+
/** Tests: back to the state before startup — nothing read, nothing kept. */
|
|
23
|
+
export declare function resetLearnedWindows(): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CW-1 batch 2 — the windows endpoints have STATED by refusing a request,
|
|
3
|
+
* kept across sessions in `<KISO_HOME>/learned-windows.json`.
|
|
4
|
+
*
|
|
5
|
+
* A refusal like "maximum context length is 1048576 tokens" is the one
|
|
6
|
+
* window source that is right for any route, relays included (the runtime
|
|
7
|
+
* reads it: window-learner.ts). The runtime's tiers take it at once; this
|
|
8
|
+
* file keeps it, so the next session starts on the real figure instead of
|
|
9
|
+
* meeting the wall again. It sits in the window chain after what the user
|
|
10
|
+
* set and before the registry: a measurement of this endpoint beats a
|
|
11
|
+
* statement about the model.
|
|
12
|
+
*
|
|
13
|
+
* Keyed by the endpoint (the baseUrl, trailing slashes dropped — a
|
|
14
|
+
* forwarder can send two paths to two upstreams) and the model id. A
|
|
15
|
+
* figure only ever goes DOWN for a key: a later, larger refusal cannot
|
|
16
|
+
* widen what a route already refused. The file is private (0600, tmp +
|
|
17
|
+
* rename); a missing or unreadable file is an empty one — the chain falls
|
|
18
|
+
* through to the registry, as it did before anything was learned.
|
|
19
|
+
*
|
|
20
|
+
* Nothing is read until the CLI's startup names the file
|
|
21
|
+
* (`useLearnedWindows`): the window chain is library code that in-process
|
|
22
|
+
* tests call, and a chain that read `~/.kiso` on its own would make their
|
|
23
|
+
* verdict depend on the machine they run on.
|
|
24
|
+
*/
|
|
25
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
import { kisoHome } from "./state.js";
|
|
28
|
+
let table = null;
|
|
29
|
+
let tablePath = null;
|
|
30
|
+
export function learnedWindowsPath(home = kisoHome()) {
|
|
31
|
+
return join(home, "learned-windows.json");
|
|
32
|
+
}
|
|
33
|
+
/** The key for one model at one endpoint; no baseUrl is the provider's own. */
|
|
34
|
+
export function learnedKey(model, baseUrl) {
|
|
35
|
+
return `${baseUrl === undefined ? "-" : baseUrl.replace(/\/+$/, "")} ${model}`;
|
|
36
|
+
}
|
|
37
|
+
function read(path) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
40
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
41
|
+
return {};
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
44
|
+
const w = v;
|
|
45
|
+
if (w !== null && typeof w === "object" && typeof w.tokens === "number" && Number.isSafeInteger(w.tokens) && w.tokens > 0 && typeof w.observedAt === "string") {
|
|
46
|
+
out[k] = { tokens: w.tokens, observedAt: w.observedAt };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Startup: read the file and keep figures learned from here on in it.
|
|
56
|
+
* Until this is called, nothing is read and nothing is kept. */
|
|
57
|
+
export function useLearnedWindows(path = learnedWindowsPath()) {
|
|
58
|
+
tablePath = path;
|
|
59
|
+
table = read(path);
|
|
60
|
+
}
|
|
61
|
+
/** The learned window for a model at an endpoint, if a refusal stated one. */
|
|
62
|
+
export function learnedWindowFor(model, baseUrl) {
|
|
63
|
+
return table === null ? undefined : table[learnedKey(model, baseUrl)];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Keep a refusal's cap. Returns true when it changed what is kept (a new
|
|
67
|
+
* key, or a smaller figure), false when a figure at or below it is kept
|
|
68
|
+
* already — or when no file was named (`useLearnedWindows`). The file is
|
|
69
|
+
* re-read before the write, so a figure another process learned meanwhile
|
|
70
|
+
* is not lost.
|
|
71
|
+
*/
|
|
72
|
+
export function recordLearnedWindow(model, baseUrl, tokens, today = new Date()) {
|
|
73
|
+
const path = tablePath;
|
|
74
|
+
if (path === null)
|
|
75
|
+
return false;
|
|
76
|
+
const key = learnedKey(model, baseUrl);
|
|
77
|
+
const current = read(path);
|
|
78
|
+
const held = current[key];
|
|
79
|
+
if (held !== undefined && held.tokens <= tokens) {
|
|
80
|
+
table = current;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const next = { ...current, [key]: { tokens, observedAt: today.toISOString().slice(0, 10) } };
|
|
84
|
+
table = next;
|
|
85
|
+
try {
|
|
86
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
87
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
88
|
+
writeFileSync(tmp, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
89
|
+
renameSync(tmp, path);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// unwritable home: the figure still holds for this process
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
/** Tests: back to the state before startup — nothing read, nothing kept. */
|
|
97
|
+
export function resetLearnedWindows() {
|
|
98
|
+
table = null;
|
|
99
|
+
tablePath = null;
|
|
100
|
+
}
|
package/dist/provider-label.d.ts
CHANGED
|
@@ -24,9 +24,14 @@
|
|
|
24
24
|
export declare function providerHost(baseUrl?: string): string | null;
|
|
25
25
|
/** `@commandcode.ai` — the provider named beside a model. `""` when the
|
|
26
26
|
* binding names no endpoint (the provider's own default origin), so a row
|
|
27
|
-
* that has nothing to say does not print a stray `@`.
|
|
28
|
-
|
|
27
|
+
* that has nothing to say does not print a stray `@`.
|
|
28
|
+
*
|
|
29
|
+
* With an `upstream` (a profile whose baseUrl is a local forwarder), the
|
|
30
|
+
* label names who is billed FIRST and where the bytes go second:
|
|
31
|
+
* `@gateway.example via 127.0.0.1:47821`. A name that is not a URL is shown as
|
|
32
|
+
* written. */
|
|
33
|
+
export declare function providerLabel(baseUrl?: string, upstream?: string): string;
|
|
29
34
|
/** The same label for a PROFILE, which knows its kind as well as its URL: a
|
|
30
35
|
* profile with no baseUrl (a first-party provider) still names itself by
|
|
31
36
|
* providerId, because a row that shows two profiles must tell them apart. */
|
|
32
|
-
export declare function profileProviderLabel(kind: string, baseUrl?: string): string;
|
|
37
|
+
export declare function profileProviderLabel(kind: string, baseUrl?: string, upstream?: string): string;
|
package/dist/provider-label.js
CHANGED
|
@@ -40,14 +40,22 @@ export function providerHost(baseUrl) {
|
|
|
40
40
|
}
|
|
41
41
|
/** `@commandcode.ai` — the provider named beside a model. `""` when the
|
|
42
42
|
* binding names no endpoint (the provider's own default origin), so a row
|
|
43
|
-
* that has nothing to say does not print a stray `@`.
|
|
44
|
-
|
|
43
|
+
* that has nothing to say does not print a stray `@`.
|
|
44
|
+
*
|
|
45
|
+
* With an `upstream` (a profile whose baseUrl is a local forwarder), the
|
|
46
|
+
* label names who is billed FIRST and where the bytes go second:
|
|
47
|
+
* `@gateway.example via 127.0.0.1:47821`. A name that is not a URL is shown as
|
|
48
|
+
* written. */
|
|
49
|
+
export function providerLabel(baseUrl, upstream) {
|
|
45
50
|
const host = providerHost(baseUrl);
|
|
51
|
+
const up = upstream === undefined || upstream.trim() === "" ? null : (providerHost(upstream) ?? upstream.trim());
|
|
52
|
+
if (up !== null)
|
|
53
|
+
return host === null || host === up ? `@${up}` : `@${up} via ${host}`;
|
|
46
54
|
return host === null ? "" : `@${host}`;
|
|
47
55
|
}
|
|
48
56
|
/** The same label for a PROFILE, which knows its kind as well as its URL: a
|
|
49
57
|
* profile with no baseUrl (a first-party provider) still names itself by
|
|
50
58
|
* providerId, because a row that shows two profiles must tell them apart. */
|
|
51
|
-
export function profileProviderLabel(kind, baseUrl) {
|
|
52
|
-
return providerLabel(baseUrl) || `@${providerIdOf(kind, baseUrl)}`;
|
|
59
|
+
export function profileProviderLabel(kind, baseUrl, upstream) {
|
|
60
|
+
return providerLabel(baseUrl, upstream) || `@${providerIdOf(kind, baseUrl)}`;
|
|
53
61
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -253,6 +253,10 @@ export declare function setConfiguredWindow(value: number | undefined): void;
|
|
|
253
253
|
* model's NAME — /model lists and switches against them. */
|
|
254
254
|
export declare let configModels: Readonly<Record<string, import("./config.js").ModelProfile>>;
|
|
255
255
|
export declare function setConfigModels(models: Readonly<Record<string, import("./config.js").ModelProfile>>): void;
|
|
256
|
+
/** CW-1: the upstream a forwarder at `baseUrl` names — a fact about the
|
|
257
|
+
* ADDRESS, so a resumed session (no profile name in hand) finds it too:
|
|
258
|
+
* the first profile at that address that names one. */
|
|
259
|
+
export declare function upstreamOf(baseUrl: string | undefined): string | undefined;
|
|
256
260
|
/** The name of the model currently driving the session ("faux" or the
|
|
257
261
|
* profile name / provider/model write / env model). */
|
|
258
262
|
/**
|
package/dist/state.js
CHANGED
|
@@ -342,6 +342,17 @@ export let configModels = {};
|
|
|
342
342
|
export function setConfigModels(models) {
|
|
343
343
|
configModels = models;
|
|
344
344
|
}
|
|
345
|
+
/** CW-1: the upstream a forwarder at `baseUrl` names — a fact about the
|
|
346
|
+
* ADDRESS, so a resumed session (no profile name in hand) finds it too:
|
|
347
|
+
* the first profile at that address that names one. */
|
|
348
|
+
export function upstreamOf(baseUrl) {
|
|
349
|
+
if (baseUrl === undefined)
|
|
350
|
+
return undefined;
|
|
351
|
+
for (const p of Object.values(configModels))
|
|
352
|
+
if (p.baseUrl === baseUrl && p.upstream !== undefined)
|
|
353
|
+
return p.upstream;
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
345
356
|
/** The name of the model currently driving the session ("faux" or the
|
|
346
357
|
* profile name / provider/model write / env model). */
|
|
347
358
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.3",
|
|
4
4
|
"description": "kiso-code \u2014 the coding agent on kiso, the durable runtime for AI agents: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,20 +18,20 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-ask-ext": "0.40.
|
|
22
|
-
"@vincemakes/kiso-core": "0.40.
|
|
23
|
-
"@vincemakes/kiso-evals": "0.40.
|
|
24
|
-
"@vincemakes/kiso-mcp-ext": "0.40.
|
|
25
|
-
"@vincemakes/kiso-provider-anthropic": "0.40.
|
|
26
|
-
"@vincemakes/kiso-provider-openai": "0.40.
|
|
27
|
-
"@vincemakes/kiso-provider-openai-responses": "0.40.
|
|
28
|
-
"@vincemakes/kiso-runtime": "0.40.
|
|
29
|
-
"@vincemakes/kiso-skills-ext": "0.40.
|
|
30
|
-
"@vincemakes/kiso-subagent-ext": "0.40.
|
|
31
|
-
"@vincemakes/kiso-task-ext": "0.40.
|
|
32
|
-
"@vincemakes/kiso-tools-node": "0.40.
|
|
33
|
-
"@vincemakes/kiso-tui": "0.40.
|
|
34
|
-
"@vincemakes/kiso-tui-cells": "0.40.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.40.3",
|
|
22
|
+
"@vincemakes/kiso-core": "0.40.3",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.40.3",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.40.3",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.40.3",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.40.3",
|
|
27
|
+
"@vincemakes/kiso-provider-openai-responses": "0.40.3",
|
|
28
|
+
"@vincemakes/kiso-runtime": "0.40.3",
|
|
29
|
+
"@vincemakes/kiso-skills-ext": "0.40.3",
|
|
30
|
+
"@vincemakes/kiso-subagent-ext": "0.40.3",
|
|
31
|
+
"@vincemakes/kiso-task-ext": "0.40.3",
|
|
32
|
+
"@vincemakes/kiso-tools-node": "0.40.3",
|
|
33
|
+
"@vincemakes/kiso-tui": "0.40.3",
|
|
34
|
+
"@vincemakes/kiso-tui-cells": "0.40.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^26.1.2",
|