@vincemakes/kiso-code 0.8.0 → 0.9.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/chat.d.ts +6 -1
- package/dist/chat.js +110 -10
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +29 -3
- package/dist/index.js +1 -0
- package/dist/state.d.ts +17 -1
- package/dist/state.js +67 -2
- package/package.json +14 -14
package/dist/chat.d.ts
CHANGED
|
@@ -45,6 +45,10 @@ export interface UsageDelta {
|
|
|
45
45
|
* recovers on the next known event). */
|
|
46
46
|
readonly total: number | null;
|
|
47
47
|
readonly missed: number | null;
|
|
48
|
+
/** TUI2-R1 (E): the CANONICAL cost of this request — null when the
|
|
49
|
+
* pricing table has no rate for the route (the R5b-④c absent stamp).
|
|
50
|
+
* Null is carried, never zeroed: a missing rate is not a free call. */
|
|
51
|
+
readonly costUsd: number | null;
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* The CLI's usage consumer (E2 1.3.0, the R2a-1 ruling 2026-08-13) — the
|
|
@@ -76,6 +80,7 @@ export declare function usageFromEvent(route: string | undefined, ev: import("@v
|
|
|
76
80
|
* is gone) — docked only, 200ms rotation between the request and the
|
|
77
81
|
* first event. */
|
|
78
82
|
export declare function startStatusSpinner(onTick: (glyph: string) => void): () => void;
|
|
83
|
+
export declare function startShellTail(sessionId: string, callId: string, command: string, startedAt: number): () => void;
|
|
79
84
|
/**
|
|
80
85
|
* Consume a run, answering approval pauses as they arrive. `resumeMode`
|
|
81
86
|
* marks a session.resume() continuation. `faux` picks the status line's
|
|
@@ -84,7 +89,7 @@ export declare function startStatusSpinner(onTick: (glyph: string) => void): ()
|
|
|
84
89
|
* echo is UI, the chip is the record; the momentary double-render is
|
|
85
90
|
* the design's explicit point).
|
|
86
91
|
*/
|
|
87
|
-
export declare function consumeRun(session: AgentSession, run: Run, input: LineInput, turnNo: number, faux: boolean, statusCb: ((usage: RunUsage, ctxRatio: number) => void) | null,
|
|
92
|
+
export declare function consumeRun(session: AgentSession, run: Run, input: LineInput, turnNo: number, faux: boolean, statusCb: ((usage: RunUsage, ctxRatio: number, costUsd?: number | null) => void) | null,
|
|
88
93
|
/** W21: the amend words ("Yes + feedback") ride the NEXT user turn —
|
|
89
94
|
* threaded from chat's submitTurn; absent in the recovery flow
|
|
90
95
|
* (resume) where a dropped amend is noticed instead. */
|
package/dist/chat.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* approval-moment mini-diff, the status spinner, and the context
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
|
-
import { escapeTerminal, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, STATUS_GLYPHS, } from "@vincemakes/kiso-tui";
|
|
7
|
+
import { readFileSync, statSync } from "node:fs";
|
|
8
|
+
import { escapeTerminal, cacheHitPct, idleStatus, palette, renderEvent, renderRecap, runningStatus, toolTarget, STATUS_GLYPHS, } from "@vincemakes/kiso-tui";
|
|
9
9
|
import { editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
|
-
import { canonicalTargetPath } from "@vincemakes/kiso-tools-node";
|
|
10
|
+
import { canonicalTargetPath, shellProgressPath } from "@vincemakes/kiso-tools-node";
|
|
11
11
|
import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
|
|
12
12
|
import { dispatch } from "./dispatch.js";
|
|
13
13
|
import { agentModel, body, bodyLog, configuredWindow, dock } from "./state.js";
|
|
@@ -89,7 +89,7 @@ export function usageFromEvent(route, ev, prevTotal) {
|
|
|
89
89
|
const m = Math.min(prevTotal, total) - c.cacheRead;
|
|
90
90
|
missed = m > CACHE_MISS_FLOOR ? m : null;
|
|
91
91
|
}
|
|
92
|
-
return { usage: { in: c.input, out: c.output, cache: c.cacheRead, known: ev.known }, total, missed };
|
|
92
|
+
return { usage: { in: c.input, out: c.output, cache: c.cacheRead, known: ev.known }, total, missed, costUsd: c.costUsd };
|
|
93
93
|
}
|
|
94
94
|
/** v2b: the spinner merged into the STATUS BAR (the v2a standalone glyph
|
|
95
95
|
* is gone) — docked only, 200ms rotation between the request and the
|
|
@@ -105,6 +105,45 @@ export function startStatusSpinner(onTick) {
|
|
|
105
105
|
timer.unref();
|
|
106
106
|
return () => clearInterval(timer);
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* TUI2-R1 (C) — the shell tailer: the READER half of the progress
|
|
110
|
+
* sidecar (the writer is the shell tool, tools-node).
|
|
111
|
+
*
|
|
112
|
+
* The CLI is the only place that holds both facts the derived key needs
|
|
113
|
+
* — the session's id and the running call's command — so the tail is
|
|
114
|
+
* read here and handed to the cell. A poll, not a watcher: fs.watch's
|
|
115
|
+
* behaviour on a file being appended to differs by platform, and the
|
|
116
|
+
* one thing this must never do is misbehave in a way that costs the run.
|
|
117
|
+
*
|
|
118
|
+
* THE FRESHNESS GUARD is the part that makes a kill -9 leftover
|
|
119
|
+
* harmless. A sidecar the writer never got to remove keeps its old
|
|
120
|
+
* mtime; a tail is read only from a file modified AT OR AFTER the call
|
|
121
|
+
* started. A ghost from a previous process cannot be shown as this
|
|
122
|
+
* call's output — and since the tail is display-only, showing nothing is
|
|
123
|
+
* always the safe answer.
|
|
124
|
+
*/
|
|
125
|
+
const TAIL_POLL_MS = 250;
|
|
126
|
+
const TAIL_BYTES = 4096; // the last lines are all the window can hold
|
|
127
|
+
export function startShellTail(sessionId, callId, command, startedAt) {
|
|
128
|
+
const path = shellProgressPath(sessionId, command);
|
|
129
|
+
const read = () => {
|
|
130
|
+
try {
|
|
131
|
+
const stat = statSync(path);
|
|
132
|
+
if (stat.mtimeMs + 1000 < startedAt)
|
|
133
|
+
return; // a ghost from a killed run — never this call's
|
|
134
|
+
const text = readFileSync(path, "utf8");
|
|
135
|
+
body.toolProgress(callId, text.slice(-TAIL_BYTES).trimEnd());
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// no sidecar yet, removed at settle, or unreadable — the tail
|
|
139
|
+
// is an observation, and its absence is never an error
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
read();
|
|
143
|
+
const timer = setInterval(read, TAIL_POLL_MS);
|
|
144
|
+
timer.unref();
|
|
145
|
+
return () => clearInterval(timer);
|
|
146
|
+
}
|
|
108
147
|
/** v2e: the approval-moment mini-diff — edit_file/write_file changes as
|
|
109
148
|
* ± lines; other tools get null (no diff, no cost). The file read is
|
|
110
149
|
* best-effort: an unreadable file yields NO diff, never a failure —
|
|
@@ -128,7 +167,9 @@ function approvalDiff(name, input) {
|
|
|
128
167
|
const replace = typeof input.replace === "string" ? input.replace : "";
|
|
129
168
|
if (search === "")
|
|
130
169
|
return null;
|
|
131
|
-
|
|
170
|
+
// TUI2-R1.5 ② (VD-2): the path rides along so a miss can name the
|
|
171
|
+
// file in its honest note instead of fabricating a diff.
|
|
172
|
+
return editFileDiff(oldContent ?? "", search, replace, path);
|
|
132
173
|
}
|
|
133
174
|
const content = typeof input.content === "string" ? input.content : "";
|
|
134
175
|
return writeFileDiff(oldContent, content);
|
|
@@ -268,6 +309,16 @@ submitTurn) {
|
|
|
268
309
|
// at the first non-thinking event (the fold needs the seconds).
|
|
269
310
|
let thoughtSeconds = 0;
|
|
270
311
|
let thinkingSince = null;
|
|
312
|
+
// TUI2-R1 (C): the shell commands seen this run, and the tailers
|
|
313
|
+
// running for them. A tailer is started when the execution starts and
|
|
314
|
+
// stopped at the call's result — and the finally below stops any that
|
|
315
|
+
// an abort left behind, so a poller can never outlive its run.
|
|
316
|
+
const shellCommands = new Map();
|
|
317
|
+
const tailers = new Map();
|
|
318
|
+
const stopTail = (callId) => {
|
|
319
|
+
tailers.get(callId)?.();
|
|
320
|
+
tailers.delete(callId);
|
|
321
|
+
};
|
|
271
322
|
try {
|
|
272
323
|
for await (const ev of run) {
|
|
273
324
|
last = ev;
|
|
@@ -295,10 +346,18 @@ submitTurn) {
|
|
|
295
346
|
if (ev.name === "edit_file")
|
|
296
347
|
editCount += 1;
|
|
297
348
|
body.toolStart(ev.name, ev.callId, ev.input ?? {});
|
|
349
|
+
// TUI2-R1 (C): the command is the sidecar key's other half —
|
|
350
|
+
// remembered here, used when the execution actually starts.
|
|
351
|
+
if (ev.name === "shell" && typeof ev.input?.command === "string")
|
|
352
|
+
shellCommands.set(ev.callId, ev.input.command);
|
|
298
353
|
break;
|
|
299
|
-
case "tool_execution_started":
|
|
354
|
+
case "tool_execution_started": {
|
|
300
355
|
body.toolRunning(ev.callId);
|
|
356
|
+
const command = shellCommands.get(ev.callId);
|
|
357
|
+
if (command !== undefined)
|
|
358
|
+
tailers.set(ev.callId, startShellTail(session.id, ev.callId, command, Date.now()));
|
|
301
359
|
break;
|
|
360
|
+
}
|
|
302
361
|
case "tool_execution_succeeded":
|
|
303
362
|
body.toolSucceeded(ev.callId);
|
|
304
363
|
break;
|
|
@@ -306,6 +365,9 @@ submitTurn) {
|
|
|
306
365
|
body.toolFailed(ev.callId, ev.error);
|
|
307
366
|
break;
|
|
308
367
|
case "tool_result": {
|
|
368
|
+
// TUI2-R1 (C): the observation window closes the instant the
|
|
369
|
+
// real result exists — the tail must never race it.
|
|
370
|
+
stopTail(ev.callId);
|
|
309
371
|
const text = typeof ev.content === "string" ? ev.content : "";
|
|
310
372
|
// W19: a DENIED call carries its reason — extracted from the
|
|
311
373
|
// result's "[Permission denied] " prefix, keyed on the
|
|
@@ -340,7 +402,9 @@ submitTurn) {
|
|
|
340
402
|
usage = delta.usage;
|
|
341
403
|
prevTotal = delta.total;
|
|
342
404
|
missed = delta.missed;
|
|
343
|
-
|
|
405
|
+
// TUI2-R1 (E): the request's canonical cost rides the same
|
|
406
|
+
// callback the usage does — one settled request, one addition.
|
|
407
|
+
statusCb?.(usage, estimateCtxRatio(session), delta.costUsd);
|
|
344
408
|
break;
|
|
345
409
|
}
|
|
346
410
|
case "uncertain_pending":
|
|
@@ -471,6 +535,11 @@ submitTurn) {
|
|
|
471
535
|
body.thinkingEnd(); // a trailing thinking block folds at the run's end
|
|
472
536
|
}
|
|
473
537
|
finally {
|
|
538
|
+
// TUI2-R1 (C): an abort or a throw leaves the loop without a
|
|
539
|
+
// tool_result — every tailer stops here regardless, so no poller
|
|
540
|
+
// outlives the run that started it.
|
|
541
|
+
for (const callId of [...tailers.keys()])
|
|
542
|
+
stopTail(callId);
|
|
474
543
|
}
|
|
475
544
|
return last;
|
|
476
545
|
}
|
|
@@ -639,6 +708,11 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
639
708
|
// glyph (▖▘▝▗ — the spinner drives it) + wall seconds + ↓ out tokens
|
|
640
709
|
// + the interrupt hint. ctx left is the live estimate everywhere.
|
|
641
710
|
let runUsage = { in: null, out: null, cache: null, known: false };
|
|
711
|
+
// TUI2-R1 (E): the session's spend so far — the CANONICAL cost of every
|
|
712
|
+
// request this process has seen, summed. Null stays null: a route with
|
|
713
|
+
// no rate in the pricing table contributes nothing and the row shows no
|
|
714
|
+
// $ at all, because a partial total presented as a total is a lie.
|
|
715
|
+
let spentUsd = null;
|
|
642
716
|
let runGlyph = "▖";
|
|
643
717
|
let runStart = Date.now();
|
|
644
718
|
// KC2 §5: the STATE (the glyph, the run's start, the usage, the dock)
|
|
@@ -651,13 +725,29 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
651
725
|
// parentheses idiom names the read-only constraint. The tier is the
|
|
652
726
|
// CALLER's word (the recovery flow passes the bare mode).
|
|
653
727
|
const paintIdle = () => {
|
|
654
|
-
if (dock.active)
|
|
655
|
-
|
|
728
|
+
if (!dock.active)
|
|
729
|
+
return;
|
|
730
|
+
// TUI2-R1 (E): the meter rides the idle row — both fields omitted
|
|
731
|
+
// when unknown, so a session that has not called the model paints
|
|
732
|
+
// exactly the pre-round row.
|
|
733
|
+
dock.setStatus(idleStatus(getMode() === "plan" ? "plan (read-only)" : getMode(), agentModel, estimateCtxRatio(session), {
|
|
734
|
+
cacheHitPct: cacheHitPct(runUsage),
|
|
735
|
+
costUsd: spentUsd,
|
|
736
|
+
}));
|
|
656
737
|
};
|
|
657
|
-
const statusCb = (u, ctx) => {
|
|
738
|
+
const statusCb = (u, ctx, costUsd) => {
|
|
658
739
|
runUsage = u;
|
|
740
|
+
addCost(costUsd ?? null);
|
|
659
741
|
paintRunning();
|
|
660
742
|
};
|
|
743
|
+
// TUI2-R1 (E): the canonical cost of one settled request, added to the
|
|
744
|
+
// session's running total. A null cost (no rate for the route) adds
|
|
745
|
+
// nothing and leaves the total as it was.
|
|
746
|
+
const addCost = (usd) => {
|
|
747
|
+
if (usd === null)
|
|
748
|
+
return;
|
|
749
|
+
spentUsd = (spentUsd ?? 0) + usd;
|
|
750
|
+
};
|
|
661
751
|
const submitTurn = (line) => {
|
|
662
752
|
const slot = { line, cancelled: false };
|
|
663
753
|
pendingTurns.push(slot);
|
|
@@ -717,6 +807,7 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
717
807
|
paintIdle,
|
|
718
808
|
submitTurn,
|
|
719
809
|
estimateCtx: () => estimateCtxRatio(session),
|
|
810
|
+
contextWindow: () => contextWindowTokens(),
|
|
720
811
|
};
|
|
721
812
|
// the ergonomics batch C8: the auto-compact check — the /compact FULL path via the
|
|
722
813
|
// shared dispatch (same notices, same chain ordering, same mid-run
|
|
@@ -756,6 +847,15 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
756
847
|
const recoveryRun = session.resume();
|
|
757
848
|
currentRun = recoveryRun;
|
|
758
849
|
turnNo += 1;
|
|
850
|
+
// TUI2-R1.5 ③ (VD-3 family): stamp the run's start AT the run's
|
|
851
|
+
// entry. Every other run path does; this one inherited the value
|
|
852
|
+
// from the process's own startup, so its "working Ns" was the
|
|
853
|
+
// session's age rather than the recovery's. The drift is small
|
|
854
|
+
// today (recovery follows startup closely) and unbounded in
|
|
855
|
+
// principle — a slow MCP connect is seconds the recovery never
|
|
856
|
+
// spent, reported as seconds it did.
|
|
857
|
+
runStart = Date.now();
|
|
858
|
+
runUsage = { in: null, out: null, cache: null, known: false };
|
|
759
859
|
const last = await consumeRun(session, recoveryRun, input, turnNo, faux, statusCb, submitTurn);
|
|
760
860
|
currentRun = null;
|
|
761
861
|
failOnFauxExhaustion(last, faux, input);
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export interface DispatchCtx {
|
|
|
22
22
|
readonly submitTurn: (line: string) => void;
|
|
23
23
|
/** the /status context estimate. */
|
|
24
24
|
readonly estimateCtx: () => number;
|
|
25
|
+
/** TUI2-R1 (E): the model's context window, as the session is
|
|
26
|
+
* configured — the /context ledger's denominator. */
|
|
27
|
+
readonly contextWindow: () => number;
|
|
25
28
|
}
|
|
26
29
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
27
30
|
* replay routes through it too — a queued "/last" must never become a
|
package/dist/dispatch.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* turns. The bodies moved verbatim from chat()'s closure; chat provides
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
|
-
import { escapeTerminal, helpRows, kUnit, palette } from "@vincemakes/kiso-tui";
|
|
6
|
+
import { contextRows, contextUnavailableRows, escapeTerminal, helpRows, kUnit, palette } from "@vincemakes/kiso-tui";
|
|
7
7
|
import { buildAdapter } from "@vincemakes/kiso-runtime/internal";
|
|
8
8
|
import { MODES, getMode, setMode } from "./mode.js";
|
|
9
|
-
import { agentModel, body, bodyLog, configModels, dock, setAgentModel, setCurrentModelName } from "./state.js";
|
|
9
|
+
import { agentModel, body, bodyLog, configModels, dock, readContextLedger, setAgentModel, setCurrentModelName } from "./state.js";
|
|
10
10
|
import { directWriteProfile, profileAvailable } from "./config.js";
|
|
11
11
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
12
12
|
* replay routes through it too — a queued "/last" must never become a
|
|
@@ -23,8 +23,10 @@ export function dispatch(line, ctx) {
|
|
|
23
23
|
// the last one still carries its own \n, so `exit` and `keys`
|
|
24
24
|
// land as two rows from one call (bodyLog splits on \n).
|
|
25
25
|
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
26
|
+
// TUI2-R1.5 9 (VD-10): /help is sentences for a human — the keys
|
|
27
|
+
// row in particular is one long line that hard-folded mid-word.
|
|
26
28
|
for (const row of helpRows())
|
|
27
|
-
bodyLog(row);
|
|
29
|
+
bodyLog(row, "words");
|
|
28
30
|
ctx.input.prompt();
|
|
29
31
|
});
|
|
30
32
|
return;
|
|
@@ -92,6 +94,30 @@ export function dispatch(line, ctx) {
|
|
|
92
94
|
ctx.chainRef.current = ctx.chainRef.current.then(land);
|
|
93
95
|
return;
|
|
94
96
|
}
|
|
97
|
+
if (trimmed === "/context") {
|
|
98
|
+
// TUI2-R1 (E): the rent-ledger attribution — where the context went,
|
|
99
|
+
// read from the session's TRACE SIDECAR (the observation file E1/E3
|
|
100
|
+
// already write, per request).
|
|
101
|
+
//
|
|
102
|
+
// THE PURITY GATE IS UNTOUCHED and this is why: the trace surface is
|
|
103
|
+
// an OBSERVATION surface (ADR-0051 §6, ruling R7) and correctness
|
|
104
|
+
// never reads it. /context is a DISPLAY command — nothing it reads
|
|
105
|
+
// reaches a recovery plan, a projection, or a request. The read is
|
|
106
|
+
// best-effort by construction: a missing, partial or unparseable
|
|
107
|
+
// ledger renders the honest fallback, never an error and never a
|
|
108
|
+
// guess. recovery-purity.test.ts's probes are unaffected: the
|
|
109
|
+
// derivation still does zero I/O and still ignores trace-shaped data.
|
|
110
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
111
|
+
const ledger = readContextLedger(ctx.session.id, ctx.contextWindow());
|
|
112
|
+
for (const row of ledger === null
|
|
113
|
+
? contextUnavailableRows("the ledger is written per request — run a turn, then ask again")
|
|
114
|
+
: contextRows(ledger)) {
|
|
115
|
+
bodyLog(row);
|
|
116
|
+
}
|
|
117
|
+
ctx.input.prompt();
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
95
121
|
if (trimmed === "/status") {
|
|
96
122
|
// B area: session id, durable event count, and the ~ context
|
|
97
123
|
// estimate — all read straight from the live session, nothing
|
package/dist/index.js
CHANGED
|
@@ -199,6 +199,7 @@ function makeLineInput() {
|
|
|
199
199
|
editor.bindAtItems(atFiles); // KC3 §5: the file source — listed per OPEN
|
|
200
200
|
dock.bindAt(() => editor.atState()); // KC3 §4: the picker's band
|
|
201
201
|
dock.bindApproval(() => editor.panelState()); // W21: the panel's bound state
|
|
202
|
+
dock.bindSheet(() => editor.sheetOpen()); // TUI2-R1 (D): the ? keys sheet
|
|
202
203
|
return editorInput(editor);
|
|
203
204
|
}
|
|
204
205
|
return readlineInput(createInterface({ input: process.stdin, output: process.stdout }));
|
package/dist/state.d.ts
CHANGED
|
@@ -15,6 +15,22 @@ export declare function kisoHome(): string;
|
|
|
15
15
|
export declare function sessionsDir(): string;
|
|
16
16
|
/** E1: the extension scan directory — KISO_EXTENSIONS_DIR overrides. */
|
|
17
17
|
export declare function extensionsDir(): string;
|
|
18
|
+
/**
|
|
19
|
+
* TUI2-R1 (E) — the /context ledger, read from the session's TRACE
|
|
20
|
+
* SIDECAR (<sessions>/traces/<id>.jsonl).
|
|
21
|
+
*
|
|
22
|
+
* THE PURITY GATE (ADR-0051 §6, ruling R7) IS UNTOUCHED. The trace
|
|
23
|
+
* surface is an observation surface; correctness never reads it, and
|
|
24
|
+
* this reader is on the DISPLAY path only — nothing it returns reaches a
|
|
25
|
+
* recovery plan, a projection or a request. The proof of that is the
|
|
26
|
+
* shape of this function: it is best-effort end to end, and its failure
|
|
27
|
+
* mode is `null`, which renders a sentence rather than a number.
|
|
28
|
+
*
|
|
29
|
+
* The LAST request line is the one that matters: rent is per request,
|
|
30
|
+
* and what the reader wants to know is what the NEXT request will cost,
|
|
31
|
+
* which is what the previous one cost.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readContextLedger(sessionId: string, window: number): import("@vincemakes/kiso-tui").ContextLedger | null;
|
|
18
34
|
/**
|
|
19
35
|
* KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
|
|
20
36
|
* daemon, no watcher, nothing to invalidate and nothing to go stale.
|
|
@@ -90,7 +106,7 @@ export declare let body: Body;
|
|
|
90
106
|
export declare function setBody(value: Body): void;
|
|
91
107
|
/** v2d: body output routes through the cell renderer — the single writer.
|
|
92
108
|
* bodyLog adds the trailing newline; internal newlines are preserved. */
|
|
93
|
-
export declare function bodyLog(text: string): void;
|
|
109
|
+
export declare function bodyLog(text: string, wrap?: "words"): void;
|
|
94
110
|
/** The model name for the status bar — set by makeAgent. */
|
|
95
111
|
export declare let agentModel: string;
|
|
96
112
|
export declare function setAgentModel(value: string): void;
|
package/dist/state.js
CHANGED
|
@@ -25,6 +25,71 @@ export function sessionsDir() {
|
|
|
25
25
|
export function extensionsDir() {
|
|
26
26
|
return process.env.KISO_EXTENSIONS_DIR ?? join(kisoHome(), "extensions");
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* TUI2-R1 (E) — the /context ledger, read from the session's TRACE
|
|
30
|
+
* SIDECAR (<sessions>/traces/<id>.jsonl).
|
|
31
|
+
*
|
|
32
|
+
* THE PURITY GATE (ADR-0051 §6, ruling R7) IS UNTOUCHED. The trace
|
|
33
|
+
* surface is an observation surface; correctness never reads it, and
|
|
34
|
+
* this reader is on the DISPLAY path only — nothing it returns reaches a
|
|
35
|
+
* recovery plan, a projection or a request. The proof of that is the
|
|
36
|
+
* shape of this function: it is best-effort end to end, and its failure
|
|
37
|
+
* mode is `null`, which renders a sentence rather than a number.
|
|
38
|
+
*
|
|
39
|
+
* The LAST request line is the one that matters: rent is per request,
|
|
40
|
+
* and what the reader wants to know is what the NEXT request will cost,
|
|
41
|
+
* which is what the previous one cost.
|
|
42
|
+
*/
|
|
43
|
+
export function readContextLedger(sessionId, window) {
|
|
44
|
+
let last = null;
|
|
45
|
+
try {
|
|
46
|
+
const text = readFileSync(join(sessionsDir(), "traces", `${sessionId}.jsonl`), "utf8");
|
|
47
|
+
for (const line of text.split("\n")) {
|
|
48
|
+
if (line === "")
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(line);
|
|
52
|
+
if (parsed.kind === "request")
|
|
53
|
+
last = parsed;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// a torn last line (the writer was mid-append) — the ledger is
|
|
57
|
+
// an observation, and a partial one is simply not the answer
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null; // no sidecar — a session that has not called the model
|
|
63
|
+
}
|
|
64
|
+
if (last === null)
|
|
65
|
+
return null;
|
|
66
|
+
const rent = Array.isArray(last.rent) ? last.rent : [];
|
|
67
|
+
if (rent.length === 0)
|
|
68
|
+
return null; // a v1/v2 sidecar carries no rent block (R2-1)
|
|
69
|
+
const sum = (pred) => rent.filter((l) => typeof l.surface === "string" && pred(l.surface)).reduce((a, l) => a + (l.estTokens ?? 0), 0);
|
|
70
|
+
const count = (pred) => rent.filter((l) => typeof l.surface === "string" && pred(l.surface)).length;
|
|
71
|
+
// the skills index is broken out: it is an INDEX of workspace content,
|
|
72
|
+
// not an instruction, and it is the one append whose size is the
|
|
73
|
+
// reader's own doing.
|
|
74
|
+
const isSkills = (s) => s === "system:ext:skills";
|
|
75
|
+
const manifest = Array.isArray(last.contextManifest) ? last.contextManifest : [];
|
|
76
|
+
const turnSegments = manifest.filter((s) => s.role === "turn" || s.role === "current_turn");
|
|
77
|
+
return {
|
|
78
|
+
window,
|
|
79
|
+
systemPrompt: sum((s) => s === "system:base" || (s.startsWith("system:ext:") && !isSkills(s))),
|
|
80
|
+
systemBase: sum((s) => s === "system:base"),
|
|
81
|
+
appends: count((s) => s.startsWith("system:ext:") && !isSkills(s)),
|
|
82
|
+
toolTable: sum((s) => s.startsWith("tool:")),
|
|
83
|
+
tools: count((s) => s.startsWith("tool:")),
|
|
84
|
+
skillsIndex: sum(isSkills),
|
|
85
|
+
// the ledger records the SURFACE, never its contents — the number
|
|
86
|
+
// of skills is not in it, and 0 tells the renderer to say so.
|
|
87
|
+
skills: 0,
|
|
88
|
+
envelope: sum((s) => s === "envelope"),
|
|
89
|
+
messages: turnSegments.reduce((a, s) => a + (s.estTokens ?? 0), 0),
|
|
90
|
+
turns: turnSegments.length,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
28
93
|
/**
|
|
29
94
|
* KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
|
|
30
95
|
* daemon, no watcher, nothing to invalidate and nothing to go stale.
|
|
@@ -95,8 +160,8 @@ export function setBody(value) {
|
|
|
95
160
|
}
|
|
96
161
|
/** v2d: body output routes through the cell renderer — the single writer.
|
|
97
162
|
* bodyLog adds the trailing newline; internal newlines are preserved. */
|
|
98
|
-
export function bodyLog(text) {
|
|
99
|
-
body.raw(text.split("\n"));
|
|
163
|
+
export function bodyLog(text, wrap) {
|
|
164
|
+
body.raw(text.split("\n"), wrap);
|
|
100
165
|
}
|
|
101
166
|
/** The model name for the status bar — set by makeAgent. */
|
|
102
167
|
export let agentModel = "faux";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.9.0",
|
|
22
|
+
"@vincemakes/kiso-core": "0.9.0",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.9.0",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.9.0",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.9.0",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.9.0",
|
|
27
|
+
"@vincemakes/kiso-runtime": "0.9.0",
|
|
28
|
+
"@vincemakes/kiso-skills-ext": "0.9.0",
|
|
29
|
+
"@vincemakes/kiso-subagent-ext": "0.9.0",
|
|
30
|
+
"@vincemakes/kiso-task-ext": "0.9.0",
|
|
31
|
+
"@vincemakes/kiso-tools-node": "0.9.0",
|
|
32
|
+
"@vincemakes/kiso-tui": "0.9.0",
|
|
33
|
+
"@vincemakes/kiso-tui-cells": "0.9.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.1.2",
|